model.php 194 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966
  1. <?php
  2. /**
  3. * The model file of doc 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 doc
  9. * @version $Id: model.php 881 2010-06-22 06:50:32Z chencongzhi520 $
  10. * @link https://www.zentao.net
  11. */
  12. class docModel extends model
  13. {
  14. /**
  15. * @var actionModel
  16. */
  17. public $action;
  18. // api doc type
  19. const DOC_TYPE_API = 'api';
  20. const DOC_TYPE_REST = 'restapi';
  21. /**
  22. * Adjust the action clickable.
  23. *
  24. * @param object $story
  25. * @param string $action
  26. * @access public
  27. * @return bool
  28. * @param object $doc
  29. */
  30. public static function isClickable($doc, $action)
  31. {
  32. global $app;
  33. $action = strtolower($action);
  34. if(!empty($doc->frozen) && in_array($action, array('edit', 'delete'))) return false;
  35. if($action == 'movedoc') return $doc->addedBy == $app->user->account;
  36. return true;
  37. }
  38. /**
  39. * Get objectID by Lib.
  40. *
  41. * @param object $lib
  42. * @param string $libType
  43. * @access public
  44. * @return int
  45. */
  46. public function getObjectIDByLib($lib, $libType = '')
  47. {
  48. if(empty($lib)) return 0;
  49. if(empty($libType)) $libType = $lib->type;
  50. $objectID = ($libType == 'custom' || $libType == 'mine') ? $lib->parent : zget($lib, $libType, 0);
  51. return (int)$objectID;
  52. }
  53. /**
  54. * 通过ID获取文档库信息。
  55. * Get library by id.
  56. *
  57. * @param int $libID
  58. * @access public
  59. * @return object|false
  60. */
  61. public function getLibByID($libID)
  62. {
  63. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDocLib();
  64. return $this->dao->findByID($libID)->from(TABLE_DOCLIB)->fetch();
  65. }
  66. /**
  67. * 通过ID获取空间类型
  68. * Get space type by id.
  69. *
  70. * @param int|string $spaceID
  71. * @access public
  72. * @return string
  73. */
  74. public function getSpaceType($spaceID)
  75. {
  76. if(is_string($spaceID) && strpos($spaceID, '.') !== false) return explode('.', $spaceID)[0];
  77. return $this->dao->findByID((int)$spaceID)->from(TABLE_DOCLIB)->fetch('type');
  78. }
  79. /**
  80. * 获取api文档库。
  81. * Get api libraries.
  82. *
  83. * @param int $appendLib
  84. * @param string $objectType nolink|product|project
  85. * @param int $objectID
  86. * @return array
  87. */
  88. public function getApiLibs($appendLib = 0, $objectType = '', $objectID = 0)
  89. {
  90. $libs = $this->dao->select('*')->from(TABLE_DOCLIB)
  91. ->where('deleted')->eq(0)
  92. ->andWhere('type')->eq('api')
  93. ->beginIF(!empty($objectType) && $objectID > 0 && $objectType != 'nolink')->andWhere($objectType)->eq($objectID)->fi()
  94. ->beginIF($objectType == 'nolink')
  95. ->andWhere('product')->eq(0)
  96. ->andWhere('project')->eq(0)
  97. ->andWhere('execution')->eq(0)
  98. ->fi()
  99. ->beginIF(!empty($appendLib))->orWhere('id')->eq($appendLib)->fi()
  100. ->orderBy('order_asc, id_asc')
  101. ->fetchAll('id', false);
  102. return array_filter($libs, array($this, 'checkPrivLib'));
  103. }
  104. /**
  105. * 获取文档库。
  106. * Get libraries.
  107. *
  108. * @param string $type all|includeDeleted|hasApi|product|project|execution|custom|mine
  109. * @param string $extra withObject|notdoc
  110. * @param int|string $appendLibs
  111. * @param int $objectID
  112. * @param string $excludeType product|project|execution|custom|mine
  113. * @access public
  114. * @return array
  115. */
  116. public function getLibs($type = '', $extra = '', $appendLibs = '', $objectID = 0, $excludeType = '')
  117. {
  118. /* 如果当前在模板中,则不过滤项目和执行库。 */
  119. if(in_array($type, array('project', 'execution')) && $objectID)
  120. {
  121. $object = $this->loadModel('project')->fetchByID((int)$objectID);
  122. if($object->isTpl) dao::$filterTpl = 'never';
  123. }
  124. $projects = $this->loadModel('project')->getPairsByProgram(0, 'all', false, 'order_asc');
  125. $products = $this->loadModel('product')->getPairs();
  126. $executions = $this->loadModel('execution')->getPairs(0, 'all', 'multiple,leaf');
  127. $waterfalls = array();
  128. if(empty($objectID) && $type == 'execution')
  129. {
  130. $waterfalls = $this->dao->select('t1.id,t2.name')->from(TABLE_EXECUTION)->alias('t1')
  131. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  132. ->where('t1.type')->eq('stage')
  133. ->andWhere('t2.type')->eq('project')
  134. ->andWhere('t2.model')->eq('waterfall')
  135. ->andWhere('t1.deleted')->eq('0')
  136. ->andWhere('t2.deleted')->eq('0')
  137. ->fetchPairs('id', 'name');
  138. }
  139. $libPairs = $this->getLibPairs($type, $extra, $objectID, $excludeType, $products, $projects, $executions, $waterfalls);
  140. if(!empty($appendLibs))
  141. {
  142. $stmt = $this->dao->select('*')->from(TABLE_DOCLIB)->where('id')->in($appendLibs)->orderBy("`order`_asc, id_asc")->query();
  143. while($lib = $stmt->fetch())
  144. {
  145. if($lib->type == 'custom' && $lib->parent == 0) continue;
  146. if(!isset($libPairs[$lib->id]) && $this->checkPrivLib($lib, $extra)) $libPairs[$lib->id] = $lib->name;
  147. }
  148. }
  149. return $libPairs;
  150. }
  151. /**
  152. * 获取文档库键值对。
  153. * Get doc liberary pairs.
  154. *
  155. * @param string $type all|includeDeleted|hasApi|product|project|execution|custom|mine
  156. * @param string $extra withObject|notdoc
  157. * @param int $objectID
  158. * @param string $excludeType product|project|execution|custom|mine
  159. * @param array $products
  160. * @param array $projects
  161. * @param array $executions
  162. * @param array $waterfalls
  163. * @access public
  164. * @return array
  165. */
  166. public function getLibPairs($type, $extra = '', $objectID = 0, $excludeType = '', $products = array(), $projects = array(), $executions = array(), $waterfalls = array())
  167. {
  168. if(in_array($type, array('all', 'includeDeleted', 'hasApi')))
  169. {
  170. $stmt = $this->dao->select('*')->from(TABLE_DOCLIB)
  171. ->where('vision')->eq($this->config->vision)
  172. ->beginIF($type == 'all')->andWhere('deleted')->eq(0)->fi()
  173. ->beginIF($type != 'hasApi')->andWhere('type')->ne('api')->fi()
  174. ->beginIF($excludeType)->andWhere('type')->notin($excludeType)->fi()
  175. ->orderBy('`order`_asc')
  176. ->query();
  177. }
  178. else
  179. {
  180. $stmt = $this->dao->select('*')->from(TABLE_DOCLIB)
  181. ->where('deleted')->eq(0)
  182. ->beginIF($type != 'product' or $this->config->vision != 'or')->andWhere('vision')->eq($this->config->vision)->fi()
  183. ->beginIF($type)->andWhere('type')->eq($type)->fi()
  184. ->beginIF(!$type)->andWhere('type')->ne('api')->fi()
  185. ->beginIF($objectID && strpos(',product,project,execution,', ",$type,") !== false)->andWhere($type)->eq($objectID)->fi()
  186. ->beginIF(($type == 'custom' || $type == 'mine') && !$objectID)->andWhere('parent')->ne(0)->fi()
  187. ->beginIF(($type == 'custom' || $type == 'mine') && $objectID)->andWhere('parent')->eq($objectID)->fi()
  188. ->orderBy('`order`_asc')
  189. ->query();
  190. }
  191. $libPairs = array();
  192. while($lib = $stmt->fetch())
  193. {
  194. if($lib->product != 0 && !isset($products[$lib->product])) continue;
  195. if($lib->execution != 0 && !isset($executions[$lib->execution])) continue;
  196. if($lib->project != 0 && !isset($projects[$lib->project]) && $lib->type == 'project') continue;
  197. if($this->checkPrivLib($lib, $extra))
  198. {
  199. if(strpos($extra, 'withObject') !== false)
  200. {
  201. if($lib->product != 0) $lib->name = zget($products, $lib->product, '') . ' / ' . $lib->name;
  202. if($lib->execution != 0)
  203. {
  204. $lib->name = zget($executions, $lib->execution, '') . ' / ' . $lib->name;
  205. $lib->name = ltrim($lib->name, '/');
  206. if(!empty($waterfalls[$lib->execution])) $lib->name = $waterfalls[$lib->execution] . ' / ' . $lib->name;
  207. $lib->name = trim($lib->name, '/');
  208. }
  209. if($lib->project != 0) $lib->name = zget($projects, $lib->project, '') . ' / ' . $lib->name;
  210. if($lib->type == 'mine' || $lib->type == 'custom')
  211. {
  212. if($lib->parent == 0) continue;
  213. $parentLib = $this->getLibByID($lib->parent);
  214. $lib->name = $parentLib->name . ' / ' . $lib->name;
  215. }
  216. }
  217. $libPairs[$lib->id] = $lib->name;
  218. }
  219. }
  220. return $libPairs;
  221. }
  222. /**
  223. * 获取一个项目下的执行库。
  224. * Get execution libraries by project.
  225. *
  226. * @param int $projectID
  227. * @access public
  228. * @return array
  229. */
  230. public function getExecutionLibPairsByProject($projectID, $extra = '', $executions = array())
  231. {
  232. $libs = $this->dao->select('*')->from(TABLE_DOCLIB)
  233. ->where('deleted')->eq(0)
  234. ->andWhere('type')->eq('execution')
  235. ->andWhere('project')->eq($projectID)
  236. ->beginIF($this->config->vision != 'or')->andWhere('vision')->eq($this->config->vision)->fi()
  237. ->fetchAll('', false);
  238. $libPairs = array();
  239. foreach($libs as $lib)
  240. {
  241. if($this->checkPrivLib($lib))
  242. {
  243. if(strpos($extra, 'withObject') !== false)
  244. {
  245. $lib->name = zget($executions, $lib->execution, '') . ' / ' . $lib->name;
  246. $lib->name = ltrim($lib->name, '/');
  247. }
  248. $libPairs[$lib->id] = $lib->name;
  249. }
  250. }
  251. return $libPairs;
  252. }
  253. /**
  254. * 获取有权限访问的文档库。
  255. * Get grant libs by doc.
  256. *
  257. * @access public
  258. * @return array
  259. */
  260. public function getPrivLibsByDoc()
  261. {
  262. static $libs;
  263. if($libs === null)
  264. {
  265. $libs = array();
  266. $stmt = $this->dao->select('lib,`groups`,users')->from(TABLE_DOC)->where('acl')->ne('open')->andWhere("(`groups` != '' or users != '')")->query();
  267. $account = ",{$this->app->user->account},";
  268. $userGroups = $this->app->user->groups;
  269. while($lib = $stmt->fetch())
  270. {
  271. if(strpos(",$lib->users,", $account) !== false)
  272. {
  273. $libs[$lib->lib] = $lib->lib;
  274. }
  275. elseif($lib->groups)
  276. {
  277. foreach($userGroups as $groupID)
  278. {
  279. if(strpos(",$lib->groups,", ",$groupID,") !== false)
  280. {
  281. $libs[$lib->lib] = $lib->lib;
  282. break;
  283. }
  284. }
  285. }
  286. }
  287. }
  288. return $libs;
  289. }
  290. /**
  291. * 创建一个文档库。
  292. * Create a lib.
  293. *
  294. * @param object $lib
  295. * @param string $libType wiki|api
  296. * @param string $type api|project|product|execution|custom|mine
  297. * @access public
  298. * @return int|bool
  299. */
  300. public function createLib($lib, $type = '', $libType = '')
  301. {
  302. if(!empty($_POST['newSpace']))
  303. {
  304. if(!$lib->spaceName)
  305. {
  306. dao::$errors['spaceName'] = sprintf($this->lang->error->notempty, $this->lang->doc->space);
  307. return false;
  308. }
  309. $space = clone $lib;
  310. $space->name = $space->spaceName;
  311. $space->parent = 0;
  312. $space->acl = 'open';
  313. $spaceID = $this->docTao->doInsertLib($space);
  314. if(dao::isError()) return false;
  315. $lib->parent = $spaceID;
  316. $this->loadModel('action')->create('docspace', $spaceID, 'created');
  317. }
  318. elseif($lib->parent <= 0 && $type == 'custom')
  319. {
  320. dao::$errors['parent'] = sprintf($this->lang->error->notempty, $this->lang->doc->space);
  321. return false;
  322. }
  323. if($lib->execution) $lib->type = 'execution';
  324. if($lib->type == 'execution' && $lib->execution && !$lib->project)
  325. {
  326. $execution = $this->loadModel('execution')->getByID((int)$lib->execution);
  327. $lib->project = $execution->project;
  328. }
  329. if($libType == 'api')
  330. {
  331. $lib->type = 'api';
  332. $this->checkApiLibName($lib, $type);
  333. }
  334. $libID = $this->docTao->doInsertLib($lib, $this->config->doc->createlib->requiredFields);
  335. $this->dao->update(TABLE_DOCLIB)->set('`order`')->eq($libID)->where('id')->eq($libID)->exec();
  336. if(dao::isError()) return false;
  337. return $libID;
  338. }
  339. /**
  340. * 创建一个API接口库。
  341. * Creat a api doc library.
  342. *
  343. * @param object $formData
  344. * @access public
  345. * @return bool|int
  346. */
  347. public function createApiLib($formData = null)
  348. {
  349. $this->app->loadLang('api');
  350. /* Replace doc library name. */
  351. $this->lang->doclib->name = $this->lang->doclib->apiLibName;
  352. $this->lang->doclib->baseUrl = $this->lang->api->baseUrl;
  353. $this->lang->doclib->project = $this->lang->api->project;
  354. $this->lang->doclib->product = $this->lang->api->product;
  355. if($formData->libType == 'product') $this->config->api->createlib->requiredFields .= ',product';
  356. if($formData->libType == 'project') $this->config->api->createlib->requiredFields .= ',project';
  357. $this->checkApiLibName($formData, $formData->libType);
  358. if(dao::isError()) return false;
  359. $formData->type = static::DOC_TYPE_API;
  360. $this->dao->insert(TABLE_DOCLIB)->data($formData, 'libType')->autoCheck()
  361. ->batchCheck($this->config->api->createlib->requiredFields, 'notempty')
  362. ->exec();
  363. if(dao::isError()) return false;
  364. $libID = $this->dao->lastInsertID();
  365. $this->loadModel('action')->create('doclib', $libID, 'created');
  366. return $libID;
  367. }
  368. /**
  369. * 更新一个API接口库。
  370. * Update an api lib.
  371. *
  372. * @param int $id
  373. * @param object $formData
  374. * @access public
  375. * @return array|false
  376. */
  377. public function updateApiLib($id, $formData = null)
  378. {
  379. $oldLib = $this->getLibByID($id);
  380. /* Replace doc library name. */
  381. $this->app->loadLang('api');
  382. $this->lang->doclib->name = $this->lang->doclib->apiLibName;
  383. $this->lang->doclib->baseUrl = $this->lang->api->baseUrl;
  384. $this->lang->doclib->project = $this->lang->api->project;
  385. $this->lang->doclib->product = $this->lang->api->product;
  386. $this->checkApiLibName($formData, $formData->type, $id);
  387. if(dao::isError()) return false;
  388. $formData->type = static::DOC_TYPE_API;
  389. $this->dao->update(TABLE_DOCLIB)->data($formData, 'type')->autoCheck()
  390. ->batchCheck($this->config->api->editlib->requiredFields, 'notempty')
  391. ->where('id')->eq($id)
  392. ->exec();
  393. if(dao::isError()) return false;
  394. $changes = common::createChanges($oldLib, $formData);
  395. if($changes)
  396. {
  397. $actionID = $this->loadModel('action')->create('docLib', $id, 'Edited');
  398. $this->action->logHistory($actionID, $changes);
  399. }
  400. return $changes;
  401. }
  402. /**
  403. * 更新一个文档库。
  404. * Update a doc lib.
  405. *
  406. * @param int $libID
  407. * @param object $lib
  408. * @access public
  409. * @return array|bool
  410. */
  411. public function updateLib($libID, $lib)
  412. {
  413. $oldLib = $this->getLibByID($libID);
  414. if($oldLib->type == 'project')
  415. {
  416. $libCreatedBy = $this->dao->select('*')->from(TABLE_ACTION)->where('objectType')->eq('doclib')->andWhere('objectID')->eq($libID)->andWhere('action')->eq('created')->fetch('actor');
  417. $openedBy = $this->dao->findById($oldLib->project)->from(TABLE_PROJECT)->fetch('openedBy');
  418. if($lib->acl == 'private' && $lib->acl == 'custom') $lib->users .= ',' . $libCreatedBy ? $libCreatedBy : $openedBy;
  419. }
  420. if($oldLib->acl != $lib->acl && $lib->acl == 'open') $lib->users = '';
  421. if($oldLib->type == 'api')
  422. {
  423. $type = 'nolink';
  424. if(!empty($oldLib->product)) $type = 'product';
  425. if(!empty($oldLib->project)) $type = 'project';
  426. $lib->product = $oldLib->product;
  427. $lib->project = $oldLib->project;
  428. $this->checkApiLibName($lib, $type, $libID);
  429. }
  430. $this->dao->update(TABLE_DOCLIB)->data($lib, 'space')->autoCheck()
  431. ->batchCheck($this->config->doc->editlib->requiredFields, 'notempty')
  432. ->where('id')->eq($libID)
  433. ->exec();
  434. $this->moveLib($libID, $lib);
  435. if(dao::isError()) return false;
  436. return common::createChanges($oldLib, $lib);
  437. }
  438. /**
  439. * 通过类型获取文档列表数据。
  440. * Get doc list data by browse type.
  441. *
  442. * @param string $browseType all|bySearch|openedbyme|editedbyme|byediteddate|collectedbyme
  443. * @param int $queryID
  444. * @param int $moduleID
  445. * @param string $sort
  446. * @param object $pager
  447. * @access public
  448. * @return array
  449. */
  450. public function getDocsByBrowseType($browseType, $queryID, $moduleID, $sort, $pager = null)
  451. {
  452. if($browseType == 'all') return $this->getDocs(0, 0, $browseType, $sort, $pager);
  453. $allLibs = $this->getLibs('all');
  454. $allLibIDList = array_keys($allLibs);
  455. $hasPrivDocIdList = $this->getPrivDocs($allLibIDList, $moduleID);
  456. if($browseType == 'bySearch')
  457. {
  458. $docs = $this->getMyDocListBySearch($queryID, $hasPrivDocIdList, $allLibIDList, $sort, $pager);
  459. }
  460. elseif($browseType == "openedbyme")
  461. {
  462. $docs = $this->docTao->getOpenedDocs($hasPrivDocIdList, $sort, $pager);
  463. }
  464. elseif($browseType == 'editedbyme')
  465. {
  466. $docs = $this->docTao->getEditedDocs($sort, $pager);
  467. }
  468. elseif($browseType == 'byediteddate')
  469. {
  470. $docs = $this->docTao->getOrderedDocsByEditedDate($hasPrivDocIdList, $allLibIDList, $pager);
  471. }
  472. elseif($browseType == "collectedbyme")
  473. {
  474. $docs = $this->docTao->getCollectedDocs($hasPrivDocIdList, $sort, $pager);
  475. }
  476. if(empty($docs)) return array();
  477. if(!in_array($browseType, array('bySearch', 'openedbyme', 'editedbyme'))) return $this->processCollector($docs);
  478. $objects = array();
  479. list($objects['project'], $objects['execution'], $objects['product']) = $this->getObjectsByDoc(array_keys($docs));
  480. foreach($docs as $docID => $doc)
  481. {
  482. $doc->objectID = zget($doc, $doc->objectType, 0);
  483. $doc->objectName = '';
  484. if(isset($objects[$doc->objectType]))
  485. {
  486. $doc->objectName = zget($objects[$doc->objectType], $doc->objectID);
  487. }
  488. else
  489. {
  490. if($doc->objectType == 'mine') $doc->objectName = $this->lang->doc->person;
  491. if($doc->objectType == 'custom') $doc->objectName = $this->lang->doc->team;
  492. }
  493. }
  494. return $this->processCollector($docs);
  495. }
  496. /**
  497. * 获取搜索后的文档列表。
  498. * Get doc list by search.
  499. *
  500. * @param int $queryID
  501. * @param array $hasPrivDocIdList
  502. * @param array $allLibIDList
  503. * @param string $sort
  504. * @param object $pager
  505. * @access public
  506. * @return array
  507. */
  508. public function getMyDocListBySearch($queryID, $hasPrivDocIdList, $allLibIDList, $sort, $pager = null)
  509. {
  510. if($queryID)
  511. {
  512. $query = $this->loadModel('search')->getQuery($queryID);
  513. if($query)
  514. {
  515. $this->session->set('contributeDocQuery', $query->sql);
  516. $this->session->set('contributeDocForm', $query->form);
  517. }
  518. else
  519. {
  520. $this->session->set('contributeDocQuery', ' 1 = 1');
  521. }
  522. }
  523. else
  524. {
  525. if($this->session->contributeDocQuery == false) $this->session->set('contributeDocQuery', ' 1 = 1');
  526. }
  527. $query = $this->getDocQuery($this->session->contributeDocQuery);
  528. $query = preg_replace('/(`\w+`)/', 't1.$1', $query);
  529. $docIDList = $this->dao->select('objectID')->from(TABLE_ACTION)
  530. ->where('objectType')->eq('doc')
  531. ->andWhere('objectID')->in($hasPrivDocIdList)
  532. ->andWhere('actor')->eq($this->app->user->account)
  533. ->andWhere('action')->eq('edited')
  534. ->fetchAll('objectID');
  535. return $this->dao->select('t1.*, t2.name as libName, t2.type as objectType')->from(TABLE_DOC)->alias('t1')
  536. ->leftJoin(TABLE_DOCLIB)->alias('t2')->on("t1.lib=t2.id")
  537. ->where('t1.deleted')->eq(0)
  538. ->andWhere($query)
  539. ->andWhere('t1.lib')->in($allLibIDList)
  540. ->andWhere('t1.templateType')->eq('')
  541. ->andWhere('t1.vision')->in($this->config->vision)
  542. ->beginIF($this->config->doc->notArticleType)->andWhere('t1.type')->notIN($this->config->doc->notArticleType)->fi()
  543. ->andWhere('t1.addedBy', 1)->eq($this->app->user->account)
  544. ->orWhere('t1.id')->in(array_keys($docIDList))
  545. ->markRight(1)
  546. ->orderBy($sort)
  547. ->page($pager)
  548. ->fetchAll('id', false);
  549. }
  550. /**
  551. * 替换查询语句中的all。
  552. * Replace all in query.
  553. *
  554. * @param string $query
  555. * @access public
  556. * @return string
  557. */
  558. public function getDocQuery($query)
  559. {
  560. $allLib = "`lib` = 'all'";
  561. if(strpos($query, $allLib) !== false)
  562. {
  563. $libPairs = $this->loadModel('doc')->getLibs('all');
  564. $libIdList = empty($libPairs) ? 0 : implode(',', array_keys($libPairs));
  565. $query = str_replace($allLib, '1', $query);
  566. $query = $query . ' AND `lib` ' . helper::dbIN($libIdList);
  567. }
  568. $allProject = "`project` = 'all'";
  569. if(strpos($query, $allProject) !== false)
  570. {
  571. $projectPairs = $this->loadModel('project')->getPairsByProgram();
  572. $projectIdList = empty($projectPairs) ? 0 : implode(',', array_keys($projectPairs));
  573. $query = str_replace($allProject, '1', $query);
  574. $query = $query . ' AND `project` in (' . $projectIdList . ')';
  575. }
  576. $allProduct = "`product` = 'all'";
  577. if(strpos($query, $allProduct) !== false)
  578. {
  579. $productPairs = $this->loadModel('product')->getPairs();
  580. $productIdList = empty($productPairs) ? 0 : implode(',', array_keys($productPairs));
  581. $query = str_replace($allProduct, '1', $query);
  582. $query = $query . ' AND `product` ' . helper::dbIN($productIdList);
  583. }
  584. $allExecution = "`execution` = 'all'";
  585. if(strpos($query, $allExecution) !== false)
  586. {
  587. $executionPairs = $this->loadModel('execution')->getPairs();
  588. $executionIdList = empty($executionPairs) ? 0 : implode(',', array_keys($executionPairs));
  589. $query = str_replace($allExecution, '1', $query);
  590. $query = $query . ' AND `execution` ' . helper::dbIN($executionIdList);
  591. }
  592. return $query;
  593. }
  594. /**
  595. * 获取文档模板列表。
  596. * Get document template list.
  597. *
  598. * @param int $libID
  599. * @param string $type
  600. * @param string $orderBy
  601. * @param object $pager
  602. * @param string $searchName
  603. * @access public
  604. * @return array
  605. */
  606. public function getDocTemplateList($libID = 0, $type = 'all', $orderBy = 'id_desc', $pager = null, $searchName = '')
  607. {
  608. return $this->dao->select('t1.*')->from(TABLE_DOC)->alias('t1')
  609. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id')
  610. ->where('t2.type')->eq('docTemplate')
  611. ->andWhere('t1.builtIn')->eq('0')
  612. ->andWhere('t1.vision')->eq($this->config->vision)
  613. ->beginIF(!$this->app->user->admin)->andWhere("(t1.`status` = 'normal' or (t1.`status` = 'draft' and t1.`addedBy`='{$this->app->user->account}'))")->fi()
  614. ->beginIF($libID)->andWhere('t1.lib')->eq($libID)->fi()
  615. ->beginIF($type == 'draft')->andWhere('t1.status')->eq('draft')->fi()
  616. ->beginIF($type == 'released')->andWhere('t1.status')->eq('normal')->fi()
  617. ->beginIF($type == 'createdByMe')->andWhere('t1.addedBy')->eq($this->app->user->account)->fi()
  618. ->beginIF($searchName)->andWhere('t1.title')->like("%{$searchName}%")->fi()
  619. ->orderBy($orderBy)
  620. ->page($pager)
  621. ->fetchAll('', false);
  622. }
  623. /**
  624. * 通过文档ID获取文档所属产品、项目、执行。
  625. * Get projects, executions and products by docIdList.
  626. *
  627. * @param array $docIdList
  628. * @access public
  629. * @return array
  630. */
  631. public function getObjectsByDoc($docIdList = array())
  632. {
  633. $projects = $executions = $products = array();
  634. if(empty($docIdList)) return array($projects, $executions, $products);
  635. $projects = $this->dao->select('t1.id, t1.name')->from(TABLE_PROJECT)->alias('t1')
  636. ->leftJoin(TABLE_DOC)->alias('t2')->on('t1.id=t2.project')
  637. ->where('t2.id')->in($docIdList)
  638. ->andWhere('t2.execution')->eq(0)
  639. ->fetchPairs();
  640. $executions = $this->dao->select('t1.id, t1.name')->from(TABLE_EXECUTION)->alias('t1')
  641. ->leftJoin(TABLE_DOC)->alias('t2')->on('t1.id=t2.execution')
  642. ->where('t2.id')->in($docIdList)
  643. ->fetchPairs();
  644. $products = $this->dao->select('t1.id, t1.name')->from(TABLE_PRODUCT)->alias('t1')
  645. ->leftJoin(TABLE_DOC)->alias('t2')->on('t1.id=t2.product')
  646. ->where('t2.id')->in($docIdList)
  647. ->fetchPairs();
  648. return array($projects, $executions, $products);
  649. }
  650. /**
  651. * 获取当前文档库下的文档列表数据。
  652. * Get doc list by libID.
  653. *
  654. * @param int $libID
  655. * @param int $moduleID
  656. * @param string $browseType all|draft
  657. * @param string $orderBy
  658. * @param object $pager
  659. * @access public
  660. * @return array
  661. */
  662. public function getDocs($libID, $moduleID, $browseType, $orderBy, $pager = null)
  663. {
  664. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDocs();
  665. if(empty($libID) && $browseType != 'all') return array();
  666. $docIdList = $this->getPrivDocs($libID ? array($libID) : array(), $moduleID, 'children');
  667. $docs = $this->dao->select('*')->from(TABLE_DOC)
  668. ->where('deleted')->eq(0)
  669. ->andWhere('vision')->eq($this->config->vision)
  670. ->andWhere('templateType')->eq('')
  671. ->andWhere('id')->in($docIdList)
  672. ->beginIF($browseType == 'all')->andWhere("(status = 'normal' or (status = 'draft' and addedBy='{$this->app->user->account}'))")->fi()
  673. ->beginIF($browseType == 'draft')->andWhere('status')->eq('draft')->andWhere('addedBy')->eq($this->app->user->account)->fi()
  674. ->orderBy($orderBy)
  675. ->page($pager)
  676. ->fetchAll('id',false);
  677. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'doc', true);
  678. return $this->processCollector($docs);
  679. }
  680. /**
  681. * Get docs by id list.
  682. *
  683. * @param array $docIdList
  684. * @access public
  685. * @return array
  686. */
  687. public function getDocsByIdList($docIdList)
  688. {
  689. return $this->dao->select('*')->from(TABLE_DOC)->where('id')->in($docIdList)->fetchAll('id', false);
  690. }
  691. /**
  692. * 获取文档列表数据。
  693. * Get doc list.
  694. *
  695. * @param array $libs
  696. * @param string $spaceType
  697. * @param int $excludeID
  698. * @param bool $queryTemplate
  699. * @access public
  700. * @return array
  701. */
  702. public function getDocsOfLibs($libs, $spaceType, $excludeID = 0, $queryTemplate = false)
  703. {
  704. $docs = $this->dao->select('t1.*,t3.content')->from(TABLE_DOC)->alias('t1')
  705. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id')
  706. ->leftJoin(TABLE_DOCCONTENT)->alias('t3')->on('t1.id=t3.doc and t1.version=t3.version')
  707. ->where('t1.lib')->in($libs)
  708. ->andWhere('t1.vision')->eq($this->config->vision)
  709. ->beginIF(!$queryTemplate)->andWhere('t1.templateType')->eq('')->andWhere('t2.type')->eq('doc')->fi()
  710. ->beginIF($queryTemplate)->andWhere('t1.templateType')->ne('')->andWhere('t1.builtIn')->eq('0')->andWhere('t2.type')->eq('docTemplate')->fi()
  711. ->andWhere("(t1.status = 'normal' or (t1.status = 'draft' and t1.addedBy='{$this->app->user->account}'))")
  712. ->andWhere("(t2.deleted = '0' or t1.parent != '0')")
  713. ->beginIF(!empty($excludeID))->andWhere("NOT FIND_IN_SET('{$excludeID}', t1.`path`)")->andWhere('t1.id')->ne($excludeID)->fi()
  714. ->orderBy('t1.`order` asc, t1.id asc')
  715. ->fetchAll('id', false);
  716. $rootDocs = $this->dao->select('t1.*,t3.content')->from(TABLE_DOC)->alias('t1')
  717. ->leftJoin(TABLE_DOCCONTENT)->alias('t3')->on('t1.id=t3.doc and t1.version=t3.version')
  718. ->where('t1.lib')->in($libs)
  719. ->andWhere('t1.vision')->eq($this->config->vision)
  720. ->beginIF(!$queryTemplate)->andWhere('t1.templateType')->eq('')->fi()
  721. ->beginIF($queryTemplate)->andWhere('t1.templateType')->ne('')->andWhere('builtIn')->eq('0')->fi()
  722. ->andWhere("(t1.status = 'normal' or (t1.status = 'draft' and t1.addedBy='{$this->app->user->account}'))")
  723. ->andWhere('t1.module')->in(array('0', ''))
  724. ->beginIF(!empty($excludeID))->andWhere("NOT FIND_IN_SET('{$excludeID}', t1.`path`)")->andWhere('t1.id')->ne($excludeID)->fi()
  725. ->orderBy('`order` asc, id_asc')
  726. ->fetchAll('id', false);
  727. $docs = arrayUnion($docs, $rootDocs);
  728. $docs = $this->docTao->filterDeletedDocs($docs);
  729. $docs = $this->filterPrivDocs($docs, $spaceType);
  730. $docs = $this->processCollector($docs);
  731. foreach($docs as &$doc)
  732. {
  733. $doc->lib = (int)$doc->lib;
  734. $doc->module = (int)$doc->module;
  735. $doc->deleted = boolval($doc->deleted);
  736. $doc->isCollector = strpos($doc->collector, ',' . $this->app->user->account . ',') !== false;
  737. $doc->title = htmlspecialchars_decode($doc->title);
  738. if(!empty($doc->keywords) && is_string($doc->keywords)) $doc->keywords = htmlspecialchars_decode($doc->keywords);
  739. $doc->hasContent = !empty($doc->content) ? true : false;
  740. unset($doc->content);
  741. unset($doc->draft);
  742. }
  743. $apis = $this->loadModel('api')->getApiListBySearch(0, 0, '', $libs);
  744. foreach($apis as &$api)
  745. {
  746. $api->id = "api.$api->id";
  747. $api->lib = (int)$api->lib;
  748. $api->module = (int)$api->module;
  749. $api->deleted = boolval($api->deleted);
  750. $api->originTitle = $api->title;
  751. $api->icon = "api is-$api->method";
  752. $api->title = "$api->method $api->path $api->title";
  753. $docs[$api->id] = $api;
  754. }
  755. return $docs;
  756. }
  757. /**
  758. * 过滤出有权限的文档列表。
  759. * Filter docs which has privilege.
  760. *
  761. * @param array $docs
  762. * @param string $spaceType
  763. * @access public
  764. * @return array
  765. */
  766. public function filterPrivDocs($docs, $spaceType)
  767. {
  768. $privDocs = array();
  769. $noPrivDocs = array();
  770. foreach($docs as $doc)
  771. {
  772. $this->setDocPriv($doc, $spaceType);
  773. if($doc->readable || $doc->editable) $privDocs[$doc->id] = $doc;
  774. else $noPrivDocs[] = $doc->id;
  775. }
  776. foreach($noPrivDocs as $docID)
  777. {
  778. foreach($privDocs as $doc)
  779. {
  780. if(strpos(",{$doc->path},", ",{$docID},") !== false) unset($privDocs[$doc->id]);
  781. }
  782. }
  783. return $privDocs;
  784. }
  785. /**
  786. * 设置文档的权限。
  787. * Set doc privilege.
  788. *
  789. * @param object $doc
  790. * @access public
  791. * @return object
  792. * @param string $spaceType
  793. */
  794. public function setDocPriv($doc, $spaceType)
  795. {
  796. $currentAccount = $this->app->user->account;
  797. $userGroups = $this->app->user->groups;
  798. $doc->readable = false;
  799. $doc->editable = false;
  800. $isOpen = $doc->acl == 'open';
  801. $isAuthorOrAdmin = $doc->addedBy == $currentAccount || ($this->app->user->admin && $spaceType !== 'mine');
  802. $isInReadUsers = strpos(",$doc->readUsers,", ",$currentAccount,") !== false;
  803. $isInEditUsers = strpos(",$doc->users,", ",$currentAccount,") !== false;
  804. if($isOpen || $isAuthorOrAdmin || $isInReadUsers || $isInEditUsers)
  805. {
  806. $doc->editable = $isOpen || $isAuthorOrAdmin || $isInEditUsers;
  807. $doc->readable = $isOpen || $isAuthorOrAdmin || $isInReadUsers || $doc->editable;
  808. if($spaceType == 'template') $doc->editable = ($isOpen && common::hasPriv('doc', 'editTemplate')) || $isAuthorOrAdmin || $isInEditUsers;
  809. }
  810. elseif(!empty($doc->groups) || !empty($doc->readGroups))
  811. {
  812. $isInReadGroups = false;
  813. $isInEditGroups = false;
  814. foreach($userGroups as $groupID)
  815. {
  816. if(strpos(",$doc->groups,", ",$groupID,") !== false) $isInEditGroups = true;
  817. if(strpos(",$doc->readGroups,", ",$groupID,") !== false) $isInReadGroups = true;
  818. }
  819. $doc->editable = $isInEditGroups;
  820. $doc->readable = $isInReadGroups || $doc->editable;
  821. }
  822. return $doc;
  823. }
  824. /**
  825. * 获取我的空间下的文档列表数据。
  826. * Get mine list.
  827. *
  828. * @param string $type view|collect|createdby|editedby
  829. * @param string $browseType all|draft|bysearch
  830. * @param int $queryID
  831. * @param string $orderBy
  832. * @param object $pager
  833. * @access public
  834. * @return array
  835. */
  836. public function getMineList($type, $browseType, $queryID = 0, $orderBy = 'id_desc', $pager = null)
  837. {
  838. $query = '';
  839. if($browseType == 'bysearch')
  840. {
  841. $query = $this->buildQuery($type, $queryID);
  842. $query = preg_replace('/(`\w+`)/', 't1.$1', $query);
  843. }
  844. if(in_array($type, array('view', 'collect', 'createdby', 'editedby'))) $docs = $this->getMySpaceDocs($type, $browseType, $query, $orderBy, $pager);
  845. $this->loadModel('tree');
  846. $currentAccount = $this->app->user->account;
  847. $objects = array();
  848. $modules = array();
  849. list($objects['project'], $objects['execution'], $objects['product']) = $this->getObjectsByDoc(array_keys($docs));
  850. foreach($docs as $docID => $doc)
  851. {
  852. if(!isset($modules[$doc->lib])) $modules[$doc->lib] = $this->tree->getOptionMenu((int)$doc->lib, 'doc', 0, 'all', 'nodeleted', 'all', ' > ');
  853. $doc->moduleName = zget($modules[$doc->lib], $doc->module);
  854. $doc->moduleName = ltrim((string)$doc->moduleName, '/');
  855. $doc->objectID = zget($doc, $doc->objectType, 0);
  856. $doc->objectName = '';
  857. $doc->editable = false;
  858. $isOpen = $doc->acl == 'open';
  859. $isAuthorOrAdmin = $doc->acl == 'private' && ($doc->addedBy == $currentAccount || ($this->app->user->admin));
  860. $isInReadUsers = strpos(",$doc->readUsers,", ",$currentAccount,") !== false;
  861. $isInEditUsers = strpos(",$doc->users,", ",$currentAccount,") !== false;
  862. if($isOpen || $isAuthorOrAdmin || $isInReadUsers || $isInEditUsers)
  863. {
  864. $doc->editable = $isOpen || $isAuthorOrAdmin || $isInEditUsers;
  865. }
  866. if(isset($objects[$doc->objectType][$doc->objectID]))
  867. {
  868. $doc->objectName = $objects[$doc->objectType][$doc->objectID];
  869. }
  870. else
  871. {
  872. if($doc->objectType == 'mine') $doc->objectName = $this->lang->doc->person;
  873. if($doc->objectType == 'custom') $doc->objectName = $this->lang->doc->team;
  874. }
  875. }
  876. $docs = $this->filterPrivDocs($docs, 'mine');
  877. return $this->processCollector($docs);
  878. }
  879. /**
  880. * 获取我的空间下的文档列表数据。
  881. * Get doc list under the my space.
  882. *
  883. * @param string $type view|collect|createdby|editedby
  884. * @param string $browseType all|draft|bysearch
  885. * @param string $orderBy
  886. * @param string $query
  887. * @param object $pager
  888. * @param string $appendDocs
  889. * @param string $filterDocs
  890. * @access public
  891. * @return array
  892. */
  893. public function getMySpaceDocs($type, $browseType, $query = '', $orderBy = 'id_desc', $pager = null, $appendDocs = '', $filterDocs = '')
  894. {
  895. if(!in_array($type, array('all', 'view', 'collect', 'createdby', 'editedby'))) return array();
  896. $allLibs = $this->getLibs('all');
  897. $allLibIDList = array_keys($allLibs);
  898. $hasPrivDocIdList = $this->getPrivDocs($allLibIDList);
  899. if(in_array($type, array('view', 'collect')))
  900. {
  901. $docSQL = $this->dao->select('MAX(id)')->from(TABLE_DOCACTION)
  902. ->where('action')->eq($type)
  903. ->andWhere('actor')->eq($this->app->user->account)
  904. ->groupBy('doc')
  905. ->get();
  906. $docs = $this->dao->select('t1.*,t2.date,t3.name as libName,t3.type as objectType')->from(TABLE_DOC)->alias('t1')
  907. ->leftJoin(TABLE_DOCACTION)->alias('t2')->on("t1.id=t2.doc")
  908. ->leftJoin(TABLE_DOCLIB)->alias('t3')->on("t1.lib=t3.id")
  909. ->where('t1.deleted')->eq(0)
  910. ->andWhere('t1.templateType')->eq('')
  911. ->andWhere('t1.lib')->ne(0)
  912. ->andWhere('t1.type')->in($this->config->doc->docTypes)
  913. ->andWhere('t1.vision')->eq($this->config->vision)
  914. ->andWhere('t2.id')->subIn($docSQL)
  915. ->beginIF(!common::hasPriv('doc', 'productSpace'))->andWhere('t3.type')->ne('product')->fi()
  916. ->beginIF(!common::hasPriv('doc', 'projectSpace'))->andWhere('t3.type')->notIN('project,execution')->fi()
  917. ->beginIF(!common::hasPriv('doc', 'teamSpace'))->andWhere('t3.type')->ne('custom')->fi()
  918. ->beginIF(in_array($browseType, array('all', 'bysearch')))->andWhere("(t1.status = 'normal' or (t1.status = 'draft' and t1.addedBy='{$this->app->user->account}'))")->fi()
  919. ->beginIF($browseType == 'draft')->andWhere('t1.status')->eq('draft')->andWhere('t1.addedBy')->eq($this->app->user->account)->fi()
  920. ->beginIF($browseType == 'bysearch')->andWhere($query)->fi()
  921. ->beginIF($browseType == 'bykeyword')->andWhere('t1.status')->eq('normal')->fi()
  922. ->beginIF($browseType == 'bykeyword' && $query)->andWhere('t1.title')->like("%$query%")->fi()
  923. ->beginIF(!empty($hasPrivDocIdList))->andWhere('t1.id')->in($hasPrivDocIdList)->fi()
  924. ->beginIF($filterDocs)->andWhere('t1.id')->notIN($filterDocs)->fi()
  925. ->beginIF($appendDocs)->orWhere('t1.id')->in($appendDocs)->fi()
  926. ->orderBy($orderBy)
  927. ->page($pager, 't1.id')
  928. ->fetchAll('id', false);
  929. }
  930. else
  931. {
  932. $docIdList = $type == 'editedby' ? $this->docTao->getEditedDocIdList() : array();
  933. $docs = $this->dao->select('t1.*,t2.name as libName,t2.type as objectType')->from(TABLE_DOC)->alias('t1')->leftJoin(TABLE_DOCLIB)->alias('t2')->on("t1.lib=t2.id")
  934. ->where('t1.deleted')->eq(0)
  935. ->andWhere('t1.lib')->ne(0)
  936. ->andWhere('t1.templateType')->eq('')
  937. ->andWhere('t1.vision')->eq($this->config->vision)
  938. ->andWhere('t1.type')->in($this->config->doc->docTypes)
  939. ->beginIF($type == 'createdby')->andWhere('t1.addedBy')->eq($this->app->user->account)->fi()
  940. ->beginIF($type == 'editedby')->andWhere('t1.id')->in($docIdList)->fi()
  941. ->beginIF(!common::hasPriv('doc', 'productSpace'))->andWhere('t2.type')->ne('product')->fi()
  942. ->beginIF(!common::hasPriv('doc', 'projectSpace'))->andWhere('t2.type')->notIN('project,execution')->fi()
  943. ->beginIF(!common::hasPriv('doc', 'teamSpace'))->andWhere('t2.type')->ne('custom')->fi()
  944. ->beginIF($browseType == 'draft')->andWhere('t1.status')->eq('draft')->andWhere('t1.addedBy')->eq($this->app->user->account)->fi()
  945. ->beginIF($browseType == 'bysearch')->andWhere($query)->fi()
  946. ->beginIF($browseType == 'bykeyword')->andWhere('t1.status')->eq('normal')->fi()
  947. ->beginIF($browseType == 'bykeyword' && $query)->andWhere('t1.title')->like("%$query%")->fi()
  948. ->beginIF(!empty($hasPrivDocIdList))->andWhere('t1.id')->in($hasPrivDocIdList)->fi()
  949. ->beginIF($filterDocs)->andWhere('t1.id')->notIN($filterDocs)->fi()
  950. ->beginIF($appendDocs)->orWhere('t1.id')->in($appendDocs)->fi()
  951. ->orderBy($orderBy)
  952. ->page($pager)
  953. ->fetchAll('id', false);
  954. }
  955. return $docs;
  956. }
  957. /**
  958. * 获取有权限查看的文档ID列表。
  959. * Get grant doc id list.
  960. *
  961. * @param array $libIdList
  962. * @param int $moduleID
  963. * @param string $mode normal|all|chidren
  964. * @access public
  965. * @return array
  966. */
  967. public function getPrivDocs($libIdList = array(), $moduleID = 0, $mode = 'normal')
  968. {
  969. $modules = $moduleID && $mode == 'children' ? $this->loadModel('tree')->getAllChildID($moduleID) : $moduleID;
  970. /* 团队空间下的空间列出所有子库的文档。 */
  971. if(count($libIdList) == 1)
  972. {
  973. $libID = current($libIdList);
  974. $lib = $this->getLibByID((int)$libID);
  975. if($lib->type == 'custom' && $lib->parent == 0)
  976. {
  977. $libs = $this->dao->select('*')->from(TABLE_DOCLIB)->where('parent')->eq($libID)->andWhere('deleted')->eq('0')->fetchAll();
  978. foreach($libs as $subLib)
  979. {
  980. if($this->checkPrivLib($subLib)) $libIdList[] = $subLib->id;
  981. }
  982. }
  983. }
  984. $docs = $this->dao->select("`id`,`addedBy`,`type`,`lib`,`acl`,`users`,`readUsers`,`groups`,`readGroups`,`status`,`path`,`deleted`")->from(TABLE_DOC)
  985. ->where('vision')->eq($this->config->vision)
  986. ->andWhere('templateType')->eq('')
  987. ->beginIF(!empty($modules))->andWhere('module')->in($modules)->fi()
  988. ->beginIF($mode == 'normal')->andWhere('deleted')->eq(0)->fi()
  989. ->beginIF($this->config->doc->notArticleType)->andWhere('type')->notIN($this->config->doc->notArticleType)->fi()
  990. ->beginIF($libIdList)->andWhere('lib')->in($libIdList)->fi()
  991. ->fetchAll('id');
  992. $docs = $this->batchCheckPrivDoc($docs);
  993. $docIdList = array_keys($docs);
  994. return array_combine($docIdList, $docIdList);
  995. }
  996. /**
  997. * 通过ID获取文档信息。
  998. * Get doc info by id.
  999. *
  1000. * @param int $docID
  1001. * @param int $version
  1002. * @param bool $setImgSize
  1003. * @access public
  1004. * @return object|false
  1005. */
  1006. public function getByID($docID, $version = 0, $setImgSize = false)
  1007. {
  1008. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDoc();
  1009. $doc = $this->dao->select('*')->from(TABLE_DOC)
  1010. ->where('id')->eq((int)$docID)
  1011. ->andWhere('vision')->eq($this->config->vision)
  1012. ->fetch();
  1013. if(!$doc) return false;
  1014. $docs = $this->processCollector(array($doc->id => $doc));
  1015. $doc = $docs[$doc->id];
  1016. $version = $version ? $version : $doc->version;
  1017. $doc->releasedBy = $doc->releasedDate = '';
  1018. if($doc->status == 'normal')
  1019. {
  1020. $releaseInfo = $this->dao->select('*')->from(TABLE_ACTION)
  1021. ->where('objectType')->eq('doc')
  1022. ->andWhere('objectID')->eq($docID)
  1023. ->andWhere('action')->eq('releaseddoc')
  1024. ->fetch();
  1025. $doc->releasedBy = $releaseInfo ? $releaseInfo->actor : $doc->addedBy;
  1026. $doc->releasedDate = $releaseInfo ? $releaseInfo->date : $doc->addedDate;
  1027. }
  1028. return $this->processDoc($doc, (int)$version, $setImgSize);
  1029. }
  1030. /**
  1031. * 通过文档ID和版本号获取文档内容,当 $version 为 0 时,获取文档的草稿内容。
  1032. * Get doc content by docID and version, when $version is 0, get doc draft content.
  1033. *
  1034. * @param int $docID
  1035. * @param int $version
  1036. * @access public
  1037. * @return ?object
  1038. */
  1039. public function getContent($docID, $version)
  1040. {
  1041. $docContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($docID)->andWhere('version')->eq($version)->fetch();
  1042. if($docContent)
  1043. {
  1044. $docContent->addedDate = empty($docContent->addedDate) ? null : $docContent->addedDate;
  1045. $docContent->editedDate = empty($docContent->editedDate) ? null : $docContent->editedDate;
  1046. $docContent->title = htmlspecialchars_decode($docContent->title);
  1047. return $docContent;
  1048. }
  1049. return null;
  1050. }
  1051. /**
  1052. * 处理文档数据。
  1053. * Process doc data.
  1054. *
  1055. * @param object $doc
  1056. * @param bool $setImgSize
  1057. * @access public
  1058. * @return object
  1059. * @param int $version
  1060. */
  1061. public function processDoc($doc, $version, $setImgSize = false)
  1062. {
  1063. /* When file change then version add one. */
  1064. $files = $this->loadModel('file')->getByObject('doc', $doc->id);
  1065. $docFiles = array();
  1066. $docContent = $this->getContent($doc->id, $version);
  1067. if($docContent)
  1068. {
  1069. foreach($files as $file)
  1070. {
  1071. $this->loadModel('file')->setFileWebAndRealPaths($file);
  1072. if(strpos(",{$docContent->files},", ",{$file->id},") !== false) $docFiles[$file->id] = $file;
  1073. }
  1074. /* Check file change. */
  1075. if($version == $doc->version && ((empty($docContent->files) && $docFiles) || (!empty($docContent->files) && count(explode(',', trim($docContent->files, ','))) != count($docFiles))))
  1076. {
  1077. unset($docContent->id);
  1078. $doc->version += 1;
  1079. $docContent->version = $doc->version;
  1080. $docContent->files = join(',', array_keys($docFiles));
  1081. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  1082. $this->dao->update(TABLE_DOC)->set('version')->eq($doc->version)->where('id')->eq($doc->id)->exec();
  1083. }
  1084. }
  1085. $doc->files = $docFiles;
  1086. $doc->title = isset($docContent->title) ? $docContent->title : $doc->title;
  1087. $doc->digest = isset($docContent->digest) ? $docContent->digest : '';
  1088. $doc->content = isset($docContent->content) ? $docContent->content : '';
  1089. $doc->rawContent = isset($docContent->rawContent) ? $docContent->rawContent : '';
  1090. $doc->contentType = isset($docContent->type) ? $docContent->type : '';
  1091. $doc->contentVersion = isset($docContent->version) ? $docContent->version : $version;
  1092. $doc->keywords = (!empty($doc->keywords) && is_string($doc->keywords)) ? htmlspecialchars_decode($doc->keywords) : $doc->keywords;
  1093. if($doc->type != 'url' && $doc->contentType != 'markdown' && $doc->contentType != 'doc') $doc = $this->loadModel('file')->replaceImgURL($doc, 'content,draft');
  1094. if($setImgSize) $doc->content = $this->file->setImgSize($doc->content);
  1095. $doc->productName = $doc->executionName = $doc->moduleName = '';
  1096. if($doc->product) $doc->productName = $this->dao->findByID($doc->product)->from(TABLE_PRODUCT)->fetch('name');
  1097. if($doc->execution) $doc->executionName = $this->dao->findByID($doc->execution)->from(TABLE_EXECUTION)->fetch('name');
  1098. if($doc->module)
  1099. {
  1100. if($doc->type == 'article' && $doc->parent)
  1101. {
  1102. $doc->moduleName = $this->dao->findByID($doc->parent)->from(TABLE_DOC)->fetch('title');
  1103. }
  1104. elseif(!empty($doc->templateType))
  1105. {
  1106. $modules = $this->getTemplateModules();
  1107. $modules = array_column($modules, 'fullName', 'id');
  1108. $doc->moduleName = zget($modules, $doc->module);
  1109. }
  1110. else
  1111. {
  1112. $doc->moduleName = $this->dao->findByID($doc->module)->from(TABLE_MODULE)->fetch('name');
  1113. }
  1114. }
  1115. return $doc;
  1116. }
  1117. /**
  1118. * 通过ID列表获取文档信息。
  1119. * Get docs info by id list.
  1120. *
  1121. * @param array $docIdList
  1122. * @access public
  1123. * @return array
  1124. */
  1125. public function getByIdList($docIdList = array())
  1126. {
  1127. return $this->dao->select('*,t1.id as docID,t1.type as docType,t1.version as docVersion,t2.type as contentType')->from(TABLE_DOC)->alias('t1')
  1128. ->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id=t2.doc and t1.version=t2.version')
  1129. ->where('t1.id')->in($docIdList)
  1130. ->andWhere('deleted')->eq(0)
  1131. ->fetchAll('docID', false);
  1132. }
  1133. /**
  1134. * 获取团队空间下的空间。
  1135. * Get team spaces.
  1136. *
  1137. * @access public
  1138. * @return array
  1139. */
  1140. public function getTeamSpaces()
  1141. {
  1142. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getSubSpaces('custom');
  1143. return $this->getSpacePairs('custom');
  1144. }
  1145. /**
  1146. * 获取文档模板中的空间。
  1147. * Get doctemplate spaces.
  1148. *
  1149. * @access public
  1150. * @return array
  1151. */
  1152. public function getDocTemplateSpaces()
  1153. {
  1154. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTeamSpaces();
  1155. $spaces = $this->getSpacePairs('doctemplate');
  1156. if(empty($spaces)) $this->initDocTemplateSpaces();
  1157. return $this->getSpacePairs('doctemplate');
  1158. }
  1159. /**
  1160. * 初始化文档模板空间。
  1161. * Init doctemplate spaces.
  1162. *
  1163. * @access public
  1164. * @return array
  1165. */
  1166. public function initDocTemplateSpaces()
  1167. {
  1168. $this->loadModel('doctempalte');
  1169. $defaultSpaces = $this->config->doctemplate->defaultSpaces;
  1170. foreach($defaultSpaces as $code => $childs)
  1171. {
  1172. $spaceID = $this->doInsertLib($this->initDocDefaultSpaces($code));
  1173. foreach($childs as $childCode) $this->doInsertLib($this->initDocDefaultSpaces($childCode, $spaceID));
  1174. }
  1175. }
  1176. /**
  1177. * 初始化文档模板空间。
  1178. * Init doctemplate spaces.
  1179. *
  1180. * @access public
  1181. * @return array
  1182. */
  1183. public function initDocDefaultSpaces($code, $parent = 0)
  1184. {
  1185. $space = new stdclass();
  1186. $space->type = 'doctemplate';
  1187. $space->vision = 'rnd';
  1188. $space->parent = $parent;
  1189. $space->name = $this->lang->doctemplate->$code;
  1190. $space->acl = 'open';
  1191. $space->addedBy = 'system';
  1192. $space->addedDate = helper::now();
  1193. return $space;
  1194. }
  1195. /**
  1196. * 获取团队空间或我的空间下的空间。
  1197. * Get team spaces or my spaces.
  1198. *
  1199. * @param string $type all|mine|custom
  1200. * @param bool $withType
  1201. * @access public
  1202. * @return array
  1203. */
  1204. public function getSubSpacesByType($type = 'all', $withType = false)
  1205. {
  1206. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getSubSpaces($type);
  1207. $objectLibs = $this->dao->select('*')->from(TABLE_DOCLIB)
  1208. ->where('deleted')->eq(0)
  1209. ->andWhere('parent')->eq(0)
  1210. ->andWhere('vision')->eq($this->config->vision)
  1211. ->beginIF($type == 'all')->andWhere('type')->in('mine,custom')->fi()
  1212. ->beginIF($type != 'all')->andWhere('type')->eq($type)->fi()
  1213. ->beginIF($type == 'mine')->andWhere('addedBy')->eq($this->app->user->account)->fi()
  1214. ->orderBy('type_desc')
  1215. ->fetchAll('', false);
  1216. $pairs = array();
  1217. foreach($objectLibs as $key => $lib)
  1218. {
  1219. if($lib->type === 'mine' && $lib->addedBy != $this->app->user->account)
  1220. {
  1221. unset($objectLibs[$key]);
  1222. continue;
  1223. }
  1224. if($this->checkPrivLib($lib))
  1225. {
  1226. $key = $withType ? "{$lib->type}.{$lib->id}" : $lib->id;
  1227. $pairs[$key] = $type == 'all' ? $this->lang->doc->spaceList[$lib->type] . '/' . $lib->name : $lib->name;
  1228. }
  1229. }
  1230. return $pairs;
  1231. }
  1232. /**
  1233. * 获取lib的targetSpace,返回的是type.id。
  1234. *
  1235. * @access public
  1236. * @return string
  1237. */
  1238. public function getLibTargetSpace($lib)
  1239. {
  1240. $type = $lib->type;
  1241. if(in_array($type, array('product', 'project')))
  1242. {
  1243. $targetSpace = "{$type}.{$lib->$type}";
  1244. }
  1245. else
  1246. {
  1247. $targetSpace = "{$type}.{$lib->parent}";
  1248. }
  1249. return $targetSpace;
  1250. }
  1251. /**
  1252. * 解析targetSpace。
  1253. *
  1254. * @param string $targetSpace
  1255. * @param string $paramType type|id
  1256. * @access public
  1257. * @return array
  1258. */
  1259. public function getParamFromTargetSpace($targetSpace, $paramType = 'type')
  1260. {
  1261. $params = explode('.', $targetSpace);
  1262. if($paramType == 'type') return $params[0];
  1263. if($paramType == 'id') return $params[1];
  1264. }
  1265. /**
  1266. * 获取所有子空间。
  1267. * Get all sub spaces.
  1268. *
  1269. * @param string $spaceType
  1270. * @access public
  1271. * @return array
  1272. */
  1273. public function getAllSubSpaces($spaceType = 'all')
  1274. {
  1275. $productList = $this->config->vision == 'rnd' && in_array($spaceType, array('all', 'product')) ? $this->loadModel('product')->getPairs('nocode') : array();
  1276. $projectList = ($this->config->vision == 'rnd' || $this->config->vision == 'lite') && in_array($spaceType, array('all', 'project', 'execution')) ? $this->loadModel('project')->getPairsByProgram() : array();
  1277. $spaceList = array();
  1278. if($spaceType == 'all')
  1279. {
  1280. $spaceList = $this->dao->select('*')->from(TABLE_DOCLIB)
  1281. ->where('deleted')->eq(0)
  1282. ->andWhere('parent')->eq(0)
  1283. ->andWhere('vision')->eq($this->config->vision)
  1284. ->andWhere('type', true)->eq('custom')
  1285. ->orWhere('(type')->eq('mine')->andWhere('addedBy')->eq($this->app->user->account)
  1286. ->markRight(2)
  1287. ->orderBy('type_desc')
  1288. ->fetchAll('', false);
  1289. }
  1290. $productPairs = $projectPairs = $spacePairs = array();
  1291. foreach($productList as $productID => $productName) $productPairs["product.{$productID}"] = $this->lang->doc->spaceList['product'] . '/' . $productName;
  1292. foreach($projectList as $projectID => $projectName) $projectPairs["project.{$projectID}"] = $this->lang->doc->spaceList['project'] . '/' . $projectName;
  1293. foreach($spaceList as $space)
  1294. {
  1295. if($this->checkPrivLib($space)) $spacePairs["{$space->type}.{$space->id}"] = $this->lang->doc->spaceList[$space->type] . '/' . $space->name;
  1296. }
  1297. return array_merge($spacePairs, $productPairs, $projectPairs);
  1298. }
  1299. /**
  1300. * 获取空间列表。
  1301. * Get spaces.
  1302. *
  1303. * @param string $type
  1304. * @param int $spaceID
  1305. * @access public
  1306. * @return array
  1307. */
  1308. public function getSpaces($type = 'custom', $spaceID = 0)
  1309. {
  1310. $spaces = array();
  1311. if($type == 'mine' || $type == 'custom')
  1312. {
  1313. $pairs = $this->getSubSpacesByType($type);
  1314. foreach($pairs as $key => $value) $spaces[] = array('id' => $key, 'name' => $value, 'type' => $type, 'canModify' => true);
  1315. }
  1316. if($type == 'product')
  1317. {
  1318. $account = $this->app->user->account;
  1319. $products = $this->loadModel('product')->getList();
  1320. $spaceID = $this->product->checkAccess($spaceID, $products);
  1321. foreach($products as $product)
  1322. {
  1323. $isMine = $product->status == 'normal' && $product->PO == $account;
  1324. $canModify = common::canModify('product', $product);
  1325. $spaces[] = array('id' => $product->id, 'name' => $product->name, 'isMine' => $isMine, 'type' => $type, 'canModify' => $canModify);
  1326. }
  1327. }
  1328. if($type == 'project')
  1329. {
  1330. $projects = $this->loadModel('project')->getListByCurrentUser('*', 'skip');
  1331. $involvedProjects = $this->project->getInvolvedListByCurrentUser();
  1332. $spaceID = $this->project->checkAccess($spaceID, $projects);
  1333. foreach($projects as $project)
  1334. {
  1335. $isMine = $project->status != 'closed' && isset($involvedProjects[$project->id]);
  1336. $canModify = common::canModify('project', $project);
  1337. $spaces[] = array('id' => $project->id, 'name' => $project->name, 'isMine' => $isMine, 'type' => $type, 'canModify' => $canModify);
  1338. }
  1339. }
  1340. if($type === 'execution' && $spaceID)
  1341. {
  1342. $execution = $this->loadModel('execution')->getByID($spaceID);
  1343. $canModify = common::canModify('execution', $execution);
  1344. $spaces[] = array('id' => $execution->id, 'name' => $execution->name, 'canModify' => $canModify);
  1345. }
  1346. $spaceIDs = array_column($spaces, 'id');
  1347. if(!in_array($spaceID, $spaceIDs) && !empty($spaces)) $spaceID = $spaces[0]['id'];
  1348. return array($spaces, $spaceID);
  1349. }
  1350. /**
  1351. * 创建独立的文档。
  1352. * Create a seperate docs.
  1353. *
  1354. * @param object $doc
  1355. * @access public
  1356. * @return array|false|string
  1357. */
  1358. public function createSeperateDocs($doc)
  1359. {
  1360. if($doc->acl == 'open') $doc->users = $doc->groups = '';
  1361. if(empty($doc->lib) && strpos((string)$doc->module, '_') !== false) list($doc->lib, $doc->module) = explode('_', $doc->module);
  1362. if(empty($doc->lib)) return dao::$errors['lib'] = sprintf($this->lang->error->notempty, $this->lang->doc->lib);
  1363. $files = $this->loadModel('file')->getUpload();
  1364. if(empty($files) || isset($files['name'])) return dao::$errors['files'] = sprintf($this->lang->error->notempty, $this->lang->doc->uploadFile);
  1365. $lib = $this->getLibByID($doc->lib);
  1366. $doc = $this->loadModel('file')->processImgURL($doc, $this->config->doc->editor->create['id'], (string)$this->post->uid);
  1367. $doc->product = $lib->product;
  1368. $doc->project = $lib->project;
  1369. $doc->execution = $lib->execution;
  1370. $docContent = new stdclass();
  1371. $docContent->title = $doc->title;
  1372. $docContent->content = '';
  1373. $docContent->type = $doc->contentType;
  1374. $docContent->digest = '';
  1375. $docContent->version = 1;
  1376. unset($doc->contentType);
  1377. unset($doc->rawContent);
  1378. return $this->insertSeperateDocs($doc, $docContent, $files);
  1379. }
  1380. /**
  1381. * 批量插入独立的文档。
  1382. * Insert seperate docs.
  1383. *
  1384. * @param object $doc
  1385. * @param object $docContent
  1386. * @param array $files
  1387. * @access public
  1388. * @return array|bool
  1389. */
  1390. public function insertSeperateDocs($doc, $docContent, $files)
  1391. {
  1392. $this->loadModel('file');
  1393. $doc->draft = '';
  1394. $docsAction = array();
  1395. foreach($files as $file)
  1396. {
  1397. $title = $file['title'];
  1398. $position = strrpos($title, '.');
  1399. if($position > 0) $title = substr($title, 0, $position);
  1400. $doc->title = $title;
  1401. $this->dao->insert(TABLE_DOC)->data($doc, 'content')->autoCheck()
  1402. ->batchCheck($this->config->doc->create->requiredFields, 'notempty')
  1403. ->exec();
  1404. if(dao::isError()) return false;
  1405. $docID = $this->dao->lastInsertID();
  1406. /* Update order and path. */
  1407. $path = ",{$docID}";
  1408. if(!empty($doc->parent))
  1409. {
  1410. $parentDoc = $this->getByID($doc->parent);
  1411. $path = $parentDoc->path . $path;
  1412. }
  1413. $this->dao->update(TABLE_DOC)->set('`order`')->eq($docID)->set('path')->eq($path)->where('id')->eq($docID)->exec();
  1414. $this->file->updateObjectID($this->post->uid, $docID, 'doc');
  1415. $fileTitle = $this->file->saveAFile($file, 'doc', $docID);
  1416. $docsAction[$docID] = $fileTitle;
  1417. if(empty($fileTitle)) continue;
  1418. if(dao::isError())
  1419. {
  1420. dao::$errors['message'][] = 'doc#' . ($file->title) . dao::getError(true);
  1421. continue;
  1422. }
  1423. $docContent->doc = $docID;
  1424. $docContent->files = ',' . $fileTitle->id;
  1425. $docContent->title = $title;
  1426. $docContent->content = '';
  1427. $docContent->type = isset($docContent->type) ? $docContent->type : 'attachment';
  1428. $docContent->digest = '';
  1429. $docContent->version = 1;
  1430. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  1431. $this->loadModel('score')->create('doc', 'create', $docID);
  1432. }
  1433. if(dao::isError()) return false;
  1434. return array('status' => 'new', 'docsAction' => $docsAction, 'docType' => 'attachment', 'libID' => $doc->lib);
  1435. }
  1436. /**
  1437. * 创建一个文档。
  1438. * Create a doc.
  1439. *
  1440. * @param object $doc
  1441. * @access public
  1442. * @return array|bool|string
  1443. */
  1444. public function create($doc)
  1445. {
  1446. if($doc->acl == 'open') $doc->users = $doc->groups = '';
  1447. if(empty($doc->lib) && strpos((string)$doc->module, '_') !== false) list($doc->lib, $doc->module) = explode('_', $doc->module);
  1448. if(empty($doc->lib)) return dao::$errors['lib'] = sprintf($this->lang->error->notempty, $this->lang->doc->lib);
  1449. $isDoc = empty($doc->templateType);
  1450. $isDraft = $doc->status == 'draft';
  1451. $lib = $this->getLibByID($doc->lib);
  1452. if($doc->contentType != 'doc') $doc = $this->loadModel('file')->processImgURL($doc, $this->config->doc->editor->create['id'], (string)$this->post->uid);
  1453. if($isDoc)
  1454. {
  1455. $doc->product = $lib->product;
  1456. $doc->project = $lib->project;
  1457. $doc->execution = $lib->execution;
  1458. }
  1459. $docContent = new stdclass();
  1460. $docContent->title = $doc->title;
  1461. $docContent->content = $doc->content;
  1462. $docContent->type = $doc->contentType;
  1463. $docContent->rawContent = isset($doc->rawContent) ? $doc->rawContent : '';
  1464. $docContent->digest = '';
  1465. $docContent->version = $isDraft ? 0 : 1;
  1466. $docContent->fromVersion = 0;
  1467. $docContent->addedBy = $this->app->user->account;
  1468. $docContent->addedDate = helper::now();
  1469. $docContent->editedBy = $docContent->addedBy;
  1470. $docContent->editedDate = $docContent->addedDate;
  1471. unset($doc->contentType);
  1472. unset($doc->rawContent);
  1473. $requiredFields = $isDraft ? 'title' : ($isDoc ? $this->config->doc->create->requiredFields : $this->config->doc->createTemplate->requiredFields);
  1474. if(strpos("url|word|ppt|excel", $doc->type) !== false) $requiredFields = trim(str_replace(",content,", ",", ",{$requiredFields},"), ',');
  1475. $checkContent = strpos(",$requiredFields,", ',content,') !== false;
  1476. if($checkContent && strpos("url|word|ppt|excel|", $lib->type) === false)
  1477. {
  1478. $requiredFields = trim(str_replace(',content,', ',', ",$requiredFields,"), ',');
  1479. if(empty($docContent->content)) return dao::$errors['content'] = sprintf($this->lang->error->notempty, $this->lang->doc->content);
  1480. }
  1481. $files = $this->loadModel('file')->getUpload();
  1482. if($doc->type == 'attachment' && empty($doc->copy) && (empty($files) || isset($files['name']))) return dao::$errors['files'] = sprintf($this->lang->error->notempty, $this->lang->doc->uploadFile);
  1483. $doc->draft = $isDraft ? $docContent->content : '';
  1484. $doc->vision = $this->config->vision;
  1485. $doc->version = $isDraft ? 0 : 1;
  1486. $this->dao->insert(TABLE_DOC)->data($doc, 'content,copy')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec();
  1487. if(dao::isError()) return false;
  1488. $docID = $this->dao->lastInsertID();
  1489. $path = ",{$docID},";
  1490. if($doc->parent)
  1491. {
  1492. $parentDoc = $this->getByID($doc->parent);
  1493. $path = ',' . trim($parentDoc->path, ',') . ',' . $path;
  1494. $doc->module = $parentDoc->module;
  1495. }
  1496. $this->dao->update(TABLE_DOC)->set('`order`')->eq($docID)->set('path')->eq($path)->where('id')->eq($docID)->exec();
  1497. $this->file->updateObjectID($this->post->uid, $docID, 'doc');
  1498. $files = $this->file->saveUpload('doc', $docID);
  1499. $docContent->doc = $docID;
  1500. $docContent->files = implode(',', array_keys($files));
  1501. if(!empty($doc->template)) $docContent->rawContent = $this->loadExtension('zentaomax')->getTemplateContent((int)$doc->template, $docID);
  1502. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  1503. $this->loadModel('score')->create('doc', $isDoc ? 'create' : 'createTemplate', $docID);
  1504. return array('status' => 'new', 'id' => $docID, 'files' => $files, 'docType' => $doc->type, 'libID' => $doc->lib);
  1505. }
  1506. /**
  1507. * 保存文档内容。
  1508. * Save doc content.
  1509. *
  1510. * @param int|object $doc 文档ID或文档对象 Document ID or doc object.
  1511. * @param object $docData
  1512. * @param int $version 文档版本号 Document version.
  1513. * @param array $files 附件ID列表 Attachment ID list.
  1514. * @access public
  1515. * @return object
  1516. * @param int $docID
  1517. */
  1518. public function saveDocContent($docID, $docData, $version, $files = array())
  1519. {
  1520. /* 获取文档草稿作为要更新的内容。 */
  1521. $docContent = $this->getContent($docID, 0);
  1522. if(!$docContent) $docContent = new stdClass();
  1523. $docContent->editedBy = $docData->editedBy;
  1524. $docContent->editedDate = helper::now();
  1525. $docContent->rawContent = $docData->rawContent;
  1526. $docContent->content = $docData->content;
  1527. $docContent->title = $docData->title;
  1528. $docContent->type = $docData->contentType;
  1529. $docContent->version = $version;
  1530. /* 如果没有文档草稿内容,则创建一个新的 doccontent 对象。 */
  1531. /* If current doc has no draft data, create a new docContent data. */
  1532. if(empty($docContent->id))
  1533. {
  1534. $docContent->doc = $docID;
  1535. $docContent->addedBy = $docContent->editedBy;
  1536. $docContent->addedDate = $docContent->editedDate;
  1537. $docContent->files = (!empty($docData->files) && is_string($docData->files)) ? (',' . $docData->files) : implode(',', $files);
  1538. $docContent->fromVersion = isset($docData->fromVersion) ? $docData->fromVersion : max(0, ($version - 1));
  1539. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  1540. $docContent->id = $this->dao->lastInsertID();
  1541. }
  1542. /* 如果有草稿内容,则直接将草稿内容作为新的版本内容。 */
  1543. /* If current doc has draft data,update the draft data as new version. */
  1544. else
  1545. {
  1546. $docContent->files = trim($docContent->files . ',' . implode(',', $files), ',');
  1547. $this->dao->update(TABLE_DOCCONTENT)->data($docContent)->where('id')->eq($docContent->id)->exec();
  1548. }
  1549. return $docContent;
  1550. }
  1551. /**
  1552. * 编辑一个文档。
  1553. * Update a doc.
  1554. *
  1555. * @param int $docID
  1556. * @param object $doc
  1557. * @access public
  1558. * @return array|string|bool
  1559. */
  1560. public function update($docID, $doc)
  1561. {
  1562. /* 检查必填项。 Check required fields. */
  1563. $requiredFields = $doc->status == 'draft' ? 'title' : $this->config->doc->edit->requiredFields;
  1564. if(strpos(",$requiredFields,", ',content,') !== false)
  1565. {
  1566. $requiredFields = trim(str_replace(',content,', ',', ",$requiredFields,"), ',');
  1567. if(isset($doc->content) && empty($doc->content)) return dao::$errors['content'] = sprintf($this->lang->error->notempty, $this->lang->doc->content);
  1568. }
  1569. $files = $this->loadModel('file')->saveUpload('doc', $docID);
  1570. if(dao::isError()) return false;
  1571. $deletedFiles = !empty($doc->deleteFiles) ? explode(',', $doc->deleteFiles) : array();
  1572. unset($doc->deleteFiles);
  1573. $oldDoc = $this->getByID($docID);
  1574. $changes = common::createChanges($oldDoc, $doc);
  1575. $oldRawContent = isset($oldDoc->rawContent) ? $oldDoc->rawContent : '';
  1576. $newRawContent = isset($doc->rawContent) ? $doc->rawContent : '';
  1577. $onlyRawChanged = $oldRawContent != $newRawContent;
  1578. $isDraft = $doc->status == 'draft';
  1579. $version = $isDraft ? 0 : ($oldDoc->version + 1);
  1580. $changed = $deletedFiles || $files || $onlyRawChanged || (!$isDraft && $oldDoc->version == 0);
  1581. $basicInfoChanged = false;
  1582. foreach($changes as $change)
  1583. {
  1584. if(in_array($change['field'], array('module', 'lib', 'acl', 'groups', 'users'))) $basicInfoChanged = true;
  1585. if($change['field'] == 'content' || $change['field'] == 'title' || $change['field'] == 'rawContent') $changed = true;
  1586. if($change['field'] == 'content') $onlyRawChanged = false;
  1587. }
  1588. $docFiles = array_diff(array_merge(array_keys($files), array_keys($oldDoc->files)), $deletedFiles);
  1589. if(empty($docFiles) && !empty($deletedFiles)) return dao::$errors['files'] = sprintf($this->lang->error->notempty, $this->lang->doc->uploadFile);
  1590. if($onlyRawChanged) $changes[] = array('field' => 'content', 'old' => $oldDoc->content, 'new' => $doc->content);
  1591. if(!empty($deletedFiles) || !empty($files)) $changes[] = array('field' => 'files', 'old' => implode(',', array_keys($oldDoc->files)), 'new' => implode(',', $docFiles));
  1592. if($changed) $this->saveDocContent($docID, $doc, $version, $docFiles);
  1593. else $version = $oldDoc->version;
  1594. if(dao::isError()) return false;
  1595. $doc->version = max($version, $oldDoc->version);
  1596. $doc->draft = $isDraft ? $doc->content : '';
  1597. $doc->content = $doc->title;
  1598. if(empty($doc->status)) $doc->status = $isDraft ? $oldDoc->status : 'normal';
  1599. if(isset($doc->parent) && $doc->parent != $oldDoc->parent)
  1600. {
  1601. $path = ",{$docID}";
  1602. if($doc->parent)
  1603. {
  1604. $parentDoc = $this->getByID($doc->parent);
  1605. $path = $parentDoc->path . $path;
  1606. $doc->module = $parentDoc->module;
  1607. }
  1608. $doc->path = $path;
  1609. }
  1610. unset($doc->files);
  1611. $this->dao->update(TABLE_DOC)->data($doc, 'content,contentType,rawContent,fromVersion,deleteFiles')
  1612. ->autoCheck()
  1613. ->batchCheck($requiredFields, 'notempty')
  1614. ->where('id')->eq($docID)
  1615. ->exec();
  1616. if(dao::isError()) return false;
  1617. if($files) $this->file->updateObjectID($this->post->uid, $docID, 'doc');
  1618. /* 文档标题变了要同步修改交付物名称. */
  1619. if(in_array($this->config->edition, array('ipd', 'max')) && $oldDoc->title != $doc->title)
  1620. {
  1621. $this->dao->update(TABLE_PROJECTDELIVERABLE)
  1622. ->set('name')->eq($doc->title)
  1623. ->set('docVersion')->eq($doc->version)
  1624. ->where('doc')->eq($docID)
  1625. ->andWhere('status')->eq('draft')
  1626. ->exec();
  1627. }
  1628. /* 如果修改了父模板,子模板也同步更新相关信息。*/
  1629. /* If the parent template is modified, the child template will also update the relevant information synchronously. */
  1630. if(!empty($oldDoc->templateType) && empty($doc->parent) && $basicInfoChanged)
  1631. {
  1632. $this->dao->update(TABLE_DOC)
  1633. ->set('module')->eq($doc->module)
  1634. ->set('lib')->eq($doc->lib)
  1635. ->set('acl')->eq($doc->acl)
  1636. ->where("FIND_IN_SET('{$docID}', `path`)")
  1637. ->exec();
  1638. }
  1639. if(in_array($oldDoc->contentType, array('html', 'attachment', 'markdown')) && !in_array($doc->contentType, array('html', 'attachment', 'markdown')))
  1640. {
  1641. $objectType = !empty($doc->templateType) ? 'doctemplate' : 'doc';
  1642. $this->loadModel('action')->create($objectType, $docID, 'convertToNewDoc');
  1643. }
  1644. return array('changes' => $changes, 'files' => $files, 'deletedFiles' => $deletedFiles);
  1645. }
  1646. /**
  1647. * 为更新文档处理数据。
  1648. * Process data for update a doc.
  1649. *
  1650. * @param object $oldDoc
  1651. * @param object $doc
  1652. * @access public
  1653. * @return array
  1654. */
  1655. public function processDocForUpdate($oldDoc, $doc)
  1656. {
  1657. $editingDate = $oldDoc->editingDate ? json_decode($oldDoc->editingDate, true) : array();
  1658. unset($editingDate[$this->app->user->account]);
  1659. $doc->editingDate = json_encode($editingDate);
  1660. if($doc->acl == 'open') $doc->users = $doc->groups = '';
  1661. if($doc->type == 'chapter' && $doc->parent)
  1662. {
  1663. $parentDoc = $this->dao->select('*')->from(TABLE_DOC)->where('id')->eq((int)$doc->parent)->fetch();
  1664. if(strpos($parentDoc->path, ",$oldDoc->id,") !== false) return dao::$errors['parent'] = $this->lang->doc->errorParentChapter;
  1665. $doc->module = $parentDoc->module;
  1666. }
  1667. $oldDocContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($oldDoc->id)->andWhere('version')->eq($oldDoc->version)->fetch();
  1668. if($oldDocContent)
  1669. {
  1670. $oldDoc->title = $oldDocContent->title;
  1671. $oldDoc->digest = $oldDocContent->digest;
  1672. $oldDoc->content = $oldDocContent->content;
  1673. $oldDoc->contentType = $oldDocContent->type;
  1674. }
  1675. $lib = !empty($doc->lib) ? $this->getLibByID($doc->lib) : '';
  1676. if(!isset($doc->contentType) || $doc->contentType !== 'doc') $doc = $this->loadModel('file')->processImgURL($doc, $this->config->doc->editor->edit['id'], (string)$this->post->uid);
  1677. if(!empty($lib))
  1678. {
  1679. $doc->product = $lib->product;
  1680. $doc->execution = $lib->execution;
  1681. }
  1682. return array($doc, $oldDocContent);
  1683. }
  1684. /**
  1685. * 构造搜索条件。
  1686. * Build search query.
  1687. *
  1688. * @param string $type
  1689. * @param int $queryID
  1690. * @access public
  1691. * @return string
  1692. */
  1693. public function buildQuery($type, $queryID = 0)
  1694. {
  1695. $queryName = $type . 'libDocQuery';
  1696. $queryForm = $type . 'libDocForm';
  1697. if($queryID)
  1698. {
  1699. $query = $this->loadModel('search')->getQuery($queryID);
  1700. if($query)
  1701. {
  1702. $this->session->set($queryName, $query->sql);
  1703. $this->session->set($queryForm, $query->form);
  1704. }
  1705. else
  1706. {
  1707. $this->session->set($queryName, ' 1 = 1');
  1708. }
  1709. }
  1710. else
  1711. {
  1712. if($this->session->$queryName == false) $this->session->set($queryName, ' 1 = 1');
  1713. }
  1714. $query = $this->session->$queryName;
  1715. if(strpos($query, "`lib` = 'all'") !== false) $query = str_replace("`lib` = 'all'", '1', $query);
  1716. if(strpos($query, "`lib` = ''") !== false) $query = str_replace("`lib` = ''", '1', $query);
  1717. return $query;
  1718. }
  1719. /**
  1720. * 获取执行文档库的所属模块的键值对。
  1721. * Gets the key-value pair of the module by execution ID.
  1722. *
  1723. * @access public
  1724. * @return array
  1725. */
  1726. public function getExecutionModulePairs()
  1727. {
  1728. return $this->dao->select('t1.id,t1.name')->from(TABLE_MODULE)->alias('t1')
  1729. ->leftJoin(TABLE_DOCLIB)->alias('t2')->on('t1.root = t2.id')
  1730. ->where('t1.type')->eq('doc')
  1731. ->andWhere('t2.execution')->ne('0')
  1732. ->fetchPairs();
  1733. }
  1734. /**
  1735. * 检查是否有权限访问文档库/文档。
  1736. * Check priv for lib.
  1737. *
  1738. * @param object|false|null $object
  1739. * @param string $extra
  1740. * @param string $docID
  1741. * @access public
  1742. * @return bool
  1743. */
  1744. public function checkPrivLib($object, $extra = '', $docID = '')
  1745. {
  1746. if(empty($object)) return false;
  1747. static $hasPrivLibs;
  1748. if(isset($hasPrivLibs[$object->id])) return $hasPrivLibs[$object->id];
  1749. $hasPrivLibs[$object->id] = true;
  1750. /* Only the creator can access the document library under my space. */
  1751. if($object->type == 'mine' && $object->addedBy == $this->app->user->account) return true;
  1752. if($this->app->user->admin && $object->type != 'mine') return true;
  1753. if($object->acl == 'open') return true;
  1754. /* When view a document, check whether current user has permission to access the document library. */
  1755. $account = ',' . $this->app->user->account . ',';
  1756. if(isset($object->addedBy) && $object->addedBy == $this->app->user->account) return true;
  1757. if(isset($object->users) && strpos(",{$object->users},", $account) !== false) return true;
  1758. if(!empty($object->groups))
  1759. {
  1760. $userGroups = $this->app->user->groups;
  1761. foreach($userGroups as $groupID)
  1762. {
  1763. if(strpos(",$object->groups,", ",$groupID,") !== false) return true;
  1764. }
  1765. }
  1766. $isProjectLib = $object->project && !$object->execution;
  1767. if($isProjectLib && $object->acl == 'default')
  1768. {
  1769. if($this->loadModel('project')->checkPriv($object->project)) return true;
  1770. $this->setDocPrivError($docID, $object->project, 'project');
  1771. }
  1772. /* The user has permission to access the owning document library that can access the document. */
  1773. if(strpos($extra, 'notdoc') !== false)
  1774. {
  1775. static $extraDocLibs;
  1776. if($extraDocLibs === null) $extraDocLibs = $this->getPrivLibsByDoc();
  1777. if(isset($extraDocLibs[$object->id])) return true;
  1778. }
  1779. /* If the acl is default, the document library cannot be accessed without object permission. */
  1780. if($object->acl == 'default' && (!empty($object->product) || !empty($object->execution)))
  1781. {
  1782. $acls = $this->app->user->rights['acls'];
  1783. if(!empty($acls['products']) && !in_array($object->product, $acls['products']))
  1784. {
  1785. $this->setDocPrivError($docID, $object->product, 'product');
  1786. $hasPrivLibs[$object->id] = false;
  1787. return false;
  1788. }
  1789. if(!empty($object->execution) && !empty($acls['sprints']) && !in_array($object->execution, $acls['sprints']))
  1790. {
  1791. $this->setDocPrivError($docID, $object->execution, 'execution');
  1792. $hasPrivLibs[$object->id] = false;
  1793. return false;
  1794. }
  1795. if(!empty($object->execution))
  1796. {
  1797. $result = $this->loadModel('execution')->checkPriv($object->execution);
  1798. if(!$result) $this->setDocPrivError($docID, $object->execution, 'execution');
  1799. $hasPrivLibs[$object->id] = $result;
  1800. return $result;
  1801. }
  1802. if(!empty($object->product))
  1803. {
  1804. $result = $this->loadModel('product')->checkPriv($object->product);
  1805. if(!$result) $this->setDocPrivError($docID, $object->product, 'product');
  1806. $hasPrivLibs[$object->id] = $result;
  1807. return $result;
  1808. }
  1809. }
  1810. $hasPrivLibs[$object->id] = false;
  1811. return false;
  1812. }
  1813. /**
  1814. * 设置文档权限错误
  1815. * Set doc priv error.
  1816. *
  1817. * @param string $docID
  1818. * @param int $objectID
  1819. * @param string $type
  1820. * @access private
  1821. * @return void
  1822. */
  1823. public function setDocPrivError($docID, $objectID, $type)
  1824. {
  1825. if(!array_key_exists("doc_{$docID}_nopriv", $_SESSION)) return;
  1826. $objectName = '';
  1827. if($type == 'product') $objectName = $this->loadModel('product')->getByID($objectID)->name;
  1828. elseif($type == 'execution') $objectName = $this->loadModel('execution')->getByID($objectID)->name;
  1829. elseif($type == 'project') $objectName = $this->loadModel('project')->getByID($objectID)->name;
  1830. if(!empty($objectName)) $_SESSION["doc_{$docID}_nopriv"] = sprintf($this->lang->doc->nopriv, $objectName);
  1831. }
  1832. /**
  1833. * 批量检查文档权限
  1834. * Batch check doc priv.
  1835. *
  1836. * @param array $docs
  1837. * @access public
  1838. * @return array
  1839. */
  1840. public function batchCheckPrivDoc($docs)
  1841. {
  1842. $libIdList = array_column($docs, 'lib');
  1843. $libs = $this->dao->select('id,type,product,project,execution,addedBy,acl,users,`groups`')->from(TABLE_DOCLIB)->where('id')->in($libIdList)->fetchAll('id');
  1844. $hasPrivDocs = array();
  1845. foreach($docs as $doc)
  1846. {
  1847. if(!$this->checkPrivLib(zget($libs, $doc->lib, null), '', (string)$doc->id)) continue;
  1848. if(!$this->checkPrivDoc($doc, false)) continue;
  1849. $hasPrivDocs[$doc->id] = $doc;
  1850. }
  1851. return $hasPrivDocs;
  1852. }
  1853. /**
  1854. * 检查文档权限。
  1855. * Check privilege for the document.
  1856. *
  1857. * @param object $doc
  1858. * @access public
  1859. * @return bool
  1860. * @param bool $checkLib
  1861. */
  1862. public function checkPrivDoc($doc, $checkLib = true)
  1863. {
  1864. if(!isset($doc->lib)) return false;
  1865. /* Asset document don't check privilege. */
  1866. if((isset($doc->assetLibType) && $doc->assetLibType) || $doc->type == 'article') return true;
  1867. /* My document are accessible only to the creator. */
  1868. if($doc->status == 'draft' && $doc->addedBy != $this->app->user->account) return false;
  1869. if($doc->status == 'normal' && $this->app->user->admin) return true;
  1870. if(empty($doc->templateType))
  1871. {
  1872. static $libs = array();
  1873. if($checkLib)
  1874. {
  1875. if(!isset($libs[$doc->lib])) $libs[$doc->lib] = $this->getLibByID((int)$doc->lib);
  1876. if(!$this->checkPrivLib($libs[$doc->lib], '', (string)$doc->id)) return false;
  1877. }
  1878. }
  1879. if(in_array($doc->acl, array('open', 'public'))) return true;
  1880. /* Whitelist users can access private document. */
  1881. $account = $this->app->user->account;
  1882. if(isset($doc->addedBy) && $doc->addedBy == $account) return true;
  1883. if(strpos(",{$doc->users},", ",{$account},") !== false || strpos(",{$doc->readUsers},", ",{$account},") !== false) return true;
  1884. if($doc->groups || $doc->readGroups)
  1885. {
  1886. foreach($this->app->user->groups as $groupID)
  1887. {
  1888. if(strpos(",{$doc->groups},", ",{$groupID},") !== false || strpos(",{$doc->readGroups},", ",{$groupID}") !== false) return true;
  1889. }
  1890. }
  1891. return false;
  1892. }
  1893. /**
  1894. * 通过对象ID获取文档库。
  1895. * Get libs by object.
  1896. *
  1897. * @param string $type
  1898. * @param int $objectID
  1899. * @param int $appendLib
  1900. * @param int $limit
  1901. * @access public
  1902. * @return array
  1903. */
  1904. public function getLibsByObject($type, $objectID = 0, $appendLib = 0, $limit = 0)
  1905. {
  1906. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDocLibs();
  1907. if(!in_array($type, array('mine', 'custom', 'product', 'project', 'execution', 'doctemplate'))) return array();
  1908. if(in_array($type, array('mine', 'custom', 'doctemplate')))
  1909. {
  1910. $objectLibs = $this->dao->select('*')->from(TABLE_DOCLIB)
  1911. ->where('deleted')->eq(0)
  1912. ->andWhere('vision')->eq($this->config->vision)
  1913. ->andWhere('type')->eq($type)
  1914. ->andWhere('parent')->eq($objectID)
  1915. ->beginIF(!empty($appendLib))->orWhere('id')->eq($appendLib)->fi()
  1916. ->orderBy('`order` asc, id_asc')
  1917. ->limit($limit)
  1918. ->fetchAll('id', false);
  1919. }
  1920. else
  1921. {
  1922. $objectLibs = $this->dao->select('*')->from(TABLE_DOCLIB)
  1923. ->where('deleted')->eq(0)
  1924. ->andWhere('vision')->eq($this->config->vision)
  1925. ->andWhere($type)->eq($objectID)
  1926. ->beginIF($type == 'project')->andWhere('type')->in('api,project')->fi()
  1927. ->beginIF(!empty($appendLib))->orWhere('id')->eq($appendLib)->fi()
  1928. ->orderBy('`order` asc, id_asc')
  1929. ->limit($limit)
  1930. ->fetchAll('id', false);
  1931. $executionIDList = array();
  1932. if($type == 'project') $executionIDList = $this->loadModel('execution')->getPairs($objectID, 'all', 'multiple,leaf');
  1933. if($executionIDList)
  1934. {
  1935. $objectLibs += $this->dao->select('*')->from(TABLE_DOCLIB)
  1936. ->where('deleted')->eq(0)
  1937. ->andWhere('vision')->eq($this->config->vision)
  1938. ->andWhere('execution')->in(array_keys($executionIDList))
  1939. ->andWhere('type')->eq('execution')
  1940. ->beginIF(!empty($appendLib))->orWhere('id')->eq($appendLib)->fi()
  1941. ->orderBy('`order` asc, id_asc')
  1942. ->limit($limit)
  1943. ->fetchAll('id', false);
  1944. }
  1945. }
  1946. $libs = array();
  1947. foreach($objectLibs as $lib)
  1948. {
  1949. if($this->checkPrivLib($lib)) $libs[$lib->id] = $lib;
  1950. }
  1951. $itemCounts = $this->statLibCounts(array_keys($libs));
  1952. foreach($libs as $libID => $lib) $libs[$libID]->allCount = $itemCounts[$libID];
  1953. return $libs;
  1954. }
  1955. /**
  1956. * 获取指定类型各个子空间文档库概要信息。
  1957. * Get doclib summary of each sub space.
  1958. *
  1959. * @param string $type
  1960. * @param array $libIDList
  1961. * @param int $limit
  1962. * @access public
  1963. * @return array
  1964. * @param string|mixed[] $spaceList
  1965. */
  1966. public function getLibsOfSpaces($type, $spaceList, $limit = 5)
  1967. {
  1968. if(is_string($spaceList)) $spaceList = explode(',', $spaceList);
  1969. if(empty($spaceList)) return array();
  1970. $map = array();
  1971. $libIDList = array();
  1972. foreach($spaceList as $spaceID)
  1973. {
  1974. $libs = $this->getLibsByObject($type, (int)$spaceID, 0, $limit);
  1975. $libIDList = array_merge($libIDList, array_keys($libs));
  1976. $map[$spaceID] = array_values($libs);
  1977. }
  1978. $docs = $this->dao->select("`id`,`addedBy`,`type`,`lib`,`acl`,`users`,`readUsers`,`groups`,`readGroups`,`status`,`path`,`deleted`")->from(TABLE_DOC)
  1979. ->where('lib')->in($libIDList)
  1980. ->andWhere('builtIn')->eq('0')
  1981. ->andWhere('type')->ne('chapter')
  1982. ->fetchAll();
  1983. $docs = $this->docTao->filterDeletedDocs($docs);
  1984. $docs = $this->batchCheckPrivDoc($docs);
  1985. $docCounts = array();
  1986. foreach($docs as $doc)
  1987. {
  1988. if(!isset($docCounts[$doc->lib])) $docCounts[$doc->lib] = 0;
  1989. $docCounts[$doc->lib] ++;
  1990. }
  1991. $executionLibs = array();
  1992. foreach($map as $spaceID => &$libs)
  1993. {
  1994. foreach($libs as &$lib)
  1995. {
  1996. $lib->docs = isset($docCounts[$lib->id]) ? $docCounts[$lib->id] : 0;
  1997. if($lib->type != 'execution') continue;
  1998. if(!isset($executionLibs[$lib->execution])) $executionLibs[$lib->execution] = array();
  1999. $executionLibs[$lib->execution][] = $lib;
  2000. }
  2001. }
  2002. if(!empty($executionLibs))
  2003. {
  2004. $executionPairs = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('id')->in(array_keys($executionLibs))->fetchPairs();
  2005. foreach($executionLibs as $executionID => &$libList)
  2006. {
  2007. foreach($libList as &$lib)
  2008. {
  2009. $lib->originName = $lib->name;
  2010. $lib->executionName = $executionPairs[$executionID];
  2011. $lib->name = $lib->executionName . '/' . $lib->name;
  2012. }
  2013. }
  2014. }
  2015. return $map;
  2016. }
  2017. /**
  2018. * 获取空间下的文档库。
  2019. * Get libs of space.
  2020. *
  2021. * @param string $type
  2022. * @param int $spaceID
  2023. * @access public
  2024. * @return array
  2025. */
  2026. public function getLibsOfSpace($type, $spaceID)
  2027. {
  2028. $libs = $this->getLibsByObject($type, $spaceID);
  2029. $executionIDList = array();
  2030. $apiLibs = array();
  2031. foreach($libs as &$lib)
  2032. {
  2033. if($lib->type == 'api') $apiLibs[$lib->id] = $lib;
  2034. if($lib->type != 'execution') continue;
  2035. $executionIDList[] = $lib->execution;
  2036. }
  2037. if($type == 'project' && !empty($executionIDList))
  2038. {
  2039. $executionPairs = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('id')->in($executionIDList)->filterTpl('skip')->fetchPairs();
  2040. foreach($libs as &$lib)
  2041. {
  2042. if($lib->type != 'execution') continue;
  2043. $lib->originName = $lib->name;
  2044. $lib->executionName = $executionPairs[$lib->execution];
  2045. $lib->name = $lib->executionName . '/' . $lib->name;
  2046. }
  2047. }
  2048. if(!empty($apiLibs))
  2049. {
  2050. $releases = $this->loadModel('api')->getReleaseByQuery(array_keys($apiLibs));
  2051. $releaseMap = array();
  2052. foreach($releases as $release) $releaseMap[$release->lib][] = $release;
  2053. foreach($apiLibs as &$lib) $lib->versions = isset($releaseMap[$lib->id]) ? $releaseMap[$lib->id] : array();
  2054. }
  2055. return $libs;
  2056. }
  2057. /**
  2058. * 获取已排序的对象数据。
  2059. * Get ordered objects for doc.
  2060. *
  2061. * @param string $objectType
  2062. * @param string $returnType nomerge|merge
  2063. * @param int $append
  2064. * @access public
  2065. * @return array
  2066. */
  2067. public function getOrderedObjects($objectType = 'product', $returnType = 'merge', $append = 0)
  2068. {
  2069. $myObjects = $normalObjects = $closedObjects = array();
  2070. if($objectType == 'product')
  2071. {
  2072. list($myObjects, $normalObjects, $closedObjects) = $this->getOrderedProducts($append);
  2073. }
  2074. elseif($objectType == 'project')
  2075. {
  2076. list($myObjects, $normalObjects, $closedObjects) = $this->getOrderedProjects($append);
  2077. }
  2078. elseif($objectType == 'execution')
  2079. {
  2080. list($myObjects, $normalObjects, $closedObjects) = $this->docTao->getOrderedExecutions($append);
  2081. }
  2082. if($returnType == 'nomerge') return array($myObjects, $normalObjects, $closedObjects);
  2083. return $myObjects + $normalObjects + $closedObjects;
  2084. }
  2085. /**
  2086. * 获取已排序的产品数据。
  2087. * Get ordered products.
  2088. *
  2089. * @param int $append
  2090. * @access public
  2091. * @return array
  2092. */
  2093. public function getOrderedProducts($append = 0)
  2094. {
  2095. $myObjects = $normalObjects = $closedObjects = array();
  2096. $products = $this->loadModel('product')->getList();
  2097. if($append && !isset($products[$append])) $products[$append] = $this->product->getByID($append);
  2098. foreach($products as $id => $product)
  2099. {
  2100. if(!$product) continue;
  2101. if($product->status != 'closed' && $product->PO == $this->app->user->account)
  2102. {
  2103. $myObjects[$id] = $product->name;
  2104. }
  2105. elseif($product->status != 'closed' && !($product->PO == $this->app->user->account))
  2106. {
  2107. $normalObjects[$id] = $product->name;
  2108. }
  2109. elseif($product->status == 'closed')
  2110. {
  2111. $closedObjects[$id] = $product->name;
  2112. }
  2113. }
  2114. return array($myObjects, $normalObjects, $closedObjects);
  2115. }
  2116. /**
  2117. * 获取已排序的项目数据。
  2118. * Get ordered projects.
  2119. *
  2120. * @param int $append
  2121. * @access public
  2122. * @return array
  2123. */
  2124. public function getOrderedProjects($append = 0)
  2125. {
  2126. $this->loadModel('program');
  2127. /* Project permissions for DocLib whitelist. */
  2128. $myObjects = $normalObjects = $closedObjects = array();
  2129. $orderedProjects = array();
  2130. if($this->app->tab == 'doc')
  2131. {
  2132. $myObjects = $this->dao->select('t1.id, t1.name')->from(TABLE_PROJECT)->alias('t1')
  2133. ->leftJoin(TABLE_DOCLIB)->alias('t2')->on('t2.project=t1.id')
  2134. ->where("CONCAT(',', t2.users, ',')")->like("%,{$this->app->user->account},%")
  2135. ->andWhere('t1.vision')->eq($this->config->vision)
  2136. ->andWhere('t1.deleted')->eq(0)
  2137. ->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->projects)->fi()
  2138. ->fetchPairs();
  2139. }
  2140. $objects = $this->dao->select('*')->from(TABLE_PROJECT)
  2141. ->where('type')->eq('project')
  2142. ->andWhere('vision')->eq($this->config->vision)
  2143. ->andWhere('deleted')->eq(0)
  2144. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
  2145. ->beginIF($append)->orWhere('id')->eq($append)->fi()
  2146. ->orderBy('order_asc')
  2147. ->fetchAll('id', false);
  2148. foreach($objects as $objectID => $object)
  2149. {
  2150. $object->parent = $this->program->getTopByID($object->parent);
  2151. $orderedProjects[$objectID] = $object;
  2152. unset($objects[$object->id]);
  2153. }
  2154. foreach($orderedProjects as $id => $project)
  2155. {
  2156. if($project->status != 'done' && $project->status != 'closed' && $project->PM == $this->app->user->account)
  2157. {
  2158. $myObjects[$id] = $project->name;
  2159. }
  2160. elseif($project->status != 'done' && $project->status != 'closed' && !($project->PM == $this->app->user->account))
  2161. {
  2162. $normalObjects[$id] = $project->name;
  2163. }
  2164. elseif($project->status == 'done' || $project->status == 'closed')
  2165. {
  2166. $closedObjects[$id] = $project->name;
  2167. }
  2168. }
  2169. return array($myObjects, $normalObjects, $closedObjects);
  2170. }
  2171. /**
  2172. * 统计文档库下的模块和文档数量。
  2173. * Stat module and document counts of lib.
  2174. *
  2175. * @param array $idList
  2176. * @access public
  2177. * @return array
  2178. */
  2179. public function statLibCounts($idList)
  2180. {
  2181. if(empty($idList)) return array();
  2182. $moduleCounts = $this->dao->select("`root`, count(id) as moduleCount")->from(TABLE_MODULE)
  2183. ->where('type')->eq('doc')
  2184. ->andWhere('root')->in($idList)
  2185. ->andWhere('deleted')->eq(0)
  2186. ->groupBy('root')
  2187. ->fetchPairs();
  2188. $docs = $this->dao->select("`id`,`addedBy`,`type`,`lib`,`acl`,`users`,`readUsers`,`groups`,`readGroups`,`status`,`path`,`deleted`")->from(TABLE_DOC)
  2189. ->where('lib')->in($idList)
  2190. ->andWhere('type')->ne('chapter')
  2191. ->andWhere('deleted')->eq('0')
  2192. ->andWhere('builtIn')->eq('0')
  2193. ->andWhere('module')->eq('0')
  2194. ->fetchAll();
  2195. $docs = $this->docTao->filterDeletedDocs($docs);
  2196. $docs = $this->batchCheckPrivDoc($docs);
  2197. $docCounts = array();
  2198. foreach($docs as $doc)
  2199. {
  2200. if(!isset($docCounts[$doc->lib])) $docCounts[$doc->lib] = 0;
  2201. $docCounts[$doc->lib] ++;
  2202. }
  2203. $itemCounts = array();
  2204. foreach($idList as $libID)
  2205. {
  2206. $docCount = isset($docCounts[$libID]) ? $docCounts[$libID] : 0;
  2207. $moduleCount = isset($moduleCounts[$libID]) ? $moduleCounts[$libID] : 0;
  2208. $itemCounts[$libID] = $docCount + $moduleCount;
  2209. }
  2210. return $itemCounts;
  2211. }
  2212. /**
  2213. * 获取文档库的附件。
  2214. * Get lib files.
  2215. *
  2216. * @param string $type product|project|execution
  2217. * @param int $objectID
  2218. * @param string|bool $searchTitle
  2219. * @param string $browseType
  2220. * @param int $param
  2221. * @param string $orderBy
  2222. * @param object $pager
  2223. * @access public
  2224. * @return array
  2225. */
  2226. public function getLibFiles($type, $objectID, $browseType = '', $param = 0, $orderBy = 'id_desc', $pager = null)
  2227. {
  2228. if(!in_array($type, array('execution', 'project', 'product'))) return array();
  2229. $searchModule = "{$type}DocFile";
  2230. $queryID = (int)$param;
  2231. $this->loadModel('search')->setQuery($searchModule, $queryID);
  2232. $docFileQuery = $this->session->{$searchModule . 'Query'};
  2233. list($bugIdList, $testReportIdList, $caseIdList, $docIdList, $storyIdList, $epicIdList, $requirementIdList, $planIdList, $releaseIdList, $issueIdList, $meetingIdList, $reviewIdList, $designIdList, $executionIdList, $taskIdList, $buildIdList, $testtaskIdList, $resultIdList) = $this->getLinkedObjectData($type, $objectID);
  2234. $files = $this->dao->select('*')->from(TABLE_FILE)
  2235. ->where('size')->gt('0')
  2236. ->andWhere('deleted')->eq('0')
  2237. ->andWhere("(objectType = '$type' and objectID = $objectID)", true)
  2238. ->beginIF($docIdList)->orWhere("(objectType = 'doc' and objectID in ($docIdList))")->fi()
  2239. ->orWhere("(objectType = 'bug' and objectID in ($bugIdList))")
  2240. ->orWhere("(objectType = 'testreport' and objectID in ($testReportIdList))")
  2241. ->orWhere("(objectType = 'testcase' and objectID in ($caseIdList))")
  2242. ->beginIF($type == 'product')
  2243. ->orWhere("(objectType in ('story', 'requirement', 'epic') and objectID in ($storyIdList))")
  2244. ->orWhere("(objectType = 'release' and objectID in ($releaseIdList))")
  2245. ->orWhere("(objectType = 'productplan' and objectID in ($planIdList))")
  2246. ->orWhere("(objectType = 'stepResult' and objectID in ($resultIdList))")
  2247. ->fi()
  2248. ->beginIF($type == 'project')
  2249. ->orWhere("(objectType = 'execution' and objectID in ($executionIdList))")
  2250. ->orWhere("(objectType = 'issue' and objectID in ($issueIdList))")
  2251. ->orWhere("(objectType = 'review' and objectID in ($reviewIdList))")
  2252. ->orWhere("(objectType = 'meeting' and objectID in ($meetingIdList))")
  2253. ->orWhere("(objectType = 'design' and objectID in ($designIdList))")
  2254. ->fi()
  2255. ->beginIF($type == 'project' || $type == 'execution')
  2256. ->orWhere("(objectType = 'task' and objectID in ($taskIdList))")
  2257. ->orWhere("(objectType = 'build' and objectID in ($buildIdList))")
  2258. ->orWhere("(objectType = 'testtask' and objectID in ($testtaskIdList))")
  2259. ->beginIF($storyIdList)->orWhere("(objectType = 'story' and objectID in ($storyIdList))")->fi()
  2260. ->fi()
  2261. ->markRight(1)
  2262. ->beginIF($browseType == 'bySearch')->andWhere("($docFileQuery)")->fi()
  2263. ->orderBy($orderBy)
  2264. ->page($pager)
  2265. ->fetchAll('id', false);
  2266. $this->loadModel('file');
  2267. $testResults = empty($resultIdList) ? array() : $this->dao->select('id,`case`')->from(TABLE_TESTRESULT)->where('id')->in($resultIdList)->fetchPairs('id', 'case');
  2268. foreach($files as $file)
  2269. {
  2270. $this->file->setFileWebAndRealPaths($file);
  2271. $file->rawObjectType = $file->objectType;
  2272. if($file->objectType == 'story' && $type == 'product')
  2273. {
  2274. if(in_array($file->objectID, $epicIdList)) $file->objectType = 'epic';
  2275. if(in_array($file->objectID, $requirementIdList)) $file->objectType = 'requirement';
  2276. }
  2277. if($file->objectType == 'stepResult' && $testResults[$file->objectID])
  2278. {
  2279. $file->objectType = 'testcase';
  2280. $file->objectID = $testResults[$file->objectID];
  2281. }
  2282. }
  2283. return $files;
  2284. }
  2285. /**
  2286. * 获取关联产品/项目/执行的数据。
  2287. * Get linked product/project/execution data.
  2288. *
  2289. * @param string $type product|project|execution
  2290. * @param int $objectID
  2291. * @access public
  2292. * @return array
  2293. */
  2294. public function getLinkedObjectData($type, $objectID)
  2295. {
  2296. if(!in_array($type, array('execution', 'project', 'product'))) return array();
  2297. $userView = $this->app->user->view->products;
  2298. if($type == 'project') $userView = $this->app->user->view->projects;
  2299. if($type == 'execution') $userView = $this->app->user->view->sprints;
  2300. $bugIdList = $testReportIdList = $caseIdList = $resultIdList = $testtaskIdList = $storyIdList = $epicIdList = $requirementIdList = $planIdList = $releaseIdList = $executionIdList = $taskIdList = $buildIdList = $issueIdList = $meetingIdList = $designIdList = $reviewIdList = 0;
  2301. $bugPairs = $this->dao->select('id')->from(TABLE_BUG)->where($type)->eq($objectID)->andWhere('deleted')->eq('0')->beginIF(!$this->app->user->admin)->andWhere($type)->in($userView)->fi()->fetchPairs('id');
  2302. if(!empty($bugPairs)) $bugIdList = implode(',', $bugPairs);
  2303. $testReportPairs = $this->dao->select('id')->from(TABLE_TESTREPORT)->where($type)->eq($objectID)->andWhere('deleted')->eq('0')->beginIF(!$this->app->user->admin)->andWhere($type)->in($userView)->fi()->fetchPairs('id');
  2304. if(!empty($testReportPairs)) $testReportIdList = implode(',', $testReportPairs);
  2305. $field = $type == 'execution' ? 'project' : $type;
  2306. $casePairs = $this->dao->select('`case`')->from(TABLE_PROJECTCASE)->where($field)->eq($objectID)->beginIF(!$this->app->user->admin)->andWhere($field)->in($userView)->fi()->fetchPairs('case');
  2307. if(!empty($casePairs)) $caseIdList = implode(',', $casePairs);
  2308. $docs = $this->dao->select('*')->from(TABLE_DOC)->where($type)->eq($objectID)->fetchAll('id', false);
  2309. $docs = $this->batchCheckPrivDoc($docs);
  2310. $docIdList = empty($docs) ? 0 : $this->dao->select('id')->from(TABLE_DOC)->where($type)->eq($objectID)->andWhere('vision')->eq($this->config->vision)->andWhere('id')->in(array_keys($docs))->get();
  2311. if($type == 'product')
  2312. {
  2313. list($storyIdList, $epicIdList, $requirementIdList, $planIdList, $releasePairs, $casePairs, $resultPairs) = $this->docTao->getLinkedProductData($objectID, $userView);
  2314. if(!empty($releasePairs)) $releaseIdList = implode(',', $releasePairs);
  2315. if(!empty($casePairs)) $caseIdList = implode(',', $casePairs);
  2316. if(!empty($resultPairs)) $resultIdList = implode(',', $resultPairs);
  2317. }
  2318. elseif($type == 'project')
  2319. {
  2320. list($storyIdList, $issueIdList, $meetingIdList, $reviewIdList, $designIdList, $executionIdList, $taskIdList, $buildIdList) = $this->getLinkedProjectData($objectID);
  2321. }
  2322. elseif($type == 'execution')
  2323. {
  2324. list($storyIdList, $taskIdList, $buildIdList, $testtaskIdList) = $this->getLinkedExecutionData($objectID);
  2325. }
  2326. return array($bugIdList, $testReportIdList, $caseIdList, $docIdList, $storyIdList, $epicIdList, $requirementIdList, $planIdList, $releaseIdList, $issueIdList, $meetingIdList, $reviewIdList, $designIdList, $executionIdList, $taskIdList, $buildIdList, $testtaskIdList, $resultIdList);
  2327. }
  2328. /**
  2329. * 获取关联项目的数据。
  2330. * Get linked project data.
  2331. *
  2332. * @param int $projectID
  2333. * @access public
  2334. * @return array
  2335. */
  2336. public function getLinkedProjectData($projectID)
  2337. {
  2338. $project = $this->loadModel('project')->getByID($projectID);
  2339. $storyIdList = $issueIdList = $meetingIdList = $reviewIdList = $designIdList = $executionIdList = $taskIdList = $buildIdList = 0;
  2340. $projectIDList = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->orWhere('project')->eq($projectID)->fetchPairs('id', 'id');
  2341. $storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in($projectIDList)->fetchPairs('story', 'story');
  2342. if(in_array($this->config->edition, array('max', 'ipd')))
  2343. {
  2344. $issueIdList = $this->dao->select('id')->from(TABLE_ISSUE)
  2345. ->where('project')->eq($projectID)
  2346. ->andWhere('deleted')->eq('0')
  2347. ->beginIF(!$this->app->user->admin)->andWhere('project')->in($this->app->user->view->projects)->fi()
  2348. ->get();
  2349. $meetingIdList = $this->dao->select('id')->from(TABLE_MEETING)
  2350. ->where('project')->eq($projectID)
  2351. ->andWhere('deleted')->eq('0')
  2352. ->beginIF(!$this->app->user->admin)->andWhere('project')->in($this->app->user->view->projects)->fi()
  2353. ->get();
  2354. $reviewIdList = $this->dao->select('id')->from(TABLE_REVIEW)
  2355. ->where('project')->eq($projectID)
  2356. ->andWhere('deleted')->eq('0')
  2357. ->beginIF(!$this->app->user->admin)->andWhere('project')->in($this->app->user->view->projects)->fi()
  2358. ->get();
  2359. }
  2360. $designIdList = $this->dao->select('id')->from(TABLE_DESIGN)
  2361. ->where('project')->eq($projectID)
  2362. ->andWhere('deleted')->eq('0')
  2363. ->beginIF(!$this->app->user->admin)->andWhere('project')->in($this->app->user->view->projects)->fi()
  2364. ->get();
  2365. $executionIdList = $this->loadModel('execution')->getIdList($projectID);
  2366. $taskPairs = $this->dao->select('id')->from(TABLE_TASK)
  2367. ->where('execution')->in($executionIdList)
  2368. ->andWhere('deleted')->eq('0')
  2369. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
  2370. ->fetchPairs('id');
  2371. if(!empty($taskPairs)) $taskIdList = implode(',', $taskPairs);
  2372. $buildPairs = $this->dao->select('id')->from(TABLE_BUILD)
  2373. ->where('execution')->in($executionIdList)
  2374. ->andWhere('deleted')->eq('0')
  2375. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
  2376. ->fetchPairs('id');
  2377. if(!empty($buildPairs)) $buildIdList = implode(',', $buildPairs);
  2378. $executionIdList = $executionIdList ? implode(',', $executionIdList) : 0;
  2379. $storyIdList = $storyIdList ? implode(',', $storyIdList) : 0;
  2380. return array($storyIdList, $issueIdList, $meetingIdList, $reviewIdList, $designIdList, $executionIdList, $taskIdList, $buildIdList);
  2381. }
  2382. /**
  2383. * 获取关联执行的数据。
  2384. * Get linked execution data.
  2385. *
  2386. * @param int $executionID
  2387. * @access public
  2388. * @return array
  2389. */
  2390. public function getLinkedExecutionData($executionID)
  2391. {
  2392. $storyIdList = $taskIdList = $buildIdList = $testtaskIdList = 0;
  2393. $execution = $this->loadModel('execution')->getByID($executionID);
  2394. $project = $execution ? $this->loadModel('project')->getByID((int)$execution->project) : '';
  2395. if($project && !$project->hasProduct) $storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->fetchPairs('story', 'story');
  2396. $storyIdList = $storyIdList ? implode(',', $storyIdList) : '';
  2397. $taskPairs = $this->dao->select('id')->from(TABLE_TASK)
  2398. ->where('execution')->eq($executionID)
  2399. ->andWhere('deleted')->eq('0')
  2400. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
  2401. ->fetchPairs('id');
  2402. if(!empty($taskPairs)) $taskIdList = implode(',', $taskPairs);
  2403. $buildPairs = $this->dao->select('id')->from(TABLE_BUILD)
  2404. ->where('execution')->eq($executionID)
  2405. ->andWhere('deleted')->eq('0')
  2406. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
  2407. ->fetchPairs('id');
  2408. if(!empty($buildPairs)) $buildIdList = implode(',', $buildPairs);
  2409. $testtaskPairs = $this->dao->select('id')->from(TABLE_TESTTASK)
  2410. ->where('execution')->eq($executionID)
  2411. ->andWhere('deleted')->eq('0')
  2412. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
  2413. ->fetchPairs('id');
  2414. if(!empty($testtaskPairs)) $testtaskIdList = implode(',', $testtaskPairs);
  2415. return array($storyIdList, $taskIdList, $buildIdList, $testtaskIdList);
  2416. }
  2417. /**
  2418. * 获取附件的来源。
  2419. * Get file source pairs.
  2420. *
  2421. * @param array $files
  2422. * @access public
  2423. * @return array
  2424. */
  2425. public function getFileSourcePairs($files)
  2426. {
  2427. $sourceList = array();
  2428. $sourcePairs = array();
  2429. foreach($files as $file)
  2430. {
  2431. if(!isset($sourceList[$file->objectType])) $sourceList[$file->objectType] = array();
  2432. $sourceList[$file->objectType][$file->objectID] = $file->objectID;
  2433. }
  2434. $this->app->loadConfig('action');
  2435. foreach($sourceList as $type => $idList)
  2436. {
  2437. $table = zget($this->config->objectTables, $type, '');
  2438. $field = zget($this->config->action->objectNameFields, $type, '');
  2439. if(empty($table) || empty($field)) continue;
  2440. $sourcePairs[$type] = $this->dao->select('id,' . $field)->from($table)->where('id')->in($idList)->fetchPairs('id', $field);
  2441. }
  2442. return $sourcePairs;
  2443. }
  2444. /**
  2445. * 获取文件图标。
  2446. * Get file icon.
  2447. *
  2448. * @param array $files
  2449. * @access public
  2450. * @return array
  2451. */
  2452. public function getFileIcon($files)
  2453. {
  2454. $this->app->loadConfig('file');
  2455. $fileIcon = array();
  2456. foreach($files as $file)
  2457. {
  2458. if(in_array($file->extension, $this->config->file->imageExtensions)) continue;
  2459. switch($file->extension)
  2460. {
  2461. case in_array($file->extension, array('zip', 'tar', 'gz', 'bz2', 'rar')):
  2462. $iconClass = 'icon-file-archive';
  2463. break;
  2464. case in_array($file->extension, array('csv', 'xls', 'xlsx')):
  2465. $iconClass = 'icon-file-excel';
  2466. break;
  2467. case in_array($file->extension, array('doc', 'docx')):
  2468. $iconClass = 'icon-file-word';
  2469. break;
  2470. case in_array($file->extension, array('ppt', 'pptx')):
  2471. $iconClass = 'icon-file-powerpoint';
  2472. break;
  2473. case in_array($file->extension, array('pdf')):
  2474. $iconClass = 'icon-file-pdf';
  2475. break;
  2476. case in_array($file->extension, array('mp3', 'ogg', 'wav')):
  2477. $iconClass = 'icon-file-audio';
  2478. break;
  2479. case in_array($file->extension, array('avi', 'mp4', 'mov')):
  2480. $iconClass = 'icon-file-video';
  2481. break;
  2482. case in_array($file->extension, array('txt', 'md')):
  2483. $iconClass = 'icon-file-text';
  2484. break;
  2485. case in_array($file->extension, array('html', 'htm')):
  2486. $iconClass = 'icon-file-globe';
  2487. break;
  2488. default:
  2489. $iconClass = 'icon-file';
  2490. break;
  2491. }
  2492. $fileIcon[$file->id] = "<i class='file-icon icon $iconClass'></i>";
  2493. }
  2494. return $fileIcon;
  2495. }
  2496. /**
  2497. * 获取文档的树形结构。
  2498. * Get doc tree.
  2499. *
  2500. * @param int $libID
  2501. * @access public
  2502. * @return array
  2503. */
  2504. public function getDocTree($libID)
  2505. {
  2506. $docTrees = $this->loadModel('tree')->getTreeStructure($libID, 'doc');
  2507. array_unshift($docTrees, array('id' => 0, 'name' => '/', 'type' => 'doc', 'actions' => false, 'root' => $libID));
  2508. foreach($docTrees as $i => $tree)
  2509. {
  2510. $tree = (object)$tree;
  2511. $docTrees[$i] = $this->buildDocNode($tree, $libID);
  2512. }
  2513. if(empty($docTrees[0]->children)) array_shift($docTrees);
  2514. return $docTrees;
  2515. }
  2516. /**
  2517. * 构造文档节点。
  2518. * Build doc node.
  2519. *
  2520. * @param object $node
  2521. * @param int $libID
  2522. * @access public
  2523. * @return object
  2524. */
  2525. public function buildDocNode($node, $libID)
  2526. {
  2527. $node = (object)$node;
  2528. static $docGroups;
  2529. if(empty($docGroups))
  2530. {
  2531. $docs = $this->dao->select('*')->from(TABLE_DOC)->where('lib')->eq($libID)->andWhere('deleted')->eq(0)->fetchAll('', false);
  2532. $docs = $this->batchCheckPrivDoc($docs);
  2533. $docGroups = array();
  2534. foreach($docs as $doc) $docGroups[$doc->module][$doc->id] = $doc;
  2535. }
  2536. if(!empty($node->children)) foreach($node->children as $i => $child) $node->children[$i] = $this->buildDocNode($child, $libID);
  2537. if(!isset($node->id)) $node->id = 0;
  2538. $node->type = 'module';
  2539. $docs = isset($docGroups[$node->id]) ? $docGroups[$node->id] : array();
  2540. $menu = !empty($node->children) ? $node->children : array();
  2541. if(!empty($docs))
  2542. {
  2543. $docItems = array();
  2544. foreach($docs as $doc)
  2545. {
  2546. $docItem = new stdclass();
  2547. $docItem->type = 'doc';
  2548. $docItem->id = $doc->id;
  2549. $docItem->title = $doc->title;
  2550. $docItem->acl = $doc->acl;
  2551. $docItem->url = helper::createLink('doc', 'view', "doc=$doc->id");
  2552. $buttons = '';
  2553. if(common::hasPriv('doc', 'edit')) $buttons .= html::a(helper::createLink('doc', 'edit', "docID={$doc->id}"), "<i class='icon icon-edit'></i>", 'hiddenwin', "class='btn-icon' title='{$this->lang->doc->edit}'");
  2554. if(common::hasPriv('doc', 'delete')) $buttons .= html::a(helper::createLink('doc', 'delete', "docID={$doc->id}"), "<i class='icon icon-remove'></i>", 'hiddenwin', "class='btn-icon' title='{$this->lang->doc->delete}'");
  2555. $docItem->buttons = $buttons;
  2556. $docItem->actions = false;
  2557. $docItems[] = $docItem;
  2558. }
  2559. /* Reorder children. The doc is top of menu. */
  2560. if($menu) $docItems = array_merge($docItems, $menu);
  2561. $node->children = $docItems;
  2562. }
  2563. $node->docsCount = isset($node->children) ? count($node->children) : 0;
  2564. $node->actions = false;
  2565. return $node;
  2566. }
  2567. /**
  2568. * 获取文档的统计信息。
  2569. * Get statistic information.
  2570. *
  2571. * @access public
  2572. * @return object
  2573. */
  2574. public function getStatisticInfo()
  2575. {
  2576. $today = date('Y-m-d');
  2577. $statistic = new stdclass();
  2578. $statistic->totalDocs = $this->dao->select('COUNT(1) AS count')->from(TABLE_DOC)
  2579. ->where('deleted')->eq('0')
  2580. ->andWhere('type')->in($this->config->doc->docTypes)
  2581. ->andWhere('templateType')->eq('')
  2582. ->andWhere('vision')->eq($this->config->vision)
  2583. ->fetch('count');
  2584. $statistic->todayEditedDocs = $this->dao->select('count(DISTINCT `objectID`) as count')->from(TABLE_ACTION)->alias('t1')
  2585. ->leftJoin(TABLE_DOC)->alias('t2')->on("t1.`objectID`=t2.id and t1.`objectType`='doc'")
  2586. ->where('t1.objectType')->eq('doc')
  2587. ->andWhere('t1.action')->eq('edited')
  2588. ->andWhere('t1.actor')->eq($this->app->user->account)
  2589. ->andWhere('t1.vision')->eq($this->config->vision)
  2590. ->andWhere('LEFT(t1.date, 10)')->eq($today)
  2591. ->andWhere('t2.deleted')->eq(0)
  2592. ->andWhere('t2.type')->in($this->config->doc->docTypes)
  2593. ->fetch('count');
  2594. $statistic->myEditedDocs = $this->dao->select('count(DISTINCT t1.`objectID`) as count')->from(TABLE_ACTION)->alias('t1')
  2595. ->leftJoin(TABLE_DOC)->alias('t2')->on("t1.`objectID`=t2.id and t1.`objectType`='doc'")
  2596. ->where('t1.objectType')->eq('doc')
  2597. ->andWhere('t1.action')->eq('edited')
  2598. ->andWhere('t1.actor')->eq($this->app->user->account)
  2599. ->andWhere('t1.vision')->eq($this->config->vision)
  2600. ->andWhere('t2.lib')->ne(0)
  2601. ->andWhere('t2.deleted')->eq(0)
  2602. ->andWhere('t2.type')->in($this->config->doc->docTypes)
  2603. ->fetch('count');
  2604. $myStatistic = $this->dao->select("COUNT(1) AS `myDocs`, SUM(views) as `docViews`, SUM(collects) as `docCollects`")->from(TABLE_DOC)
  2605. ->where('addedBy')->eq($this->app->user->account)
  2606. ->andWhere('type')->in($this->config->doc->docTypes)
  2607. ->andWhere('templateType')->eq('')
  2608. ->andWhere('deleted')->eq(0)
  2609. ->andWhere('vision')->eq($this->config->vision)
  2610. ->andWhere('lib')->ne(0)
  2611. ->fetch();
  2612. $statistic->myDocs = $myStatistic->myDocs;
  2613. $statistic->myDoc = new stdclass();
  2614. $statistic->myDoc->docViews = $myStatistic->docViews;
  2615. $statistic->myDoc->docCollects = $myStatistic->docCollects;
  2616. return $statistic;
  2617. }
  2618. /**
  2619. * 获取收信人和抄送人列表。
  2620. * Get toList and ccList.
  2621. *
  2622. * @param object $doc
  2623. * @access public
  2624. * @return bool|array
  2625. */
  2626. public function getToAndCcList($doc)
  2627. {
  2628. /* Set toList and ccList. */
  2629. $toList = '';
  2630. $ccList = str_replace(' ', '', trim($doc->mailto, ','));
  2631. if(empty($ccList)) return false;
  2632. if(strpos($ccList, ',') === false)
  2633. {
  2634. $toList = $ccList;
  2635. $ccList = '';
  2636. }
  2637. else
  2638. {
  2639. $commaPos = strpos($ccList, ',');
  2640. $toList = substr($ccList, 0, $commaPos);
  2641. $ccList = substr($ccList, $commaPos + 1);
  2642. }
  2643. return array($toList, $ccList);
  2644. }
  2645. /**
  2646. * 获取下拉菜单的链接。
  2647. * Get the dropmenu link.
  2648. *
  2649. * @param string $type product|project
  2650. * @param int $objectID
  2651. * @access public
  2652. * @return string
  2653. */
  2654. public function getDropMenuLink($type, $objectID)
  2655. {
  2656. if(!in_array($type, array('product', 'project'))) return '';
  2657. $currentModule = $this->app->rawModule;
  2658. $currentMethod = $this->app->rawMethod;
  2659. if($currentModule == 'api' && $currentMethod == 'index')
  2660. {
  2661. $currentModule = 'doc';
  2662. $currentMethod = $type . 'Space';
  2663. }
  2664. return helper::createLink('doc', 'ajaxGetDropMenu', "objectType={$type}&objectID={$objectID}&module={$currentModule}&method={$currentMethod}");
  2665. }
  2666. /**
  2667. * 统计当前页面上文件的数量和大小。
  2668. * Count the number and size of files on the current page.
  2669. *
  2670. * @param array $files
  2671. * @access public
  2672. * @return string
  2673. */
  2674. public function summary($files)
  2675. {
  2676. $filesCount = 0;
  2677. $sizeCount = 0;
  2678. $extensionCount = array();
  2679. $extensionSummary = '';
  2680. foreach($files as $file)
  2681. {
  2682. if(!isset($extensionCount[$file->extension])) $extensionCount[$file->extension] = 0;
  2683. $sizeCount += $file->size;
  2684. $filesCount++;
  2685. $extensionCount[$file->extension] ++;
  2686. }
  2687. /* Unit conversion. */
  2688. $i = 0;
  2689. while ($sizeCount > 1024 and $i <= 4)
  2690. {
  2691. $sizeCount = $sizeCount / 1024;
  2692. $i++;
  2693. }
  2694. $unitList = array('B', 'K', 'M', 'G', 'T');
  2695. $sizeCount = round($sizeCount, 1) . $unitList[$i];
  2696. /* Summary of each type. */
  2697. foreach($extensionCount as $extension => $count)
  2698. {
  2699. if(in_array($this->app->getClientLang(), array('zh-cn', 'zh-tw')))
  2700. {
  2701. $extensionSummary .= $extension . ' ' . $count . $this->lang->doc->ge . $this->lang->doc->point;
  2702. }
  2703. else
  2704. {
  2705. $extensionSummary .= $extension . ' ' . $this->lang->doc->ge . ' ' . $count . $this->lang->doc->point;
  2706. }
  2707. }
  2708. $extensionSummary = rtrim($extensionSummary, $this->lang->doc->point);
  2709. return sprintf($this->lang->doc->summary, $filesCount, $sizeCount, $extensionSummary);
  2710. }
  2711. /**
  2712. * 设置文档的导航。
  2713. * Set doc menu by type.
  2714. *
  2715. * @param string $type mine|project|execution|product|custom
  2716. * @param int $objectID
  2717. * @param int $libID
  2718. * @param int $appendLib
  2719. * @access public
  2720. * @return array|string
  2721. */
  2722. public function setMenuByType($type, $objectID, $libID, $appendLib = 0)
  2723. {
  2724. if(empty($type))
  2725. {
  2726. $doclib = $this->getLibByID($libID);
  2727. $type = $doclib->type == 'execution' ? 'project' : $doclib->type;
  2728. $objectID = isset($doclib->{$type}) ? $doclib->{$type} : 0;
  2729. }
  2730. $isExecution = in_array($this->app->tab, array('project', 'doc')) && $type == 'execution';
  2731. $type = $isExecution ? 'project' : $type;
  2732. $objectDropdown = array('text' => '', 'link' => '');
  2733. $appendObject = $objectID;
  2734. if(in_array($type, array('project', 'product', 'execution')))
  2735. {
  2736. $object = $this->dao->select('id,name,status,deleted' . ($isExecution ? ',project' : ''))->from($this->config->objectTables[$type])->where('id')->eq($objectID)->fetch();
  2737. $objectID = $isExecution ? (int)$object->project : $objectID;
  2738. if(empty($object)) return helper::createLink($type, $type == 'project' && $this->config->vision != 'lite' ? 'createGuide' : 'create', $type == 'project' && $this->config->vision == 'lite' ? 'model=kanban' : '');
  2739. $this->loadModel($type);
  2740. $objects = $this->getOrderedObjects($type, 'merge', $objectID);
  2741. $objectID = method_exists($this->$type, 'saveState') ? $this->{$type}->saveState($objectID, $objects) : $this->{$type}->checkAccess($objectID, $objects);
  2742. $libs = $this->getLibsByObject($type, $objectID, $appendLib);
  2743. if(($libID == 0 || !isset($libs[$libID])) && !empty($libs)) $libID = reset($libs)->id;
  2744. if($this->app->tab != 'doc' && isset($libs[$libID]))
  2745. {
  2746. $objectDropdown['text'] = zget($libs[$libID], 'name', '');
  2747. }
  2748. else
  2749. {
  2750. $objectDropdown['text'] = zget($objects, $objectID, '');
  2751. $objectDropdown['link'] = $this->getDropMenuLink($type, $appendObject);
  2752. }
  2753. }
  2754. else
  2755. {
  2756. if($type == 'custom' || $type == 'doctemplate')
  2757. {
  2758. $spaces = $type == 'custom' ? $this->getTeamSpaces() : $this->getDocTemplateSpaces();
  2759. if(empty($objectID)) $objectID = (int)key($spaces);
  2760. if(!isset($spaces[$objectID])) $objectID = (int)key($spaces);
  2761. }
  2762. $libs = $this->getLibsByObject($type, $objectID, $appendLib);
  2763. if(($libID == 0 || !isset($libs[$libID])) && !empty($libs)) $libID = reset($libs)->id;
  2764. if(isset($libs[$libID])) $objectDropdown['text'] = zget($libs[$libID], 'name', '');
  2765. if($type == 'custom' || $type == 'doctemplate')
  2766. {
  2767. $spaceExtra = $type == 'custom' ? 'nomine' : 'doctemplate';
  2768. $objectDropdown['text'] = zget($spaces, $objectID, '');
  2769. $objectDropdown['link'] = helper::createLink('doc', 'ajaxGetSpaceMenu', "libID={$objectID}&module={$this->app->rawModule}&method={$this->app->rawMethod}&extra={$spaceExtra}");
  2770. }
  2771. $object = $this->dao->select('id,name,deleted')->from(TABLE_DOCLIB)->where('id')->eq($objectID)->fetch();
  2772. }
  2773. $tab = strpos(',my,doc,product,project,execution,', ",{$this->app->tab},") !== false ? $this->app->tab : 'doc';
  2774. if($type == 'mine') $type = 'my';
  2775. if($type == 'custom') $type = 'team';
  2776. if($tab == 'doc' && !common::hasPriv('doc', $type . 'Space')) return helper::createLink('user', 'deny', "module=doc&method={$type}Space");
  2777. if($tab != 'doc' && method_exists($type . 'Model', 'setMenu'))
  2778. {
  2779. $this->loadModel($type)->setMenu($objectID);
  2780. }
  2781. elseif($tab == 'doc' && isset($this->lang->doc->menu->{$type}['alias']))
  2782. {
  2783. $this->lang->doc->menu->{$type}['alias'] .= ',' . $this->app->rawMethod;
  2784. }
  2785. return array($libs, $libID, $object, $objectID, $objectDropdown);
  2786. }
  2787. /**
  2788. * 通过搜索获取文档列表数据。
  2789. * Get docs by search.
  2790. *
  2791. * @param string $type product|project|execution|custom|book
  2792. * @param int $objectID
  2793. * @param int $libID
  2794. * @param int $queryID
  2795. * @param string $orderBy
  2796. * @param object $pager
  2797. * @access public
  2798. * @return array
  2799. */
  2800. public function getDocsBySearch($type, $objectID, $libID, $queryID, $orderBy = 'id_desc', $pager = null)
  2801. {
  2802. $query = $this->buildQuery($type, $queryID);
  2803. $libs = $this->getLibsByObject($type, $objectID, $libID);
  2804. $docIdList = $this->getPrivDocs(array_keys($libs));
  2805. $docs = $this->dao->select('*')->from(TABLE_DOC)
  2806. ->where('deleted')->eq(0)
  2807. ->andWhere('builtIn')->eq('0')
  2808. ->andWhere($query)
  2809. ->andWhere('lib')->in(array_keys($libs))
  2810. ->andWhere('vision')->eq($this->config->vision)
  2811. ->beginIF($this->config->doc->notArticleType)->andWhere('type')->notIN($this->config->doc->notArticleType)->fi()
  2812. ->beginIF(!empty($docIdList))->andWhere('id')->in($docIdList)->fi()
  2813. ->andWhere("(status = 'normal' or (status = 'draft' and addedBy='{$this->app->user->account}'))")
  2814. ->orderBy($orderBy)
  2815. ->page($pager)
  2816. ->fetchAll('id', false);
  2817. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'doc', true);
  2818. return $this->processCollector($docs);
  2819. }
  2820. /**
  2821. * 获取给定文档库的目录列表。
  2822. * Get modules from libs.
  2823. *
  2824. * @param array $libs Lib id list.
  2825. * @param string $type doc|api
  2826. * @access public
  2827. * @return array
  2828. */
  2829. public function getModulesOfLibs($libs, $type = 'doc,api')
  2830. {
  2831. $types = explode(',', $type);
  2832. return $this->dao->select('*')->from(TABLE_MODULE)
  2833. ->where('root')->in($libs)
  2834. ->beginIF(count($types) > 1)->andWhere('type')->in($types)->fi()
  2835. ->beginIF(count($types) == 1)->andWhere('type')->eq($type)->fi()
  2836. ->andWhere('deleted')->eq('0')
  2837. ->orderBy('grade desc, `order`')
  2838. ->fetchAll('id', false);
  2839. }
  2840. /**
  2841. * Get module tree.
  2842. *
  2843. * @param int $rootID
  2844. * @param int $moduleID
  2845. * @param string $type doc|api
  2846. * @param int $parent
  2847. * @param array $modules
  2848. * @access public
  2849. * @return array
  2850. */
  2851. public function getModuleTree($rootID, $moduleID = 0, $type = 'doc', $parent = 0, $modules = array(), $docID = 0)
  2852. {
  2853. if(is_array($modules) and empty($modules))
  2854. {
  2855. $modules = $this->dao->select('*')->from(TABLE_MODULE)
  2856. ->where('root')->eq((int)$rootID)
  2857. ->andWhere('type')->eq($type)
  2858. ->andWhere('deleted')->eq(0)
  2859. ->orderBy('grade desc, `order`')
  2860. ->fetchAll('id', false);
  2861. }
  2862. $moduleTree = array();
  2863. foreach($modules as $module)
  2864. {
  2865. if($module->parent != $parent) continue;
  2866. unset($modules[$module->id]);
  2867. $item = new stdclass();
  2868. $item->id = $module->id;
  2869. $item->name = $module->name;
  2870. $item->objectType = $module->type;
  2871. $item->type = 'module';
  2872. $item->libID = $rootID;
  2873. $item->active = $module->id == $moduleID ? 1 : 0;
  2874. $item->order = $module->order;
  2875. $item->children = $this->getModuleTree($rootID, $moduleID, $type, $module->id, $modules, $docID);
  2876. $showDoc = $this->loadModel('setting')->getItem('owner=' . $this->app->user->account . '&module=doc&key=showDoc');
  2877. $showDoc = $showDoc === '0' ? 0 : 1;
  2878. if($this->app->rawMethod == 'view' and $module->type != 'api' and $showDoc)
  2879. {
  2880. $docIDList = $this->getPrivDocs(array($rootID), $module->id);
  2881. $docs = $this->dao->select('*, title as name')->from(TABLE_DOC)
  2882. ->where('id')->in($docIDList)
  2883. ->andWhere('deleted')->eq(0)
  2884. ->fetchAll('id', false);
  2885. if(!empty($docs))
  2886. {
  2887. $docs = array_values($docs);
  2888. foreach($docs as $doc) $doc->active = $doc->id == $docID ? 1 : 0;
  2889. $item->children = array_merge($docs, $item->children);
  2890. }
  2891. }
  2892. $moduleTree[$module->id] = $item;
  2893. }
  2894. return array_values($moduleTree);
  2895. }
  2896. /**
  2897. * 获取文档库的树形结构。
  2898. * Get lib tree.
  2899. *
  2900. * @param int $libID
  2901. * @param array $libs
  2902. * @param string $type mine|product|project|execution|api|custom
  2903. * @param int $moduleID
  2904. * @param int $objectID
  2905. * @param string $browseType bysearch|byrelease
  2906. * @param int $param
  2907. * @param int $docID
  2908. * @access public
  2909. * @return array
  2910. */
  2911. public function getLibTree($libID, $libs, $type, $moduleID, $objectID = 0, $browseType = '', $param = 0, $docID = 0)
  2912. {
  2913. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getLibTree();
  2914. $type = $this->app->tab == 'doc' && $type == 'execution' ? 'project' : $type;
  2915. list($libTree, $apiLibs, $apiLibIDList) = $this->getObjectTree($libID, $libs, $type, $moduleID, $objectID, strtolower($browseType), $param, $docID);
  2916. $libTree = $this->processObjectTree($libTree, $type, $libID, $objectID, $apiLibs, $apiLibIDList);
  2917. if($type != 'project') $libTree = array_values($libTree[$type]);
  2918. if($type == 'mine')
  2919. {
  2920. $children = $libTree;
  2921. $libType = isset($this->app->rawParams['type']) ? $this->app->rawParams['type'] : '';
  2922. $libType = $this->app->rawMethod == 'view' ? 'mine' : $libType;
  2923. $mineMethods = array('mine' => 'myLib', 'view' => 'myView', 'collect' => 'myCollection', 'createdBy' => 'myCreation', 'editedBy' => 'myEdited');
  2924. $libTree = array();
  2925. foreach($mineMethods as $type => $mineMethod)
  2926. {
  2927. if($mineMethod != 'myLib' && !common::hasPriv('doc', $mineMethod)) continue;
  2928. $myItem = new stdclass();
  2929. $myItem->id = 0;
  2930. $myItem->name = $type == 'mine' ? '' : $this->lang->doc->{$mineMethod};
  2931. $myItem->type = $type;
  2932. $myItem->objectType = 'doc';
  2933. $myItem->objectID = 0;
  2934. $myItem->hasAction = false;
  2935. $myItem->active = strtolower($libType) == strtolower($type) ? 1 : 0;
  2936. if($type == 'mine') $myItem->children = $children;
  2937. $libTree[] = $myItem;
  2938. }
  2939. }
  2940. return $libTree;
  2941. }
  2942. /**
  2943. * 获取产品、项目、执行文档库的树形结构。
  2944. * Get a tree structure of the product, project, and execution document library.
  2945. *
  2946. * @param int $libID
  2947. * @param array $libs
  2948. * @param string $type mine|product|project|execution|api|custom
  2949. * @param int $moduleID
  2950. * @param int $objectID
  2951. * @param string $browseType bysearch|byrelease
  2952. * @param int $docID
  2953. * @param int $param
  2954. * @access public
  2955. * @return array
  2956. */
  2957. public function getObjectTree($libID, $libs, $type, $moduleID = 0, $objectID = 0, $browseType = '', $param = 0, $docID = 0)
  2958. {
  2959. if($type == 'project')
  2960. {
  2961. $executionLibs = array();
  2962. foreach($libs as $lib)
  2963. {
  2964. if($lib->type != 'execution') continue;
  2965. $executionLibs[$lib->execution][$lib->id] = $lib;
  2966. }
  2967. $executionPairs = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('id')->in(array_keys($executionLibs))->fetchPairs();
  2968. }
  2969. $release = $browseType == 'byrelease' && $param ? $this->loadModel('api')->getRelease(0, 'byId', $param) : null;
  2970. $libTree = array($type => array());
  2971. $apiLibs = $apiLibIDList = array();
  2972. foreach($libs as $lib)
  2973. {
  2974. $item = $this->buildLibItem($libID, $lib, $type, $moduleID, $objectID, $browseType, $docID, $release);
  2975. if(($type == 'project' && $lib->type != 'execution') || $type != 'project')
  2976. {
  2977. if($item->type == 'docLib') $libTree[$lib->type][$lib->id] = $item;
  2978. if($item->type == 'apiLib') $apiLibs[$lib->id] = $item;
  2979. }
  2980. else
  2981. {
  2982. $executionID = $lib->execution;
  2983. if(empty($libTree['execution'][$executionID]))
  2984. {
  2985. $execution = new stdclass();
  2986. $execution->id = $executionID;
  2987. $execution->name = zget($executionPairs, $executionID);
  2988. $execution->type = 'execution';
  2989. $execution->active = $item->active;
  2990. $execution->hasAction = false;
  2991. $execution->objectType = 'execution';
  2992. $execution->children = array();
  2993. if(count($executionLibs[$executionID]) == 1)
  2994. {
  2995. $execution->id = $item->id;
  2996. $execution->type = 'docLib';
  2997. $execution->hasAction = true;
  2998. $execution->children = $item->children;
  2999. }
  3000. $libTree['execution'][$executionID] = $execution;
  3001. if(count($executionLibs[$executionID]) == 1) continue;
  3002. }
  3003. $libTree['execution'][$executionID]->active = $item->active ? 1 : $libTree['execution'][$executionID]->active;
  3004. $libTree['execution'][$executionID]->children[] = $item;
  3005. }
  3006. if($item->type == 'apiLib') $apiLibIDList[] = $lib->id;
  3007. }
  3008. return array($libTree, $apiLibs, $apiLibIDList);
  3009. }
  3010. /**
  3011. * 构建文档库树形结构的节点。
  3012. * Build a node of the tree structure of the document library.
  3013. *
  3014. * @param int $libID
  3015. * @param object $lib
  3016. * @param string $type mine|product|project|execution|api|custom
  3017. * @param int $moduleID
  3018. * @param int $objectID
  3019. * @param string $browseType bysearch|byrelease
  3020. * @param int $docID
  3021. * @param mixed $release
  3022. * @access public
  3023. * @return object
  3024. */
  3025. public function buildLibItem($libID, $lib, $type, $moduleID = 0, $objectID = 0, $browseType = '', $docID = 0, $release = null)
  3026. {
  3027. $releaseModule = array();
  3028. if($release && $release->lib == $lib->id)
  3029. {
  3030. foreach($release->snap['modules'] as $module) $releaseModule[$module['id']] = (object)$module;
  3031. }
  3032. $item = new stdclass();
  3033. $item->id = $lib->id;
  3034. $item->type = $lib->type == 'api' ? 'apiLib' : 'docLib';
  3035. $item->name = $lib->name;
  3036. $item->parent = isset($lib->parent) ? $lib->parent : '';
  3037. $item->order = $lib->order;
  3038. $item->main = zget($lib, 'main', 0);
  3039. $item->objectType = $type;
  3040. $item->objectID = $objectID;
  3041. $item->addedBy = $lib->addedBy;
  3042. $item->active = $lib->id == $libID && $browseType != 'bysearch' ? 1 : 0;
  3043. $item->children = $this->getModuleTree($lib->id, $moduleID, $lib->type == 'api' ? 'api' : 'doc', 0, $releaseModule, $docID);
  3044. $showDoc = $this->loadModel('setting')->getItem('owner=' . $this->app->user->account . '&module=doc&key=showDoc');
  3045. $showDoc = $showDoc === '0' ? 0 : 1;
  3046. $showDocWithNotAPI = $showDoc && $lib->type != 'api';
  3047. $isSpace = $lib->type == 'custom' && $lib->parent == 0;
  3048. if($this->app->rawMethod == 'view' && $showDocWithNotAPI && !$isSpace)
  3049. {
  3050. $docIDList = $this->getPrivDocs(array($lib->id));
  3051. $docs = $this->dao->select('*, title as name')->from(TABLE_DOC)
  3052. ->where('id')->in($docIDList)
  3053. ->andWhere("(status = 'normal' or (status = 'draft' && addedBy='{$this->app->user->account}'))")
  3054. ->andWhere('deleted')->eq(0)
  3055. ->andWhere('module')->eq(0)
  3056. ->fetchAll('id', false);
  3057. if(!empty($docs))
  3058. {
  3059. $docs = array_values($docs);
  3060. foreach($docs as $doc) $doc->active = $doc->id == $docID ? 1 : 0;
  3061. $item->children = array_merge($docs, $item->children);
  3062. }
  3063. }
  3064. return $item;
  3065. }
  3066. /**
  3067. * 处理产品、项目、执行的文档库树形结构。
  3068. * Process the tree structure of the document library of product, project, and execution.
  3069. *
  3070. * @param array $libTree
  3071. * @param array $apiLibs
  3072. * @param array $apiLibIDList
  3073. * @param string $type mine|product|project|execution|api|custom
  3074. * @param int $libID
  3075. * @param int $objectID
  3076. * @access public
  3077. * @return array
  3078. */
  3079. public function processObjectTree($libTree, $type, $libID = 0, $objectID = 0, $apiLibs = array(), $apiLibIDList = array())
  3080. {
  3081. if(in_array($type, array('product', 'project', 'execution')))
  3082. {
  3083. $libTree[$type] = array_merge($libTree[$type], $apiLibs);
  3084. if($type == 'project' && !empty($libTree['execution'])) $libTree['execution'] = array_values($libTree['execution']);
  3085. $annex = new stdclass();
  3086. $annex->id = 'annex';
  3087. $annex->name = $this->lang->doclib->files;
  3088. $annex->type = 'annex';
  3089. $annex->objectType = $type;
  3090. $annex->objectID = $objectID;
  3091. $annex->active = empty($libID) ? 1 : 0;
  3092. if($type == 'project')
  3093. {
  3094. $newLibTree = array();
  3095. foreach($libTree as $key => $libs)
  3096. {
  3097. $newLibTree[$key] = $libs;
  3098. if($key == 'project') $newLibTree['annex'][''] = $annex;
  3099. }
  3100. $libTree = $newLibTree;
  3101. }
  3102. else
  3103. {
  3104. $libTree[$type][''] = $annex;
  3105. }
  3106. }
  3107. elseif($type == 'api')
  3108. {
  3109. $libTree[$type] = array_merge($libTree[$type], $apiLibs);
  3110. }
  3111. /* Add release for api. */
  3112. $releases = $this->loadModel('api')->getReleaseByQuery($apiLibIDList);
  3113. foreach($libTree as &$libList)
  3114. {
  3115. foreach($libList as &$lib)
  3116. {
  3117. if(!is_object($lib) || $lib->type != 'apiLib') continue;
  3118. $lib->versions = array();
  3119. foreach($releases as $index => $release)
  3120. {
  3121. if($lib->id != $release->lib) continue;
  3122. $lib->versions[] = $release;
  3123. unset($releases[$index]);
  3124. }
  3125. if(empty($lib->versions)) continue;
  3126. /* Set default version. */
  3127. $defaultVersion = new stdclass();
  3128. $defaultVersion->id = 0;
  3129. $defaultVersion->version = $this->lang->build->common;
  3130. $lib->versions = array_merge(array($defaultVersion), $lib->versions);
  3131. }
  3132. }
  3133. return $libTree;
  3134. }
  3135. /**
  3136. * 检查接口库名称。
  3137. * Check api library name.
  3138. *
  3139. * @param object $lib
  3140. * @param string $libType product|project
  3141. * @param int $libID
  3142. * @access public
  3143. * @return bool
  3144. */
  3145. public function checkApiLibName($lib, $libType, $libID = 0)
  3146. {
  3147. $sameNames = $this->dao->select('*')
  3148. ->from(TABLE_DOCLIB)
  3149. ->where('`product`')->eq($lib->product)
  3150. ->andWhere('`project`')->eq($lib->project)
  3151. ->andWhere('`name`')->eq($lib->name)
  3152. ->andWhere('`type`')->eq('api')
  3153. ->beginIF(!empty($libID))->andWhere('`id`')->ne($libID)->fi()
  3154. ->fetchAll('', false);
  3155. if(count($sameNames) > 0 && $libType == 'product') dao::$errors['name'] = $this->lang->doclib->apiNameUnique[$libType] . sprintf($this->lang->error->unique, $this->lang->doclib->name, $lib->name);
  3156. if(count($sameNames) > 0 && $libType == 'project') dao::$errors['name'] = $this->lang->doclib->apiNameUnique[$libType] . sprintf($this->lang->error->unique, $this->lang->doclib->name, $lib->name);
  3157. if(count($sameNames) > 0 && $libType == 'nolink') dao::$errors['name'] = $this->lang->doclib->apiNameUnique[$libType] . sprintf($this->lang->error->unique, $this->lang->doclib->name, $lib->name);
  3158. return !dao::isError();
  3159. }
  3160. /**
  3161. * 创建一个操作。
  3162. * Create an action.
  3163. *
  3164. * @param int $docID
  3165. * @param string $action view|collect
  3166. * @param string $account
  3167. * @access public
  3168. * @return int|bool
  3169. */
  3170. public function createAction($docID, $action, $account = '')
  3171. {
  3172. if(empty($docID)) return false;
  3173. $docStatus = $this->dao->select('status')->from(TABLE_DOC)->where('id')->eq($docID)->fetch('status');
  3174. if(empty($account))$account = $this->app->user->account;
  3175. if($action == 'collect') $this->dao->delete()->from(TABLE_DOCACTION)->where('doc')->eq($docID)->andWhere('action')->eq('collect')->andWhere('actor')->eq($account)->exec();
  3176. if($action == 'view')
  3177. {
  3178. $lastView = $this->dao->select('date')->from(TABLE_DOCACTION)->where('doc')->eq($docID)->andWhere('action')->eq('view')->andWhere('actor')->eq($account)->orderBy('id_desc')->fetch('date');
  3179. if($lastView and (time() - strtotime($lastView) < 4)) return false;
  3180. }
  3181. $data = new stdclass();
  3182. $data->doc = $docID;
  3183. $data->action = $action;
  3184. $data->actor = $account;
  3185. $data->date = helper::now();
  3186. $this->dao->insert(TABLE_DOCACTION)->data($data)->autoCheck()->exec();
  3187. if(dao::isError()) return false;
  3188. $actionID = $this->dao->lastInsertID();
  3189. if($action == 'view' && $docStatus == 'normal') $this->dao->update(TABLE_DOC)->set('views = views + 1')->where('id')->eq($docID)->exec();
  3190. if($action == 'collect') $this->dao->update(TABLE_DOC)->set('collects = collects + 1')->where('id')->eq($docID)->exec();
  3191. return $actionID;
  3192. }
  3193. /**
  3194. * 获取文档的所有操作信息。
  3195. * Get action by doc ID.
  3196. *
  3197. * @param int $docID
  3198. * @param string $action view|collect
  3199. * @param string $account
  3200. * @access public
  3201. * @return object|false
  3202. */
  3203. public function getActionByObject($docID, $action, $account = '')
  3204. {
  3205. if(empty($account)) $account = $this->app->user->account;
  3206. return $this->dao->select('*')->from(TABLE_DOCACTION)->where('doc')->eq($docID)->andWhere('action')->eq($action)->andWhere('actor')->eq($account)->fetch();
  3207. }
  3208. /**
  3209. * 删除一个动作。
  3210. * Delete an action.
  3211. *
  3212. * @param int $actionID
  3213. * @access public
  3214. * @return bool
  3215. */
  3216. public function deleteAction($actionID)
  3217. {
  3218. $action = $this->dao->select('*')->from(TABLE_DOCACTION)->where('id')->eq($actionID)->fetch();
  3219. if(!$action) return false;
  3220. $this->dao->delete()->from(TABLE_DOCACTION)->where('id')->eq($actionID)->exec();
  3221. if($action->action == 'collect') $this->dao->update(TABLE_DOC)->set('collects = collects - 1')->where('id')->eq($action->doc)->exec();
  3222. return !dao::isError();
  3223. }
  3224. /**
  3225. * 处理文档的收藏者信息。
  3226. * Process collector to account.
  3227. *
  3228. * @param array $docs
  3229. * @access public
  3230. * @return array
  3231. */
  3232. public function processCollector($docs)
  3233. {
  3234. $actionGroup = $this->dao->select('*')->from(TABLE_DOCACTION)->where('doc')->in(array_keys($docs))->andWhere('action')->eq('collect')->fetchGroup('doc', 'actor');
  3235. foreach($docs as $docID => $doc)
  3236. {
  3237. $doc->collector = '';
  3238. if(isset($actionGroup[$docID])) $doc->collector = ',' . implode(',', array_keys($actionGroup[$docID])) . ',';
  3239. }
  3240. return $docs;
  3241. }
  3242. /**
  3243. * 检查文档是否正在被其他人编辑。
  3244. * Check other editing.
  3245. *
  3246. * @param int $docID
  3247. * @access public
  3248. * @return bool
  3249. */
  3250. public function checkOtherEditing($docID)
  3251. {
  3252. $now = time();
  3253. $account = $this->app->user->account;
  3254. $docID = (int)$docID;
  3255. $doc = $this->dao->select('id,editingDate')->from(TABLE_DOC)->where('id')->eq($docID)->fetch();
  3256. if(empty($doc)) return false;
  3257. $editingDate = $doc->editingDate ? json_decode($doc->editingDate, true) : array();
  3258. $otherEditing = false;
  3259. foreach($editingDate as $editingAccount => $timestamp)
  3260. {
  3261. if($editingAccount != $account && ($now - $timestamp) <= $this->config->doc->saveDraftInterval)
  3262. {
  3263. $otherEditing = true;
  3264. break;
  3265. }
  3266. }
  3267. $editingDate[$account] = $now;
  3268. $this->dao->update(TABLE_DOC)->set('editingDate')->eq(json_encode($editingDate))->where('id')->eq($docID)->exec();
  3269. return $otherEditing;
  3270. }
  3271. /**
  3272. * 获取文档动态。
  3273. * Get document dynamic.
  3274. *
  3275. * @param object $pager
  3276. * @access public
  3277. * @return array
  3278. */
  3279. public function getDynamic($pager = null)
  3280. {
  3281. $allLibs = $this->getLibs('hasApi');
  3282. $hasPrivDocIdList = $this->getPrivDocs(array(), 0, 'all');
  3283. $apiList = $this->loadModel('api')->getPrivApis('all');
  3284. $actionCondition = $this->loadModel('action')->getActionCondition('doc');
  3285. $actions = $this->dao->select('*')->from(TABLE_ACTION)
  3286. ->where('vision')->eq($this->config->vision)
  3287. ->andWhere('((objectType')->eq('doclib')
  3288. ->andWhere('objectID')->in(array_keys($allLibs))
  3289. ->markRight(1)
  3290. ->orWhere('(objectType')->eq('doc')
  3291. ->andWhere('objectID')->in($hasPrivDocIdList)
  3292. ->markRight(1)
  3293. ->orWhere('(objectType')->eq('api')
  3294. ->andWhere('objectID')->in(array_keys($apiList))
  3295. ->markRight(2)
  3296. ->beginIF($actionCondition)->andWhere("($actionCondition)")->fi()
  3297. ->orderBy('date_desc,id_asc')
  3298. ->page($pager)
  3299. ->fetchAll('', false);
  3300. return $this->loadModel('action')->transformActions($actions);
  3301. }
  3302. /**
  3303. * 将当前用户从文档的正在编辑者列表中移除。
  3304. * Removes the current user from the list of people editing the document.
  3305. *
  3306. * @param object|bool $doc
  3307. * @access public
  3308. * @return bool
  3309. */
  3310. public function removeEditing($doc)
  3311. {
  3312. if(empty($doc->id) || empty($doc->editingDate)) return false;
  3313. $account = $this->app->user->account;
  3314. $editingDate = json_decode($doc->editingDate, true);
  3315. if(!isset($editingDate[$account])) return false;
  3316. unset($editingDate[$account]);
  3317. $this->dao->update(TABLE_DOC)->set('editingDate')->eq(json_encode($editingDate))->where('id')->eq($doc->id)->exec();
  3318. return !dao::isError();
  3319. }
  3320. /**
  3321. * 获取编辑过文档的用户列表。
  3322. * Get editors of a document.
  3323. *
  3324. * @param int $docID
  3325. * @param string $objectType
  3326. * @access public
  3327. * @return array
  3328. */
  3329. public function getEditors($docID = 0, $objectType = 'doc')
  3330. {
  3331. if(!$docID) return array();
  3332. $actions = $this->dao->select('*')->from(TABLE_ACTION)
  3333. ->where('objectType')->eq($objectType)
  3334. ->andWhere('objectID')->eq($docID)
  3335. ->andWhere('action')->in('edited')
  3336. ->orderBy('date_desc')
  3337. ->fetchAll('id', false);
  3338. $editors = array();
  3339. foreach($actions as $action)
  3340. {
  3341. $editor = new stdclass();
  3342. $editor->account = $action->actor;
  3343. $editor->date = $action->date;
  3344. $editors[] = $editor;
  3345. }
  3346. return $editors;
  3347. }
  3348. /**
  3349. * 构造搜索表单。
  3350. * Build search form.
  3351. *
  3352. * @param int $libID
  3353. * @param array $libs
  3354. * @param int $queryID
  3355. * @param string $actionURL
  3356. * @param string $type mine|product|project|execution|custom
  3357. * @access public
  3358. * @return void
  3359. */
  3360. public function buildSearchForm($libID, $libs, $queryID, $actionURL, $type)
  3361. {
  3362. $this->loadModel('product');
  3363. if($this->app->rawMethod == 'contribute')
  3364. {
  3365. $this->config->doc->search['module'] = 'contributeDoc';
  3366. $this->config->doc->search['params']['project']['values'] = $this->loadModel('project')->getPairsByProgram(0, 'all', false, 'order_asc') + array('all' => $this->lang->doc->allProjects);
  3367. $this->config->doc->search['params']['execution']['values'] = $this->loadModel('execution')->getPairs(0, 'all', 'multiple,leaf,noprefix,withobject') + array('all' => $this->lang->doc->allExecutions);
  3368. $this->config->doc->search['params']['lib']['values'] = $this->loadModel('doc')->getLibs('all', 'withObject') + array('all' => $this->lang->doclib->all);
  3369. $this->config->doc->search['params']['product']['values'] = $this->product->getPairs() + array('all' => $this->lang->doc->allProduct);
  3370. unset($this->config->doc->search['fields']['module'], $this->config->doc->search['params']['module']);
  3371. }
  3372. else
  3373. {
  3374. if(!isset($libs[$libID])) $libs[$libID] = $this->getLibByID($libID);
  3375. $libPairs = array();
  3376. $queryName = $type . 'libDoc';
  3377. foreach($libs as $lib)
  3378. {
  3379. if(empty($lib)) continue;
  3380. if($lib->type == 'api') continue;
  3381. $libPairs[$lib->id] = $lib->name;
  3382. }
  3383. if($type == 'project')
  3384. {
  3385. $this->config->doc->search['params']['execution']['values'] = $this->loadModel('execution')->getPairs((int)$this->session->project, 'all', 'multiple,leaf,noprefix') + array('all' => $this->lang->doc->allExecutions);
  3386. }
  3387. else
  3388. {
  3389. if($type == 'mine' || $type == 'createdby')
  3390. {
  3391. unset($this->config->doc->search['fields']['addedBy'], $this->config->doc->search['params']['addedBy']);
  3392. if($type == 'mine') unset($this->config->doc->search['fields']['editedBy'], $this->config->doc->search['params']['editedBy']);
  3393. }
  3394. unset($this->config->doc->search['fields']['execution'], $this->config->doc->search['params']['execution']);
  3395. }
  3396. if(in_array($type, array('view', 'collect', 'createdby', 'editedby'))) $libPairs = $this->getLibs('all', 'withObject');
  3397. $this->config->doc->search['module'] = $queryName;
  3398. $this->config->doc->search['params']['lib']['values'] = $libPairs + array('all' => $this->lang->doclib->all);
  3399. unset($this->config->doc->search['fields']['product'], $this->config->doc->search['params']['product']);
  3400. unset($this->config->doc->search['fields']['module'], $this->config->doc->search['params']['module']);
  3401. }
  3402. unset($this->config->doc->search['params']['status']['values']['']);
  3403. $this->config->doc->search['actionURL'] = $actionURL;
  3404. $this->config->doc->search['queryID'] = $queryID;
  3405. $this->loadModel('search')->setSearchParams($this->config->doc->search);
  3406. }
  3407. /**
  3408. * 删除附件。
  3409. * Delete files.
  3410. *
  3411. * @param array $idList
  3412. * @access public
  3413. * @return bool
  3414. */
  3415. public function deleteFiles($idList)
  3416. {
  3417. if(empty($idList)) return true;
  3418. $this->dao->update(TABLE_FILE)->set('deleted')->eq('1')->where('id')->in($idList)->exec();
  3419. return !dao::isError();
  3420. }
  3421. /**
  3422. * 通过ID获取产品/项目/执行的信息。
  3423. * Get product/project/execution by ID.
  3424. *
  3425. * @param string $type product|project|execution
  3426. * @param int $objectID
  3427. * @access public
  3428. * @return object|false
  3429. */
  3430. public function getObjectByID($type, $objectID)
  3431. {
  3432. $table = zget($this->config->objectTables, $type, '');
  3433. if(!$table) return false;
  3434. return $this->dao->select('*')->from($table)->where('id')->eq($objectID)->fetch();
  3435. }
  3436. /**
  3437. * 更新文档库顺序。
  3438. * Update doclib order.
  3439. *
  3440. * @param int $catalogID
  3441. * @param int $order
  3442. * @access public
  3443. * @return bool
  3444. * @param int $id
  3445. */
  3446. public function updateDoclibOrder($id, $order)
  3447. {
  3448. $this->dao->update(TABLE_DOCLIB)->set('`order`')->eq($order)->where('id')->eq($id)->exec();
  3449. return !dao::isError();
  3450. }
  3451. /**
  3452. * 更新文档顺序。
  3453. * Update doc order.
  3454. *
  3455. * @param array $sortedIdList
  3456. * @access public
  3457. * @return void
  3458. */
  3459. public function updateDocOrder($sortedIdList)
  3460. {
  3461. /* Remove programID. */
  3462. $sortedIdList = array_values(array_filter(array_map(function($id){return (is_numeric($id) and $id > 0) ? $id : null;}, $sortedIdList)));
  3463. if(empty($sortedIdList)) return;
  3464. $docs = $this->dao->select('`order`, id')->from(TABLE_DOC)->where('id')->in($sortedIdList)->orderBy('order_asc')->fetchPairs('order', 'id');
  3465. /* Update order by sorted id list. */
  3466. foreach($docs as $order => $id)
  3467. {
  3468. $newID = array_shift($sortedIdList);
  3469. if($id == $newID) continue;
  3470. $this->dao->update(TABLE_DOC)->set('`order`')->eq($order)->where('id')->eq($newID)->exec();
  3471. }
  3472. }
  3473. /**
  3474. * 更新目录顺序。
  3475. * Update catalog order.
  3476. *
  3477. * @param int $catalogID
  3478. * @param int $order
  3479. * @param string $order
  3480. * @access public
  3481. * @return bool
  3482. * @param string $type
  3483. */
  3484. public function updateOrder($catalogID, $order, $type = 'doc')
  3485. {
  3486. $this->dao->update(TABLE_MODULE)->set('`order`')->eq($order)->where('id')->eq($catalogID)->andWhere('type')->eq($type)->exec();
  3487. return !dao::isError();
  3488. }
  3489. /**
  3490. * 更新文档中的附件信息。
  3491. * Update doc file.
  3492. *
  3493. * @param int $docID
  3494. * @param int $fileID
  3495. * @access public
  3496. * @return bool
  3497. */
  3498. public function updateDocFile($docID, $fileID)
  3499. {
  3500. $docContent = $this->dao->select('t1.*')->from(TABLE_DOCCONTENT)->alias('t1')
  3501. ->leftJoin(TABLE_DOC)->alias('t2')->on('t1.doc=t2.id and t1.version=t2.version')
  3502. ->where('t2.id')->eq($docID)
  3503. ->fetch();
  3504. unset($docContent->id);
  3505. $docContent->files = trim(str_replace(",{$fileID},", ',', ",{$docContent->files},"), ',');
  3506. $docContent->version += 1;
  3507. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  3508. if(dao::isError()) return false;
  3509. $this->dao->update(TABLE_DOC)->set('version')->eq($docContent->version)->where('id')->eq($docID)->exec();
  3510. return !dao::isError();
  3511. }
  3512. /**
  3513. * 移动文档库。
  3514. * Move library.
  3515. *
  3516. * @param int $docID
  3517. * @param object $data
  3518. * @access public
  3519. * @return bool
  3520. * @param int $libID
  3521. */
  3522. public function moveLib($libID, $data)
  3523. {
  3524. if(empty($libID) || empty($data->space)) return false;
  3525. $lib = $this->getLibByID($libID);
  3526. $spaceType = $this->getParamFromTargetSpace($data->space, 'type');
  3527. $spaceID = $this->getParamFromTargetSpace($data->space, 'id');
  3528. if(is_numeric($spaceID))
  3529. {
  3530. $data->type = $spaceType;
  3531. if($spaceType == 'mine') $data->addedBy = $this->app->user->account;
  3532. /* 先清空所有标志性字段,如果是项目空间、执行空间、产品空间,则直接改对应字段,否则就是我的空间和团队空间 */
  3533. if($lib->product) $data->product = 0;
  3534. if($lib->project) $data->project = 0;
  3535. if($lib->execution) $data->execution = 0;
  3536. if($lib->parent) $data->parent = 0;
  3537. if(in_array($spaceType, array('project', 'product', 'execution')))
  3538. {
  3539. $data->$spaceType = $spaceID;
  3540. }
  3541. else
  3542. {
  3543. $data->parent = $spaceID;
  3544. }
  3545. }
  3546. else
  3547. {
  3548. return false;
  3549. }
  3550. $changes = common::createChanges($lib, $data);
  3551. if(empty($changes)) return false;
  3552. unset($data->space);
  3553. $this->dao->update(TABLE_DOCLIB)->data($data)->where('id')->eq($libID)->exec();
  3554. $getFromTo = function ($obj)
  3555. {
  3556. if(!in_array($obj->type, array('project', 'product'))) return "{$obj->type}.{$obj->parent}";
  3557. if(isset($obj->project)) return "{$obj->type}.{$obj->project}";
  3558. return "{$obj->type}.{$obj->product}";
  3559. };
  3560. $from = $getFromTo($lib);
  3561. $to = $getFromTo($data);
  3562. $actionID = $this->loadModel('action')->create('docLib', $libID, 'Moved', '', json_encode(array('from' => $from, 'to' => $to)));
  3563. $this->action->logHistory($actionID, $changes);
  3564. return true;
  3565. }
  3566. /**
  3567. * Batch move document.
  3568. *
  3569. * @param object $data
  3570. * @param array $docIdList
  3571. * @access public
  3572. * @return bool
  3573. */
  3574. public function batchMoveDoc($data, $docIdList)
  3575. {
  3576. $this->dao->update(TABLE_DOC)->data($data)->where('id')->in($docIdList)->exec();
  3577. return !dao::isError();
  3578. }
  3579. /**
  3580. * 删除文档。
  3581. * Delete document.
  3582. *
  3583. * @param string $table
  3584. * @param int $id
  3585. * @access public
  3586. * @return bool
  3587. */
  3588. public function delete($table, $id)
  3589. {
  3590. if($table != TABLE_DOC) return false;
  3591. $this->dao->update($table)->set('deleted')->eq('1')->where('id')->eq($id)->exec();
  3592. $this->loadModel('action')->create('doc', $id, 'deleted', '', ACTIONMODEL::CAN_UNDELETED);
  3593. return !dao::isError();
  3594. }
  3595. /**
  3596. * 判断文档库下是否有其他人创建的文档。
  3597. * Check if there are other documents created under the document library.
  3598. *
  3599. * @param object $lib
  3600. * @access public
  3601. * @return bool
  3602. */
  3603. public function hasOthersDoc($lib)
  3604. {
  3605. if($lib->type != 'custom') return false;
  3606. $docID = $this->dao->select('id')->from(TABLE_DOC)
  3607. ->where('vision')->eq($this->config->vision)
  3608. ->andWhere('templateType')->eq('')
  3609. ->andWhere('deleted')->eq(0)
  3610. ->andWhere('lib')->eq($lib->id)
  3611. ->andWhere('addedBy')->ne($this->app->user->account)
  3612. ->fetch('id');
  3613. return !empty($docID);
  3614. }
  3615. /**
  3616. * 构建模板类型数据。
  3617. * Build data of template type module.
  3618. *
  3619. * @param int $scope
  3620. * @param int $parent
  3621. * @param string $name
  3622. * @param string $code
  3623. * @param int $grade
  3624. * @param string $path
  3625. * @access public
  3626. * @return object
  3627. */
  3628. public function buildTemplateModule($scope, $parent, $name, $code, $grade = 1, $path = '')
  3629. {
  3630. $module = new stdclass();
  3631. $module->type = 'docTemplate';
  3632. $module->root = $scope;
  3633. $module->parent = $parent;
  3634. $module->name = $name;
  3635. $module->short = $code;
  3636. $module->grade = $grade;
  3637. $module->path = $path;
  3638. return $module;
  3639. }
  3640. /**
  3641. * 检查文档模板是否已升级。
  3642. * Check if doc template has been upgraded
  3643. *
  3644. * @access public
  3645. * @return bool
  3646. */
  3647. public function checkIsTemplateUpgraded()
  3648. {
  3649. $templateModule = $this->dao->select('1')->from(TABLE_MODULE)->where('type')->eq('docTemplate')->fetch();
  3650. return !empty($templateModule);
  3651. }
  3652. /**
  3653. * 升级模板类型数据。
  3654. * Upgrade document template types.
  3655. *
  3656. * @access public
  3657. * @return bool
  3658. */
  3659. public function upgradeTemplateTypes()
  3660. {
  3661. $currentLang = $this->app->getClientLang();
  3662. $conditions = "`module` = 'baseline' AND `section` = 'objectList' AND `vision` = '{$this->config->vision}'";
  3663. $templateTypes = $this->dao->select('`key`, `value`')->from(TABLE_LANG)->where($conditions)->andWhere('lang')->eq($currentLang)->fetchPairs();
  3664. if(empty($templateTypes)) $templateTypes = $this->dao->select('`key`, `value`')->from(TABLE_LANG)->where($conditions)->andWhere('lang')->eq('all')->fetchPairs();
  3665. if(empty($templateTypes))
  3666. {
  3667. $this->app->loadLang('baseline');
  3668. $templateTypes = $this->lang->baseline->objectList;
  3669. }
  3670. $usedTemplateTypes = $this->dao->select('`key`, `value`')->from(TABLE_LANG)->alias('t1')
  3671. ->leftJoin(TABLE_DOC)->alias('t2')->on('t1.key = t2.templateType')
  3672. ->leftJoin(TABLE_MODULE)->alias('t3')->on('t1.module = t3.id')
  3673. ->where('t1.module')->eq('baseline')
  3674. ->andWhere('t1.section')->eq('objectList')
  3675. ->andWhere('t1.lang', true)->ne($currentLang)
  3676. ->orWhere('t1.vision')->ne($this->config->vision)
  3677. ->markRight(1)
  3678. ->andWhere('t1.key')->notin(array_keys($templateTypes))
  3679. ->andWhere('t3.type')->eq('docTemplate')
  3680. ->andWhere('t2.lib')->eq('')
  3681. ->andWhere('t2.module')->eq('')
  3682. ->andWhere('t2.deleted')->eq(0)
  3683. ->fetchPairs();
  3684. $oldTemplateTypes = array_filter(arrayUnion($templateTypes, $usedTemplateTypes));
  3685. if(empty($oldTemplateTypes)) return true;
  3686. $this->loadModel('setting');
  3687. foreach(array('rnd', 'or') as $vision)
  3688. {
  3689. $scopeMaps = $this->setting->getItem("vision={$vision}&owner=system&module=doc&key=builtInScopeMaps");
  3690. $scopeMaps = json_decode($scopeMaps, true);
  3691. $scopeID = $vision == 'rnd' ? zget($scopeMaps, 'project', 0) : zget($scopeMaps, 'product', 0);
  3692. if(!$scopeID) continue;
  3693. $parentTypes = array();
  3694. foreach($this->lang->docTemplate->types as $key => $value)
  3695. {
  3696. $module = $this->buildTemplateModule($scopeID, 0, $value, $key, 1);
  3697. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  3698. if(dao::isError()) return false;
  3699. $moduleID = $this->dao->lastInsertID();
  3700. $this->dao->update(TABLE_MODULE)->set('path')->eq(",{$moduleID},")->where('id')->eq($moduleID)->exec();
  3701. $parentTypes[$key] = $moduleID;
  3702. }
  3703. foreach($oldTemplateTypes as $key => $value)
  3704. {
  3705. $parentKey = zget($this->config->doc->templateTypeParents, $key, 'other');
  3706. $parentID = $parentTypes[$parentKey];
  3707. $module = $this->buildTemplateModule($scopeID, $parentID, $value, $key, 2);
  3708. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  3709. if(dao::isError()) return false;
  3710. $moduleID = $this->dao->lastInsertID();
  3711. $this->dao->update(TABLE_MODULE)->set('path')->eq(",{$parentID},{$moduleID},")->where('id')->eq($moduleID)->exec();
  3712. }
  3713. }
  3714. return true;
  3715. }
  3716. /**
  3717. * 添加内置的模板范围。
  3718. * Add built in scopes.
  3719. *
  3720. * @access public
  3721. * @return bool
  3722. */
  3723. public function addBuiltInScopes()
  3724. {
  3725. $builtInScopes = $this->dao->select('*')->from(TABLE_DOCLIB)->where('type')->eq('template')->andWhere('main')->eq('1')->fetchAll();
  3726. if(!empty($builtInScopes)) return;
  3727. $this->loadModel('setting');
  3728. $scope = new stdClass();
  3729. $scope->type = 'template';
  3730. $scope->main = '1';
  3731. $scope->addedBy = 'system';
  3732. $scope->addedDate = helper::now();
  3733. foreach($this->lang->docTemplate->builtInScopes as $vision => $scopeList)
  3734. {
  3735. $scopeMaps = array();
  3736. $scope->vision = $vision;
  3737. foreach($scopeList as $scopeKey => $scopeName)
  3738. {
  3739. if(empty($scopeName)) continue;
  3740. $scope->name = $scopeName;
  3741. $this->dao->insert(TABLE_DOCLIB)->data($scope)->exec();
  3742. $scopeID = $this->dao->lastInsertID();
  3743. $scopeMaps[$scopeKey] = $scopeID;
  3744. }
  3745. if(!empty($scopeMaps)) $this->setting->setItem("system.doc.builtInScopeMaps@{$vision}", json_encode($scopeMaps));
  3746. }
  3747. }
  3748. /**
  3749. * 添加内置的模板分类。
  3750. * Add built in template type.
  3751. *
  3752. * @access public
  3753. * @return void
  3754. */
  3755. public function addBuiltInDocTemplateType()
  3756. {
  3757. $rndScopeMaps = $this->loadModel('setting')->getItem('vision=rnd&owner=system&module=doc&key=builtInScopeMaps');
  3758. $rndScopeMaps = json_decode($rndScopeMaps, true);
  3759. $projectScopeID = zget($rndScopeMaps, 'project', 0);
  3760. $builtInTemplateTypes = $this->dao->select('id')->from(TABLE_MODULE)->where('type')->eq('docTemplate')->andWhere('root')->eq($projectScopeID)->fetchPairs();
  3761. if(!empty($builtInTemplateTypes)) return;
  3762. $this->app->loadLang('baseline');
  3763. $parentTemplateTypes = array_filter($this->lang->docTemplate->types, function($key){return in_array($key, array('plan', 'story', 'design', 'test'));}, ARRAY_FILTER_USE_KEY);
  3764. foreach($parentTemplateTypes as $parentKey => $parentValue)
  3765. {
  3766. /* 创建文档模板一级分类。*/
  3767. /* Add the parent type of doc template. */
  3768. $parentType = $this->buildTemplateModule($projectScopeID, 0, $parentValue, $parentKey, 1);
  3769. $this->dao->insert(TABLE_MODULE)->data($parentType)->exec();
  3770. $parentTypeID = $this->dao->lastInsertID();
  3771. $this->dao->update(TABLE_MODULE)->set('path')->eq(",{$parentTypeID},")->where('id')->eq($parentTypeID)->exec();
  3772. /* 创建文档模板二级分类。*/
  3773. /* Add the child type of doc template. */
  3774. foreach($this->config->docTemplate->builtInTypes[$parentKey] as $childKey)
  3775. {
  3776. $childType = $this->buildTemplateModule($projectScopeID, $parentTypeID, $this->lang->baseline->objectList[$childKey], $childKey, 2);
  3777. $this->dao->insert(TABLE_MODULE)->data($childType)->exec();
  3778. $childTypeID = $this->dao->lastInsertID();
  3779. $this->dao->update(TABLE_MODULE)->set('path')->eq(",{$parentTypeID},{$childTypeID},")->where('id')->eq($childTypeID)->exec();
  3780. }
  3781. }
  3782. }
  3783. /**
  3784. * 升级文档模板的范围和类型。
  3785. * Upgrade lib and module of template.
  3786. *
  3787. * @param array $templateIdList
  3788. * @access public
  3789. * @return bool
  3790. */
  3791. public function upgradeTemplateLibAndModule($templateIdList = array())
  3792. {
  3793. $templateList = $this->dao->select('*')->from(TABLE_DOC)->where('id')->in($templateIdList)->fetchAll('id');
  3794. if(empty($templateList)) return true;
  3795. $this->loadModel('setting');
  3796. $rndScopeMaps = $this->setting->getItem('vision=rnd&owner=system&module=doc&key=builtInScopeMaps');
  3797. $orScopeMaps = $this->setting->getItem('vision=or&owner=system&module=doc&key=builtInScopeMaps');
  3798. $rndScopeMaps = json_decode($rndScopeMaps, true);
  3799. $orScopeMaps = json_decode($orScopeMaps, true);
  3800. $moduleGroup = $this->dao->select('root,short,id')->from(TABLE_MODULE)->where('deleted')->eq('0')->andWhere('root')->in(array($rndScopeMaps['project'], $orScopeMaps['product']))->fetchGroup('root', 'short');
  3801. foreach($templateList as $id => $template)
  3802. {
  3803. $templateVision = $template->vision;
  3804. $templateType = $template->templateType;
  3805. $templateScopeID = $templateVision == 'or' ? zget($orScopeMaps, 'product', 0) : zget($rndScopeMaps, 'project', 0);
  3806. $moduleList = zget($moduleGroup, $templateScopeID, array());
  3807. $moduleInfo = isset($moduleList[$templateType]) ? $moduleList[$templateType] : zget($moduleList, 'other', '');
  3808. $template->lib = $templateScopeID;
  3809. $template->module = empty($moduleInfo) ? 0 : $moduleInfo->id;
  3810. $template->assignedDate = helper::isZeroDate($template->assignedDate) ? null : $template->assignedDate;
  3811. $template->approvedDate = helper::isZeroDate($template->approvedDate) ? null : $template->approvedDate;
  3812. $this->dao->update(TABLE_DOC)->data($template)->where('id')->eq($id)->exec();
  3813. if(dao::isError()) return false;
  3814. }
  3815. return true;
  3816. }
  3817. /**
  3818. * 获取文档模板类型的模块。
  3819. * Get modules of doc template type.
  3820. *
  3821. * @param bool $onlyNode
  3822. * @param string|int $root
  3823. * @param string $grade
  3824. * @access public
  3825. * @return array
  3826. */
  3827. public function getTemplateModules($root = 'all', $grade = 'all')
  3828. {
  3829. $modules = $this->dao->select('*')->from(TABLE_MODULE)
  3830. ->where('deleted')->eq('0')
  3831. ->andWhere('type')->eq('docTemplate')
  3832. ->beginIF($root != 'all')->andWhere('root')->eq($root)->fi()
  3833. ->beginIF($grade != 'all')->andWhere('grade')->eq($grade)->fi()
  3834. ->fetchAll('id');
  3835. foreach($modules as $module)
  3836. {
  3837. $path = explode(',', trim($module->path, ','));
  3838. $names = array('');
  3839. foreach($path as $id)
  3840. {
  3841. if(!isset($modules[$id])) continue;
  3842. $names[] = $modules[$id]->name;
  3843. }
  3844. $module->fullName = implode('/', $names);
  3845. }
  3846. return array_values($modules);
  3847. }
  3848. /**
  3849. * 获取范围数据。
  3850. * Get scope items.
  3851. *
  3852. * @param array $scopeList
  3853. * @access public
  3854. * @return array
  3855. */
  3856. public function getScopeItems($scopeList = array())
  3857. {
  3858. $items = array();
  3859. foreach($scopeList as $scope) $items[] = array('value' => $scope->id, 'text' => $scope->name);
  3860. return $items;
  3861. }
  3862. /**
  3863. * 判断一个模块是否是内置的文档模板类型。
  3864. * Judge whether a module is builtin template type.
  3865. *
  3866. * @param array $scopeIdList
  3867. * @access public
  3868. * @return bool
  3869. */
  3870. public function getScopeTemplates($scopeIdList = array())
  3871. {
  3872. $scopeTemplates = array();
  3873. foreach($scopeIdList as $scopeID)
  3874. {
  3875. $templates = $this->getHotTemplates($scopeID, 5);
  3876. $scopeTemplates[$scopeID] = $this->filterPrivDocs($templates, 'template');
  3877. }
  3878. return $scopeTemplates;
  3879. }
  3880. /**
  3881. * 获取范围内的最近顶层文档模板。
  3882. * Get the recent top template of scope.
  3883. *
  3884. * @param int $scopeID
  3885. * @param int $limit
  3886. * @access public
  3887. * @return array
  3888. */
  3889. public function getHotTemplates($scopeID = 0, $limit = 0)
  3890. {
  3891. return $this->dao->select('t1.*, CASE WHEN t1.addedDate > t1.editedDate THEN t1.addedDate ELSE t1.editedDate END as hotDate')->from(TABLE_DOC)->alias('t1')
  3892. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module = t2.id')
  3893. ->where('t2.type')->eq('docTemplate')
  3894. ->andWhere('t1.builtIn')->eq('0')
  3895. ->andWhere('t1.deleted')->eq('0')
  3896. ->andWhere('t1.status')->eq('normal')
  3897. ->andWhere('t1.parent')->eq(0)
  3898. ->andWhere('t1.vision')->eq($this->config->vision)
  3899. ->beginIF($scopeID)->andWhere('t1.lib')->eq($scopeID)->fi()
  3900. ->orderBy('hotDate_desc')
  3901. ->beginIF($limit)->limit($limit)->fi()
  3902. ->fetchAll('', false);
  3903. }
  3904. /**
  3905. * 删除一个文档模板。
  3906. * Delete a doc template.
  3907. *
  3908. * @param string $table
  3909. * @param int $id
  3910. * @param string $objectType
  3911. * @access public
  3912. * @return void
  3913. */
  3914. public function deleteTemplate($id)
  3915. {
  3916. if(empty($id)) return false;
  3917. $this->dao->update(TABLE_DOC)->set('deleted')->eq(1)->where('id')->eq($id)->exec();
  3918. $this->loadModel('action')->create('doctemplate', $id, 'deleted', '', ACTIONMODEL::CAN_UNDELETED);
  3919. return true;
  3920. }
  3921. /**
  3922. * 添加文档模板类型。
  3923. * Add template type.
  3924. *
  3925. * @param object $moduleData
  3926. * @access public
  3927. * @return int
  3928. */
  3929. public function addTemplateType($moduleData)
  3930. {
  3931. $this->dao->insert(TABLE_MODULE)->data($moduleData)->autoCheck()->exec();
  3932. $moduleID = $this->dao->lastInsertID();
  3933. $path = $moduleData->grade == 1 ? ",{$moduleID}," : ",{$moduleData->parent},{$moduleID},";
  3934. $this->dao->update(TABLE_MODULE)->set('path')->eq($path)->set('short')->eq("custom{$moduleID}")->where('id')->eq($moduleID)->exec();
  3935. return $moduleID;
  3936. }
  3937. /**
  3938. * 获取某个模板类型下的所有模板。
  3939. * Get template list by type.
  3940. *
  3941. * @param int|null $type
  3942. * @param string $status
  3943. * @access public
  3944. * @return int
  3945. */
  3946. public function getTemplatesByType($type = null, $status = 'all')
  3947. {
  3948. $types = array();
  3949. if(!is_null($type))
  3950. {
  3951. $subTypes = $this->dao->select('id')->from(TABLE_MODULE)
  3952. ->where('deleted')->eq('0')
  3953. ->andWhere('parent')->eq($type)
  3954. ->andWhere('type')->eq('docTemplate')
  3955. ->fetchPairs('id');
  3956. $types = array_values($subTypes);
  3957. $types[] = $type;
  3958. }
  3959. return $this->dao->select('t1.*')->from(TABLE_DOC)->alias('t1')
  3960. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id')
  3961. ->where('t1.deleted')->eq('0')
  3962. ->andWhere('t2.type')->eq('docTemplate')
  3963. ->andWhere('t1.builtIn')->eq('0')
  3964. ->andWhere('t1.vision')->eq($this->config->vision)
  3965. ->beginIF(!is_null($type))->andWhere('t1.module')->in($types)->fi()
  3966. ->beginIF($status != 'all')->andWhere('t1.status')->eq($status)->fi()
  3967. ->fetchAll('id', false);
  3968. }
  3969. /**
  3970. * 获取上次访问的文档对象。
  3971. * Get last viewed doc object.
  3972. *
  3973. * @param string $type
  3974. * @access public
  3975. * @return string
  3976. */
  3977. public function getLastViewed($type)
  3978. {
  3979. $typeList = array('lastViewedSpace', 'lastViewedSpaceHome', 'lastViewedLib');
  3980. if(!in_array($type, $typeList)) return null;
  3981. return $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=common&section=doc&key=doc-$type");
  3982. }
  3983. /**
  3984. * 设置上次访问的文档对象。
  3985. * Set last viewed doc object.
  3986. *
  3987. * @param array $value
  3988. * @access public
  3989. * @return void
  3990. */
  3991. public function setLastViewed($value)
  3992. {
  3993. $items = array();
  3994. foreach($value as $k => $v)
  3995. {
  3996. if(in_array($k, array('lastViewedSpace', 'lastViewedSpaceHome', 'lastViewedLib'))) $items["doc-$k"] = $v;
  3997. }
  3998. if(!empty($items)) $this->loadModel('setting')->setItems("{$this->app->user->account}.common.doc", $items);
  3999. }
  4000. /**
  4001. * 获取文档块。
  4002. * Get doc block.
  4003. *
  4004. * @param int $blockID
  4005. * @access public
  4006. * @return object|bool
  4007. */
  4008. public function getDocBlock($blockID)
  4009. {
  4010. return $this->dao->select('*')->from(TABLE_DOCBLOCK)->where('id')->eq($blockID)->fetch();
  4011. }
  4012. /**
  4013. * 获取文档块内容。
  4014. * Get doc block content.
  4015. *
  4016. * @param int $blockID
  4017. * @access public
  4018. * @return array|bool
  4019. */
  4020. public function getDocBlockContent($blockID)
  4021. {
  4022. $content = $this->dao->select('content')->from(TABLE_DOCBLOCK)->where('id')->eq($blockID)->fetch('content');
  4023. if(!$content) return false;
  4024. return json_decode($content, true);
  4025. }
  4026. /**
  4027. * 获取文档块列表。
  4028. * Get doc block list.
  4029. *
  4030. * @param int $docID
  4031. * @access public
  4032. * @return ?object
  4033. * @param int $blockID
  4034. */
  4035. public function getZentaoList($blockID)
  4036. {
  4037. $blockData = $this->dao->select('*')->from(TABLE_DOCBLOCK)->where('id')->eq($blockID)->fetch();
  4038. if(!is_object($blockData)) return null;
  4039. if(is_string($blockData->content)) $blockData->content = json_decode($blockData->content);
  4040. return $blockData;
  4041. }
  4042. /**
  4043. * 通过parentID获取子文档列表。
  4044. * Get docs by parentID.
  4045. *
  4046. * @param int $parentID
  4047. * @access public
  4048. * @return array
  4049. */
  4050. public function getDocsByParent($parentID)
  4051. {
  4052. return $this->dao->select('*')->from(TABLE_DOC)
  4053. ->where('parent')->eq($parentID)
  4054. ->andWhere('status')->eq('normal')
  4055. ->andWhere('deleted')->eq(0)
  4056. ->orderBy('`order` asc, id_asc')
  4057. ->fetchAll('id', false);
  4058. }
  4059. /**
  4060. * 通过标题获取标题ID。
  4061. * Get ID by title.
  4062. *
  4063. * @param int $originPageID 当前Confluence文档ID
  4064. * @param string $title 选择的父页面标题
  4065. * @access public
  4066. * @return int
  4067. */
  4068. public function getDocIdByTitle($originPageID, $title = '')
  4069. {
  4070. if(!defined('CONFLUENCE_TMPRELATION')) define('CONFLUENCE_TMPRELATION', '`confluencetmprelation`');
  4071. $docID = $this->dao->dbh($this->dbh)->select('BID')->from(CONFLUENCE_TMPRELATION)->where('BType')->eq('zdoc')->andWhere('AID')->eq($originPageID)->fetch('BID');
  4072. $doc = $this->getByID((int)$docID);
  4073. $docIdList = $this->dao->select('id')->from(TABLE_DOC)->where('lib')->eq($doc->lib)->andWhere('title')->eq($title)->andWhere('status')->eq('normal')->andWhere('deleted')->eq(0)->fetchAll();
  4074. $idList = array();
  4075. foreach($docIdList as $item) $idList[] = $item->id;
  4076. $parentID = $this->dao->dbh($this->dbh)->select('BID')->from(CONFLUENCE_TMPRELATION)->where('BType')->eq('zdoc')->andWhere('BID')->in($idList)->fetch('BID');
  4077. return $parentID ? (int)$parentID : 0;
  4078. }
  4079. /**
  4080. * 通过Confluence的用户ID获取对应禅道的用户信息。
  4081. * Retrieve the user information of the corresponding Zen path through Confluence's user ID.
  4082. *
  4083. * @param string $userName
  4084. * @access public
  4085. * @return object|false
  4086. */
  4087. public function getUserByConfluenceUserID($userName)
  4088. {
  4089. return $this->dao->select('t2.*')
  4090. ->from(JIRA_TMPRELATION)->alias('t1')
  4091. ->leftJoin(TABLE_USER)->alias('t2')->on('t1.BID = t2.account')
  4092. ->where('t1.AID')->eq($userName)
  4093. ->andWhere('t1.Btype')->eq('zuser')
  4094. ->andWhere('t1.Atype')->eq('juser')
  4095. ->andWhere('t2.deleted')->eq('0')
  4096. ->fetch();
  4097. }
  4098. /**
  4099. * 构建文档层级。
  4100. *
  4101. * @param array $docs
  4102. * @param array $modules
  4103. * @param bool $addPrefix
  4104. * @access public
  4105. * @return array
  4106. */
  4107. public function buildNestedDocs($docs, $modules = array(), $addPrefix = true)
  4108. {
  4109. if(!empty($modules))
  4110. {
  4111. $moduleList = array();
  4112. $modules = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->in(array_keys($modules))->fetchAll('id');
  4113. foreach($modules as $moduleID => $module)
  4114. {
  4115. $moduleKey = $addPrefix ? 'm_' . $moduleID : $moduleID;
  4116. $module->id = $moduleKey;
  4117. $module->title = $module->name;
  4118. $module->parent = empty($module->parent) ? 0 : ($addPrefix ? 'm_' . $module->parent : $module->parent);
  4119. $moduleList[$moduleKey] = $module;
  4120. }
  4121. foreach($docs as $doc)
  4122. {
  4123. if(!$doc) continue;
  4124. if(empty($doc->parent) && !empty($doc->module)) $doc->parent = $addPrefix ? 'm_' . $doc->module : $doc->module;
  4125. }
  4126. $docs = $docs + $moduleList;
  4127. }
  4128. $children = array();
  4129. foreach($docs as $doc)
  4130. {
  4131. if(!$doc) continue;
  4132. $children[$doc->parent][] = $doc;
  4133. }
  4134. /* 找到所有根节点,如果没有parent为0的文档,尝试找到父节点不在docs中的文档。*/
  4135. $rootDocs = isset($children[0]) ? $children[0] : array();
  4136. if(empty($rootDocs))
  4137. {
  4138. foreach($docs as $doc)
  4139. {
  4140. if(!isset($docs[$doc->parent])) $rootDocs[$doc->id] = $doc;
  4141. }
  4142. }
  4143. $result = array();
  4144. foreach($rootDocs as $rootDoc) $result[$rootDoc->id] = $this->buildDocItems($rootDoc->id, $rootDoc->title, $children);
  4145. return $result;
  4146. }
  4147. /**
  4148. * 构建文档items。
  4149. *
  4150. * @param int|string $docID
  4151. * @param string $docTitle
  4152. * @param array $children
  4153. * @access public
  4154. * @return array
  4155. */
  4156. public function buildDocItems($docID, $docTitle, $children)
  4157. {
  4158. $items = array('value' => $docID, 'text' => $docTitle);
  4159. if(isset($children[$docID]))
  4160. {
  4161. foreach($children[$docID] as $childDoc)
  4162. {
  4163. $items['items'][] = $this->buildDocItems($childDoc->id, $childDoc->title, $children);
  4164. }
  4165. }
  4166. return $items;
  4167. }
  4168. /**
  4169. * 获取文档模板的范围。
  4170. * Get the scope of template.
  4171. *
  4172. * @access public
  4173. * @return array
  4174. */
  4175. public function getTemplateScopes()
  4176. {
  4177. return $this->dao->select('*')->from(TABLE_DOCLIB)->where('type')->eq('template')->andWhere('vision')->eq($this->config->vision)->andWhere('deleted')->eq('0')->fetchAll('id');
  4178. }
  4179. /**
  4180. * 更新模板范围。
  4181. * Update the scope of template.
  4182. *
  4183. * @param array scopeList
  4184. * @access public
  4185. * @return void
  4186. */
  4187. public function updateTemplateScopes($scopeList = array())
  4188. {
  4189. foreach($scopeList as $id => $name)
  4190. {
  4191. $this->dao->update(TABLE_DOCLIB)->set('name')->eq($name)->where('id')->eq($id)->andWhere('vision')->eq($this->config->vision)->exec();
  4192. }
  4193. }
  4194. /**
  4195. * 插入模板范围。
  4196. * Insert the scope of template.
  4197. *
  4198. * @param array scopeList
  4199. * @access public
  4200. * @return void
  4201. */
  4202. public function insertTemplateScopes($scopeList = array())
  4203. {
  4204. foreach($scopeList as $name)
  4205. {
  4206. if(empty($name)) continue;
  4207. $scope = new stdClass();
  4208. $scope->name = $name;
  4209. $scope->type = 'template';
  4210. $scope->main = '0';
  4211. $scope->vision = $this->config->vision;
  4212. $scope->addedBy = $this->app->user->account;
  4213. $scope->addedDate = helper::now();
  4214. $this->dao->insert(TABLE_DOCLIB)->data($scope)->exec();
  4215. }
  4216. }
  4217. /**
  4218. * 删除模板范围。
  4219. * Delete the scope of template.
  4220. *
  4221. * @param array scopeList
  4222. * @access public
  4223. * @return void
  4224. */
  4225. public function deleteTemplateScopes($scopeIdList = array())
  4226. {
  4227. $this->dao->update(TABLE_DOCLIB)->set('deleted')->eq('1')->where('id')->in($scopeIdList)->exec();
  4228. }
  4229. /**
  4230. * 复制模板,将研发界面的除Wiki类型的模板复制到OR界面,将OR界面的所有文档模板复制到研发界面。
  4231. * Copy templates of R&D interface, except for wiki type templates, to OR interface, and copy all templates of OR interface to R&D interface.
  4232. *
  4233. * @param array templateIdList
  4234. * @access public
  4235. * @return void
  4236. */
  4237. public function copyTemplate($templateIdList = array())
  4238. {
  4239. if(empty($templateIdList)) return array();
  4240. $oldTemplateList = $this->dao->select('*')->from(TABLE_DOC)->where('id')->in($templateIdList)->fetchAll('id');
  4241. $oldContentList = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->in($templateIdList)->fetchGroup('doc', 'id');
  4242. $oldChapterList = $this->dao->select('*')->from(TABLE_DOC)->where('template')->ne('')->andWhere('type')->in('chapter,article')->fetchGroup('template', 'id');
  4243. $newTemplateList = array();
  4244. foreach($oldTemplateList as $oldTemplateID => $template)
  4245. {
  4246. /* 研发界面的Wiki类型模板不复制到OR界面。*/
  4247. /* The wiki type template of R&D interface is not copied to OR interface. */
  4248. if($template->vision == 'rnd' && $template->type == 'book') continue;
  4249. /* 复制模板及模板内容。*/
  4250. /* Copy template and content. */
  4251. unset($template->id);
  4252. $templateVision = $template->vision == 'rnd' ? 'or' : 'rnd';
  4253. $template->vision = $templateVision;
  4254. $template->assignedDate = helper::isZeroDate($template->assignedDate) ? null : $template->assignedDate;
  4255. $template->approvedDate = helper::isZeroDate($template->approvedDate) ? null : $template->approvedDate;
  4256. $this->dao->insert(TABLE_DOC)->data($template)->exec();
  4257. $newTemplateID = $this->dao->lastInsertID();
  4258. foreach($oldContentList[$oldTemplateID] as $content)
  4259. {
  4260. unset($content->id);
  4261. $content->doc = $newTemplateID;
  4262. $content->addedDate = helper::isZeroDate($content->addedDate) ? null : $content->addedDate;
  4263. $content->editedDate = helper::isZeroDate($content->editedDate) ? null : $content->editedDate;
  4264. $this->dao->insert(TABLE_DOCCONTENT)->data($content)->exec();
  4265. }
  4266. /* OR界面的Wiki类型模板复制到研发界面。*/
  4267. /* Copy the wiki type template of OR interface to R&D interface. */
  4268. if($template->type == 'book' && isset($oldChapterList[$oldTemplateID]))
  4269. {
  4270. $chapterList = $oldChapterList[$oldTemplateID];
  4271. $chapterContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->in(array_keys($chapterList))->fetchGroup('doc', 'id');
  4272. foreach($chapterList as $oldChapterID => $chapter)
  4273. {
  4274. unset($chapter->id);
  4275. $chapter->vision = 'rnd';
  4276. $chapter->template = $newTemplateID;
  4277. $this->dao->insert(TABLE_DOC)->data($chapter)->exec();
  4278. $newChapterID = $this->dao->lastInsertID();
  4279. foreach($chapterContent[$oldChapterID] as $content)
  4280. {
  4281. unset($content->id);
  4282. $content->doc = $newChapterID;
  4283. $this->dao->insert(TABLE_DOCCONTENT)->data($content)->exec();
  4284. }
  4285. }
  4286. }
  4287. if(!isset($newTemplateList['all'])) $newTemplateList['all'] = array();
  4288. if(!isset($newTemplateList['wiki'])) $newTemplateList['wiki'] = array();
  4289. if(!isset($newTemplateList['html'])) $newTemplateList['html'] = array();
  4290. $newTemplateList['all'][] = $newTemplateID;
  4291. if($template->type == 'book') $newTemplateList['wiki'][] = $newTemplateID;
  4292. if($template->type == 'html' || $template->type == 'markdown') $newTemplateList['html'][] = $newTemplateID;
  4293. }
  4294. return $newTemplateList;
  4295. }
  4296. /**
  4297. * 添加内置文档模板。
  4298. * Add the built-in doc template.
  4299. *
  4300. * @access public
  4301. * @return void
  4302. */
  4303. public function addBuiltInDocTemplateByType()
  4304. {
  4305. $builtInDocTemplateType = array('PP', 'SRS', 'HLDS', 'DDS', 'ADS', 'DBDS', 'ITTC', 'STTC');
  4306. $builtInDocTemplate = $this->dao->select('*')->from(TABLE_DOC)->where('builtIn')->eq('1')->andWhere('templateType')->in($builtInDocTemplateType)->fetchAll();
  4307. if(!empty($builtInDocTemplate)) return;
  4308. $rndScopeMaps = $this->loadModel('setting')->getItem('vision=rnd&owner=system&module=doc&key=builtInScopeMaps');
  4309. $rndScopeMaps = json_decode($rndScopeMaps, true);
  4310. $projectScopeID = zget($rndScopeMaps, 'project', 0);
  4311. $modulePairs = $this->dao->select('short,id')->from(TABLE_MODULE)->where('root')->eq($projectScopeID)->andWhere('type')->eq('docTemplate')->fetchPairs('short');
  4312. $builtInTemplate = new stdClass();
  4313. $builtInTemplate->lib = $projectScopeID;
  4314. $builtInTemplate->type = 'text';
  4315. $builtInTemplate->addedBy = 'system';
  4316. $builtInTemplate->addedDate = helper::now();
  4317. $builtInTemplate->builtIn = '1';
  4318. $templateContent = new stdClass();
  4319. $templateContent->type = 'doc';
  4320. $templateContent->version = 1;
  4321. $templateContent->addedBy = 'system';
  4322. $templateContent->addedDate = helper::now();
  4323. $this->loadModel('upgrade');
  4324. $this->app->loadLang('baseline');
  4325. foreach($builtInDocTemplateType as $type)
  4326. {
  4327. /* 创建与分类同名的内置文档模板。*/
  4328. /* Add the doc template with the same name as the type. */
  4329. $builtInTemplate->module = zget($modulePairs, $type, 0);
  4330. $builtInTemplate->title = $this->lang->baseline->objectList[$type];
  4331. $builtInTemplate->templateType = $type;
  4332. $this->dao->insert(TABLE_DOC)->data($builtInTemplate)->exec();
  4333. $templateID = $this->dao->lastInsertID();
  4334. /* 获取模板的动态区块。*/
  4335. /* Get the block of doc template. */
  4336. $templateBlock = $this->upgrade->getTemplateBlock($templateID);
  4337. $templateHtml = empty($templateBlock) ? '' : "<div class='tml-zentaolist' data-title='{$templateBlock['blockTitle']}' data-export-url='{$templateBlock['exportUrl']}' data-fetcher='{$templateBlock['fetcherUrl']}'></div>";
  4338. /* 添加模板内容。*/
  4339. /* Add the content of doc template. */
  4340. $templateContent->content = $templateHtml;
  4341. $templateContent->rawContent = json_encode(array('$migrate' => 'html', '$data' => $templateHtml));
  4342. $templateContent->doc = $templateID;
  4343. $templateContent->title = $builtInTemplate->title;
  4344. $this->dao->insert(TABLE_DOCCONTENT)->data($templateContent)->exec();
  4345. }
  4346. /* 记录文档模板的更新时间。*/
  4347. /* Record the time of upgrade doc template. */
  4348. $this->setting->setItem("system.doc.upgradeTime", helper::now());
  4349. }
  4350. /**
  4351. * 遍历文档的区块。
  4352. * For each doc block.
  4353. *
  4354. * 下面为一个遍历文档中所有附件,并获取所有附件 sourceId 例子:
  4355. *
  4356. * ```php
  4357. * // 定义遍历回调函数:
  4358. * $callback = function($block, $data, $level)
  4359. * {
  4360. * if(!empty($block['props']['sourceId'])) $data[] = $block['props']['sourceId'];
  4361. * return $data;
  4362. * };
  4363. *
  4364. * // 遍历文档,并获取最终的 sourceId 列表:
  4365. * $sourceIdList = static::forEachDocBlock($rawContent, $callback, array(), 'affine:attachment');
  4366. * ```
  4367. *
  4368. * @param array $rawContent 文档的区块内容
  4369. * @param callable $callback 回调函数,用于处理每个区块,参数包括区块内容、传递的数据、区块级别、区块索引
  4370. * @param mixed $data 用于在遍历过程中需要传递的数据,此参数可选,默认为 null
  4371. * @param string $flavours 区块的 flavour,例如 affine:attachment,可以用英文逗号匹配多个 flavour,如果为空则匹配所有 flavour
  4372. * @param string $types 区块的 type,包括页面(page)和块(block),默认仅匹配块(block),如果为空则匹配所有类型
  4373. * @param ?array $props 区块的 props,例如 array('type' => 'h1'),可以指定多个属性值
  4374. * @param int $level 区块的级别,用于递归遍历,如果要指定起始级别,则可以使用此参数指定,默认为 0
  4375. * @param int $index 区块的索引,用于递归遍历,如果要指定起始索引,则可以使用此参数指定,默认为 0
  4376. * @access public
  4377. * @return void
  4378. */
  4379. public static function forEachDocBlock($rawContent, $callback, $data = null, $flavours = '', $types = 'block', $props = null, $level = 0, $index = 0)
  4380. {
  4381. $arrayIsList = function (array $array) : bool {
  4382. if (function_exists('array_is_list')) {
  4383. return array_is_list($array);
  4384. }
  4385. if ($array === []) {
  4386. return true;
  4387. }
  4388. $current_key = 0;
  4389. foreach ($array as $key => $noop) {
  4390. if ($key !== $current_key) {
  4391. return false;
  4392. }
  4393. ++$current_key;
  4394. }
  4395. return true;
  4396. };
  4397. /* 如果内容是列表,则遍历列表。 */
  4398. if($arrayIsList($rawContent))
  4399. {
  4400. foreach($rawContent as $idx => $block)
  4401. {
  4402. $data = static::forEachDocBlock($block, $callback, $data, $flavours, $types, $props, $level, $idx);
  4403. }
  4404. return $data;
  4405. }
  4406. /* 获取内容类型。 */
  4407. if(!isset($rawContent['type'])) return $data;
  4408. $type = $rawContent['type'];
  4409. /* 判断内容是否匹配类型。 */
  4410. $blockMatched = empty($types) || strpos(',' . $types . ',', ',' . $type . ',') !== false;
  4411. /* 判断内容是否匹配 flavour。 */
  4412. if($blockMatched && !empty($flavours))
  4413. {
  4414. $flavour = empty($rawContent['flavour']) ? '' : $rawContent['flavour'];
  4415. $blockMatched = !empty($flavour) && strpos(',' . $flavours . ',', ',' . $flavour . ',') !== false;
  4416. }
  4417. /* 判断内容是否匹配 props。 */
  4418. if($blockMatched && !empty($props))
  4419. {
  4420. $blockProps = isset($rawContent['props']) ? $rawContent['props'] : null;
  4421. if(empty($blockProps))
  4422. {
  4423. $blockMatched = false;
  4424. }
  4425. else
  4426. {
  4427. foreach($props as $prop => $value)
  4428. {
  4429. if($blockProps[$prop] !== $value)
  4430. {
  4431. $blockMatched = false;
  4432. break;
  4433. }
  4434. }
  4435. }
  4436. }
  4437. /* 如果内容匹配,则调用回调函数。 */
  4438. if($blockMatched)
  4439. {
  4440. $data = $callback($rawContent, $data, 0, $level, $index);
  4441. }
  4442. /* 如果内容是页面,则遍历页面内容。 */
  4443. if($type === 'page')
  4444. {
  4445. return static::forEachDocBlock($rawContent['blocks'], $callback, $data, $flavours, $types, $props, $level + 1);
  4446. }
  4447. /* 如果内容包含 children,则遍历 children。 */
  4448. $children = isset($rawContent['children']) ? $rawContent['children'] : null;
  4449. if(empty($children)) return $data;
  4450. return static::forEachDocBlock($children, $callback, $data, $flavours, $types, $props, $level + 1);
  4451. }
  4452. }