model.php 517 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296
  1. <?php
  2. /**
  3. * The model file of upgrade 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 upgrade
  9. * @version $Id: model.php 5019 2013-07-05 02:02:31Z wyd621@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class upgradeModel extends model
  15. {
  16. static $errors = array();
  17. /**
  18. * 升级前的版本号。
  19. * The version before upgrade.
  20. *
  21. * @var string
  22. * access public
  23. */
  24. public $fromVersion = '';
  25. /**
  26. * 升级前的版本类型。
  27. * The edition before upgrade.
  28. *
  29. * @var string
  30. * access public
  31. */
  32. public $fromEdition = '';
  33. /**
  34. * 已执行的升级内容。
  35. * Executed Changes.
  36. *
  37. * @var array
  38. * access private
  39. */
  40. private $executedChanges = [];
  41. /**
  42. * 构造函数。
  43. * Construct function.
  44. *
  45. * @access public
  46. * @return void
  47. */
  48. public function __construct()
  49. {
  50. parent::__construct();
  51. $this->loadModel('setting');
  52. }
  53. /**
  54. * 获取升级的版本。
  55. * Get versions to update.
  56. *
  57. * @param string $openVersion
  58. * @param string $fromEdition open|pro|biz|max|ipd
  59. * @access public
  60. * @return array
  61. */
  62. public function getVersionsToUpdate($openVersion, $fromEdition)
  63. {
  64. $versions = array();
  65. /* pms20beta2 is a version released between pms18.11 and pms18.12, which was upgraded to pms20 and lost 18.11 upgrade sql. */
  66. if($openVersion == '20_0_beta2') $versions['18_11'] = array('pro' => array(), 'biz' => array(), 'max' => array(), 'ipd' => array());
  67. /* Always update open sql. */
  68. foreach($this->lang->upgrade->fromVersions as $version => $versionName)
  69. {
  70. if(!is_numeric($version[0])) continue;
  71. if(version_compare(str_replace('_', '.', $version), str_replace('_', '.', $openVersion)) < 0) continue;
  72. $versions[$version] = array('pro' => array(), 'biz' => array(), 'max' => array(), 'ipd' => array());
  73. }
  74. if($fromEdition == 'open') return $versions;
  75. /* Update pro sql from pro|biz|max|ipd. */
  76. foreach($this->config->upgrade->proVersion as $pro => $open)
  77. {
  78. if(isset($versions[$open])) $versions[$open]['pro'][] = $pro;
  79. }
  80. if($fromEdition == 'pro') return $versions;
  81. /* Update biz sql from biz|max|ipd. */
  82. foreach($this->config->upgrade->bizVersion as $biz => $open)
  83. {
  84. if(isset($versions[$open])) $versions[$open]['biz'][] = $biz;
  85. }
  86. if($fromEdition == 'biz') return $versions;
  87. /* Update max sql from max|ipd. */
  88. foreach($this->config->upgrade->maxVersion as $max => $open)
  89. {
  90. if(isset($versions[$open])) $versions[$open]['max'][] = $max;
  91. }
  92. if($fromEdition == 'max') return $versions;
  93. /* Update ipd sql only from ipd. */
  94. foreach($this->config->upgrade->ipdVersion as $ipd => $open)
  95. {
  96. if(isset($versions[$open])) $versions[$open]['ipd'][] = $ipd;
  97. }
  98. return $versions;
  99. }
  100. /**
  101. * 获取额外执行的方法。
  102. * Get other methods to execute.
  103. *
  104. * @param string $fromEdition
  105. * @access public
  106. * @return array
  107. */
  108. public function getOtherMethods($fromEdition)
  109. {
  110. $methods = [];
  111. /* Means open source/pro upgrade to biz or max. */
  112. if($this->config->edition != 'open' && ($fromEdition == 'open' || $fromEdition == 'pro'))
  113. {
  114. $methods['importBuildinModules'] = [];
  115. $methods['importLiteModules'] = [];
  116. $methods['addSubStatus'] = [];
  117. $methods['processDataset'] = [];
  118. }
  119. $methods['upgradeScreenAndMetricData'] = [];
  120. $methods['upgradeBIData'] = [];
  121. $methods['disableFeaturesByMode'] = []; // 新增了一些功能,轻量级升级上来要禁用掉
  122. $methods['convertCharset'] = [];
  123. $methods['program-refreshStats'] = [true];
  124. $methods['product-refreshStats'] = [true];
  125. $methods['deletePatch'] = [];
  126. return $methods;
  127. }
  128. /**
  129. * 记录已升级的 SQL 和方法。
  130. * Record executed SQLs and methods.
  131. *
  132. * @access public
  133. * @return bool
  134. */
  135. public function recordExecutedChanges()
  136. {
  137. $this->loadModel('setting')->setItem('system.upgrade.executedChanges', json_encode($this->executedChanges));
  138. return !dao::isError();
  139. }
  140. /**
  141. * 执行升级 sql 文件。
  142. * The execute method. According to the $fromVersion call related methods.
  143. *
  144. * @param string $fromVersion
  145. * @param string $toVersion
  146. * @access public
  147. * @return bool
  148. */
  149. public function execute($fromVersion, $toVersion)
  150. {
  151. set_time_limit(0);
  152. if(!isset($this->app->user)) $this->loadModel('user')->su();
  153. $this->dao->exec("SET @@sql_mode=''");
  154. /* Set real time log file. */
  155. dao::$realTimeFile = $this->getLogFile();
  156. if(file_exists(dao::$realTimeFile)) unlink(dao::$realTimeFile);
  157. if(strpos($fromVersion, 'lite') !== false) $fromVersion = $this->config->upgrade->liteVersion[$fromVersion];
  158. if(empty($this->config->global->hideUpgradeGuide))
  159. {
  160. if(strpos($fromVersion, 'ipd') !== false)
  161. {
  162. if(version_compare($fromVersion, 'ipd2.0.0', '>=')) $this->loadModel('setting')->setItem("system.common.global.hideUpgradeGuide", 1);
  163. }
  164. elseif(strpos($fromVersion, 'max') !== false)
  165. {
  166. if(version_compare($fromVersion, 'max5.0.0', '>=')) $this->loadModel('setting')->setItem("system.common.global.hideUpgradeGuide", 1);
  167. }
  168. elseif(strpos($fromVersion, 'pro') !== false || strpos($fromVersion, 'biz') !== false)
  169. {
  170. if(version_compare($fromVersion, 'biz10.0.0', '>=')) $this->loadModel('setting')->setItem("system.common.global.hideUpgradeGuide", 1);
  171. }
  172. else
  173. {
  174. if(version_compare($fromVersion, '20.0.beta1', '>=')) $this->loadModel('setting')->setItem("system.common.global.hideUpgradeGuide", 1);
  175. }
  176. }
  177. $this->fromVersion = $fromVersion;
  178. $this->fromEdition = $this->getEditionByVersion($fromVersion);
  179. $this->executedChanges = empty($this->config->upgrade->executedChanges) ? [] : json_decode($this->config->upgrade->executedChanges, true);
  180. /* Execute. */
  181. $fromOpenVersion = $this->getOpenVersion(str_replace('.', '_', $this->config->installedVersion));
  182. $toOpenVersion = $this->getOpenVersion($toVersion);
  183. $upgradeVersions = $this->getVersionsToUpdate($fromOpenVersion, $this->fromEdition);
  184. /* Execute charge edition. */
  185. foreach($upgradeVersions as $openVersion => $chargedVersions)
  186. {
  187. if(version_compare($openVersion, $fromOpenVersion, '<')) continue;
  188. if(version_compare($openVersion, $toOpenVersion, '>=')) continue;
  189. /* Execute open edition. */
  190. $this->saveLogs("Execute $openVersion");
  191. $result = $this->execSQL($this->getUpgradeFile(str_replace('_', '.', $openVersion)), $openVersion);
  192. if(!$result) return $this->recordExecutedChanges();
  193. $result = $this->executeByConfig($openVersion);
  194. if(!$result) return $this->recordExecutedChanges();
  195. foreach($chargedVersions as $chargedVersion)
  196. {
  197. foreach($chargedVersion as $version)
  198. {
  199. $this->saveLogs("Execute $version");
  200. $result = $this->execSQL($this->getUpgradeFile(str_replace('_', '.', $version)),$version);
  201. if(!$result) return $this->recordExecutedChanges();
  202. $result = $this->executeByConfig($version);
  203. if(!$result) return $this->recordExecutedChanges();
  204. }
  205. }
  206. }
  207. /* 如果此次升级到最终版本,则执行额外的数据处理流程。*/
  208. if(version_compare($toVersion, $this->config->version, '='))
  209. {
  210. $result = $this->executeOthers($this->fromEdition);
  211. if(!$result) return $this->recordExecutedChanges();
  212. }
  213. else
  214. {
  215. /**
  216. * 升级到最终版本之前,每升级完一个版本,更新一次版本号,防止中途失败重复升级。
  217. * 升级到最终版本之后,则在执行完所有后续的数据处理流程后再更新版本号。
  218. */
  219. $this->loadModel('setting')->updateVersion($toVersion);
  220. }
  221. return !$this->isError() && !dao::isError();
  222. }
  223. /**
  224. * 执行导入模块、刷新项目集和产品信息并且删除补丁。
  225. * Import buildin modules, and refresh stats of programs and products, delete path.
  226. *
  227. * @param string $fromEdition
  228. * @access public
  229. * @return bool
  230. */
  231. public function executeOthers($fromEdition)
  232. {
  233. $version = str_replace('.', '_', $this->config->installedVersion);
  234. $methods = $this->getOtherMethods($fromEdition);
  235. foreach($methods as $method => $params)
  236. {
  237. $result = $this->executeUpgradeMethod($version, $method, $params);
  238. if(!$result) return false;
  239. }
  240. return true;
  241. }
  242. /**
  243. * 通过配置执行升级方法。
  244. * Execute upgrade methods by config.
  245. *
  246. * @param string $version
  247. * @access public
  248. * @return bool
  249. */
  250. public function executeByConfig($version)
  251. {
  252. $execConfig = zget($this->config->upgrade->execFlow, $version, array());
  253. $functions = zget($execConfig, 'functions', '');
  254. $params = zget($execConfig, 'params', array());
  255. $xxsqls = zget($execConfig, 'xxsqls', '');
  256. $xxfunctions = zget($execConfig, 'xxfunctions', '');
  257. foreach(array_filter(explode(',', $functions)) as $function)
  258. {
  259. $result = $this->executeUpgradeMethod($version, $function, zget($params, $function, array()));
  260. if(!$result) return false;
  261. }
  262. if($version == 'pro1_1_1')
  263. {
  264. $result = $this->execSQL($this->getUpgradeFile('pro1.1'), $version);
  265. if(!$result) return false;
  266. }
  267. if($version == 'pro8_3')
  268. {
  269. $result = $this->execSQL($this->getUpgradeFile('pro8.2'), $version);
  270. if(!$result) return false;
  271. }
  272. if(!empty($xxsqls))
  273. {
  274. foreach(array_filter(explode(',', $xxsqls)) as $sqlFile)
  275. {
  276. $result = $this->execSQL($sqlFile, $version);
  277. if(!$result) return false;
  278. }
  279. }
  280. if(!empty($xxfunctions))
  281. {
  282. foreach(array_filter(explode(',', $xxfunctions)) as $function)
  283. {
  284. $result = $this->executeUpgradeMethod($version, $function, zget($params, $function, array()));
  285. if(!$result) return false;
  286. }
  287. }
  288. return true;
  289. }
  290. /**
  291. * 执行单个升级方法。
  292. * Execute single upgrade method.
  293. *
  294. * @param string $version
  295. * @param string $rawMethod
  296. * @param array $params
  297. * @access public
  298. * @return bool
  299. */
  300. public function executeUpgradeMethod($version, $rawMethod, $params = [])
  301. {
  302. if(isset($this->executedChanges[$version]['methods'][$rawMethod])) return true;
  303. $this->saveLogs("Run Method {$rawMethod}");
  304. $model = $this;
  305. $method = $rawMethod;
  306. if(strpos($rawMethod, '-') !== false)
  307. {
  308. list($module, $method) = explode('-', $rawMethod);
  309. $model = $this->loadModel($module);
  310. }
  311. dao::$realTimeLog = true;
  312. call_user_func_array(array($model, $method), $params);
  313. dao::$realTimeLog = false;
  314. if($this->isError()) return false;
  315. if(dao::isError())
  316. {
  317. static::$errors = array_merge(static::$errors, dao::getError());
  318. return false;
  319. }
  320. $this->executedChanges[$version]['methods'][$rawMethod] = true;
  321. $this->recordExecutedChanges();
  322. return true;
  323. }
  324. /**
  325. * 获取确认 SQL 内容。
  326. * Get the confirm contents.
  327. *
  328. * @param string $fromVersion
  329. * @access public
  330. * @return string
  331. */
  332. public function getConfirm($fromVersion)
  333. {
  334. $confirmContent = '';
  335. $fromEdition = $this->getEditionByVersion($fromVersion);
  336. $fromOpenVersion = $this->getOpenVersion($fromVersion);
  337. $versions = $this->getVersionsToUpdate($fromOpenVersion, $fromEdition);
  338. foreach($versions as $openVersion => $chargedVersions)
  339. {
  340. /* Get open contents. */
  341. $sqlFile = $this->getUpgradeFile(str_replace('_', '.', $openVersion));
  342. if(file_exists($sqlFile)) $confirmContent .= file_get_contents($sqlFile);
  343. /* Get xuanxuan contents. */
  344. if(isset($this->config->upgrade->execFlow[$openVersion]['xxsqls']))
  345. {
  346. foreach(array_filter(explode(',', $this->config->upgrade->execFlow[$openVersion]['xxsqls'])) as $sqlFile)
  347. {
  348. if(file_exists($sqlFile)) $confirmContent .= file_get_contents($sqlFile);
  349. }
  350. }
  351. /* Get charge contents. */
  352. foreach($chargedVersions as $chargedVersion)
  353. {
  354. foreach($chargedVersion as $version)
  355. {
  356. $sqlFile = $this->getUpgradeFile(str_replace('_', '.', $version));
  357. if(file_exists($sqlFile)) $confirmContent .= file_get_contents($sqlFile);
  358. /* Get xuanxuan contents. */
  359. if(!isset($this->config->upgrade->execFlow[$version]['xxsqls'])) continue;
  360. foreach(array_filter(explode(',', $this->config->upgrade->execFlow[$version]['xxsqls'])) as $sqlFile)
  361. {
  362. if(file_exists($sqlFile)) $confirmContent .= file_get_contents($sqlFile);
  363. }
  364. }
  365. }
  366. }
  367. return str_replace('zt_', $this->config->db->prefix, $confirmContent);
  368. }
  369. /**
  370. * 通过版本号获取产品版本类型。
  371. * Get edition by version.
  372. *
  373. * @param string $version
  374. * @access public
  375. * @return string
  376. */
  377. public function getEditionByVersion($version)
  378. {
  379. $editions = array('p' => 'pro', 'b' => 'biz', 'm' => 'max', 'i' => 'ipd');
  380. return is_numeric($version[0]) ? 'open' : $editions[$version[0]];
  381. }
  382. /**
  383. * 获取开源版版本。
  384. * Get open version.
  385. *
  386. * @param string $version.
  387. * @access public
  388. * @return string
  389. */
  390. public function getOpenVersion($version)
  391. {
  392. $edition = $this->getEditionByVersion($version);
  393. return is_numeric($version[0]) ? $version : zget($this->config->upgrade->{$edition . 'Version'}, $version);
  394. }
  395. /**
  396. * Update company field.
  397. *
  398. * This method is used to update since 1.0 beta. Any new tables added after 1.0 beta should skip.
  399. *
  400. * @access public
  401. * @return void
  402. */
  403. public function updateCompany()
  404. {
  405. /* Get user defined constants. */
  406. $constants = get_defined_constants(true);
  407. $userConstants = $constants['user'];
  408. /* Update tables. */
  409. foreach($userConstants as $key => $value)
  410. {
  411. if(strpos($key, 'TABLE') === false) continue;
  412. if($key == 'TABLE_COMPANY') continue;
  413. $table = $value;
  414. $result = $this->dbh->query("SHOW TABLES LIKE '$table'");
  415. if($result->rowCount() > 0)
  416. {
  417. $this->dbh->exec("UPDATE $table SET company = '{$this->app->company->id}'");
  418. }
  419. }
  420. }
  421. /**
  422. * Check table consistency.
  423. *
  424. * @param string $sql
  425. * @access private
  426. * @return array
  427. */
  428. private function checkTableConsistency($sql)
  429. {
  430. $changes = array();
  431. $lines = explode("\n", $sql);
  432. $createHead = array_shift($lines);
  433. preg_match_all('/CREATE TABLE [^`]*`([^`]*)`/', $createHead, $out);
  434. if(!isset($out[1][0])) return $changes;
  435. $table = str_replace('zt_', $this->config->db->prefix, $out[1][0]);
  436. if($table == $this->config->db->prefix . 'metriclib') return $changes; // 度量库表数据量过大,检查到表结构不一致执行升级会导致长时间卡死,跳过检查。Skip checking metriclib table because it has too much data and checking it will cause long time stuck.
  437. $fields = array();
  438. try
  439. {
  440. $dbCreateSQL = $this->dbh->query("SHOW CREATE TABLE `$table`")->fetch(PDO::FETCH_ASSOC);
  441. $dbSQLLines = explode("\n", $dbCreateSQL['Create Table']);
  442. foreach($dbSQLLines as $dbSQLLine)
  443. {
  444. $dbSQLLine = trim($dbSQLLine);
  445. if(!preg_match('/^`([^`]*)` /', $dbSQLLine)) continue; // Skip no describe field line.
  446. $dbSQLLine = rtrim($dbSQLLine, ',');
  447. $dbSQLLine = str_replace('utf8 COLLATE utf8_general_ci', 'utf8', $dbSQLLine);
  448. $dbSQLLine = preg_replace('/ DEFAULT (\-?\d+\.?\d*)$/i', " DEFAULT '$1'", $dbSQLLine);
  449. list($field) = explode(' ', $dbSQLLine);
  450. $fields[$field] = rtrim($dbSQLLine, ',');
  451. }
  452. }
  453. catch(PDOException $e)
  454. {
  455. $message = $e->getMessage();
  456. if($message && strpos($message, '1146') !== false) return array($sql);
  457. }
  458. if(empty($fields)) return $changes;
  459. foreach($lines as $line)
  460. {
  461. $change = $this->checkFieldConsistency($table, $line, $fields);
  462. if($change) $changes[] = $change;
  463. }
  464. return $changes;
  465. }
  466. /**
  467. * Check field consistency.
  468. *
  469. * @param string $table
  470. * @param string $line
  471. * @param string $fields
  472. * @access private
  473. * @return array
  474. */
  475. private function checkFieldConsistency($table, $line, $fields)
  476. {
  477. $line = trim($line);
  478. if(!preg_match('/^`([^`]*)` /', $line)) return ''; // Skip no describe field line.
  479. $line = rtrim($line, ',');
  480. $line = str_replace('utf8 COLLATE utf8_general_ci', 'utf8', $line);
  481. $line = preg_replace('/ DEFAULT (\-?\d+\.?\d*)$/i', " DEFAULT '$1'", $line);
  482. list($field) = explode(' ', $line);
  483. if(isset($fields[$field]) and strpos($fields[$field], ' NULL') === false and strpos($line, ' DEFAULT NULL') !== false) $line = str_replace(' DEFAULT NULL', '', $line); // e.g. standard sql like [ `content` text DEFAULT NULL ] , but current db sql like [ `content` text ].
  484. /* Dont change if matched. */
  485. if(isset($fields[$field]) && $line == $fields[$field]) return '';
  486. /* Add field. */
  487. if(!isset($fields[$field]))
  488. {
  489. $execSQL = "ALTER TABLE `$table` ADD $line";
  490. if(stripos($execSQL, 'auto_increment') !== false && stripos($execSQL, ' PRIMARY KEY ') === false) $execSQL .= ' PRIMARY KEY';
  491. }
  492. else
  493. {
  494. /* Modify field. */
  495. $execSQL = $this->checkFieldSQL($table, $line, $fields[$field]);
  496. }
  497. if(stripos($execSQL, 'auto_increment') !== false) $execSQL .= ' FIRST';
  498. return $execSQL;
  499. }
  500. /**
  501. * Check field SQL.
  502. *
  503. * @param string $table
  504. * @param string $stdField
  505. * @param string $dbField
  506. * @access private
  507. * @return string
  508. */
  509. private function checkFieldSQL($table, $stdField, $dbField)
  510. {
  511. $needModify = false;
  512. if($this->checkFieldTypeDiff($stdField, $dbField)) $needModify = true;
  513. if($this->checkFieldNullDiff($stdField, $dbField)) $needModify = true;
  514. if($this->checkFieldDefaultDiff($stdField, $dbField)) $needModify = true;
  515. $fieldName = explode(' ', $dbField)[0];
  516. return $needModify ? "ALTER TABLE `$table` CHANGE $fieldName $stdField" : '';
  517. }
  518. /**
  519. * Check field null diff.
  520. *
  521. * @param string $stdField
  522. * @param string $dbField
  523. * @access private
  524. * @return bool
  525. */
  526. private function checkFieldNullDiff($stdField, $dbField)
  527. {
  528. $dbNotNull = stripos($dbField, 'NOT NULL') !== false;
  529. $stdNotNull = stripos($stdField, 'NOT NULL') !== false;
  530. if($dbNotNull && !$stdNotNull) return true;
  531. }
  532. /**
  533. * Check field default diff.
  534. *
  535. * @param string $stdField
  536. * @param string $dbField
  537. * @access private
  538. * @return bool
  539. */
  540. private function checkFieldDefaultDiff($stdField, $dbField)
  541. {
  542. $defaultPos = stripos($stdField, ' DEFAULT ');
  543. if($defaultPos === false) return false; // No default in std, don't change.
  544. $stdDefault = str_replace("'", '', substr($stdField, $defaultPos + 9));
  545. $commentPos = stripos($stdDefault, ' COMMENT ');
  546. if($commentPos !== false) $stdDefault = substr($stdDefault, 0, $commentPos);
  547. if($stdDefault == 'NULL') return false; // Default is NULL, don't change.
  548. if(strpos($stdField, 'text') !== false && empty($stdDefault)) return false; // Default is '' and text type, don't change.
  549. $defaultPos = stripos($dbField, ' DEFAULT ');
  550. if($defaultPos === false) return true; // No default in db, change it.
  551. $dbDefault = str_replace("'", '', substr($dbField, $defaultPos + 9));
  552. $commentPos = stripos($dbDefault, ' COMMENT ');
  553. if($commentPos !== false) $dbDefault = substr($dbDefault, 0, $commentPos);
  554. return $stdDefault != $dbDefault;
  555. }
  556. /**
  557. * Check field type diff .
  558. *
  559. * @param string $stdField
  560. * @param string $dbField
  561. * @access private
  562. * @return bool
  563. */
  564. private function checkFieldTypeDiff($stdField, $dbField)
  565. {
  566. $stdConfigs = preg_split('/\s+/', $stdField);
  567. $dbConfigs = preg_split('/\s+/', $dbField);
  568. $stdType = $stdConfigs[1];
  569. $dbType = $dbConfigs[1];
  570. if($stdType == $dbType) return false;
  571. $stdLength = 0;
  572. $dbLength = 0;
  573. preg_match_all('/^(\w+)(\((\d+)\))?$/', $stdConfigs[1], $stdOutput);
  574. if(!empty($stdOutput[1][0])) $stdType = $stdOutput[1][0];
  575. if(!empty($stdOutput[3][0])) $stdLength = $stdOutput[3][0];
  576. preg_match_all('/^(\w+)(\((\d+)\))?$/', $dbConfigs[1], $dbOutput);
  577. if(!empty($dbOutput[1][0])) $dbType = $dbOutput[1][0];
  578. if(!empty($dbOutput[3][0])) $dbLength = $dbOutput[3][0];
  579. $stdIsVarchar = stripos($stdType, 'varchar') !== false;
  580. $stdIsText = stripos($stdType, 'text') !== false;
  581. $stdIsFloat = preg_match('/float|decimal|double/i', $stdType);
  582. $dbIsInt = stripos($dbType, 'int') !== false;
  583. $dbIsVarchar = stripos($dbType, 'varchar') !== false;
  584. if($dbIsInt)
  585. {
  586. if($stdIsFloat || $stdIsText || $stdIsVarchar) return true;
  587. if($dbLength != 0 && $stdLength > $dbLength) return true; // MySQL 8.0 int has no length.
  588. }
  589. elseif($dbIsVarchar)
  590. {
  591. if($stdIsText) return true;
  592. if($dbLength && $stdLength > $dbLength) return true;
  593. }
  594. return false;
  595. }
  596. /**
  597. * 检查一致性。
  598. * Check consistency.
  599. *
  600. * @param string $version
  601. * @access public
  602. * @return string
  603. */
  604. public function checkConsistency($version = '')
  605. {
  606. $alterSQL = array();
  607. $sqls = $this->getStandardSQLs($version);
  608. foreach(explode(';', $sqls) as $sql)
  609. {
  610. $sql = trim($sql);
  611. if(strpos($sql, 'CREATE TABLE ') !== 0) continue;
  612. $changes = $this->checkTableConsistency($sql);
  613. if(!empty($changes)) $alterSQL = array_merge($alterSQL, $changes);
  614. }
  615. return implode(";\n", $alterSQL);
  616. }
  617. /**
  618. * 为一致性问题执行修复sql。
  619. * Exec fix sql for consistency.
  620. *
  621. * @param string $version
  622. * @access public
  623. * @return void
  624. */
  625. public function fixConsistency($version)
  626. {
  627. $logFile = $this->getConsistencyLogFile();
  628. $lockFile = $logFile . '.lock';
  629. if(file_exists($lockFile) && (time() - filemtime($lockFile)) < 60) return;
  630. if(file_exists($logFile)) unlink($logFile);
  631. touch($lockFile);
  632. $hasError = false;
  633. $fixSqls = $this->checkConsistency($version);
  634. if($fixSqls) $fixSqls = "SET @@sql_mode= '';\n" . $fixSqls;
  635. /* 记录执行的sql并且记录执行结果。 */
  636. /* Record the executed sql and the result. */
  637. $sqlLines = explode(';', $fixSqls);
  638. file_put_contents($logFile, count($sqlLines) . "\n");
  639. foreach($sqlLines as $fixSQL)
  640. {
  641. file_put_contents($logFile, $fixSQL, FILE_APPEND);
  642. try
  643. {
  644. $this->dbh->exec($fixSQL);
  645. }
  646. catch(PDOException $e)
  647. {
  648. $hasError = true;
  649. file_put_contents($logFile, $e->getMessage() . "\n", FILE_APPEND);
  650. }
  651. }
  652. $finishTag = "\nFinished";
  653. if($hasError) $finishTag = "\nHasError" . $finishTag;
  654. file_put_contents($logFile, $finishTag, FILE_APPEND);
  655. unlink($lockFile);
  656. }
  657. /**
  658. * 删除临时 model 文件。
  659. * Delete tmp model files.
  660. *
  661. * @access public
  662. * @return void
  663. */
  664. public function deleteTmpModel()
  665. {
  666. $zfile = $this->app->loadClass('zfile');
  667. $tmpModelDir = $this->app->getTmpRoot() . 'model/';
  668. foreach(glob($tmpModelDir . '/*') as $tmpModelFile)
  669. {
  670. if(is_file($tmpModelFile)) unlink($tmpModelFile);
  671. if(is_dir($tmpModelFile)) $zfile->removeDir($tmpModelFile);
  672. }
  673. }
  674. /**
  675. * 删除无用的文件。
  676. * Delete Useless Files.
  677. *
  678. * @param string $script
  679. * @access public
  680. * @return string
  681. */
  682. public function deleteFiles($script)
  683. {
  684. $dir = dirname($script);
  685. if(!is_writable($dir)) return "chmod 777 {$dir}";
  686. $command = array();
  687. $zfile = $this->app->loadClass('zfile');
  688. foreach($this->config->delete as $deleteFiles)
  689. {
  690. $basePath = $this->app->getBasePath();
  691. foreach($deleteFiles as $file)
  692. {
  693. if(isset($this->config->excludeFiles[$file])) continue;
  694. $fullPath = $basePath . str_replace('/', DS, $file);
  695. if(file_exists($fullPath))
  696. {
  697. $isDir = is_dir($fullPath);
  698. /* Get the command to delete files that could not be deleted. */
  699. if(!is_writable($fullPath) || ($isDir && !$zfile->removeDir($fullPath)) ||
  700. (!$isDir && !$zfile->removeFile($fullPath)))
  701. {
  702. $command[] = 'rm -fr ' . $fullPath;
  703. }
  704. }
  705. }
  706. }
  707. /* Delete all patch files when upgrade zentao. */
  708. $patchPath = $this->app->getTmpRoot() . 'patch';
  709. $isDir = is_dir($patchPath);
  710. if(file_exists($patchPath))
  711. {
  712. /* Get the command to delete files that could not be deleted. */
  713. if(!is_writable($patchPath) || ($isDir && !$zfile->removeDir($patchPath)) ||
  714. (!$isDir && !$zfile->removeDir($patchPath)))
  715. {
  716. $command[] = 'rm -fr ' . $patchPath;
  717. }
  718. }
  719. if(!$command) return '';
  720. asort($command);
  721. $content = "#!/bin/bash\n" . implode("\n", $command);
  722. file_put_contents($script, $content);
  723. return "/bin/bash $script";
  724. }
  725. /**
  726. * Update nl to br from 1.2 version.
  727. *
  728. * @access public
  729. * @return void
  730. */
  731. public function updateNL1_2()
  732. {
  733. $tasks = $this->dao->select('id, `desc`')->from(TABLE_TASK)->fetchAll();
  734. $stories = $this->dao->select('story, version, spec')->from($this->config->db->prefix . 'storySpec')->fetchAll();
  735. $todos = $this->dao->select('id, `desc`')->from(TABLE_TODO)->fetchAll();
  736. $testTasks = $this->dao->select('id, `desc`')->from($this->config->db->prefix . 'testTask')->fetchAll();
  737. foreach($tasks as $task)
  738. {
  739. $task->desc = nl2br($task->desc);
  740. $this->dao->update(TABLE_TASK)->data($task)->where('id')->eq($task->id)->exec();
  741. }
  742. foreach($stories as $story)
  743. {
  744. $story->spec = nl2br($story->spec);
  745. $this->dao->update($this->config->db->prefix . 'storySpec')->data($story)->where('story')->eq($story->story)->andWhere('version')->eq($story->version)->exec();
  746. }
  747. foreach($todos as $todo)
  748. {
  749. $todo->desc = nl2br($todo->desc);
  750. $this->dao->update(TABLE_TODO)->data($todo)->where('id')->eq($todo->id)->exec();
  751. }
  752. foreach($testTasks as $testtask)
  753. {
  754. $testtask->desc = nl2br($testtask->desc);
  755. $this->dao->update($this->config->db->prefix . 'testTask')->data($testtask)->where('id')->eq($testtask->id)->exec();
  756. }
  757. }
  758. /**
  759. * Update nl to br from 1.3 version.
  760. *
  761. * @access public
  762. * @return void
  763. */
  764. public function updateNL1_3()
  765. {
  766. $products = $this->dao->select('id, `desc`')->from(TABLE_PRODUCT)->fetchAll();
  767. $plans = $this->dao->select('id, `desc`')->from($this->config->db->prefix . 'productPlan')->fetchAll();
  768. $releases = $this->dao->select('id, `desc`')->from(TABLE_RELEASE)->fetchAll();
  769. $projects = $this->dao->select('id, `desc`, goal')->from(TABLE_PROJECT)->fetchAll();
  770. $builds = $this->dao->select('id, `desc`')->from(TABLE_BUILD)->fetchAll();
  771. $account = isset($this->app->user->account) ? $this->app->user->account : '';
  772. foreach($products as $product)
  773. {
  774. $product->desc = nl2br($product->desc);
  775. $this->dao->update(TABLE_PRODUCT)->data($product)->where('id')->eq($product->id)->exec();
  776. }
  777. foreach($plans as $plan)
  778. {
  779. $plan->desc = nl2br($plan->desc);
  780. $this->dao->update($this->config->db->prefix . 'productPlan')->data($plan)->where('id')->eq($plan->id)->exec();
  781. }
  782. foreach($releases as $release)
  783. {
  784. $release->desc = nl2br($release->desc);
  785. $this->dao->update(TABLE_RELEASE)->data($release)->where('id')->eq($release->id)->exec();
  786. }
  787. foreach($projects as $project)
  788. {
  789. $project->desc = nl2br($project->desc);
  790. $project->goal = nl2br($project->goal);
  791. $project->lastEditedBy = $account;
  792. $project->lastEditedDate = helper::now();
  793. $this->dao->update(TABLE_PROJECT)->data($project)->where('id')->eq($project->id)->exec();
  794. }
  795. foreach($builds as $build)
  796. {
  797. $build->desc = nl2br($build->desc);
  798. $this->dao->update(TABLE_BUILD)->data($build)->where('id')->eq($build->id)->exec();
  799. }
  800. }
  801. /**
  802. * Update task fields.
  803. *
  804. * @access public
  805. * @return void
  806. */
  807. public function updateTasks()
  808. {
  809. /* Get all actions of tasks. */
  810. $actions = $this->dao->select('*')->from(TABLE_ACTION)
  811. ->where('objectType')->eq('task')
  812. ->orderBy('id')
  813. ->fetchAll('id', false);
  814. /* Get histories about status field. */
  815. $histories = $this->dao->select('*')->from(TABLE_HISTORY)
  816. ->where('action')->in(array_keys($actions))
  817. ->andWhere('field')->eq('status')
  818. ->orderBy('id')
  819. ->fetchGroup('action');
  820. $tasks = array();
  821. foreach($actions as $action)
  822. {
  823. if(!isset($tasks[$action->objectID])) $tasks[$action->objectID] = new stdclass;
  824. $task = $tasks[$action->objectID];
  825. $task->id = $action->objectID;
  826. $actionType = strtolower($action->action);
  827. /* Set the openedBy info. */
  828. if($actionType == 'opened')
  829. {
  830. $task->openedBy = $action->actor;
  831. $task->openedDate = $action->date;
  832. }
  833. else
  834. {
  835. if(!isset($histories[$action->id])) continue;
  836. $actionHistories = $histories[$action->id];
  837. foreach($actionHistories as $history)
  838. {
  839. /* Finished by. */
  840. if($history->new == 'done')
  841. {
  842. $task->finishedBy = $action->actor;
  843. $task->finishedDate = $action->date;
  844. $action->action = 'finished';
  845. }
  846. /* Canceled By. */
  847. elseif($history->new == 'cancel')
  848. {
  849. $task->canceledBy = $action->actor;
  850. $task->canceledDate = $action->date;
  851. $action->action = 'canceled';
  852. }
  853. }
  854. /* Last edited by .*/
  855. $task->lastEditedBy = $action->actor;
  856. $task->lastEditedDate = $action->date;
  857. /* Update action type. */
  858. $this->dao->update(TABLE_ACTION)->set('action')->eq($action->action)->where('id')->eq($action->id)->exec();
  859. }
  860. }
  861. /* Update db. */
  862. foreach($tasks as $task) $this->dao->update(TABLE_TASK)->data($task)->where('id')->eq($task->id)->exec();
  863. $this->dao->update(TABLE_TASK)->set('assignedTo=openedBy, assignedDate = finishedDate')->where('status')->eq('done')->exec();
  864. $this->dao->update(TABLE_TASK)->set('assignedTo=openedBy, assignedDate = canceledDate')->where('status')->eq('cancel')->exec();
  865. }
  866. /**
  867. * Update activated count of Bug.
  868. *
  869. * @access public
  870. * @return void
  871. */
  872. public function updateActivatedCountOfBug()
  873. {
  874. $bugActivatedActions = $this->dao->select('*')->from(TABLE_ACTION)->where('action')->eq('activated')->andWhere('objectType')->eq('bug')->fetchAll('id', false);
  875. if(!empty($bugActivatedActions))
  876. {
  877. foreach($bugActivatedActions as $action)
  878. {
  879. if(!isset($counts[$action->objectID])) $counts[$action->objectID] = 0;
  880. $counts[$action->objectID] ++;
  881. }
  882. foreach($counts as $key => $count) $this->dao->update(TABLE_BUG)->set('activatedCount')->eq($count)->where('id')->eq($key)->exec();
  883. }
  884. }
  885. /**
  886. * Update lastRun and lastResult field in zt_case
  887. *
  888. * @access public
  889. * @return void
  890. */
  891. public function updateCases()
  892. {
  893. $results = $this->dao->select('`case`, date, caseResult')->from($this->config->db->prefix . 'testResult')->orderBy('id desc')->fetchGroup('case');
  894. foreach($results as $result)
  895. {
  896. $this->dao->update(TABLE_CASE)
  897. ->set('lastRun')->eq($result[0]->date)
  898. ->set('lastResult')->eq($result[0]->caseResult)
  899. ->where('id')->eq($result[0]->case)
  900. ->exec();
  901. }
  902. }
  903. /**
  904. * Update type of projects.
  905. *
  906. * @access public
  907. * @return void
  908. */
  909. public function updateProjectType()
  910. {
  911. $projects = $this->dao->select('root')->from(TABLE_MODULE)->where('type')->eq('task')->fetchPairs('root');
  912. $this->dao->update(TABLE_PROJECT)->set('type')->eq('waterfall')->where('id')->in($projects)->exec();
  913. return true;
  914. }
  915. /**
  916. * Update estimate priv.
  917. *
  918. * @access public
  919. * @return void
  920. */
  921. public function updateEstimatePriv()
  922. {
  923. $methods = ['recordWorkhour', 'editEffort', 'deleteWorkhour'];
  924. $privTable = $this->config->db->prefix . 'groupPriv';
  925. $groups = $this->dao->select('*')->from($privTable)
  926. ->where('module')->eq('task')
  927. ->andWhere('method')->eq('edit')
  928. ->fetchAll();
  929. $this->dao->begin();
  930. foreach($groups as $group)
  931. {
  932. $this->dao->delete()->from($privTable)
  933. ->where('`group`')->eq($group->group)
  934. ->andWhere('module')->eq('task')
  935. ->andWhere('method')->in($methods)
  936. ->exec();
  937. if(dao::isError())
  938. {
  939. $this->dao->rollback();
  940. return false;
  941. }
  942. foreach($methods as $method)
  943. {
  944. $this->dao->insert($privTable)
  945. ->set('company')->eq($group->company)
  946. ->set('`group`')->eq($group->group)
  947. ->set('module')->eq('task')
  948. ->set('method')->eq($method)
  949. ->exec();
  950. if(dao::isError())
  951. {
  952. $this->dao->rollback();
  953. return false;
  954. }
  955. }
  956. }
  957. return $this->dao->commit();
  958. }
  959. /**
  960. * Update the data of action.
  961. *
  962. * @access public
  963. * @return void
  964. */
  965. public function updateAction()
  966. {
  967. /* Get projects and tasks from action table. */
  968. $projects = $this->dao->select('id')->from(TABLE_PROJECT)->fetchPairs('id');
  969. $tasks = $this->dao->select('id, project')->from(TABLE_TASK)->fetchPairs('id');
  970. /* Get products of projects and tasks. */
  971. $projectProducts = $this->dao->select('project,product')->from($this->config->db->prefix . 'projectProduct')->where('project')->in(array_keys($projects))->fetchGroup('project', 'product');
  972. $taskProducts = $this->dao->select('t1.id, t2.product')->from(TABLE_TASK)->alias('t1')
  973. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
  974. ->where('t1.id')->in(array_keys($tasks))
  975. ->fetchPairs('id');
  976. /* Process project actions. */
  977. foreach($projects as $projectID)
  978. {
  979. $productList = isset($projectProducts[$projectID]) ? implode(',', array_keys($projectProducts[$projectID])) : '';
  980. $this->dao->update(TABLE_ACTION)->set('product')->eq($productList)->where('objectType')->eq('project')->andWhere('objectID')->eq($projectID)->exec();
  981. }
  982. /* Process task actions. */
  983. foreach($tasks as $taskID => $projectID)
  984. {
  985. $productList = '';
  986. if($taskProducts[$taskID])
  987. {
  988. $productList = $taskProducts[$taskID];
  989. }
  990. else
  991. {
  992. $productList = isset($projectProducts[$projectID]) ? implode(',', array_keys($projectProducts[$projectID])) : '';
  993. }
  994. $this->dao->update(TABLE_ACTION)->set('product')->eq($productList)->where('objectType')->eq('task')->andWhere('objectID')->eq($taskID)->andWhere('project')->eq($projectID)->exec();
  995. }
  996. $this->dao->update(TABLE_ACTION)->set("product = concat(',',product,',')")->exec();
  997. return true;
  998. }
  999. /**
  1000. * Init the data of product and project order field.
  1001. *
  1002. * @access public
  1003. * @return void
  1004. */
  1005. public function setOrderData()
  1006. {
  1007. $products = $this->dao->select('*')->from(TABLE_PRODUCT)->where('deleted')->eq(0)->orderBy('code')->fetchAll('id');
  1008. foreach(array_keys($products) as $key => $productID)
  1009. {
  1010. $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq(($key + 1) * 10)->where('id')->eq($productID)->exec();
  1011. }
  1012. $projects = $this->dao->select('*')->from(TABLE_PROJECT)->where('iscat')->eq(0)->andWhere('deleted')->eq(0)->orderBy('status, id desc')->fetchAll('id');
  1013. foreach(array_keys($projects) as $key => $projectID)
  1014. {
  1015. $this->dao->update(TABLE_PROJECT)->set('`order`')->eq(($key + 1) * 10)->where('id')->eq($projectID)->exec();
  1016. }
  1017. return true;
  1018. }
  1019. /**
  1020. * Update task assignedTo.
  1021. *
  1022. * @access public
  1023. * @return void
  1024. */
  1025. public function updateTaskAssignedTo()
  1026. {
  1027. $this->dao->update(TABLE_TASK)->set('assignedTo')->eq('closed')
  1028. ->where('status')->eq('closed')
  1029. ->andWhere('assignedTo')->eq('')
  1030. ->exec();
  1031. return true;
  1032. }
  1033. /**
  1034. * 删除补丁记录。
  1035. * Delete the patch record.
  1036. *
  1037. * @access public
  1038. * @return bool
  1039. */
  1040. public function deletePatch()
  1041. {
  1042. $this->dao->delete()->from(TABLE_EXTENSION)->where('type')->eq('patch')->exec();
  1043. $this->dao->delete()->from(TABLE_EXTENSION)->where('code')->in('zentaopatch,patch')->exec();
  1044. return !dao::isError();
  1045. }
  1046. /**
  1047. * 获取升级 sql 文件路径。
  1048. * Get the upgrade sql file.
  1049. *
  1050. * @param string $version
  1051. * @access public
  1052. * @return string
  1053. */
  1054. public function getUpgradeFile($version)
  1055. {
  1056. return $this->app->getAppRoot() . 'db' . DS . 'update' . $version . '.sql';
  1057. }
  1058. /**
  1059. * 获取项目集下的项目。
  1060. * Get the project of the program it belongs to.
  1061. *
  1062. * @param int $programID
  1063. * @access public
  1064. * @return array
  1065. */
  1066. public function getProjectPairsByProgram($programID = 0)
  1067. {
  1068. return $this->dao->select('id,name')->from(TABLE_PROJECT)->where('deleted')->eq(0)->andWhere('type')->eq('project')->andWhere('parent')->eq($programID)->fetchPairs();
  1069. }
  1070. /**
  1071. * 执行一个 sql 文件。
  1072. * Execute a sql.
  1073. *
  1074. * @param string $sqlFile
  1075. * @param string $version
  1076. * @access public
  1077. * @return bool
  1078. */
  1079. public function execSQL($sqlFile, $version = '')
  1080. {
  1081. $this->saveLogs('Run Method ' . __FUNCTION__);
  1082. $ignoreCode = '|1050|1054|1060|1091|1061|';
  1083. $sqls = $this->parseToSqls($sqlFile); // Get sqls in the file.
  1084. foreach($sqls as $sql)
  1085. {
  1086. if(empty($sql)) continue;
  1087. $sqlMd5 = md5($sql);
  1088. $fileMd5 = md5($sqlFile);
  1089. if(isset($this->executedChanges[$version]['sqls'][$fileMd5][$sqlMd5])) continue; // Skip the sql that has been executed.
  1090. try
  1091. {
  1092. $this->saveLogs($sql);
  1093. $this->dbh->exec($sql);
  1094. $this->executedChanges[$version]['sqls'][$fileMd5][$sqlMd5] = true; // Mark the sql has been executed.
  1095. $this->recordExecutedChanges();
  1096. }
  1097. catch(PDOException $e)
  1098. {
  1099. $errorInfo = $e->errorInfo;
  1100. $errorCode = !empty($errorInfo) ? $errorInfo[1] : '';
  1101. if(strpos($ignoreCode, "|$errorCode|") !== false)
  1102. {
  1103. $this->executedChanges[$version]['sqls'][$fileMd5][$sqlMd5] = true; // Mark the sql has been executed.
  1104. $this->recordExecutedChanges();
  1105. }
  1106. else
  1107. {
  1108. static::$errors[] = $e->getMessage();
  1109. return false;
  1110. }
  1111. }
  1112. }
  1113. return true;
  1114. }
  1115. /**
  1116. * 将 sql 文件解析为 sql 语句。
  1117. * Parse sql file to sqls.
  1118. *
  1119. * @param string $sqlFile
  1120. * @access public
  1121. * @return array
  1122. */
  1123. public function parseToSqls($sqlFile)
  1124. {
  1125. if(!file_exists($sqlFile)) return [];
  1126. /* Read the sql file to lines, remove the comment lines, then join theme by ';'. */
  1127. $sqls = array();
  1128. $lines = explode("\n", file_get_contents($sqlFile));
  1129. foreach($lines as $line)
  1130. {
  1131. $line = trim($line);
  1132. /* Skip sql that is note and empty sql. */
  1133. if(!$line || preg_match('/^--|^#|^\/\*/', $line)) continue;
  1134. $sqls[] = $line;
  1135. }
  1136. $this->loadModel('install');
  1137. $sqls = array_filter(explode(';', implode("\n", $sqls)));
  1138. foreach($sqls as $key => $sql)
  1139. {
  1140. $sql = trim($sql); // Trim \n and space.
  1141. $sql = $this->install->replaceContantsInSQL($sql);
  1142. $sql = $this->install->appendMySQLTableOptions($sql);
  1143. $sqls[$key] = $sql;
  1144. }
  1145. return $sqls;
  1146. }
  1147. /**
  1148. * Add priv for version 4.0.1
  1149. *
  1150. * @access public
  1151. * @return void
  1152. */
  1153. public function addPriv4_0_1()
  1154. {
  1155. $privTable = $this->config->db->prefix . 'groupPriv';
  1156. $oldPriv = $this->dao->select('*')->from($privTable)
  1157. ->where('module')->eq('company')
  1158. ->andWhere('method')->eq('edit')
  1159. ->fetchAll();
  1160. foreach($oldPriv as $item)
  1161. {
  1162. $this->dao->replace($privTable)
  1163. ->set('company')->eq($item->company)
  1164. ->set('module')->eq('company')
  1165. ->set('method')->eq('view')
  1166. ->set('`group`')->eq($item->group)
  1167. ->exec();
  1168. }
  1169. $oldPriv = $this->dao->select('*')->from($privTable)
  1170. ->where('module')->eq('todo')
  1171. ->andWhere('method')->eq('finish')
  1172. ->fetchAll();
  1173. foreach($oldPriv as $item)
  1174. {
  1175. $this->dao->replace($privTable)
  1176. ->set('company')->eq($item->company)
  1177. ->set('module')->eq('todo')
  1178. ->set('method')->eq('batchFinish')
  1179. ->set('`group`')->eq($item->group)
  1180. ->exec();
  1181. }
  1182. return true;
  1183. }
  1184. /**
  1185. * Add priv for version 4.1
  1186. *
  1187. * @access public
  1188. * @return bool
  1189. */
  1190. public function addPriv4_1()
  1191. {
  1192. $privTable = $this->config->db->prefix . 'groupPriv';
  1193. $oldPriv = $this->dao->select('*')->from($privTable)
  1194. ->where('module')->eq('tree')
  1195. ->andWhere('method')->eq('browse')
  1196. ->fetchAll();
  1197. foreach($oldPriv as $item)
  1198. {
  1199. $this->dao->replace($privTable)
  1200. ->set('company')->eq($item->company)
  1201. ->set('module')->eq('tree')
  1202. ->set('method')->eq('browseTask')
  1203. ->set('`group`')->eq($item->group)
  1204. ->exec();
  1205. }
  1206. return true;
  1207. }
  1208. /**
  1209. * Add priv for version 8.1
  1210. *
  1211. * @access public
  1212. * @return bool
  1213. */
  1214. public function addPriv8_1()
  1215. {
  1216. $methods = [
  1217. 'bug' => ['linkBugs', 'unlinkBug'],
  1218. 'story' => ['linkStory', 'unlinkStory'],
  1219. 'testcase' => ['linkCases', 'unlinkCase']
  1220. ];
  1221. $privTable = $this->config->db->prefix . 'grouppriv';
  1222. $oldPrivs = $this->dao->select('*')->from($privTable)
  1223. ->where('module')->in(array_keys($methods))
  1224. ->andWhere('method')->eq('edit')
  1225. ->fetchGroup('module');
  1226. $this->dao->begin();
  1227. foreach($oldPrivs as $module => $privs)
  1228. {
  1229. foreach($privs as $item)
  1230. {
  1231. foreach($methods[$module] as $method)
  1232. {
  1233. $this->dao->replace($privTable)
  1234. ->set('module')->eq($module)
  1235. ->set('method')->eq($method)
  1236. ->set('`group`')->eq($item->group)
  1237. ->exec();
  1238. if(dao::isError())
  1239. {
  1240. $this->dao->rollback();
  1241. return false;
  1242. }
  1243. }
  1244. }
  1245. }
  1246. return $this->dao->commit();
  1247. }
  1248. /**
  1249. * Add priv for version 12.3.3
  1250. *
  1251. * @access public
  1252. * @return bool
  1253. */
  1254. public function addPriv12_3_3()
  1255. {
  1256. $privTable = $this->config->db->prefix . 'grouppriv';
  1257. $oldPriv = $this->dao->select('*')->from($privTable)
  1258. ->where('module')->eq('todo')
  1259. ->andWhere('method')->eq('edit')
  1260. ->fetchAll();
  1261. foreach($oldPriv as $item)
  1262. {
  1263. $this->dao->replace($privTable)
  1264. ->set('module')->eq('todo')
  1265. ->set('method')->eq('start')
  1266. ->set('`group`')->eq($item->group)
  1267. ->exec();
  1268. }
  1269. return true;
  1270. }
  1271. /**
  1272. * Add priv for 8.2.
  1273. *
  1274. * @access public
  1275. * @return bool
  1276. */
  1277. public function addPriv8_2_beta()
  1278. {
  1279. $privTable = $this->config->db->prefix . 'grouppriv';
  1280. /* Change product-all priv. */
  1281. $groups = $this->dao->select('`group`')->from($privTable)->where('`module`')->eq('product')->andWhere('`method`')->eq('index')->fetchPairs('group', 'group');
  1282. foreach($groups as $group)
  1283. {
  1284. $this->dao->replace($privTable)->set('module')->eq('product')->set('method')->eq('all')->set('`group`')->eq($group)->exec();
  1285. }
  1286. /* Change project-all priv. */
  1287. $groups = $this->dao->select('`group`')->from($privTable)->where('`module`')->eq('project')->andWhere('`method`')->eq('index')->fetchPairs('group', 'group');
  1288. foreach($groups as $group)
  1289. {
  1290. $this->dao->replace($privTable)->set('module')->eq('project')->set('method')->eq('all')->set('`group`')->eq($group)->exec();
  1291. }
  1292. /* Add kanban and tree priv. */
  1293. $groups = $this->dao->select('`group`')->from($privTable)->where('`module`')->eq('project')->andWhere('`method`')->eq('task')->fetchPairs('group', 'group');
  1294. foreach($groups as $group)
  1295. {
  1296. $this->dao->replace($privTable)->set('module')->eq('project')->set('method')->eq('kanban')->set('`group`')->eq($group)->exec();
  1297. $this->dao->replace($privTable)->set('module')->eq('project')->set('method')->eq('tree')->set('`group`')->eq($group)->exec();
  1298. }
  1299. /* Change manageContacts priv. */
  1300. $groups = $this->dao->select('`group`')->from($privTable)->where('`module`')->eq('user')->andWhere('`method`')->eq('manageContacts')->fetchPairs('group', 'group');
  1301. foreach($groups as $group)
  1302. {
  1303. $this->dao->replace($privTable)->set('module')->eq('my')->set('method')->eq('manageContacts')->set('`group`')->eq($group)->exec();
  1304. }
  1305. /* Change deleteContacts priv. */
  1306. $groups = $this->dao->select('`group`')->from($privTable)->where('`module`')->eq('user')->andWhere('`method`')->eq('deleteContacts')->fetchPairs('group', 'group');
  1307. foreach($groups as $group)
  1308. {
  1309. $this->dao->replace($privTable)->set('module')->eq('my')->set('method')->eq('deleteContacts')->set('`group`')->eq($group)->exec();
  1310. }
  1311. /* Change batchChangeModule priv. */
  1312. $oldPriv = $this->dao->select('*')->from($privTable)
  1313. ->where("(`module`='story' and `method`='edit')")
  1314. ->orWhere("(`module`='task' and `method`='edit')")
  1315. ->orWhere("(`module`='bug' and `method`='edit')")
  1316. ->orWhere("(`module`='testcase' and `method`='edit')")
  1317. ->fetchAll();
  1318. foreach($oldPriv as $item)
  1319. {
  1320. $this->dao->replace($privTable)->set('module')->eq($item->module)->set('method')->eq('batchChangeModule')->set('`group`')->eq($item->group)->exec();
  1321. }
  1322. return true;
  1323. }
  1324. /**
  1325. * 为系统管理员添加 or 页面的权限。
  1326. * Add or view priv for adminer.
  1327. *
  1328. * @param string $openVersion
  1329. * @access public
  1330. * @return bool
  1331. */
  1332. public function addORPriv($openVersion = '')
  1333. {
  1334. /* Get admin users. */
  1335. $admins = $this->dao->select('admins')->from(TABLE_COMPANY)->where('deleted')->eq(0)->fetchPairs();
  1336. $admins = explode(',', implode(',', $admins));
  1337. $admins = array_unique($admins);
  1338. $users = $this->dao->select('account,visions')->from(TABLE_USER)->where('account')->in($admins)->fetchPairs();
  1339. /* Add or to visions. */
  1340. foreach($users as $account => $visions)
  1341. {
  1342. if(strpos($visions, 'or') === false)
  1343. {
  1344. $visions = 'or,' . $visions;
  1345. $this->dao->update(TABLE_USER)->set('visions')->eq($visions)->where('account')->eq($account)->exec();
  1346. }
  1347. }
  1348. include('priv.php');
  1349. /* Add or groups. */
  1350. $orGroup = $this->dao->select('name')->from(TABLE_GROUP)->where('vision')->eq('or')->fetchAll();
  1351. if(empty($orGroup))
  1352. {
  1353. $group = new stdclass();
  1354. $group->vision = 'or';
  1355. foreach($orData as $role => $name)
  1356. {
  1357. $group->name = $name;
  1358. $group->role = $role;
  1359. $group->desc = $name;
  1360. $this->dao->insert(TABLE_GROUP)->data($group)->exec();
  1361. if(dao::isError()) continue;
  1362. $groupID = (string)$this->dao->lastInsertID();
  1363. $sql = 'REPLACE INTO' . TABLE_GROUPPRIV . '(`group`, `module`, `method`) VALUES ' . str_replace('GROUPID', $groupID, ${$role . 'Priv'});
  1364. $this->dao->exec($sql);
  1365. }
  1366. }
  1367. return true;
  1368. }
  1369. /**
  1370. * Adjust config section and key.
  1371. *
  1372. * @access public
  1373. * @return bool
  1374. */
  1375. public function adjustConfigSectionAndKey()
  1376. {
  1377. $this->dao->update(TABLE_CONFIG)->set('`key`')->eq('productProject')->where('`key`')->eq('productproject')->andWhere('module')->eq('custom')->exec();
  1378. $this->dao->update(TABLE_CONFIG)->set('section')->eq('bugBrowse')->where('section')->eq('bugbrowse')->andWhere('module')->eq('datatable')->exec();
  1379. $this->dao->update(TABLE_CONFIG)->set('section')->eq('productBrowse')->where('section')->eq('productbrowse')->andWhere('module')->eq('datatable')->exec();
  1380. $this->dao->update(TABLE_CONFIG)->set('section')->eq('projectTask')->where('section')->eq('projecttask')->andWhere('module')->eq('datatable')->exec();
  1381. $this->dao->update(TABLE_CONFIG)->set('section')->eq('testcaseBrowse')->where('section')->eq('testcasebrowse')->andWhere('module')->eq('datatable')->exec();
  1382. $this->dao->update(TABLE_CONFIG)->set('section')->eq('testtaskCases')->where('section')->eq('testtaskcases')->andWhere('module')->eq('datatable')->exec();
  1383. return true;
  1384. }
  1385. /**
  1386. * To lower table.
  1387. *
  1388. * @param string $build
  1389. * @access public
  1390. * @return bool
  1391. */
  1392. public function toLowerTable($build = 'basic')
  1393. {
  1394. $results = $this->dao->query("show Variables like '%table_names'")->fetchAll();
  1395. $hasLowered = false;
  1396. foreach($results as $result)
  1397. {
  1398. if(strtolower($result->Variable_name) == 'lower_case_table_names' and $result->Value == 1)
  1399. {
  1400. $hasLowered = true;
  1401. break;
  1402. }
  1403. }
  1404. if($hasLowered) return true;
  1405. if($build == 'basic') $tables2Rename = $this->config->upgrade->lowerTables;
  1406. if($build == 'pro') $tables2Rename = $this->config->upgrade->lowerProTables;
  1407. if(!isset($tables2Rename)) return false;
  1408. $tablesExists = $this->dao->query('SHOW TABLES')->fetchAll();
  1409. foreach($tablesExists as $key => $table) $tablesExists[$key] = current((array)$table);
  1410. $tablesExists = array_flip($tablesExists);
  1411. foreach($tables2Rename as $oldTable => $newTable)
  1412. {
  1413. if(!isset($tablesExists[$oldTable])) continue;
  1414. $upgradebak = $newTable . '_othertablebak';
  1415. if(isset($tablesExists[$upgradebak]))
  1416. {
  1417. $this->dao->query("DROP TABLE `$upgradebak`");
  1418. }
  1419. if(isset($tablesExists[$newTable]))
  1420. {
  1421. $this->dao->query("RENAME TABLE `$newTable` TO `$upgradebak`");
  1422. }
  1423. $tempTable = $oldTable . '_zentaotmp';
  1424. $this->dao->query("RENAME TABLE `$oldTable` TO `$tempTable`");
  1425. $this->dao->query("RENAME TABLE `$tempTable` TO `$newTable`");
  1426. }
  1427. return true;
  1428. }
  1429. /**
  1430. * Process finishedBy and finishedDate of task.
  1431. *
  1432. * @access public
  1433. * @return bool
  1434. */
  1435. public function processTaskFinish()
  1436. {
  1437. $this->dao->update(TABLE_TASK)
  1438. ->set('finishedBy = lastEditedBy')
  1439. ->set('finishedDate = lastEditedDate')
  1440. ->where('status')->in('done,closed')
  1441. ->andWhere('finishedBy')->eq('')
  1442. ->exec();
  1443. return true;
  1444. }
  1445. /**
  1446. * Process bugs which import to project tasks but canceled.
  1447. *
  1448. * @access public
  1449. * @return void
  1450. */
  1451. public function processImport2TaskBugs()
  1452. {
  1453. $bugs = $this->dao->select('t1.id')->from(TABLE_BUG)->alias('t1')
  1454. ->leftJoin(TABLE_TASK)->alias('t2')->on('t1.toTask = t2.id')
  1455. ->where('t1.toTask')->ne(0)
  1456. ->andWhere('t1.status')->eq('active')
  1457. ->andWhere('t2.canceledBy')->ne('')
  1458. ->fetchPairs();
  1459. $this->dao->update(TABLE_BUG)->set('toTask')->eq(0)->where('id')->in($bugs)->exec();
  1460. return true;
  1461. }
  1462. /**
  1463. * Delete company field for the table of zt_config and zt_groupPriv.
  1464. *
  1465. * @access public
  1466. * @return void
  1467. */
  1468. public function deleteCompany()
  1469. {
  1470. $privTable = $this->config->db->prefix . 'groupPriv';
  1471. /* Delete priv that is not in this company. Prevent conflict when delete company's field.*/
  1472. $this->dao->delete()->from($privTable)->where('company')->ne($this->app->company->id)->exec();
  1473. $this->dao->exec("ALTER TABLE " . $privTable . " DROP `company`;");
  1474. /* Delete config that don't conform to the rules. Prevent conflict when delete company's field.*/
  1475. $rows = $this->dao->select('*')->from(TABLE_CONFIG)->orderBy('id desc')->fetchAll('id', false);
  1476. $items = array();
  1477. $delList = array();
  1478. foreach($rows as $config)
  1479. {
  1480. if(isset($items[$config->owner][$config->module][$config->section][$config->key]))
  1481. {
  1482. $delList[] = $config->id;
  1483. continue;
  1484. }
  1485. $items[$config->owner][$config->module][$config->section][$config->key] = $config->id;
  1486. }
  1487. if($delList) $this->dao->delete()->from(TABLE_CONFIG)->where('id')->in($delList)->exec();
  1488. $this->dao->exec("ALTER TABLE " . TABLE_CONFIG . " DROP `company`;");
  1489. return true;
  1490. }
  1491. /**
  1492. * Merge the goal and desc of project.
  1493. *
  1494. * @access public
  1495. * @return void
  1496. */
  1497. public function mergeProjectGoalAndDesc()
  1498. {
  1499. $projects = $this->dao->select('*')->from(TABLE_PROJECT)->fetchAll('id', false);
  1500. foreach($projects as $id => $project)
  1501. {
  1502. if(!isset($project->goal)) continue;
  1503. $this->dao->update(TABLE_PROJECT)
  1504. ->set('`desc`')->eq($project->desc . '<br />' . $project->goal)
  1505. ->where('id')->eq($id)
  1506. ->exec();
  1507. }
  1508. return true;
  1509. }
  1510. /**
  1511. * Fix OS info of bugs.
  1512. *
  1513. * @access public
  1514. * @return void
  1515. */
  1516. public function fixBugOSInfo()
  1517. {
  1518. $this->dao->update(TABLE_BUG)->set('os')->eq('android')->where('os')->eq('andriod')->exec();
  1519. $this->dao->update(TABLE_BUG)->set('os')->eq('osx')->where('os')->eq('mac')->exec();
  1520. }
  1521. /**
  1522. * Fix finishedBy of task.
  1523. *
  1524. * @access public
  1525. * @return void
  1526. */
  1527. public function fixTaskFinishedBy()
  1528. {
  1529. $tasks = $this->dao->select('t1.id,t2.actor,t2.date')->from(TABLE_TASK)->alias('t1')
  1530. ->leftJoin(TABLE_ACTION)->alias('t2')
  1531. ->on('t1.id = t2.objectID')
  1532. ->leftJoin(TABLE_HISTORY)->alias('t3')
  1533. ->on('t2.id = t3.action')
  1534. ->where('t3.new')->eq(0)
  1535. ->andWhere('t3.field')->eq('left')
  1536. ->andWhere('t2.objectType')->eq('task')
  1537. ->andWhere('t1.finishedBy')->eq('')
  1538. ->andWhere('t1.status')->in('done,closed')
  1539. ->andWhere('t1.deleted')->eq(0)
  1540. ->fetchAll('id');
  1541. foreach($tasks as $taskID => $task)
  1542. {
  1543. $this->dao->update(TABLE_TASK)
  1544. ->set('finishedBy')->eq($task->actor)
  1545. ->set('finishedDate')->eq($task->date)
  1546. ->where('id')->eq($taskID)
  1547. ->exec();
  1548. }
  1549. }
  1550. /**
  1551. * Touch index.html for upload when has not it.
  1552. *
  1553. * @access public
  1554. * @return bool
  1555. */
  1556. public function fixDataIndex()
  1557. {
  1558. $savePath = $this->loadModel('file')->savePath;
  1559. foreach(glob($savePath . '*') as $childDir)
  1560. {
  1561. if(is_dir($childDir) and !is_file($childDir . '/index.html')) @touch($childDir . '/index.html');
  1562. }
  1563. return true;
  1564. }
  1565. /**
  1566. * Init order.
  1567. *
  1568. * @access public
  1569. * @return bool
  1570. */
  1571. public function initOrder()
  1572. {
  1573. $dataList = $this->dao->select('id')->from(TABLE_PRODUCT)->orderBy('code_desc')->fetchAll();
  1574. $i = 1;
  1575. foreach($dataList as $data)
  1576. {
  1577. $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($i++)->where('id')->eq($data->id)->exec();
  1578. }
  1579. $dataList = $this->dao->select('id')->from(TABLE_PROJECT)->orderBy('code_desc')->fetchAll();
  1580. $i = 1;
  1581. foreach($dataList as $data)
  1582. {
  1583. $this->dao->update(TABLE_PROJECT)->set('`order`')->eq($i++)->where('id')->eq($data->id)->exec();
  1584. }
  1585. return true;
  1586. }
  1587. /**
  1588. * Adjust order for 7.3
  1589. *
  1590. * @access public
  1591. * @return void
  1592. */
  1593. public function adjustOrder7_3()
  1594. {
  1595. $this->loadModel('product')->fixOrder();
  1596. $this->loadModel('execution')->fixOrder();
  1597. return true;
  1598. }
  1599. /**
  1600. * Adjust priv for 7.4.beta
  1601. *
  1602. * @access public
  1603. * @return void
  1604. */
  1605. public function adjustPriv7_4_beta()
  1606. {
  1607. $groups = $this->dao->select('id')->from(TABLE_GROUP)->where('name')->ne('guest')->fetchPairs('id', 'id');
  1608. foreach($groups as $groupID)
  1609. {
  1610. $groupPriv = new stdclass();
  1611. $groupPriv->group = $groupID;
  1612. $groupPriv->module = 'my';
  1613. $groupPriv->method = 'unbind';
  1614. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  1615. }
  1616. return true;
  1617. }
  1618. /**
  1619. * Adjust doc module.
  1620. *
  1621. * @access public
  1622. * @return bool
  1623. */
  1624. public function adjustDocModule()
  1625. {
  1626. $this->app->loadLang('doc');
  1627. $productDocModules = $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('productdoc')->orderBy('grade,id')->fetchAll('id');
  1628. $allProductIdList = $this->dao->select('id,name,acl,whitelist,createdBy')->from(TABLE_PRODUCT)->where('deleted')->eq('0')->fetchAll('id');
  1629. foreach($allProductIdList as $productID => $product)
  1630. {
  1631. $this->dao->delete()->from(TABLE_DOCLIB)->where('product')->eq($productID)->exec();
  1632. $lib = new stdclass();
  1633. $lib->product = $productID;
  1634. $lib->name = $this->lang->doclib->main['product'];
  1635. $lib->main = 1;
  1636. $lib->acl = $product->acl == 'open' ? 'open' : 'custom';
  1637. $lib->users = $product->createdBy;
  1638. if($product->acl == 'custom') $lib->groups = $product->whitelist;
  1639. $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
  1640. $libID = $this->dao->lastInsertID();
  1641. $relation = array();
  1642. foreach($productDocModules as $moduleID => $module)
  1643. {
  1644. unset($module->id);
  1645. $module->root = $libID;
  1646. $module->type = 'doc';
  1647. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  1648. $newModuleID = $this->dao->lastInsertID();
  1649. $relation[$moduleID] = $newModuleID;
  1650. $newPaths = array();
  1651. foreach(explode(',', trim($module->path, ',')) as $path)
  1652. {
  1653. if(isset($relation[$path])) $newPaths[] = $relation[$path];
  1654. }
  1655. $newPaths = implode(',', $newPaths);
  1656. $this->dao->update(TABLE_MODULE)->set('path')->eq($newPaths)->set('parent')->eq($relation[$module->parent])->where('id')->eq($newModuleID)->exec();
  1657. $this->dao->update(TABLE_DOC)->set('module')->eq($newModuleID)->where('product')->eq($productID)->andWhere('module')->eq($moduleID)->andWhere('lib')->eq('product')->exec();
  1658. }
  1659. $this->dao->update(TABLE_DOC)->set('lib')->eq($libID)->where('product')->eq($productID)->exec();
  1660. }
  1661. $this->dao->delete()->from(TABLE_MODULE)->where('id')->in(array_keys($productDocModules))->exec();
  1662. $projectDocModules = $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('projectdoc')->orderBy('grade,id')->fetchAll('id');
  1663. $allProjectIdList = $this->dao->select('id,name,acl,whitelist')->from(TABLE_PROJECT)->where('deleted')->eq('0')->fetchAll('id');
  1664. foreach($allProjectIdList as $projectID => $project)
  1665. {
  1666. $this->dao->delete()->from(TABLE_DOCLIB)->where('project')->eq($projectID)->exec();
  1667. $lib = new stdclass();
  1668. $lib->project = $projectID;
  1669. $lib->name = $this->lang->doclib->main['project'];
  1670. $lib->main = 1;
  1671. $lib->acl = $project->acl == 'open' ? 'open' : 'custom';
  1672. $teams = $this->dao->select('project, account')->from(TABLE_TEAM)->where('project')->eq($projectID)->fetchPairs('account', 'account');
  1673. $lib->users = implode(',', $teams);
  1674. if($project->acl == 'custom') $lib->groups = $project->whitelist;
  1675. $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
  1676. $libID = $this->dao->lastInsertID();
  1677. $docLibs = $this->dao->select('id,users')->from(TABLE_DOCLIB)->alias('t1')
  1678. ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.product=t2.product')
  1679. ->where('t2.project')->eq($projectID)
  1680. ->andWhere('t1.acl')->eq('custom')
  1681. ->fetchAll('id');
  1682. foreach($docLibs as $lib)
  1683. {
  1684. $docUsers = $teams + explode(',', $lib->users);
  1685. $docUsers = array_unique($docUsers);
  1686. $this->dao->update(TABLE_DOCLIB)->set('users')->eq(implode(',', $docUsers))->where('id')->eq($lib->id)->exec();
  1687. }
  1688. $relation = array();
  1689. foreach($projectDocModules as $moduleID => $module)
  1690. {
  1691. unset($module->id);
  1692. $module->root = $libID;
  1693. $module->type = 'doc';
  1694. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  1695. $newModuleID = $this->dao->lastInsertID();
  1696. $relation[$moduleID] = $newModuleID;
  1697. $newPaths = array();
  1698. foreach(explode(',', trim($module->path, ',')) as $path)
  1699. {
  1700. if(isset($relation[$path])) $newPaths[] = $relation[$path];
  1701. }
  1702. $newPaths = implode(',', $newPaths);
  1703. $newPaths = ",$newPaths,";
  1704. $this->dao->update(TABLE_MODULE)->set('path')->eq($newPaths)->where('id')->eq($newModuleID)->exec();
  1705. $this->dao->update(TABLE_DOC)->set('module')->eq($newModuleID)->where('project')->eq($projectID)->andWhere('module')->eq($moduleID)->exec();
  1706. }
  1707. $this->dao->update(TABLE_DOC)->set('lib')->eq($libID)->where('project')->eq($projectID)->exec();
  1708. }
  1709. $this->dao->delete()->from(TABLE_MODULE)->where('id')->in(array_keys($projectDocModules))->exec();
  1710. return true;
  1711. }
  1712. /**
  1713. * 更新在编辑器中的文件 objectID。
  1714. * Update file objectID in editor.
  1715. *
  1716. * @param string $type
  1717. * @param int $lastID
  1718. * @access public
  1719. * @return array
  1720. */
  1721. public function updateFileObjectID($type = '', $lastID = 0)
  1722. {
  1723. $limit = 100;
  1724. if(empty($type)) $type = 'comment';
  1725. if($type == 'comment')
  1726. {
  1727. $actions = $this->dao->select('id,objectType,objectID,comment')->from(TABLE_ACTION)->where('comment')->like('%data/upload/%')->andWhere('id')->gt($lastID)->orderBy('id')->limit($limit)->fetchAll('id');
  1728. foreach($actions as $action)
  1729. {
  1730. $files = array();
  1731. preg_match_all('/"data\/upload\/.*1\/([0-9]{6}\/[^"]+)"/', $action->comment, $output);
  1732. foreach($output[1] as $path) $files[$path] = $path;
  1733. $this->dao->update(TABLE_FILE)->set('objectType')->eq($action->objectType)->set('objectID')->eq($action->objectID)->set('extra')->eq('editor')->where('pathname')->in($files)->exec();
  1734. }
  1735. $result['type'] = 'comment';
  1736. $result['count'] = count($actions);
  1737. $result['lastID'] = count($actions) < $limit ? 0 : $action->id;
  1738. return $result;
  1739. }
  1740. list($objectCount, $lastID) = $this->upgradeTao->updateFileObjects($type, $lastID, $limit);
  1741. if($objectCount < $limit)
  1742. {
  1743. $editorKeys = array_keys($this->config->upgrade->editors);
  1744. foreach($editorKeys as $i => $objectType)
  1745. {
  1746. if($type == $objectType)
  1747. {
  1748. $nextType = isset($editorKeys[$i + 1]) ? $editorKeys[$i + 1] : '';
  1749. break;
  1750. }
  1751. }
  1752. $result['type'] = empty($nextType) ? 'finish' : $nextType;
  1753. $result['count'] = $objectCount;
  1754. $result['lastID'] = 0;
  1755. }
  1756. else
  1757. {
  1758. $result['type'] = $type;
  1759. $result['count'] = $objectCount;
  1760. $result['lastID'] = $lastID;
  1761. }
  1762. return $result;
  1763. }
  1764. /**
  1765. * Move doc content to table zt_doccontent.
  1766. *
  1767. * @access public
  1768. * @return bool
  1769. */
  1770. public function moveDocContent()
  1771. {
  1772. $fieldDesc = $this->dao->descTable(TABLE_DOC);
  1773. $fieldPairs = array_column($fieldDesc, 'field', 'field');
  1774. if(!isset($fieldPairs['content']) || !isset($fieldPairs['digest']) || !isset($fieldPairs['url'])) return true;
  1775. $this->dao->exec('TRUNCATE TABLE ' . TABLE_DOCCONTENT);
  1776. $stmt = $this->dao->select('id,title,digest,content,url')->from(TABLE_DOC)->query();
  1777. $fileGroups = $this->dao->select('id,objectID')->from(TABLE_FILE)->where('objectType')->eq('doc')->fetchGroup('objectID', 'id');
  1778. while($doc = $stmt->fetch())
  1779. {
  1780. $url = empty($doc->url) ? '' : urldecode($doc->url);
  1781. $docContent = new stdclass();
  1782. $docContent->doc = $doc->id;
  1783. $docContent->title = $doc->title;
  1784. $docContent->digest = $doc->digest;
  1785. $docContent->content = $doc->content;
  1786. $docContent->content .= empty($url) ? '' : $url;
  1787. $docContent->version = 1;
  1788. $docContent->type = 'html';
  1789. if(isset($fileGroups[$doc->id])) $docContent->files = implode(',', array_keys($fileGroups[$doc->id]));
  1790. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  1791. }
  1792. $this->dao->exec('ALTER TABLE ' . TABLE_DOC . ' DROP `digest`');
  1793. $this->dao->exec('ALTER TABLE ' . TABLE_DOC . ' DROP `content`');
  1794. $this->dao->exec('ALTER TABLE ' . TABLE_DOC . ' DROP `url`');
  1795. return true;
  1796. }
  1797. /**
  1798. * Adjust priv 8.3
  1799. *
  1800. * @access public
  1801. * @return bool
  1802. */
  1803. public function adjustPriv8_3()
  1804. {
  1805. $docPrivGroups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('doc')->andWhere('method')->eq('index')->fetchPairs('group', 'group');
  1806. foreach($docPrivGroups as $groupID)
  1807. {
  1808. $data = new stdclass();
  1809. $data->group = $groupID;
  1810. $data->module = 'doc';
  1811. $data->method = 'allLibs';
  1812. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1813. $data->method = 'showFiles';
  1814. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1815. $data->method = 'objectLibs';
  1816. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1817. }
  1818. return true;
  1819. }
  1820. /**
  1821. * Rename main lib.
  1822. *
  1823. * @access public
  1824. * @return bool
  1825. */
  1826. public function renameMainLib()
  1827. {
  1828. $this->app->loadLang('doc');
  1829. $this->dao->update(TABLE_DOCLIB)->set('name')->eq($this->lang->doclib->main['product'])->where('product')->gt(0)->andWhere('main')->eq(1)->exec();
  1830. $this->dao->update(TABLE_DOCLIB)->set('name')->eq($this->lang->doclib->main['project'])->where('project')->gt(0)->andWhere('main')->eq(1)->exec();
  1831. return true;
  1832. }
  1833. /**
  1834. * Adjust priv for 8.4.
  1835. *
  1836. * @access public
  1837. * @return bool
  1838. */
  1839. public function adjustPriv8_4()
  1840. {
  1841. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('branch')->andWhere('method')->eq('manage')->fetchPairs('group', 'group');
  1842. foreach($groups as $groupID)
  1843. {
  1844. $data = new stdclass();
  1845. $data->group = $groupID;
  1846. $data->module = 'branch';
  1847. $data->method = 'sort';
  1848. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1849. }
  1850. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('story')->andWhere('method')->eq('tasks')->fetchPairs('group', 'group');
  1851. foreach($groups as $groupID)
  1852. {
  1853. $data = new stdclass();
  1854. $data->group = $groupID;
  1855. $data->module = 'story';
  1856. $data->method = 'bugs';
  1857. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1858. $data->method = 'cases';
  1859. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1860. }
  1861. return true;
  1862. }
  1863. /**
  1864. * Adjust priv for 9.0
  1865. *
  1866. * @access public
  1867. * @return void
  1868. */
  1869. public function adjustPriv9_0()
  1870. {
  1871. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('testtask')->andWhere('method')->eq('results')->fetchPairs('group', 'group');
  1872. foreach($groups as $groupID)
  1873. {
  1874. $data = new stdclass();
  1875. $data->group = $groupID;
  1876. $data->module = 'testcase';
  1877. $data->method = 'bugs';
  1878. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1879. }
  1880. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('mail')->andWhere('method')->eq('delete')->fetchPairs('group', 'group');
  1881. foreach($groups as $groupID)
  1882. {
  1883. $data = new stdclass();
  1884. $data->group = $groupID;
  1885. $data->module = 'mail';
  1886. $data->method = 'resend';
  1887. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1888. }
  1889. return true;
  1890. }
  1891. /**
  1892. * Fix projectproduct data.
  1893. *
  1894. * @access public
  1895. * @return bool
  1896. */
  1897. public function fixProjectProductData()
  1898. {
  1899. $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('product')->eq(0)->exec();
  1900. return true;
  1901. }
  1902. /**
  1903. * Add bug deadline for custom fields.
  1904. *
  1905. * @access public
  1906. * @return bool
  1907. */
  1908. public function addBugDeadlineToCustomFields()
  1909. {
  1910. $createFieldsItems = $this->dao->select('id, value')->from(TABLE_CONFIG)
  1911. ->where('module')->eq('bug')
  1912. ->andWhere('section')->eq('custom')
  1913. ->andWhere('`key`')->eq('createFields')
  1914. ->fetchAll();
  1915. $batchEditFieldsItems = $this->dao->select('id, value')->from(TABLE_CONFIG)
  1916. ->where('module')->eq('bug')
  1917. ->andWhere('section')->eq('custom')
  1918. ->andWhere('`key`')->eq('batchEditFields')
  1919. ->fetchAll();
  1920. foreach($createFieldsItems as $createFieldsItem)
  1921. {
  1922. $value = empty($createFieldsItem->value) ? 'deadline' : $createFieldsItem->value . ",deadline";
  1923. $this->dao->update(TABLE_CONFIG)->set('value')->eq($value)->where('id')->eq($createFieldsItem->id)->exec();
  1924. }
  1925. foreach($batchEditFieldsItems as $batchEditFieldsItem)
  1926. {
  1927. $value = empty($batchEditFieldsItem->value) ? 'deadline' : $batchEditFieldsItem->value . ",deadline";
  1928. $this->dao->update(TABLE_CONFIG)->set('value')->eq($value)->where('id')->eq($batchEditFieldsItem->id)->exec();
  1929. }
  1930. return true;
  1931. }
  1932. /**
  1933. * Adjust priv for 9.0.1.
  1934. *
  1935. * @access public
  1936. * @return bool
  1937. */
  1938. public function adjustPriv9_0_1()
  1939. {
  1940. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('testcase')->andWhere('method')->eq('edit')->fetchPairs('group', 'group');
  1941. foreach($groups as $groupID)
  1942. {
  1943. $data = new stdclass();
  1944. $data->group = $groupID;
  1945. $data->module = 'testcase';
  1946. $newMethods = array('review', 'batchReview', 'batchChangeType', 'batchConfirmStoryChange');
  1947. foreach($newMethods as $method)
  1948. {
  1949. $data->method = $method;
  1950. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1951. }
  1952. $data->module = 'testsuite';
  1953. $newMethods = array('create', 'edit', 'delete', 'linkCase', 'unlinkCase', 'batchUnlinkCases');
  1954. foreach($newMethods as $method)
  1955. {
  1956. $data->method = $method;
  1957. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1958. }
  1959. }
  1960. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('testtask')->andWhere('method')->eq('start')->fetchPairs('group', 'group');
  1961. foreach($groups as $groupID)
  1962. {
  1963. $data = new stdclass();
  1964. $data->group = $groupID;
  1965. $data->module = 'testtask';
  1966. $newMethods = array('activate', 'block', 'report');
  1967. foreach($newMethods as $method)
  1968. {
  1969. $data->method = $method;
  1970. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1971. }
  1972. }
  1973. $groups = $this->dao->select('distinct `group`')->from(TABLE_GROUPPRIV)->fetchPairs('group', 'group');
  1974. foreach($groups as $groupID)
  1975. {
  1976. $data = new stdclass();
  1977. $data->group = $groupID;
  1978. $data->module = 'testsuite';
  1979. $newMethods = array('index', 'browse', 'view');
  1980. foreach($newMethods as $method)
  1981. {
  1982. $data->method = $method;
  1983. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  1984. }
  1985. }
  1986. return true;
  1987. }
  1988. /**
  1989. * Adjust priv for 9.2.
  1990. *
  1991. * @access public
  1992. * @return void
  1993. */
  1994. public function adjustPriv9_2()
  1995. {
  1996. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('testsuite')->andWhere('method')->eq('createCase')->fetchPairs('group', 'group');
  1997. foreach($groups as $groupID)
  1998. {
  1999. $data = new stdclass();
  2000. $data->group = $groupID;
  2001. $data->module = 'testsuite';
  2002. $newMethods = array('batchCreateCase', 'exportTemplate', 'import', 'showImport');
  2003. foreach($newMethods as $method)
  2004. {
  2005. $data->method = $method;
  2006. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  2007. }
  2008. }
  2009. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('product')->andWhere('method')->eq('index')->fetchPairs('group', 'group');
  2010. foreach($groups as $groupID)
  2011. {
  2012. $data = new stdclass();
  2013. $data->group = $groupID;
  2014. $data->module = 'product';
  2015. $data->method = 'build';
  2016. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  2017. }
  2018. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('custom')->andWhere('method')->eq('flow')->fetchPairs('group', 'group');
  2019. foreach($groups as $groupID)
  2020. {
  2021. $data = new stdclass();
  2022. $data->group = $groupID;
  2023. $data->module = 'custom';
  2024. $data->method = 'working';
  2025. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  2026. }
  2027. return true;
  2028. }
  2029. /**
  2030. * Adjust priv for 9.4.
  2031. *
  2032. * @access public
  2033. * @return bool
  2034. */
  2035. public function adjustPriv9_4()
  2036. {
  2037. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('bug')->andWhere('method')->eq('activate')->fetchPairs('group', 'group');
  2038. foreach($groups as $groupID)
  2039. {
  2040. $data = new stdclass();
  2041. $data->group = $groupID;
  2042. $data->module = 'bug';
  2043. $data->method = 'batchActivate';
  2044. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  2045. }
  2046. return true;
  2047. }
  2048. /**
  2049. * Adjust priv for 11.4.
  2050. *
  2051. * @access public
  2052. * @return bool
  2053. */
  2054. public function addPriv11_4()
  2055. {
  2056. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('story')->andWhere('method')->eq('edit')->fetchPairs('group', 'group');
  2057. foreach($groups as $groupID)
  2058. {
  2059. $data = new stdclass();
  2060. $data->group = $groupID;
  2061. $data->module = 'story';
  2062. $data->method = 'assignTo';
  2063. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  2064. }
  2065. return true;
  2066. }
  2067. /**
  2068. * Add Priv for 11.5
  2069. *
  2070. * @access public
  2071. * @return bool
  2072. */
  2073. public function addPriv11_5()
  2074. {
  2075. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('bug')->andWhere('method')->eq('setPublic')->fetchPairs('group', 'group');
  2076. foreach($groups as $groupID)
  2077. {
  2078. $data = new stdclass();
  2079. $data->group = $groupID;
  2080. $data->module = 'user';
  2081. $data->method = 'setPublicTemplate';
  2082. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  2083. }
  2084. return true;
  2085. }
  2086. /**
  2087. * Add unique key for stage.
  2088. *
  2089. * @access public
  2090. * @return bool
  2091. */
  2092. public function addUniqueKey4Stage()
  2093. {
  2094. $stmt = $this->dao->select('story,branch')->from(TABLE_STORYSTAGE)->orderBy('story,branch')->query();
  2095. $preStage = '';
  2096. while($stage = $stmt->fetch())
  2097. {
  2098. if($preStage == "{$stage->story}_{$stage->branch}") $this->dao->delete()->from(TABLE_STORYSTAGE)->where('story')->eq($stage->story)->andWhere('branch')->eq($stage->branch)->exec();
  2099. $preStage = "{$stage->story}_{$stage->branch}";
  2100. }
  2101. $this->dao->exec("ALTER TABLE " . TABLE_STORYSTAGE . " ADD UNIQUE `story_branch` (`story`, `branch`)");
  2102. return true;
  2103. }
  2104. /**
  2105. * 判断是否出现错误。
  2106. * Judge any error occurs.
  2107. *
  2108. * @access public
  2109. * @return bool
  2110. */
  2111. public function isError()
  2112. {
  2113. return !empty(static::$errors);
  2114. }
  2115. /**
  2116. * 获取升级时的错误。
  2117. * Get errors during the upgrading.
  2118. *
  2119. * @access public
  2120. * @return array
  2121. */
  2122. public function getError()
  2123. {
  2124. $errors = static::$errors;
  2125. static::$errors = array();
  2126. return $errors;
  2127. }
  2128. /**
  2129. * 获取一致性检查的日志文件。
  2130. * Get upgrade log file.
  2131. *
  2132. * @access public
  2133. * @return string
  2134. */
  2135. public function getConsistencyLogFile()
  2136. {
  2137. return $this->app->getTmpRoot() . 'log/consistency.' . date('Ymd') . '.log.php';
  2138. }
  2139. /**
  2140. * 获取升级日志文件。
  2141. * Get upgrade log file.
  2142. *
  2143. * @access public
  2144. * @return string
  2145. */
  2146. public function getLogFile()
  2147. {
  2148. return $this->app->getTmpRoot() . 'log/upgrade.' . date('Ymd') . '.log.php';
  2149. }
  2150. /**
  2151. * 检查流程。
  2152. * Check weither process or not.
  2153. *
  2154. * @access public
  2155. * @return array
  2156. */
  2157. public function checkProcess()
  2158. {
  2159. $fromVersion = $this->config->installedVersion;
  2160. $needProcess = array();
  2161. if(strpos($fromVersion, 'ipd') !== false) return $needProcess;
  2162. if(strpos($fromVersion, 'max') === false && strpos($fromVersion, 'biz') === false && (strpos($fromVersion, 'pro') === false ? version_compare($fromVersion, '8.3', '<') : version_compare($fromVersion, 'pro5.4', '<'))) $needProcess['updateFile'] = 'process';
  2163. if(strpos($fromVersion, 'max') === false && $this->config->systemMode == 'new')
  2164. {
  2165. if(strpos($fromVersion, 'pro') !== false)
  2166. {
  2167. if(version_compare($fromVersion, 'pro10.0', '<')) $needProcess['search'] = 'notice';
  2168. }
  2169. elseif(strpos($fromVersion, 'biz') !== false)
  2170. {
  2171. if(version_compare($fromVersion, 'biz5.0', '<')) $needProcess['search'] = 'notice';
  2172. }
  2173. elseif(version_compare($fromVersion, '15.0.rc1', '<'))
  2174. {
  2175. $needProcess['search'] = 'notice';
  2176. }
  2177. }
  2178. $openVersion = $this->getOpenVersion(str_replace('.', '_', $fromVersion));
  2179. if(version_compare($openVersion, '17_4', '<=')) $needProcess['changeEngine'] = 'notice';
  2180. return $needProcess;
  2181. }
  2182. /**
  2183. * Process customMenus for different working.
  2184. *
  2185. * @access public
  2186. * @return void
  2187. */
  2188. public function processCustomMenus()
  2189. {
  2190. $this->loadModel('setting')->setItem('system.common.global.flow', 'full');
  2191. $customMenus = $this->dao->select('*')->from(TABLE_CONFIG)->where('section')->eq('customMenu')->fetchAll();
  2192. foreach($customMenus as $customMenu)
  2193. {
  2194. $this->dao->update(TABLE_CONFIG)->set('`key`')->eq("full_{$customMenu->key}")->where('id')->eq($customMenu->id)->exec();
  2195. }
  2196. return !dao::isError();
  2197. }
  2198. /**
  2199. * Init project story order.
  2200. *
  2201. * @access public
  2202. * @return bool
  2203. */
  2204. public function initProjectStoryOrder()
  2205. {
  2206. $storyGroup = $this->dao->select('t1.*')->from(TABLE_PROJECTSTORY)->alias('t1')
  2207. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id')
  2208. ->orderBy('t2.pri_desc,t1.story_asc')
  2209. ->fetchGroup('project', 'story');
  2210. foreach($storyGroup as $projectID => $stories)
  2211. {
  2212. $order = 1;
  2213. foreach($stories as $storyID => $projectStory)
  2214. {
  2215. $this->dao->update(TABLE_PROJECTSTORY)->set('`order`')->eq($order)->where('project')->eq($projectID)->andWhere('story')->eq($storyID)->exec();
  2216. $order++;
  2217. }
  2218. }
  2219. return true;
  2220. }
  2221. /**
  2222. * Fix datatable cols config.
  2223. *
  2224. * @access public
  2225. * @return bool
  2226. */
  2227. public function fixDatatableColsConfig()
  2228. {
  2229. $config = $this->dao->select('*')->from(TABLE_CONFIG)
  2230. ->where('module')->eq('datatable')
  2231. ->andWhere('section')->eq('projectTask')
  2232. ->andWhere('`key`')->eq('cols')
  2233. ->fetchAll('id', false);
  2234. foreach($config as $datatableCols)
  2235. {
  2236. $cols = json_decode($datatableCols->value);
  2237. foreach($cols as $i => $col)
  2238. {
  2239. if($col->id == 'progess') $col->id = 'progress';
  2240. if($col->id == 'actions' and $col->width == 'auto') $col->width = '180px';
  2241. }
  2242. $this->dao->update(TABLE_CONFIG)->set('value')->eq(json_encode($cols))->where('id')->eq($datatableCols->id)->exec();
  2243. }
  2244. return true;
  2245. }
  2246. /**
  2247. * Add limited group.
  2248. *
  2249. * @access public
  2250. * @return bool
  2251. */
  2252. public function addLimitedGroup()
  2253. {
  2254. $limitedGroup = $this->dao->select('*')->from(TABLE_GROUP)->where('`role`')->eq('limited')->fetch();
  2255. if(empty($limitedGroup))
  2256. {
  2257. $group = new stdclass();
  2258. $group->name = 'limited';
  2259. $group->role = 'limited';
  2260. $group->desc = 'For limited user';
  2261. $this->dao->insert(TABLE_GROUP)->data($group)->exec();
  2262. $groupID = $this->dao->lastInsertID();
  2263. }
  2264. else
  2265. {
  2266. $groupID = $limitedGroup->id;
  2267. }
  2268. $limitedGroups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  2269. ->where('module')->eq('my')
  2270. ->andWhere('method')->eq('limited')
  2271. ->fetchPairs('group', 'group');
  2272. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('module')->eq('my')->andWhere('method')->eq('limited')->exec();
  2273. $limitedUsers = $this->dao->select('account')->from(TABLE_USERGROUP)->where('`group`')->in($limitedGroups)->fetchPairs('account', 'account');
  2274. foreach($limitedUsers as $limitedUser)
  2275. {
  2276. $this->dao->replace(TABLE_USERGROUP)->set('account')->eq($limitedUser)->set('`group`')->eq($groupID)->exec();
  2277. }
  2278. $groupPriv = new stdclass();
  2279. $groupPriv->group = $groupID;
  2280. $groupPriv->module = 'my';
  2281. $groupPriv->method = 'limited';
  2282. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2283. return true;
  2284. }
  2285. /**
  2286. * Change limited name.
  2287. *
  2288. * @access public
  2289. * @return bool
  2290. */
  2291. public function changeLimitedName()
  2292. {
  2293. $this->app->loadLang('install');
  2294. $this->dao->update(TABLE_GROUP)->set('name')->eq($this->lang->install->groupList['LIMITED']['name'])
  2295. ->set('desc')->eq($this->lang->install->groupList['LIMITED']['desc'])
  2296. ->where('role')->eq('limited')
  2297. ->exec();
  2298. return true;
  2299. }
  2300. /**
  2301. * Adjust Priv for 9.7
  2302. *
  2303. * @access public
  2304. * @return bool
  2305. */
  2306. public function adjustPriv9_7()
  2307. {
  2308. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('method')->eq('edit')->andWhere('module')->in('story,task,bug,testcase')->fetchPairs('group', 'group');
  2309. foreach($groups as $groupID)
  2310. {
  2311. $groupPriv = new stdclass();
  2312. $groupPriv->group = $groupID;
  2313. $groupPriv->module = 'action';
  2314. $groupPriv->method = 'comment';
  2315. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2316. }
  2317. return true;
  2318. }
  2319. /**
  2320. * Change story field width.
  2321. *
  2322. * @access public
  2323. * @return bool
  2324. */
  2325. public function changeStoryWidth()
  2326. {
  2327. $projectCustom = $this->dao->select('*')->from(TABLE_CONFIG)->where('section')->eq('projectTask')->andWhere('`key`')->in('cols,tablecols')->fetchAll('id', false);
  2328. foreach($projectCustom as $configID => $projectTask)
  2329. {
  2330. $fields = json_decode($projectTask->value);
  2331. foreach($fields as $i => $field)
  2332. {
  2333. if($field->id == 'story') $field->width = '40px';
  2334. }
  2335. $this->dao->update(TABLE_CONFIG)->set('value')->eq(json_encode($fields))->where('id')->eq($configID)->exec();
  2336. }
  2337. return true;
  2338. }
  2339. /**
  2340. * Change team field for 9.8.
  2341. *
  2342. * @access public
  2343. * @return bool
  2344. */
  2345. public function changeTeamFields()
  2346. {
  2347. $fields = $this->dao->descTable(TABLE_TEAM);
  2348. foreach($fields as $field)
  2349. {
  2350. if($field->field == 'root') return true;
  2351. }
  2352. $this->dao->exec("ALTER TABLE " . TABLE_TEAM . " CHANGE `project` `root` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0'");
  2353. $this->dao->exec("ALTER TABLE " . TABLE_TEAM . " ADD `type` ENUM('project', 'task') NOT NULL DEFAULT 'project' AFTER `root`");
  2354. $this->dao->exec("UPDATE " . TABLE_TEAM . " SET `root` = `task`, `type` = 'task' WHERE `task` > '0'");
  2355. $this->dao->exec("ALTER TABLE " . TABLE_TEAM . " DROP PRIMARY KEY");
  2356. $this->dao->exec("ALTER TABLE " . TABLE_TEAM . " DROP `task`");
  2357. return true;
  2358. }
  2359. /**
  2360. * Move data to notify.
  2361. *
  2362. * @access public
  2363. * @return bool
  2364. */
  2365. public function moveData2Notify()
  2366. {
  2367. $mailQueueTable = '`' . $this->config->db->prefix . 'mailqueue`';
  2368. $syncBeginDate = date('Y-m-d', time() - 15 * 24 * 3600);
  2369. $stmt = $this->dao->select('*')->from($mailQueueTable)->where('addedDate')->ge($syncBeginDate)->orderBy('id')->query();
  2370. while($mailQueue = $stmt->fetch())
  2371. {
  2372. $notify = new stdclass();
  2373. $notify->objectType = 'mail';
  2374. $notify->toList = $mailQueue->toList;
  2375. $notify->ccList = $mailQueue->ccList;
  2376. $notify->subject = $mailQueue->subject;
  2377. $notify->data = $mailQueue->body;
  2378. $notify->createdBy = $mailQueue->addedBy;
  2379. $notify->createdDate = $mailQueue->addedDate;
  2380. $notify->sendTime = $mailQueue->sendTime;
  2381. $notify->status = $mailQueue->status;
  2382. $notify->failReason = $mailQueue->failReason;
  2383. $this->dao->insert(TABLE_NOTIFY)->data($notify)->exec();
  2384. }
  2385. $webhookDataTable = '`' . $this->config->db->prefix . 'webhookdatas`';
  2386. $stmt = $this->dao->select('*')->from($webhookDataTable)->orderBy('id')->limit($offset, $rows)->query();
  2387. while($webhookData = $stmt->fetch())
  2388. {
  2389. $notify = new stdclass();
  2390. $notify->objectType = 'webhook';
  2391. $notify->objectID = $webhookData->webhook;
  2392. $notify->action = $webhookData->action;
  2393. $notify->data = $webhookData->data;
  2394. $notify->createdBy = $webhookData->createdBy;
  2395. $notify->createdDate = $webhookData->createdDate;
  2396. $notify->status = $webhookData->status;
  2397. $this->dao->insert(TABLE_NOTIFY)->data($notify)->exec();
  2398. }
  2399. return true;
  2400. }
  2401. /**
  2402. * Adjust priv 9.8.
  2403. *
  2404. * @access public
  2405. * @return bool
  2406. */
  2407. public function adjustPriv9_8()
  2408. {
  2409. $groups = $this->dao->select('id')->from(TABLE_GROUP)->fetchPairs('id', 'id');
  2410. foreach($groups as $groupID)
  2411. {
  2412. $groupPriv = new stdclass();
  2413. $groupPriv->group = $groupID;
  2414. $groupPriv->module = 'todo';
  2415. $groupPriv->method = 'createcycle';
  2416. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2417. }
  2418. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('mail')->orWhere('module')->eq('webhook')->fetchPairs('group', 'group');
  2419. foreach($groups as $groupID)
  2420. {
  2421. $groupPriv = new stdclass();
  2422. $groupPriv->group = $groupID;
  2423. $groupPriv->module = 'message';
  2424. $groupPriv->method = 'index';
  2425. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2426. }
  2427. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('project')->andWhere('method')->eq('linkStory')->fetchPairs('group', 'group');
  2428. foreach($groups as $groupID)
  2429. {
  2430. $groupPriv = new stdclass();
  2431. $groupPriv->group = $groupID;
  2432. $groupPriv->module = 'project';
  2433. $groupPriv->method = 'importPlanStories';
  2434. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2435. }
  2436. return true;
  2437. }
  2438. /**
  2439. * Fix task finishedBy.
  2440. *
  2441. * @access public
  2442. * @return bool
  2443. */
  2444. public function fixTaskFinishedInfo()
  2445. {
  2446. $stmt = $this->dao->select('t1.id as historID,t2.objectType,t2.objectID,t2.actor')->from(TABLE_HISTORY)->alias('t1')
  2447. ->leftJoin(TABLE_ACTION)->alias('t2')->on('t1.action=t2.id')
  2448. ->where('t1.field')->eq('finishedBy')
  2449. ->andWhere('t2.objectType')->eq('task')
  2450. ->andWhere('t2.action')->eq('finished')
  2451. ->andWhere('t2.actor != t1.`new`')
  2452. ->query();
  2453. while($action = $stmt->fetch())
  2454. {
  2455. $this->dao->update(TABLE_HISTORY)->set('`new`')->eq($action->actor)->where('id')->eq($action->historID)->exec();
  2456. $this->dao->update(TABLE_TASK)->set('`finishedBy`')->eq($action->actor)->where('id')->eq($action->objectID)->exec();
  2457. }
  2458. return true;
  2459. }
  2460. /**
  2461. * Fix assignedTo for closed tasks, but assignedTo is not closed.
  2462. *
  2463. * @access public
  2464. * @return bool
  2465. */
  2466. public function fixTaskAssignedTo()
  2467. {
  2468. $minParent = $this->dao->select('parent')->from(TABLE_TASK)->where('parent')->ne(0)->orderBy('parent')->limit(1)->fetch('parent');
  2469. if(empty($minParent)) return true;
  2470. $needUpdateTasks = $this->dao->select('id,parent,closedBy')->from(TABLE_TASK)
  2471. ->where('status')->eq('closed')
  2472. ->andWhere('assignedTo')->ne('closed')
  2473. ->andWhere('id')->ge($minParent)
  2474. ->fetchAll('id');
  2475. if(empty($needUpdateTasks)) return true;
  2476. $needUpdateParentTasks = array();
  2477. $needUpdateChildTasks = array();
  2478. foreach($needUpdateTasks as $taskID => $task)
  2479. {
  2480. if(!$task->parent)
  2481. {
  2482. $needUpdateParentTasks[$taskID] = $task;
  2483. }
  2484. else
  2485. {
  2486. if(!isset($needUpdateChildTasks[$task->parent])) $needUpdateChildTasks[$task->parent] = array();
  2487. $needUpdateChildTasks[$task->parent][$taskID] = $task;
  2488. }
  2489. }
  2490. /* Update parent task.*/
  2491. $childTasks = $this->dao->select('id,parent,assignedDate,closedBy,closedDate,closedReason')->from(TABLE_TASK)->where('parent')->in(array_keys($needUpdateParentTasks))->fetchGroup('parent');
  2492. $lastChildTasks = array();
  2493. foreach($childTasks as $parentID => $tasks)
  2494. {
  2495. foreach($tasks as $task)
  2496. {
  2497. if(isset($lastChildTasks[$parentID]))
  2498. {
  2499. if($lastChildTasks[$parentID]->closedDate < $task->closedDate) $lastChildTasks[$parentID] = $task;
  2500. }
  2501. else
  2502. {
  2503. $lastChildTasks[$parentID] = $task;
  2504. }
  2505. }
  2506. }
  2507. foreach($needUpdateParentTasks as $parentTask)
  2508. {
  2509. $lastChild = isset($lastChildTasks[$parentTask->id]) ? $lastChildTasks[$parentTask->id] : '';
  2510. $stmt = $this->dao->update(TABLE_TASK)->set('assignedTo')->eq('closed');
  2511. if($lastChild) $stmt->set('assignedDate')->eq($lastChild->assignedDate);
  2512. $stmt->where('id')->eq($parentTask->id)->exec();
  2513. if(empty($parentTask->closedBy) && !empty($lastChild->closedBy))
  2514. {
  2515. $this->dao->update(TABLE_TASK)->set('closedBy')->eq($lastChild->closedBy)->set('closedDate')->eq($lastChild->closedDate)->set('closedReason')->eq($lastChild->closedReason)->where('id')->eq($parentTask->id)->exec();
  2516. }
  2517. }
  2518. /* Update children task.*/
  2519. $parentTasks = $this->dao->select('id,assignedDate,closedBy,closedDate,closedReason')->from(TABLE_TASK)
  2520. ->where('parent')->eq(0)
  2521. ->andWhere('id')->in(array_keys($needUpdateChildTasks))
  2522. ->fetchAll('id');
  2523. foreach($needUpdateChildTasks as $parentID => $childTasks)
  2524. {
  2525. $parent = isset($parentTasks[$parentID]) ? $parentTasks[$parentID] : '';
  2526. foreach($childTasks as $childTask)
  2527. {
  2528. $stmt = $this->dao->update(TABLE_TASK)->set('assignedTo')->eq('closed');
  2529. if(!empty($parent)) $stmt->set('assignedDate')->eq($parent->assignedDate);
  2530. $stmt->where('id')->eq($childTask->id)->exec();
  2531. if(empty($childTask->closedBy) && !empty($parent->closedBy))
  2532. {
  2533. $this->dao->update(TABLE_TASK)->set('closedBy')->eq($parent->closedBy)->set('closedDate')->eq($parent->closedDate)->set('closedReason')->eq($parent->closedReason)->where('id')->eq($childTask->id)->exec();
  2534. }
  2535. }
  2536. }
  2537. return dao::isError();
  2538. }
  2539. /**
  2540. * Fix project closedBy and closedDate.
  2541. *
  2542. * @access public
  2543. * @return bool
  2544. */
  2545. public function fixProjectClosedInfo()
  2546. {
  2547. $stmt = $this->dao->select('t1.id as historID, t2.id, t2.objectType,t2.objectID,t2.actor,t2.date')->from(TABLE_HISTORY)->alias('t1')
  2548. ->leftJoin(TABLE_ACTION)->alias('t2')->on('t1.action=t2.id')
  2549. ->where('t1.field')->eq('status')
  2550. ->andWhere('t2.objectType')->eq('project')
  2551. ->andWhere('t2.action')->eq('closed')
  2552. ->query();
  2553. while($action = $stmt->fetch())
  2554. {
  2555. $this->dao->insert(TABLE_HISTORY)->set('`new`')->eq($action->actor)->set('`field`')->eq('closedBy')->set('`action`')->eq($action->id)->exec();
  2556. $this->dao->insert(TABLE_HISTORY)->set('`new`')->eq($action->date)->set('`old`')->eq('0000-00-00 00:00:00')->set('`field`')->eq('closedDate')->set('`action`')->eq($action->id)->exec();
  2557. $this->dao->update(TABLE_HISTORY)->set('`new`')->eq('closed')->where('`action`')->eq($action->id)->andWhere('field')->eq('status')->exec();
  2558. $this->dao->update(TABLE_PROJECT)
  2559. ->set('`status`')->eq('closed')
  2560. ->set('`closedBy`')->eq($action->actor)
  2561. ->set('`closedDate`')->eq($action->date)
  2562. ->where('id')->eq($action->objectID)
  2563. ->andWhere('status')->eq('done')
  2564. ->exec();
  2565. }
  2566. return !dao::isError();
  2567. }
  2568. /**
  2569. * Set the value of deleted product line to 0.
  2570. *
  2571. * @access public
  2572. * @return bool
  2573. */
  2574. public function resetProductLine()
  2575. {
  2576. $deletedLines = $this->dao->select('id')->from(TABLE_MODULE)->where('type')->eq('line')->andWhere('deleted')->eq('1')->fetchPairs('id', 'id');
  2577. $this->dao->update(TABLE_PRODUCT)->set('line')->eq(0)->where('line')->in($deletedLines)->exec();
  2578. return !dao::isError();
  2579. }
  2580. /**
  2581. * Add unique key to team table.
  2582. *
  2583. * @access public
  2584. * @return bool
  2585. */
  2586. public function addUniqueKeyToTeam()
  2587. {
  2588. $members = $this->dao->select('root, type, account')->from(TABLE_TEAM)->groupBy('root, type, account')->having('count(1)')->gt(1)->fetchAll();
  2589. foreach($members as $member)
  2590. {
  2591. $maxID = $this->dao->select('MAX(id) id')
  2592. ->from(TABLE_TEAM)
  2593. ->where('root')->eq($member->root)
  2594. ->andWhere('`type`')->eq($member->type)
  2595. ->andWhere('account')->eq($member->account)
  2596. ->fetch('id');
  2597. $this->dao->delete()->from(TABLE_TEAM)
  2598. ->where('root')->eq($member->root)
  2599. ->andWhere('`type`')->eq($member->type)
  2600. ->andWhere('account')->eq($member->account)
  2601. ->andWhere('id')->ne($maxID)
  2602. ->exec();
  2603. }
  2604. $this->dao->exec("ALTER TABLE " . TABLE_TEAM . " ADD UNIQUE `team` (`root`, `type`, `account`)");
  2605. return !dao::isError();
  2606. }
  2607. /**
  2608. * Adjust priv for 10_0_alpha.
  2609. *
  2610. * @access public
  2611. * @return bool
  2612. */
  2613. public function adjustPriv10_0_alpha()
  2614. {
  2615. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('my')->andWhere('method')->eq('todo')->fetchPairs('group', 'group');
  2616. foreach($groups as $groupID)
  2617. {
  2618. $groupPriv = new stdclass();
  2619. $groupPriv->group = $groupID;
  2620. $groupPriv->module = 'my';
  2621. $groupPriv->method = 'calendar';
  2622. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2623. }
  2624. return true;
  2625. }
  2626. /**
  2627. * Fix project statistic block.
  2628. *
  2629. * @access public
  2630. * @return void
  2631. */
  2632. public function fixProjectStatisticBlock()
  2633. {
  2634. $block = $this->dao->select('*')->from(TABLE_BLOCK)->where('module')->eq('my')->andWhere('source')->eq('project')->andWhere('block')->eq('statistic')->fetch();
  2635. if($block)
  2636. {
  2637. $blockParams = json_decode($block->params);
  2638. if($blockParams->type == 'noclosed')
  2639. {
  2640. $blockParams->type = 'undone';
  2641. $this->dao->update(TABLE_BLOCK)->set('params')->eq(helper::jsonEncode($blockParams))->where('id')->eq($block->id)->exec();
  2642. return !dao::isError();
  2643. }
  2644. }
  2645. return true;
  2646. }
  2647. /**
  2648. * Fix story spec title.
  2649. *
  2650. * @access public
  2651. * @return bool
  2652. */
  2653. public function fixStorySpecTitle()
  2654. {
  2655. $stories = $this->dao->select('t1.id, t1.title')->from(TABLE_STORY)->alias('t1')
  2656. ->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story && t1.title != t2.title && t1.version = t2.version')
  2657. ->where('t2.version')->eq(1)
  2658. ->fetchPairs('id', 'title');
  2659. foreach($stories as $story => $title)
  2660. {
  2661. $this->dao->update(TABLE_STORYSPEC)->set('title')->eq($title)->where('story')->eq($story)->andWhere('version')->eq(1)->exec();
  2662. }
  2663. return !dao::isError();
  2664. }
  2665. /**
  2666. * Remove unlink privilege for story, bug and testcase module.
  2667. *
  2668. * @access public
  2669. * @return bool
  2670. */
  2671. public function removeUnlinkPriv()
  2672. {
  2673. $this->dao->delete()->from(TABLE_GROUPPRIV)
  2674. ->where('((module')->eq('story')
  2675. ->andWhere('method')->eq('unlinkStory')
  2676. ->markRight(1)
  2677. ->orWhere('(module')->eq('bug')
  2678. ->andWhere('method')->eq('unlinkBug')
  2679. ->markRight(1)
  2680. ->orWhere('(module')->eq('testcase')
  2681. ->andWhere('method')->eq('unlinkCase')
  2682. ->markRight(2)
  2683. ->exec();
  2684. return !dao::isError();
  2685. }
  2686. /**
  2687. * Change task parent to -1 for 10.4 .
  2688. * @return bool
  2689. */
  2690. public function changeTaskParentValue()
  2691. {
  2692. $tasks = $this->dao->select('*')->from(TABLE_TASK)->where('parent')->gt(0)->fetchGroup('parent');
  2693. if($tasks)
  2694. {
  2695. $this->dao->update(TABLE_TASK)->set('parent')->eq('-1')->where('id')->in(array_keys($tasks))->exec();
  2696. }
  2697. return !dao::isError();
  2698. }
  2699. /**
  2700. * Remove custom menu.
  2701. *
  2702. * @access public
  2703. * @return bool
  2704. */
  2705. public function removeCustomMenu()
  2706. {
  2707. $customMenuMain = $this->dao->select('*')->from(TABLE_CONFIG)->where('module')->eq('common')->andWhere('section')->eq('customMenu')->andWhere("(`key`='full_main' OR `key`='onlyTask_main' OR `key`='onlyStory_main' OR `key`='onlyTest_main')")->fetchAll('id', false);
  2708. foreach($customMenuMain as $mainMenu)
  2709. {
  2710. $mainMenuValue = json_decode($mainMenu->value);
  2711. foreach($mainMenuValue as $menu)
  2712. {
  2713. /* If has admin in custom value, then delete old custom menu config. */
  2714. if($menu->name == 'admin')
  2715. {
  2716. $this->dao->delete()->from(TABLE_CONFIG)->where('module')->eq('common')
  2717. ->andWhere('section')->eq('customMenu')
  2718. ->andWhere('owner')->eq($mainMenu->owner)
  2719. ->exec();
  2720. }
  2721. }
  2722. }
  2723. $this->dao->delete()->from(TABLE_CONFIG)->where('module')->eq('common')->andWhere('section')->eq('customMenu')->andWhere('`key`')->eq('full_project')->exec();
  2724. $this->dao->delete()->from(TABLE_CONFIG)->where('module')->eq('common')->andWhere('section')->eq('customMenu')->andWhere('`key`')->eq('onlyTask_project')->exec();
  2725. return !dao::isError();
  2726. }
  2727. /**
  2728. * 初始化用户视图.
  2729. * Initialize user view.
  2730. *
  2731. * @access public
  2732. * @return bool
  2733. */
  2734. public function initUserView()
  2735. {
  2736. $this->dao->delete()->from(TABLE_USERVIEW)->exec();
  2737. return true;
  2738. }
  2739. /**
  2740. * Init Xuanxuan.
  2741. *
  2742. * @access public
  2743. * @return bool
  2744. */
  2745. public function initXuanxuan()
  2746. {
  2747. $this->loadModel('setting');
  2748. $keyID = $this->dao->select('id')->from(TABLE_CONFIG)->where('owner')->eq('system')->andWhere('module')->eq('xuanxuan')->andWhere('`key`')->eq('key')->fetch('id');
  2749. if($keyID)
  2750. {
  2751. $existKey = $this->dao->select('id')->from(TABLE_CONFIG)->where('owner')->eq('system')->andWhere('module')->eq('common')->andWhere('section')->eq('xuanxuan')->andWhere('`key`')->eq('key')->fetch('id');
  2752. if($existKey) $this->dao->delete()->from(TABLE_CONFIG)->where('id')->eq($existKey)->exec();
  2753. $this->dao->update(TABLE_CONFIG)->set('module')->eq('common')->set('section')->eq('xuanxuan')->where('id')->eq($keyID)->exec();
  2754. $this->setting->setItem('system.common.xuanxuan.turnon', '0');
  2755. $this->setting->setItem('system.common.xxserver.noticed', '1');
  2756. }
  2757. return true;
  2758. }
  2759. /**
  2760. * Process doc lib acl.
  2761. *
  2762. * @access public
  2763. * @return void
  2764. */
  2765. public function processDocLibAcl()
  2766. {
  2767. $this->dao->update(TABLE_DOCLIB)->set('acl')->eq('default')->where('type')->in('product,project')->andWhere('acl')->in('open,private')->exec();
  2768. return !dao::isError();
  2769. }
  2770. /**
  2771. * Update xuanxuan for 11_5.
  2772. *
  2773. * @access public
  2774. * @return bool
  2775. */
  2776. public function updateXX_11_5()
  2777. {
  2778. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('admin')->andWhere('method')->eq('xuanxuan')->fetchPairs('group', 'group');
  2779. foreach($groups as $groupID)
  2780. {
  2781. $groupPriv = new stdclass();
  2782. $groupPriv->group = $groupID;
  2783. $groupPriv->module = 'setting';
  2784. $groupPriv->method = 'xuanxuan';
  2785. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2786. }
  2787. try
  2788. {
  2789. $this->dao->update(TABLE_GROUPPRIV)->set('module')->eq('setting')->where('module')->eq('admin')->andWhere('method')->eq('downloadxxd')->exec();
  2790. }
  2791. catch(PDOException $e){}
  2792. return true;
  2793. }
  2794. /**
  2795. * Adjust webhook type list when webhook use bearychat.
  2796. *
  2797. * @access public
  2798. * @return void
  2799. */
  2800. public function adjustWebhookType()
  2801. {
  2802. $bearychatCount = $this->dao->select('COUNT(1) AS count')->from(TABLE_WEBHOOK)->where('type')->eq('bearychat')->fetch('count');
  2803. if($bearychatCount)
  2804. {
  2805. $item = new stdclass();
  2806. $item->module = 'webhook';
  2807. $item->section = 'typeList';
  2808. foreach(array('zh-cn', 'zh-tw', 'en', 'de') as $currentLang)
  2809. {
  2810. $langFile = $this->app->getModuleRoot() . 'webhook' . DS . 'lang' . DS . $currentLang . '.php';
  2811. if(!file_exists($langFile)) continue;
  2812. $lang = new stdclass();
  2813. $lang->webhook = new stdclass();
  2814. $lang->productCommon = $this->config->productCommonList[$currentLang][0];
  2815. $lang->executionCommon = $this->config->executionCommonList[$currentLang][0];
  2816. include $langFile;
  2817. if(!isset($lang->webhook->typeList)) continue;
  2818. $item->lang = $currentLang;
  2819. $item->key = 'bearychat';
  2820. $item->value = $this->config->upgrade->bearychat[$currentLang];
  2821. $this->dao->replace(TABLE_LANG)->data($item)->exec();
  2822. foreach($lang->webhook->typeList as $typeKey => $typeName)
  2823. {
  2824. if(empty($typeKey)) continue;
  2825. $item->key = $typeKey;
  2826. $item->value = $typeName;
  2827. $this->dao->replace(TABLE_LANG)->data($item)->exec();
  2828. }
  2829. }
  2830. }
  2831. return true;
  2832. }
  2833. /**
  2834. * Adjust priv for 11.6.2.
  2835. *
  2836. * @access public
  2837. * @return bool
  2838. */
  2839. public function adjustPriv11_6_2()
  2840. {
  2841. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('method')->eq('index')->andWhere('module')->in('message')->fetchPairs('group', 'group');
  2842. foreach($groups as $groupID)
  2843. {
  2844. $groupPriv = new stdclass();
  2845. $groupPriv->group = $groupID;
  2846. $groupPriv->module = 'message';
  2847. $groupPriv->method = 'browser';
  2848. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2849. }
  2850. return true;
  2851. }
  2852. /**
  2853. * Adjust priv for 11.6.4.
  2854. *
  2855. * @access public
  2856. * @return void
  2857. */
  2858. public function adjustPriv11_6_4()
  2859. {
  2860. $this->dao->update(TABLE_GROUPPRIV)->set('module')->eq('caselib')->set('method')->eq('browse')->where('module')->eq('testsuite')->andWhere('method')->eq('library')->exec();
  2861. $this->dao->update(TABLE_GROUPPRIV)->set('module')->eq('caselib')->set('method')->eq('create')->where('module')->eq('testsuite')->andWhere('method')->eq('createLib')->exec();
  2862. $this->dao->update(TABLE_GROUPPRIV)->set('module')->eq('caselib')->set('method')->eq('view')->where('module')->eq('testsuite')->andWhere('method')->eq('libView')->exec();
  2863. $this->dao->update(TABLE_GROUPPRIV)->set('module')->eq('caselib')->where('module')->eq('testsuite')->andWhere('method')->in('exportTemplate,import,showImport,batchCreateCase,createCase')->exec();
  2864. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('testsuite')->andWhere('method')->eq('edit')->fetchPairs('group', 'group');
  2865. foreach($groups as $groupID)
  2866. {
  2867. $groupPriv = new stdclass();
  2868. $groupPriv->group = $groupID;
  2869. $groupPriv->module = 'caselib';
  2870. $groupPriv->method = 'edit';
  2871. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2872. }
  2873. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('testsuite')->andWhere('method')->eq('delete')->fetchPairs('group', 'group');
  2874. foreach($groups as $groupID)
  2875. {
  2876. $groupPriv = new stdclass();
  2877. $groupPriv->group = $groupID;
  2878. $groupPriv->module = 'caselib';
  2879. $groupPriv->method = 'delete';
  2880. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2881. }
  2882. return true;
  2883. }
  2884. /**
  2885. * Fix group acl.
  2886. *
  2887. * @access public
  2888. * @return bool
  2889. */
  2890. public function fixGroupAcl()
  2891. {
  2892. $groups = $this->dao->select('*')->from(TABLE_GROUP)->fetchAll('id', false);
  2893. foreach($groups as $group)
  2894. {
  2895. if(empty($group->acl)) continue;
  2896. $acl = json_decode($group->acl, true);
  2897. if(isset($acl['products']))
  2898. {
  2899. $isEmpty = true;
  2900. foreach($acl['products'] as $productID)
  2901. {
  2902. if(!empty($productID)) $isEmpty = false;
  2903. }
  2904. if($isEmpty) unset($acl['products']);
  2905. }
  2906. if(isset($acl['projects']))
  2907. {
  2908. $isEmpty = true;
  2909. foreach($acl['projects'] as $projectID)
  2910. {
  2911. if(!empty($projectID)) $isEmpty = false;
  2912. }
  2913. if($isEmpty) unset($acl['projects']);
  2914. }
  2915. $acl = json_encode($acl);
  2916. $this->dao->update(TABLE_GROUP)->set('acl')->eq($acl)->where('id')->eq($group->id)->exec();
  2917. }
  2918. return true;
  2919. }
  2920. /**
  2921. * Adjust 11.7 priv.
  2922. *
  2923. * @access public
  2924. * @return void
  2925. */
  2926. public function adjustPriv11_7()
  2927. {
  2928. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('editor')->fetchPairs('group', 'group');
  2929. foreach($groups as $groupID)
  2930. {
  2931. $groupPriv = new stdclass();
  2932. $groupPriv->group = $groupID;
  2933. $groupPriv->module = 'dev';
  2934. $groupPriv->method = 'editor';
  2935. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2936. }
  2937. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('translate')->fetchPairs('group', 'group');
  2938. foreach($groups as $groupID)
  2939. {
  2940. $groupPriv = new stdclass();
  2941. $groupPriv->group = $groupID;
  2942. $groupPriv->module = 'dev';
  2943. $groupPriv->method = 'translate';
  2944. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  2945. }
  2946. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('module')->eq('translate')->exec();
  2947. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('module')->eq('editor')->exec();
  2948. return true;
  2949. }
  2950. /**
  2951. * Fix bug typeList.
  2952. *
  2953. * @access public
  2954. * @return bool
  2955. */
  2956. public function fixBugTypeList()
  2957. {
  2958. $customedTypeList4All = $this->dao->select('*')->from(TABLE_LANG)
  2959. ->where('lang')->eq("all")
  2960. ->andWhere('module')->eq('bug')
  2961. ->andWhere('section')->eq('typeList')
  2962. ->fetchPairs('`key`', 'value');
  2963. foreach($this->config->upgrade->discardedBugTypes as $langCode => $types)
  2964. {
  2965. $bugs = $this->dao->select('type')->from(TABLE_BUG)->where('type')->in(array_keys($types))->fetchPairs('type', 'type');
  2966. if(empty($bugs)) return true;
  2967. $usedTypes = array_keys($bugs);
  2968. $customedTypeList = $this->dao->select('*')->from(TABLE_LANG)
  2969. ->where('lang')->eq($langCode)
  2970. ->andWhere('module')->eq('bug')
  2971. ->andWhere('section')->eq('typeList')
  2972. ->fetchPairs('`key`', 'value');
  2973. $typesToSave = array_diff($usedTypes, empty($customedTypeList) ? $customedTypeList4All : $customedTypeList);
  2974. if(empty($typesToSave)) continue;
  2975. $langs = array();
  2976. foreach($typesToSave as $type) $langs[$type] = $types[$type];
  2977. if(empty($customedTypeList) and empty($customedTypeList4All))
  2978. {
  2979. $lang = new stdclass;
  2980. $lang->bug = new stdclass;
  2981. $lang->productCommon = '';
  2982. $lang->executionCommon = '';
  2983. $lang->more = '';
  2984. $langFile = $this->app->getModuleRoot() . DS . 'bug' . DS . 'lang' . DS . $langCode . '.php';
  2985. if(is_file($langFile)) include $langFile;
  2986. $langs = array_merge($lang->bug->typeList, $langs);
  2987. }
  2988. elseif(empty($customedTypeList))
  2989. {
  2990. $langs = array_merge($customedTypeList4All, $langs);
  2991. }
  2992. $this->loadModel('custom');
  2993. foreach($langs as $type => $typeName) $this->custom->setItem("{$langCode}.bug.typeList.{$type}.1", $typeName);
  2994. }
  2995. return true;
  2996. }
  2997. /**
  2998. * Remove editor and translate.
  2999. *
  3000. * @access public
  3001. * @return bool
  3002. */
  3003. public function rmEditorAndTranslateDir()
  3004. {
  3005. $zfile = $this->app->loadClass('zfile');
  3006. $moduleRoot = $this->app->getModuleRoot();
  3007. $editorDir = $moduleRoot . 'editor';
  3008. if(is_dir($editorDir)) $zfile->removeDir($editorDir);
  3009. $translateDir = $moduleRoot . 'translate';
  3010. if(is_dir($translateDir)) $zfile->removeDir($translateDir);
  3011. return true;
  3012. }
  3013. /**
  3014. * Set concept setted.
  3015. *
  3016. * @access public
  3017. * @return bool
  3018. */
  3019. public function setConceptSetted()
  3020. {
  3021. $conceptSetted = $this->dao->select('*')->from(TABLE_CONFIG)->where('owner')->eq('system')->andWhere('module')->eq('common')->andWhere('`key`')->eq('conceptSetted')->fetchAll();
  3022. if(empty($conceptSetted))
  3023. {
  3024. $setting = new stdclass();
  3025. $setting->owner = 'system';
  3026. $setting->module = 'custom';
  3027. $setting->key = 'storyRequirement';
  3028. $setting->value = '0';
  3029. $this->dao->insert(TABLE_CONFIG)->data($setting)->exec();
  3030. $setting->key = 'hourPoint';
  3031. $setting->value = '0';
  3032. $this->dao->insert(TABLE_CONFIG)->data($setting)->exec();
  3033. $setting->module = 'common';
  3034. $setting->key = 'conceptSetted';
  3035. $setting->value = '1';
  3036. $this->dao->insert(TABLE_CONFIG)->data($setting)->exec();
  3037. }
  3038. return true;
  3039. }
  3040. /**
  3041. * Adjust priv 12.0.
  3042. *
  3043. * @access public
  3044. * @return bool
  3045. */
  3046. public function adjustPriv12_0()
  3047. {
  3048. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('file')->andWhere('method')->eq('delete')->fetchPairs('group', 'group');
  3049. foreach($groups as $groupID)
  3050. {
  3051. $groupPriv = new stdclass();
  3052. $groupPriv->group = $groupID;
  3053. $groupPriv->module = 'doc';
  3054. $groupPriv->method = 'deleteFile';
  3055. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3056. }
  3057. return true;
  3058. }
  3059. /**
  3060. * Save repo from svn and git config.
  3061. *
  3062. * @access public
  3063. * @return bool
  3064. */
  3065. public function importRepoFromConfig()
  3066. {
  3067. $this->app->loadConfig('svn');
  3068. if(isset($this->config->svn->repos))
  3069. {
  3070. $scm = $this->app->loadClass('scm');
  3071. foreach($this->config->svn->repos as $i => $repo)
  3072. {
  3073. $repoPath = $repo['path'];
  3074. if(empty($repoPath)) continue;
  3075. $existRepo = $this->dao->select('*')->from(TABLE_REPO)->where('path')->eq($repoPath)->andWhere('SCM')->eq('Subversion')->fetch();
  3076. if($existRepo) continue;
  3077. $svnRepo = new stdclass();
  3078. $svnRepo->client = $this->config->svn->client;
  3079. $svnRepo->name = basename($repoPath);
  3080. $svnRepo->path = $repoPath;
  3081. $svnRepo->SCM = 'Subversion';
  3082. $svnRepo->account = $repo['username'];
  3083. $svnRepo->password = $repo['password'];
  3084. $svnRepo->encrypt = 'base64';
  3085. $svnRepo->encoding = zget($repo, 'encoding', $this->config->svn->encoding);
  3086. $scm->setEngine($svnRepo);
  3087. $info = $scm->info('');
  3088. $svnRepo->prefix = empty($info->root) ? '' : trim(str_ireplace($info->root, '', str_replace('\\', '/', $svnRepo->path)), '/');
  3089. if($svnRepo->prefix) $svnRepo->prefix = '/' . $svnRepo->prefix;
  3090. $svnRepo->password = base64_encode($repo['password']);
  3091. $this->dao->insert(TABLE_REPO)->data($svnRepo)->exec();
  3092. }
  3093. }
  3094. $this->app->loadConfig('git');
  3095. if(isset($this->config->git->repos))
  3096. {
  3097. foreach($this->config->git->repos as $i => $repo)
  3098. {
  3099. $repoPath = $repo['path'];
  3100. if(empty($repoPath)) continue;
  3101. $existRepo = $this->dao->select('*')->from(TABLE_REPO)->where('path')->eq($repoPath)->andWhere('SCM')->eq('Git')->fetch();
  3102. if($existRepo) continue;
  3103. $gitRepo = new stdclass();
  3104. $gitRepo->client = $this->config->git->client;
  3105. $gitRepo->name = basename($repoPath);
  3106. $gitRepo->path = $repoPath;
  3107. $gitRepo->prefix = '';
  3108. $gitRepo->SCM = 'Git';
  3109. $gitRepo->account = '';
  3110. $gitRepo->password = '';
  3111. $gitRepo->encrypt = 'base64';
  3112. $gitRepo->encoding = zget($repo, 'encoding', $this->config->git->encoding);
  3113. $this->dao->insert(TABLE_REPO)->data($gitRepo)->exec();
  3114. }
  3115. }
  3116. return true;
  3117. }
  3118. /**
  3119. * Fix fromCaseVersion field for zt_case table.
  3120. *
  3121. * @access public
  3122. * @return bool
  3123. */
  3124. public function fixFromCaseVersion()
  3125. {
  3126. /* Get imported cases and cases version is null. */
  3127. $errorCasePairs = $this->dao->select('id,fromCaseID,fromCaseVersion')->from(TABLE_CASE)->where('fromCaseID')->ne(0)->andWhere('fromCaseVersion')->eq(0)->fetchPairs('id', 'fromCaseID');
  3128. if(empty($errorCasePairs)) return true;
  3129. /* Get from case versions by from cases. */
  3130. $fromCaseIdList = array_unique(array_values($errorCasePairs));
  3131. $fromCaseVersions = $this->dao->select('id,version')->from(TABLE_CASE)->where('id')->in($fromCaseIdList)->fetchPairs('id', 'version');
  3132. /* Fix fromCaseVersion field. */
  3133. foreach($errorCasePairs as $caseID => $fromCaseID)
  3134. {
  3135. $fromCaseVersion = zget($fromCaseVersions, $fromCaseID, 1);
  3136. $this->dao->update(TABLE_CASE)->set('fromCaseVersion')->eq($fromCaseVersion)->where('id')->eq($caseID)->exec();
  3137. }
  3138. return true;
  3139. }
  3140. /**
  3141. * Adjust priv 12.5.
  3142. *
  3143. * @access public
  3144. * @return bool
  3145. */
  3146. public function adjustPriv12_5()
  3147. {
  3148. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('custom')->andWhere('method')->eq('set')->fetchPairs('group', 'group');
  3149. foreach($groups as $groupID)
  3150. {
  3151. $groupPriv = new stdclass();
  3152. $groupPriv->group = $groupID;
  3153. $groupPriv->module = 'custom';
  3154. $groupPriv->method = 'product';
  3155. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3156. $groupPriv->method = 'project';
  3157. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3158. }
  3159. $groups = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('task')->andWhere('method')->eq('create')->fetchPairs('group', 'group');
  3160. foreach($groups as $groupID)
  3161. {
  3162. $groupPriv = new stdclass();
  3163. $groupPriv->group = $groupID;
  3164. $groupPriv->module = 'story';
  3165. $groupPriv->method = 'batchToTask';
  3166. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3167. }
  3168. return true;
  3169. }
  3170. /**
  3171. * Adjust priv 15.0.
  3172. *
  3173. * @access public
  3174. * @return true
  3175. */
  3176. public function adjustPriv15_0()
  3177. {
  3178. $executionPriv = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('execution')->limit(1)->fetch();
  3179. if(empty($executionPriv))
  3180. {
  3181. $projectPrivList = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('project')->fetchAll();
  3182. $this->dao->update(TABLE_GROUPPRIV)->set('module')->eq('execution')->where('module')->eq('project')->exec();
  3183. foreach($projectPrivList as $projectPriv)
  3184. {
  3185. if(!in_array($projectPriv->method, array('browse', 'story', 'bug', 'testtask', 'doc', 'build', 'index', 'create', 'edit', 'batchedit', 'start', 'activate', 'suspend', 'close', 'delete', 'export', 'manageProducts', 'manageMembers', 'team', 'unlinkMember', 'unlinkStory', 'view'))) continue;
  3186. $this->dao->replace(TABLE_GROUPPRIV)->data($projectPriv)->exec();
  3187. }
  3188. }
  3189. $groups = $this->dao->select('id')->from(TABLE_GROUP)->fetchPairs('id', 'id');
  3190. foreach($groups as $groupID)
  3191. {
  3192. $groupPriv = new stdclass();
  3193. $groupPriv->group = $groupID;
  3194. $groupPriv->module = 'my';
  3195. $groupPriv->method = 'work';
  3196. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3197. $groupPriv->method = 'contribute';
  3198. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3199. $groupPriv->method = 'team';
  3200. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  3201. }
  3202. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('my')->andWhere('method')->eq('project')->query();
  3203. while($grouppriv = $stmt->fetch())
  3204. {
  3205. $grouppriv->method = 'execution';
  3206. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3207. }
  3208. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('program')->andWhere('method')->like('PGM%')->query();
  3209. while($grouppriv = $stmt->fetch())
  3210. {
  3211. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('module')->eq($grouppriv->module)->andWhere('method')->eq($grouppriv->method)->exec();
  3212. $grouppriv->method = strtolower(str_ireplace('PGM', '', $grouppriv->method));
  3213. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3214. $grouppriv->method = 'index';
  3215. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3216. }
  3217. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('program')->andWhere('method')->like('PRJ%')->query();
  3218. while($grouppriv = $stmt->fetch())
  3219. {
  3220. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('module')->eq($grouppriv->module)->andWhere('method')->eq($grouppriv->method)->exec();
  3221. $grouppriv->module = 'project';
  3222. $grouppriv->method = strtolower(str_ireplace('PRJ', '', $grouppriv->method));
  3223. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3224. $grouppriv->method = 'index';
  3225. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3226. }
  3227. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('project')->andWhere('method')->eq('story')->query();
  3228. while($grouppriv = $stmt->fetch())
  3229. {
  3230. $grouppriv->module = 'projectstory';
  3231. $grouppriv->method = 'story';
  3232. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3233. }
  3234. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('story')->andWhere('method')->eq('view')->query();
  3235. while($grouppriv = $stmt->fetch())
  3236. {
  3237. $grouppriv->module = 'projectstory';
  3238. $grouppriv->method = 'view';
  3239. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3240. }
  3241. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('project')->andWhere('method')->eq('linkstory')->query();
  3242. while($grouppriv = $stmt->fetch())
  3243. {
  3244. $grouppriv->module = 'projectstory';
  3245. $grouppriv->method = 'linkstory';
  3246. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3247. }
  3248. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('project')->andWhere('method')->eq('unlinkstory')->query();
  3249. while($grouppriv = $stmt->fetch())
  3250. {
  3251. $grouppriv->module = 'projectstory';
  3252. $grouppriv->method = 'unlinkstory';
  3253. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3254. }
  3255. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('execution')->andWhere('method')->eq('all')->query();
  3256. while($grouppriv = $stmt->fetch())
  3257. {
  3258. $grouppriv->module = 'project';
  3259. $grouppriv->method = 'execution';
  3260. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3261. $grouppriv->module = 'project';
  3262. $grouppriv->method = 'browse';
  3263. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3264. $grouppriv->module = 'project';
  3265. $grouppriv->method = 'index';
  3266. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3267. }
  3268. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('doc')->andWhere('method')->eq('createlib')->query();
  3269. while($grouppriv = $stmt->fetch())
  3270. {
  3271. $grouppriv->method = 'createLib';
  3272. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3273. }
  3274. $stmt = $this->dao->select('`group`,module,method')->from(TABLE_GROUPPRIV)->where('module')->eq('doc')->andWhere('method')->eq('editlib')->query();
  3275. while($grouppriv = $stmt->fetch())
  3276. {
  3277. $grouppriv->method = 'editLib';
  3278. $this->dao->replace(TABLE_GROUPPRIV)->data($grouppriv)->exec();
  3279. }
  3280. return true;
  3281. }
  3282. /**
  3283. * Adjust userview.
  3284. *
  3285. * @access public
  3286. * @return bool
  3287. */
  3288. public function adjustUserView()
  3289. {
  3290. $userViews = $this->dao->select('`account`,`sprints`,`projects`')->from(TABLE_USERVIEW)->where('projects')->ne('')->fetchAll('account');
  3291. $projectIdList = array();
  3292. $accountProjects = array();
  3293. $accountExecutions = array();
  3294. foreach($userViews as $account => $userView)
  3295. {
  3296. $projects = explode(',', trim($userView->projects, ','));
  3297. foreach($projects as $projectID)
  3298. {
  3299. if(empty($projectID)) continue;
  3300. $accountProjects[$account][$projectID] = $projectID;
  3301. if(isset($projectIdList[$projectID])) continue;
  3302. $projectIdList[$projectID] = $projectID;
  3303. }
  3304. $executions = explode(',', trim($userView->sprints, ','));
  3305. foreach($executions as $executionID)
  3306. {
  3307. if(empty($executionID)) continue;
  3308. $accountExecutions[$account][$executionID] = $executionID;
  3309. }
  3310. }
  3311. $executionPairs = $this->dao->select('id')->from(TABLE_PROJECT)->where('id')->in($projectIdList)->andWhere('type')->in('sprint,stage,kanban')->fetchPairs('id', 'id');
  3312. foreach($userViews as $account => $userView)
  3313. {
  3314. $projects = zget($accountProjects, $account, array());
  3315. if(empty($projects)) continue;
  3316. $executions = zget($accountExecutions, $account, array());
  3317. foreach($projects as $projectID)
  3318. {
  3319. if(isset($executionPairs[$projectID]))
  3320. {
  3321. $executions[$projectID] = $projectID;
  3322. unset($projects[$projectID]);
  3323. }
  3324. }
  3325. $this->dao->update(TABLE_USERVIEW)->set('sprints')->eq(implode(',', $executions))->set('projects')->eq(implode(',', $projects))->where('account')->eq($account)->exec();
  3326. }
  3327. return true;
  3328. }
  3329. /**
  3330. * 存储日志。
  3331. * Save logs.
  3332. *
  3333. * @param string $log
  3334. * @access public
  3335. * @return void
  3336. */
  3337. public function saveLogs($log)
  3338. {
  3339. $logFile = $this->getLogFile();
  3340. $log = date('Y-m-d H:i:s') . ' ' . trim($log) . "\n";
  3341. if(!file_exists($logFile)) $log = "<?php\ndie();\n?" . ">\n" . $log;
  3342. file_put_contents($logFile, $log, FILE_APPEND);
  3343. }
  3344. /**
  3345. * 手动归并数据。
  3346. * Manually merge data.
  3347. *
  3348. * @param array $projectIdList
  3349. * @access public
  3350. * @return int
  3351. */
  3352. public function createProgram($projectIdList = array())
  3353. {
  3354. $data = fixer::input('post')->get();
  3355. /* 新增项目集或者更新项目集状态。*/
  3356. /* Create program or update program status. */
  3357. if(isset($data->newProgram) && $this->config->systemMode != 'light')
  3358. {
  3359. $result = $this->createNewProgram($data, $projectIdList);
  3360. if(dao::isError()) return false;
  3361. if(isset($result['result']) && $result['result'] == 'fail') return $result;
  3362. $programID = $result;
  3363. }
  3364. else
  3365. {
  3366. $programID = $data->programID ? $data->programID : (int)$data->programs;
  3367. $this->dao->update(TABLE_PROGRAM)->set('status')->eq($data->programStatus)->where('id')->eq($programID)->exec();
  3368. if($data->programStatus == 'closed') $this->loadModel('action')->create('program', $programID, 'closedbysystem');
  3369. }
  3370. /* 新增产品线,返回产品线 ID。*/
  3371. /* Create product line, and return product line ID. */
  3372. $lineID = 0;
  3373. if(isset($data->newLine))
  3374. {
  3375. if(!empty($data->lineName))
  3376. {
  3377. $lineID = $this->createNewLine($data->lineName, $programID);
  3378. if(dao::isError()) return false;
  3379. }
  3380. }
  3381. else
  3382. {
  3383. if(!empty($data->lines)) $lineID = (int)$data->lines;
  3384. }
  3385. if(!isset($data->sprints)) return array($programID, 0, $lineID);
  3386. /* 新增项目或者更新项目状态。*/
  3387. $projectList = array();
  3388. if(isset($data->newProject))
  3389. {
  3390. $result = $this->createNewProject($data, $programID, $projectIdList);
  3391. if(dao::isError()) return false;
  3392. if(isset($result['result']) && $result['result'] == 'fail') return $result;
  3393. $projectList = $result;
  3394. }
  3395. elseif(!empty($data->projects))
  3396. {
  3397. $projectList = (int)$data->projects;
  3398. $this->dao->update(TABLE_PROJECT)->set('status')->eq($data->projectStatus)->where('id')->eq($projectList)->exec();
  3399. if($data->projectStatus == 'closed') $this->loadModel('action')->create('project', $projectList, 'closedbysystem');
  3400. }
  3401. return array($programID, $projectList, $lineID);
  3402. }
  3403. /**
  3404. * 创建新的项目集。
  3405. * Create new program.
  3406. *
  3407. * @param object $data
  3408. * @param array $projectIdList
  3409. * @access public
  3410. * @return bool|int|array
  3411. */
  3412. public function createNewProgram($data, $projectIdList)
  3413. {
  3414. $projectType = zget($data, 'projectType', 'project');
  3415. $this->upgradeTao->checkProgramRequired($data, $projectType);
  3416. if(dao::isError()) return false;
  3417. if($projectType == 'project')
  3418. {
  3419. $duplicateList = $this->upgradeTao->getDuplicateProjectName($projectIdList);
  3420. if($duplicateList) return array('result' => 'fail', 'callback' => "loadModal('" . helper::createLink('upgrade', 'renameObject', "type=project&duplicateList={$duplicateList}") . "')");
  3421. }
  3422. /* Insert program. */
  3423. $program = new stdclass();
  3424. $program->name = $data->programName;
  3425. $program->type = 'program';
  3426. $program->status = $data->programStatus;
  3427. $program->begin = !empty($data->begin) ? $data->begin : helper::now();
  3428. $program->end = !empty($data->end) ? $data->end : LONG_TIME;
  3429. $program->openedBy = isset($this->app->user->account) ? $this->app->user->account : '';
  3430. $program->openedDate = helper::now();
  3431. $program->openedVersion = $this->config->version;
  3432. $program->acl = isset($data->programAcl) ? $data->programAcl : 'open';
  3433. $program->days = $this->computeDaysDelta($program->begin, $program->end);
  3434. $program->PM = $projectType == 'project' ? zget($data, 'PM', '') : '';
  3435. $program->vision = 'rnd';
  3436. $this->app->loadLang('program');
  3437. $this->app->loadLang('project');
  3438. $this->lang->project->name = $this->lang->program->name;
  3439. $this->dao->insert(TABLE_PROJECT)->data($program)
  3440. ->batchcheck('name,begin', 'notempty')
  3441. ->checkIF($program->end != '', 'end', 'gt', $program->begin)
  3442. ->check('name', 'unique', "deleted='0' and type= 'program'")
  3443. ->exec();
  3444. if(dao::isError())
  3445. {
  3446. $errors = dao::getError();
  3447. if(isset($errors['name']))
  3448. {
  3449. $errors['programName'] = $errors['name'];
  3450. unset($errors['name']);
  3451. }
  3452. dao::$errors = $errors;
  3453. return false;
  3454. }
  3455. $programID = $this->dao->lastInsertId();
  3456. $this->dao->update(TABLE_PROGRAM)->set('grade')->eq(1)->set('path')->eq(",{$programID},")->set('`order`')->eq($programID * 5)->where('id')->eq($programID)->exec();
  3457. $this->loadModel('action')->create('program', $programID, 'openedbysystem');
  3458. if($data->programStatus == 'closed') $this->loadModel('action')->create('program', $programID, 'closedbysystem');
  3459. return $programID;
  3460. }
  3461. /**
  3462. * 创建新的产品线。
  3463. * Create new product line.
  3464. *
  3465. * @param string $lineName
  3466. * @param int $programID
  3467. * @access public
  3468. * @return bool|int
  3469. */
  3470. public function createNewLine($lineName, $programID)
  3471. {
  3472. /* Insert product line. */
  3473. $maxOrder = $this->dao->select("MAX(`order`) AS maxOrder")->from(TABLE_MODULE)->where('type')->eq('line')->fetch('maxOrder');
  3474. $maxOrder = $maxOrder ? $maxOrder + 10 : 0;
  3475. $line = new stdClass();
  3476. $line->type = 'line';
  3477. $line->parent = 0;
  3478. $line->grade = 1;
  3479. $line->name = $lineName;
  3480. $line->root = $programID;
  3481. $line->order = $maxOrder;
  3482. $this->dao->insert(TABLE_MODULE)->data($line)->exec();
  3483. $lineID = $this->dao->lastInsertID();
  3484. $path = ",$lineID,";
  3485. $this->dao->update(TABLE_MODULE)->set('path')->eq($path)->where('id')->eq($lineID)->exec();
  3486. if(dao::isError()) return false;
  3487. return $lineID;
  3488. }
  3489. /**
  3490. * 创建新的项目。
  3491. * Create new project.
  3492. *
  3493. * @param object $data
  3494. * @param int $programID
  3495. * @param array $projectIdList
  3496. * @access public
  3497. * @return bool|int|array
  3498. */
  3499. public function createNewProject($data, $programID, $projectIdList)
  3500. {
  3501. if(!isset($data->longTime) && !$data->end)
  3502. {
  3503. dao::$errors['end'][] = sprintf($this->lang->error->notempty, $this->lang->upgrade->end);
  3504. return false;
  3505. }
  3506. /* Create a project. */
  3507. $this->lang->project->name = $this->lang->upgrade->projectName;
  3508. /* Use historical projects as execution upgrades. */
  3509. $projectType = zget($data, 'projectType', 'project');
  3510. if($projectType == 'execution') return $this->createProject($programID, $data);
  3511. /* Use historical projects as project upgrades. */
  3512. $projects = $this->dao->select('id,name,begin,end,status,PM,acl,team')->from(TABLE_PROJECT)->where('id')->in($projectIdList)->fetchAll('id');
  3513. $projectPairs = $this->dao->select('name,id')->from(TABLE_PROJECT)->where('deleted')->eq('0')->andWhere('type')->eq('project')->andWhere('parent')->eq($programID)->fetchPairs();
  3514. $duplicateList = '';
  3515. foreach($projects as $projectID => $project)
  3516. {
  3517. if(isset($projectPairs[$project->name]))
  3518. {
  3519. $duplicateList .= "$projectID,";
  3520. $duplicateList .= "{$projectPairs[$project->name]},";
  3521. }
  3522. }
  3523. if($duplicateList) return array('result' => 'fail', 'callback' => "loadModal('" . helper::createLink('upgrade', 'renameObject', "type=project&duplicateList={$duplicateList}") . "')");
  3524. $projectList = array();
  3525. foreach($projectIdList as $projectID)
  3526. {
  3527. if(!isset($projects[$projectID])) continue;
  3528. $data->projectName = $projects[$projectID]->name;
  3529. $data->begin = $projects[$projectID]->begin;
  3530. $data->end = $projects[$projectID]->end;
  3531. $data->projectStatus = $projects[$projectID]->status;
  3532. $data->team = empty($projects[$projectID]->team) ? $projects[$projectID]->name : $projects[$projectID]->team;
  3533. $data->PM = $projects[$projectID]->PM;
  3534. $data->projectAcl = $projects[$projectID]->acl == 'custom' ? 'private' : $projects[$projectID]->acl;
  3535. $projectList[$projectID] = $this->createProject($programID, $data);
  3536. }
  3537. return $projectList;
  3538. }
  3539. /**
  3540. * 创建一个项目。
  3541. * Create a project.
  3542. *
  3543. * @param int $programID
  3544. * @param object $data
  3545. * @access public
  3546. * @return int|bool
  3547. */
  3548. public function createProject($programID = 0, $data = null)
  3549. {
  3550. $now = helper::now();
  3551. $account = isset($this->app->user->account) ? $this->app->user->account : '';
  3552. /* Insert project. */
  3553. $project = new stdclass();
  3554. $project->name = $data->projectName;
  3555. $project->type = 'project';
  3556. $project->model = 'scrum';
  3557. $project->parent = $programID;
  3558. $project->status = $data->projectStatus;
  3559. $project->team = !empty($data->team) ? $data->team : $data->projectName;
  3560. $project->begin = $data->begin;
  3561. $project->end = !empty($data->end) ? $data->end : LONG_TIME;
  3562. $project->days = $this->computeDaysDelta($project->begin, $project->end);
  3563. $project->PM = $data->PM;
  3564. $project->auth = 'extend';
  3565. $project->openedBy = $account;
  3566. $project->openedDate = $now;
  3567. $project->openedVersion = $this->config->version;
  3568. $project->lastEditedBy = $account;
  3569. $project->lastEditedDate = $now;
  3570. $project->acl = isset($data->projectAcl) ? $data->projectAcl : 'open';
  3571. $project->storyType = 'story';
  3572. $this->dao->insert(TABLE_PROJECT)->data($project)
  3573. ->batchcheck('name', 'notempty')
  3574. ->check('name', 'unique', "type='project' AND parent='{$programID}' AND deleted='0'")
  3575. ->exec();
  3576. if(dao::isError()) return false;
  3577. $projectID = $this->dao->lastInsertID();
  3578. $this->dao->update(TABLE_PROJECT)->set('grade')->eq(2)->set('path')->eq(",{$programID},{$projectID},")->set('`order`')->eq($projectID * 5)->where('id')->eq($projectID)->exec();
  3579. /* Modify the begin and end of the program to match the begin and end of the project. */
  3580. $programDate = $this->dao->select('begin,end')->from(TABLE_PROGRAM)->where('id')->eq($programID)->fetch();
  3581. if($data->begin < $programDate->begin) $this->dao->update(TABLE_PROGRAM)->set('begin')->eq($data->begin)->where('id')->eq($programID)->exec();
  3582. if(isset($data->end) && $data->end > $programDate->end) $this->dao->update(TABLE_PROGRAM)->set('end')->eq($data->end)->where('id')->eq($programID)->exec();
  3583. $this->loadModel('action')->create('project', $projectID, 'openedbysystem');
  3584. if($data->projectStatus == 'closed') $this->action->create('project', $projectID, 'closedbysystem');
  3585. /* Create doc lib. */
  3586. $this->app->loadLang('doc');
  3587. $lib = new stdclass();
  3588. $lib->project = $projectID;
  3589. $lib->name = $this->lang->doclib->main['project'];
  3590. $lib->type = 'project';
  3591. $lib->main = '1';
  3592. $lib->acl = $project->acl != 'program' ? $project->acl : 'custom';
  3593. $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
  3594. return $projectID;
  3595. }
  3596. /**
  3597. * 计算开始和结束日期中间的工作日数。
  3598. * Compute delta between the begin and end date.
  3599. *
  3600. * @param string begin
  3601. * @param string end
  3602. * @access public
  3603. * @return int
  3604. */
  3605. public function computeDaysDelta($begin, $end)
  3606. {
  3607. if($end == LONG_TIME) return 0;
  3608. $delta = helper::diffDate($end, $begin);
  3609. $week = date('w', strtotime($begin));
  3610. $weekend = 0;
  3611. for($i = 0; $i < $delta; $i++)
  3612. {
  3613. $week = $week % 7;
  3614. if($week == 0 || $week == 6) $weekend ++;
  3615. $week ++;
  3616. }
  3617. return $delta - $weekend;
  3618. }
  3619. /**
  3620. * 替换产品和项目相关联对象的项目集或者产品id。
  3621. * Replace program or project id for product and project linked objects.
  3622. *
  3623. * @param int $programID
  3624. * @param int $projectID
  3625. * @param int $lineID
  3626. * @param array $productIdList
  3627. * @param array $projectIdList
  3628. * @access public
  3629. * @return bool
  3630. * @param mixed[] $sprintIdList
  3631. */
  3632. public function processMergedData($programID, $projectID, $lineID = 0, $productIdList = array(), $sprintIdList = array())
  3633. {
  3634. if(!$projectID || !$sprintIdList) return false;
  3635. if($lineID) $this->dao->update(TABLE_MODULE)->set('root')->eq($programID)->where('id')->eq($lineID)->andWhere('root')->eq('0')->exec();
  3636. if(empty($productIdList)) $productIdList = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->in($sprintIdList)->fetchPairs('product', 'product');
  3637. $this->computeProductAcl($productIdList, $programID, $lineID);
  3638. $this->upgradeTao->updateProjectByProduct($projectID, $productIdList);
  3639. $this->upgradeTao->updateProjectByExecution($projectID, $sprintIdList);
  3640. $this->upgradeTao->moveSprintStoryToProject($projectID, $sprintIdList);
  3641. $this->upgradeTao->moveSprintCaseToProject($projectID, $sprintIdList);
  3642. $this->upgradeTao->moveAllTestTaskToProject($projectID, $sprintIdList);
  3643. $this->upgradeTao->syncProjectInfoForSprints($projectID, $sprintIdList, $programID, isset($_POST['projectType']) && $_POST['projectType'] == 'execution');
  3644. $this->upgradeTao->setProjectProductsRelation($projectID, $productIdList, $sprintIdList);
  3645. return true;
  3646. }
  3647. /**
  3648. * 计算产品的 acl。
  3649. * Compute product acl.
  3650. *
  3651. * @param array $productIdList
  3652. * @param int $programID
  3653. * @param mixed $lineID
  3654. * @access public
  3655. * @return void
  3656. */
  3657. public function computeProductAcl($productIdList = array(), $programID = 0, $lineID = null)
  3658. {
  3659. /* Compute product acl. */
  3660. $products = $this->dao->select('id,program,acl')->from(TABLE_PRODUCT)->where('id')->in($productIdList)->fetchAll();
  3661. foreach($products as $product)
  3662. {
  3663. if($product->program) continue; // Skip the product that belongs to a program.
  3664. $data = new stdclass();
  3665. $data->program = $programID;
  3666. $data->acl = $product->acl == 'custom' ? 'private' : $product->acl;
  3667. if($lineID !== null) $data->line = $lineID;
  3668. $this->dao->update(TABLE_PRODUCT)->data($data)->where('id')->eq($product->id)->exec();
  3669. }
  3670. }
  3671. /**
  3672. * 计算项目、产品、迭代的成员。
  3673. * Compute project, product and sprint members.
  3674. *
  3675. * @access public
  3676. * @return void
  3677. */
  3678. public function computeObjectMembers()
  3679. {
  3680. /* Update the project members. */
  3681. $this->updateProjectMembers();
  3682. /* Update the product members. */
  3683. $this->loadModel('personnel');
  3684. $customProducts = $this->dao->select('id,whitelist,acl')->from(TABLE_PRODUCT)->where('whitelist')->ne('')->fetchAll('id');
  3685. $whitelistACL = $this->dao->select('objectID,account')->from(TABLE_ACL)->where('objectID')->in(array_keys($customProducts))->andWhere('objectType')->eq('product')->andWhere('type')->eq('whitelist')->fetchGroup('objectID', 'account');
  3686. $groupAccounts = $this->dao->select('`group`,account')->from(TABLE_USERGROUP)->fetchGroup('group', 'account');
  3687. foreach($customProducts as $product)
  3688. {
  3689. if($product->acl != 'private') continue;
  3690. $whitelist = array();
  3691. foreach(explode(',', $product->whitelist) as $group)
  3692. {
  3693. foreach(zget($groupAccounts, $group, array()) as $account => $userGroup) $whitelist[$account] = $account;
  3694. }
  3695. $whitelist += zget($whitelistACL, $product->id, array());
  3696. $this->personnel->updateWhitelist(array_keys($whitelist), 'product', $product->id, 'whitelist', 'upgrade', 'increase');
  3697. }
  3698. /* Update the sprint members. */
  3699. $customSprints = $this->dao->select('id,whitelist,acl')->from(TABLE_PROJECT)->where('whitelist')->ne('')->andWhere('type')->in('sprint,stage,kanban')->fetchAll('id');
  3700. $whitelistACL = $this->dao->select('objectID,account')->from(TABLE_ACL)->where('objectID')->in(array_keys($customSprints))->andWhere('objectType')->eq('sprint')->andWhere('type')->eq('whitelist')->fetchGroup('objectID', 'account');
  3701. foreach($customSprints as $sprint)
  3702. {
  3703. if($sprint->acl != 'private') continue;
  3704. $whitelist = array();
  3705. foreach(explode(',', $sprint->whitelist) as $group)
  3706. {
  3707. foreach(zget($groupAccounts, $group, array()) as $account => $userGroup) $whitelist[$account] = $account;
  3708. }
  3709. $whitelist += zget($whitelistACL, $sprint->id, array());
  3710. $this->personnel->updateWhitelist(array_keys($whitelist), 'sprint', $sprint->id, 'whitelist', 'upgrade', 'increase');
  3711. }
  3712. }
  3713. /**
  3714. * 更新项目成员。
  3715. * Update project members.
  3716. *
  3717. * @access protected
  3718. * @return void
  3719. */
  3720. protected function updateProjectMembers()
  3721. {
  3722. /* Get projects and project teams. */
  3723. $sprints = $this->dao->select('id,project,days,PM,multiple')->from(TABLE_PROJECT)->where('type')->eq('sprint')->andWhere('multiple')->eq('0')->fetchAll('project');
  3724. $projects = $this->dao->select('id,project,days,PM,multiple')->from(TABLE_PROJECT)->where('type')->eq('project')->fetchAll('id');
  3725. $projectTeams = $this->getProjectTeams(array_keys($projects));
  3726. $this->app->loadLang('user');
  3727. /* Insert product and sprint team into project team. */
  3728. $today = helper::today();
  3729. $users = $this->dao->select('*')->from(TABLE_USER)->where('deleted')->eq('0')->fetchAll('account');
  3730. foreach($projectTeams as $projectID => $projectMember)
  3731. {
  3732. if(empty($projectMember)) continue;
  3733. /* Get project and project members in the project. */
  3734. $project = zget($projects, $projectID, '');
  3735. $projectMember = array_filter($projectMember);
  3736. if(!empty($project) && !isset($projectMember[$project->PM])) $projectMember[$project->PM] = $project->PM; // Append PM to project members if PM isn't among the project members.
  3737. /* Update the users in the project's main doc lib. */
  3738. $this->dao->update(TABLE_DOCLIB)
  3739. ->set('users')->eq(trim(implode(',', $projectMember), ','))
  3740. ->where('project')->eq($projectID)
  3741. ->andWhere('main')->eq(1)
  3742. ->exec();
  3743. /* Replace the members to project team. */
  3744. foreach($projectMember as $account)
  3745. {
  3746. if(!isset($users[$account])) continue;
  3747. $user = $users[$account];
  3748. $team = new stdclass();
  3749. $team->root = $projectID;
  3750. $team->type = 'project';
  3751. $team->account = $account;
  3752. $team->role = zget($this->lang->user->roleList, $user->role);
  3753. $team->join = $today;
  3754. $team->days = $project->days;
  3755. $team->hours = '7.0';
  3756. $this->dao->replace(TABLE_TEAM)->data($team)->exec();
  3757. }
  3758. if(empty($project->multiple) && !empty($sprints[$project->id]))
  3759. {
  3760. $sprint = $sprints[$project->id];
  3761. foreach($projectMember as $account)
  3762. {
  3763. if(!isset($users[$account])) continue;
  3764. $user = $users[$account];
  3765. $team = new stdclass();
  3766. $team->root = $sprint->id;
  3767. $team->type = 'execution';
  3768. $team->account = $account;
  3769. $team->role = zget($this->lang->user->roleList, $user->role);
  3770. $team->join = $today;
  3771. $team->days = $sprint->days;
  3772. $team->hours = '7.0';
  3773. $this->dao->replace(TABLE_TEAM)->data($team)->exec();
  3774. }
  3775. }
  3776. }
  3777. }
  3778. /**
  3779. * 获取项目的团队。
  3780. * Get project teams.
  3781. *
  3782. * @param array $projectIdList
  3783. * @access protected
  3784. * @return array
  3785. */
  3786. protected function getProjectTeams($projectIdList)
  3787. {
  3788. $productGroups = $this->dao->select('t1.project,t1.product,t3.*')->from(TABLE_PROJECTPRODUCT)->alias('t1')
  3789. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  3790. ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product=t3.id')
  3791. ->where('t2.id')->in($projectIdList)
  3792. ->fetchGroup('project', 'product');
  3793. $sprintGroups = $this->dao->select('*')->from(TABLE_PROJECT)->where('project')->in($projectIdList)->fetchGroup('project', 'id');
  3794. $teamGroups = $this->dao->select('root,account')->from(TABLE_TEAM)->where('type')->eq('execution')->fetchGroup('root', 'account');
  3795. $projectTeams = array();
  3796. foreach($projectIdList as $projectID)
  3797. {
  3798. $teams = array();
  3799. $products = zget($productGroups, $projectID, array());
  3800. foreach($products as $product)
  3801. {
  3802. $teams[$product->PO] = $product->PO;
  3803. $teams[$product->QD] = $product->QD;
  3804. $teams[$product->RD] = $product->RD;
  3805. if(isset($product->feedback)) $teams[$product->feedback] = $product->feedback;
  3806. }
  3807. $sprints = zget($sprintGroups, $projectID, array());
  3808. foreach($sprints as $sprint)
  3809. {
  3810. $teams[$sprint->PO] = $sprint->PO;
  3811. $teams[$sprint->PM] = $sprint->PM;
  3812. $teams[$sprint->QD] = $sprint->QD;
  3813. $teams[$sprint->RD] = $sprint->RD;
  3814. if(isset($sprint->feedback)) $teams[$sprint->feedback] = $sprint->feedback;
  3815. $sprintTeams = zget($teamGroups, $sprint->id, array());
  3816. foreach($sprintTeams as $account => $team) $teams[$account] = $account;
  3817. }
  3818. $projectTeams[$projectID] = $teams;
  3819. }
  3820. return $projectTeams;
  3821. }
  3822. /**
  3823. * 合并代码库。
  3824. * Merge repo.
  3825. *
  3826. * @param array $repoes
  3827. * @param string $products
  3828. * @access public
  3829. * @return void
  3830. */
  3831. public function mergeRepo($repoes, $products)
  3832. {
  3833. foreach($repoes as $repoID) $this->dao->update(TABLE_REPO)->set('product')->eq($products)->where('id')->eq($repoID)->exec();
  3834. }
  3835. /**
  3836. * 设置项目集默认权限。
  3837. * Set program default priv.
  3838. *
  3839. * @access public
  3840. * @return void
  3841. */
  3842. public function setDefaultPriv()
  3843. {
  3844. $groups = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->ne('limited')->andWhere('role')->ne('projectAdmin')->fetchPairs();
  3845. foreach($groups as $groupID)
  3846. {
  3847. $data = new stdclass();
  3848. $data->group = $groupID;
  3849. $data->module = 'program';
  3850. $data->method = 'pgmindex';
  3851. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  3852. $data->method = 'prjbrowse';
  3853. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  3854. $data->method = 'index';
  3855. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  3856. }
  3857. /* If is project admin, have all project priv. */
  3858. $projectAdminGroupID = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->eq('projectAdmin')->fetch('id');
  3859. if(empty($projectAdminGroupID)) return;
  3860. $this->app->loadLang('group');
  3861. foreach($this->lang->resource->program as $method => $methodLang)
  3862. {
  3863. $data = new stdclass();
  3864. $data->group = $projectAdminGroupID;
  3865. $data->module = 'program';
  3866. $data->method = $method;
  3867. $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec();
  3868. }
  3869. }
  3870. /**
  3871. * Set work to full.
  3872. *
  3873. * @access public
  3874. * @return bool
  3875. */
  3876. public function setWork2Full()
  3877. {
  3878. $this->loadModel('setting')->setItem('system.common.global.flow', 'full');
  3879. return true;
  3880. }
  3881. /**
  3882. * Init story sort of plan.
  3883. *
  3884. * @access public
  3885. * @return bool
  3886. */
  3887. public function initStoryOfPlan()
  3888. {
  3889. /* Get all the planned stories and story sort. */
  3890. $stories = $this->dao->select('id, plan')->from(TABLE_STORY)->where('plan')->ne(0)->andWhere('plan')->ne('')->orderBy('id_desc')->fetchAll('id');
  3891. $planOrder = $this->dao->select('id, `order`')->from(TABLE_PRODUCTPLAN)->where('`order`')->ne('')->fetchAll('id');
  3892. /* Organize the stories according to the plan. */
  3893. $plans = array();
  3894. foreach($stories as $storyID => $story)
  3895. {
  3896. $planIDList = explode(',', trim($story->plan, ','));
  3897. foreach($planIDList as $planID) $plans[$planID][$storyID] = $storyID;
  3898. }
  3899. foreach($plans as $planID => $storyIDList)
  3900. {
  3901. /* Order the story according to the plan. */
  3902. if(!empty($planOrder[$planID]))
  3903. {
  3904. $sortIDList = array();
  3905. $storySort = explode(',', $planOrder[$planID]->order);
  3906. /* Reorder story id list by story order of plan. */
  3907. foreach($storySort as $storyID)
  3908. {
  3909. if(empty($storyID)) continue;
  3910. if(!isset($storyIDList[$storyID])) continue;
  3911. $sortIDList[$storyID] = $storyID;
  3912. unset($storyIDList[$storyID]);
  3913. }
  3914. if($storyIDList) $sortIDList += $storyIDList;
  3915. $storyIDList = $sortIDList;
  3916. unset($sortIDList);
  3917. }
  3918. /* Loop insert sort data by plan. */
  3919. $order = 1;
  3920. foreach($storyIDList as $storyID)
  3921. {
  3922. $this->dao->replace(TABLE_PLANSTORY)
  3923. ->set('plan')->eq($planID)
  3924. ->set('story')->eq($storyID)
  3925. ->set('`order`')->eq($order)
  3926. ->exec();
  3927. $order++;
  3928. }
  3929. }
  3930. return true;
  3931. }
  3932. /**
  3933. * Unify the format of the stories and bugs fields in the zt_build table.
  3934. *
  3935. * @access public
  3936. * @return bool
  3937. */
  3938. public function processBuildTable()
  3939. {
  3940. $builds = $this->dao->select('*')->from(TABLE_BUILD)->fetchAll('id', false);
  3941. foreach($builds as $build)
  3942. {
  3943. $data = array();
  3944. if(!empty($build->stories) and $build->stories[0] != ',') $data['stories'] = ',' . $build->stories;
  3945. if(!empty($build->bugs) and $build->bugs[0] != ',') $data['bugs'] = ',' . $build->bugs;
  3946. if($data) $this->dao->update(TABLE_BUILD)->data($data)->where('id')->eq($build->id)->exec();
  3947. }
  3948. return true;
  3949. }
  3950. /**
  3951. * Adjust the project field of the zt_bug table.
  3952. *
  3953. * @access public
  3954. * @return bool
  3955. */
  3956. public function adjustBugOfProject()
  3957. {
  3958. if($this->config->systemMode != 'new') return true;
  3959. $bugs = $this->dao->select('id,execution')->from(TABLE_BUG)->where('execution')->ne('0')->andWhere('project')->eq(0)->fetchPairs('id', 'execution');
  3960. $executions = $this->dao->select('id,project')->from(TABLE_EXECUTION)->where('id')->in(array_unique(array_values($bugs)))->fetchPairs('id', 'project');
  3961. foreach($bugs as $id => $executionID)
  3962. {
  3963. if(isset($executions[$executionID])) $this->dao->update(TABLE_BUG)->set('project')->eq($executions[$executionID])->where('id')->eq($id)->exec();
  3964. }
  3965. return true;
  3966. }
  3967. /**
  3968. * Adjust the whitelist of projects.
  3969. *
  3970. * @access public
  3971. * @return bool
  3972. */
  3973. public function adjustWhitelistOfProject()
  3974. {
  3975. $projects = $this->dao->select('*')->from(TABLE_PROJECT)->where('acl')->eq('custom')->andWhere('type')->eq('sprint')->fetchAll('id', false);
  3976. foreach($projects as $project)
  3977. {
  3978. $groups = explode(',', $project->whitelist);
  3979. $accounts = $this->dao->select('account')->from(TABLE_USERGROUP)->where('`group`')->in($groups)->fetchPairs('account');
  3980. $whitelist = '';
  3981. foreach($accounts as $account)
  3982. {
  3983. $acl = new stdclass();
  3984. $acl->account = $account;
  3985. $acl->objectType = $project->type;
  3986. $acl->objectID = $project->id;
  3987. $acl->type = 'whitelist';
  3988. $acl->source = 'upgrade';
  3989. $this->dao->insert(TABLE_ACL)->data($acl)->exec();
  3990. $whitelist .= ',' . $account;
  3991. }
  3992. $this->dao->update(TABLE_PROJECT)->set('acl')->eq('private')->set('whitelist')->eq($whitelist)->where('id')->eq($project->id)->exec();
  3993. }
  3994. return true;
  3995. }
  3996. /**
  3997. * Adjust the whitelist of projects.
  3998. *
  3999. * @access public
  4000. * @return bool
  4001. */
  4002. public function adjustWhitelistOfProduct()
  4003. {
  4004. $products = $this->dao->select('*')->from(TABLE_PRODUCT)->where('acl')->eq('custom')->fetchAll('id', false);
  4005. foreach($products as $product)
  4006. {
  4007. $groups = explode(',', $product->whitelist);
  4008. $accounts = $this->dao->select('account')->from(TABLE_USERGROUP)->where('`group`')->in($groups)->fetchPairs('account');
  4009. $whitelist = '';
  4010. foreach($accounts as $account)
  4011. {
  4012. $acl = new stdclass();
  4013. $acl->account = $account;
  4014. $acl->objectType = 'product';
  4015. $acl->objectID = $product->id;
  4016. $acl->type = 'whitelist';
  4017. $acl->source = 'upgrade';
  4018. $this->dao->insert(TABLE_ACL)->data($acl)->exec();
  4019. $whitelist .= ',' . $account;
  4020. }
  4021. $this->dao->update(TABLE_PRODUCT)->set('acl')->eq('private')->set('whitelist')->eq($whitelist)->where('id')->eq($product->id)->exec();
  4022. }
  4023. return true;
  4024. }
  4025. /**
  4026. * Update execution main doclib type.
  4027. *
  4028. * @access public
  4029. * @return bool
  4030. */
  4031. public function updateLibType()
  4032. {
  4033. $executionList = $this->dao->select('id')->from(TABLE_EXECUTION)->where('type')->eq('sprint')->fetchAll('id');
  4034. if(empty($executionList)) return true;
  4035. $this->dao->update(TABLE_DOCLIB)->set('type')->eq('execution')->where('execution')->in(array_keys($executionList))->exec();
  4036. return true;
  4037. }
  4038. /**
  4039. * Update the testtask related cases status.
  4040. *
  4041. * @access public
  4042. * @return bool
  4043. */
  4044. public function updateRunCaseStatus()
  4045. {
  4046. $this->dao->update(TABLE_TESTRUN)->set('status')->eq('normal')->where('status')->in('wait,done')->exec();
  4047. return true;
  4048. }
  4049. /**
  4050. * Fix for task link project.
  4051. *
  4052. * @access public
  4053. * @return bool
  4054. */
  4055. public function fix4TaskLinkProject()
  4056. {
  4057. if($this->config->systemMode != 'new') return true;
  4058. $executionIdList = $this->dao->select('distinct execution')->from(TABLE_TASK)->where('project')->eq(0)->fetchPairs('execution', 'execution');
  4059. $executionPairs = $this->dao->select('id,project')->from(TABLE_PROJECT)->where('id')->in($executionIdList)->andWhere('project')->ne('0')->fetchPairs('id', 'project');
  4060. foreach($executionPairs as $executionID => $projectID) $this->dao->update(TABLE_TASK)->set('project')->eq($projectID)->where('execution')->eq($executionID)->exec();
  4061. return true;
  4062. }
  4063. /**
  4064. * Fix execution team.
  4065. *
  4066. * @access public
  4067. * @return bool
  4068. */
  4069. public function fixExecutionTeam()
  4070. {
  4071. $errorTeams = $this->dao->select('id,root,account')->from(TABLE_TEAM)->where('type')->eq('')->fetchGroup('root', 'id');
  4072. $duplicateTeams = $this->dao->select('root,account')->from(TABLE_TEAM)->where('root')->in(array_keys($errorTeams))->andWhere('type')->ne('')->fetchGroup('root', 'account');
  4073. foreach($errorTeams as $root => $teams)
  4074. {
  4075. if(!isset($duplicateTeams[$root]))
  4076. {
  4077. $this->dao->update(TABLE_TASK)->set('type')->eq('execution')->where('id')->in(array_keys($teams))->exec();
  4078. }
  4079. else
  4080. {
  4081. $existsTeams = $duplicateTeams[$root];
  4082. foreach($teams as $team)
  4083. {
  4084. if(isset($existsTeams[$team->account]))
  4085. {
  4086. $this->dao->delete()->from(TABLE_TEAM)->where('id')->eq($team->id);
  4087. }
  4088. else
  4089. {
  4090. $this->dao->update(TABLE_TASK)->set('type')->eq('execution')->where('id')->in($team->id)->exec();
  4091. }
  4092. }
  4093. }
  4094. }
  4095. return true;
  4096. }
  4097. /**
  4098. * Update the createdVersion field of the zt_product table.
  4099. *
  4100. * @access public
  4101. * @return void
  4102. */
  4103. public function updateProductVersion()
  4104. {
  4105. $this->dao->update(TABLE_PRODUCT)->set('createdVersion')->eq($this->config->version)->where('createdVersion')->eq('')->andWhere('createdDate')->gt('2020-01-01')->exec();
  4106. return true;
  4107. }
  4108. /**
  4109. * Unique projectAdmin group.
  4110. *
  4111. * @access public
  4112. * @return void
  4113. */
  4114. public function uniqueProjectAdmin()
  4115. {
  4116. $projectAdmins = $this->dao->select('*')->from(TABLE_GROUP)->where('role')->eq('projectAdmin')->orderBy('id')->fetchAll('id');
  4117. if(count($projectAdmins) <= 1) return true;
  4118. $holdGroup = reset($projectAdmins);
  4119. unset($projectAdmins[$holdGroup->id]);
  4120. $userGroups = $this->dao->select('*')->from(TABLE_USERGROUP)->where('`group`')->in(array_keys($projectAdmins))->fetchGroup('group', 'account');
  4121. foreach($userGroups as $groupID => $groups)
  4122. {
  4123. foreach($groups as $account => $userGroup)
  4124. {
  4125. $this->dao->delete()->from(TABLE_USERGROUP)->where('`group`')->eq($userGroup->group)->andWhere('account')->eq($userGroup->account)->exec();
  4126. $newUserGroup = new stdclass();
  4127. $newUserGroup->account = $account;
  4128. $newUserGroup->project = $userGroup->project;
  4129. $newUserGroup->group = $holdGroup->id;
  4130. $this->dao->replace(TABLE_USERGROUP)->data($newUserGroup)->exec();
  4131. }
  4132. }
  4133. $this->dao->delete()->from(TABLE_GROUP)->where('id')->in(array_keys($projectAdmins))->exec();
  4134. return true;
  4135. }
  4136. /**
  4137. * Process gitlab repo data.
  4138. *
  4139. * @access public
  4140. * @return bool
  4141. */
  4142. public function processGitlabRepo()
  4143. {
  4144. $repoList = $this->dao->select('*')->from(TABLE_REPO)->where('SCM')->eq('Gitlab')->fetchAll();
  4145. foreach($repoList as $repo)
  4146. {
  4147. if(is_numeric($repo->path)) continue;
  4148. /* Create gitlab from repo. */
  4149. $gitlab = new stdclass;
  4150. $gitlab->type = 'gitlab';
  4151. $gitlab->name = $repo->client;
  4152. $gitlab->url = $repo->client;
  4153. $gitlab->token = $repo->encrypt == 'base64' ? base64_decode($repo->password) : $repo->password;
  4154. $gitlab->private = md5(uniqid());
  4155. $this->dao->insert(TABLE_PIPELINE)->data($gitlab)->exec();
  4156. $gitlabID = $this->dao->lastInsertID();
  4157. $this->dao->update(TABLE_REPO)->set('client')->eq($gitlabID)->set('path')->eq($repo->extra)->where('id')->eq($repo->id)->exec();
  4158. }
  4159. $this->dao->update(TABLE_REPO)->set('prefix')->eq('')->where('SCM')->eq('Gitlab')->exec();
  4160. return true;
  4161. }
  4162. /**
  4163. * Process story file type to requirement.
  4164. *
  4165. * @access public
  4166. * @return bool
  4167. */
  4168. public function processStoryFileType()
  4169. {
  4170. $requirementList = $this->dao->select('id')->from(TABLE_STORY)->where('type')->eq('requirement')->fetchPairs('id');
  4171. $this->dao->update(TABLE_FILE)->set('objectType')->eq('requirement')
  4172. ->where('objectID')->in($requirementList)
  4173. ->andWhere('objectType')->eq('story')
  4174. ->exec();
  4175. return true;
  4176. }
  4177. /**
  4178. * Leave the project field of the product document blank.
  4179. *
  4180. * @access public
  4181. * @return bool
  4182. */
  4183. public function processProductDoc()
  4184. {
  4185. $this->dao->update(TABLE_DOC)->set('project')->eq(0)
  4186. ->where('product')->ne(0)
  4187. ->andWhere('project')->ne(0)
  4188. ->exec();
  4189. return true;
  4190. }
  4191. /**
  4192. * Adjust priv 15.3
  4193. *
  4194. * @access public
  4195. * @return bool
  4196. */
  4197. public function adjustPriv15_3()
  4198. {
  4199. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('doc')->andWhere('method')->in('view,objectLibs')->fetchPairs('group', 'group');
  4200. foreach($groups as $groupID)
  4201. {
  4202. $groupPriv = new stdclass();
  4203. $groupPriv->group = $groupID;
  4204. $groupPriv->module = 'doc';
  4205. $groupPriv->method = 'objectLibs';
  4206. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  4207. $groupPriv->method = 'tableContents';
  4208. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  4209. $groupPriv->method = 'showFiles';
  4210. $this->dao->replace(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  4211. }
  4212. return true;
  4213. }
  4214. /**
  4215. * Actual finished date of processing testtask.
  4216. *
  4217. * @access public
  4218. * @return bool
  4219. */
  4220. public function processTesttaskDate()
  4221. {
  4222. $this->dao->update(TABLE_TESTTASK)->set("realFinishedDate = end")
  4223. ->where('status')->eq('done')
  4224. ->andWhere('realFinishedDate')->eq('0000-00-00 00:00:00')
  4225. ->exec();
  4226. return true;
  4227. }
  4228. /**
  4229. * Store the body of the document in a temporary field.
  4230. *
  4231. * @access public
  4232. * @return bool
  4233. */
  4234. public function processDocTempContent()
  4235. {
  4236. $docContentList = $this->dao->select('doc,content')->from(TABLE_DOCCONTENT)->fetchAll('doc');
  4237. foreach($docContentList as $docID => $doc)
  4238. {
  4239. if(empty($doc->content)) continue;
  4240. $this->dao->update(TABLE_DOC)
  4241. ->set('draft')->eq($doc->content)
  4242. ->where('id')->eq($docID)
  4243. ->exec();
  4244. }
  4245. return true;
  4246. }
  4247. /**
  4248. * Move kanban card data to kanbancell table.
  4249. *
  4250. * @access public
  4251. * @return void
  4252. *
  4253. */
  4254. public function moveKanbanData()
  4255. {
  4256. /* Move common kanban data. */
  4257. $cards = $this->dao->select('id,kanban,`column`,lane')->from(TABLE_KANBANCARD)->fetchAll('id');
  4258. $cellGroup = array();
  4259. foreach($cards as $cardID => $card)
  4260. {
  4261. if(!$card->lane or !$card->column) continue;
  4262. $key = $card->kanban . '-' . $card->lane . '-' . $card->column;
  4263. $cards = isset($cellGroup[$key]) ? $cellGroup[$key] . "$cardID," : ",$cardID,";
  4264. $cellGroup[$key] = $cards;
  4265. }
  4266. foreach($cellGroup as $key => $cards)
  4267. {
  4268. $key = explode('-', $key);
  4269. if(!is_array($key)) continue;
  4270. $cell = new stdclass();
  4271. $cell->kanban = $key[0];
  4272. $cell->lane = $key[1];
  4273. $cell->column = $key[2];
  4274. $cell->type = 'common';
  4275. $cell->cards = $cards;
  4276. $this->dao->insert(TABLE_KANBANCELL)->data($cell)->exec();
  4277. }
  4278. /* Drop group kanban data. */
  4279. $groupLanePairs = $this->dao->select('id')->from(TABLE_KANBANLANE)->where('`groupby`')->ne('')->fetchPairs();
  4280. if(!empty($groupLanePairs))
  4281. {
  4282. $this->dao->delete()->from(TABLE_KANBANLANE)->where('id')->in($groupLanePairs)->exec();
  4283. $this->dao->delete()->from(TABLE_KANBANCOLUMN)->where('lane')->in($groupLanePairs)->exec();
  4284. }
  4285. /* Move execution kanban data. */
  4286. $executionKanban = $this->dao->select('t1.id as `lane`, t1.execution, t1.type, t2.id as `column`, t2.cards')->from(TABLE_KANBANLANE)->alias('t1')
  4287. ->leftJoin(TABLE_KANBANCOLUMN)->alias('t2')->on('t1.id = t2.lane')
  4288. ->where('t1.execution')->gt(0)
  4289. ->fetchGroup('lane');
  4290. foreach($executionKanban as $laneID => $laneGroup)
  4291. {
  4292. foreach($laneGroup as $colData)
  4293. {
  4294. if(!$laneID or !$colData->column) continue;
  4295. $cell = new stdclass();
  4296. $cell->kanban = $colData->execution;
  4297. $cell->lane = $laneID;
  4298. $cell->column = $colData->column;
  4299. $cell->type = $colData->type;
  4300. $cell->cards = $colData->cards;
  4301. $this->dao->insert(TABLE_KANBANCELL)->data($cell)->exec();
  4302. }
  4303. }
  4304. }
  4305. /**
  4306. * Update kanban space team.
  4307. *
  4308. * @access public
  4309. * @return void
  4310. */
  4311. public function updateSpaceTeam()
  4312. {
  4313. $kanbanUsers = $this->dao->select("space, CONCAT(owner, ',', team, ',', whitelist) as users") ->from(TABLE_KANBAN)->fetchAll('space');
  4314. $spaceUsers = $this->dao->select("id, CONCAT(owner, ',', team, ',', whitelist) as users")->from(TABLE_KANBANSPACE)->fetchAll('id');
  4315. foreach($kanbanUsers as $spaceID => $kanban)
  4316. {
  4317. $team = zget($spaceUsers, $spaceID)->users;
  4318. $team = $team . ',' . $kanban->users;
  4319. $team = explode(',', $team);
  4320. $team = array_filter($team);
  4321. $team = array_unique($team);
  4322. $team = implode(',', $team);
  4323. $team = trim($team, ',');
  4324. $this->dao->update(TABLE_KANBANSPACE)->set('`team`')->eq($team)->where('id')->eq($spaceID)->exec();
  4325. }
  4326. $this->dao->update(TABLE_KANBANSPACE)->set('`whitelist`')->eq('')->exec();
  4327. $this->dao->update(TABLE_KANBAN)->set('`whitelist`')->eq('')->exec();
  4328. }
  4329. /**
  4330. * Adjust for bug required field.
  4331. *
  4332. * @access public
  4333. * @return bool
  4334. */
  4335. public function adjustBugRequired()
  4336. {
  4337. $data = $this->dao->select('*')->from(TABLE_CONFIG)
  4338. ->where('owner')->eq('system')
  4339. ->andWhere('module')->eq('bug')
  4340. ->andWhere('section')->eq('create')
  4341. ->andWhere('`key`')->eq('requiredFields')
  4342. ->fetch();
  4343. if(empty($data)) return true;
  4344. $data->value = ',' . $data->value . ',';
  4345. $data->value = str_replace(',project,', ',', $data->value);
  4346. $this->dao->update(TABLE_CONFIG)->set('`value`')->eq(trim($data->value, ','))->where('id')->eq($data->id)->exec();
  4347. return true;
  4348. }
  4349. /**
  4350. * Update branch when object have module.
  4351. *
  4352. * @access public
  4353. * @return bool
  4354. */
  4355. public function updateObjectBranch()
  4356. {
  4357. $moduleBranchPairs = $this->dao->select('id,branch')->from(TABLE_MODULE)->where('branch')->ne(0)->fetchPairs();
  4358. if(empty($moduleBranchPairs)) return true;
  4359. $storyModulePairs = $this->dao->select('module')->from(TABLE_STORY)->where('module')->in(array_keys($moduleBranchPairs))->andWhere('branch')->eq(0)->fetchPairs();
  4360. foreach($storyModulePairs as $moduleID)
  4361. {
  4362. if(!isset($moduleBranchPairs[$moduleID])) continue;
  4363. $this->dao->update(TABLE_STORY)->set('`branch`')->eq($moduleBranchPairs[$moduleID])->where('module')->eq($moduleID)->exec();
  4364. }
  4365. $bugModulePairs = $this->dao->select('module')->from(TABLE_BUG)->where('module')->in(array_keys($moduleBranchPairs))->andWhere('branch')->eq(0)->fetchPairs();
  4366. foreach($bugModulePairs as $moduleID)
  4367. {
  4368. if(!isset($moduleBranchPairs[$moduleID])) continue;
  4369. $this->dao->update(TABLE_BUG)->set('`branch`')->eq($moduleBranchPairs[$moduleID])->where('module')->eq($moduleID)->exec();
  4370. }
  4371. $caseModulePairs = $this->dao->select('module')->from(TABLE_CASE)->where('module')->in(array_keys($moduleBranchPairs))->andWhere('branch')->eq(0)->fetchPairs();
  4372. foreach($caseModulePairs as $moduleID)
  4373. {
  4374. if(!isset($moduleBranchPairs[$moduleID])) continue;
  4375. $this->dao->update(TABLE_CASE)->set('`branch`')->eq($moduleBranchPairs[$moduleID])->where('module')->eq($moduleID)->exec();
  4376. }
  4377. return true;
  4378. }
  4379. /**
  4380. * Update branch of project linked stories.
  4381. *
  4382. * @access public
  4383. * @return bool
  4384. */
  4385. public function updateProjectStories()
  4386. {
  4387. $storyPairs = $this->dao->select('t1.story, t2.branch')->from(TABLE_PROJECTSTORY)->alias('t1')
  4388. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id')
  4389. ->fetchPairs();
  4390. foreach($storyPairs as $storyID => $branch)
  4391. {
  4392. $this->dao->update(TABLE_PROJECTSTORY)->set('branch')->eq($branch)->where('story')->eq($storyID)->exec();
  4393. }
  4394. return true;
  4395. }
  4396. /**
  4397. * Update project linked branch.
  4398. *
  4399. * @access public
  4400. * @return void
  4401. */
  4402. public function updateProjectLinkedBranch()
  4403. {
  4404. $projectProducts = $this->dao->select('t1.project,t1.story,t2.branch,t2.product')->from(TABLE_PROJECTSTORY)->alias('t1')
  4405. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
  4406. ->where('t2.branch')->ne(0)
  4407. ->fetchGroup('project');
  4408. $projectBranches = array();
  4409. foreach($projectProducts as $projectID => $stories)
  4410. {
  4411. foreach($stories as $story)
  4412. {
  4413. if(!isset($projectBranches[$projectID])) $projectBranches[$projectID] = array();
  4414. if(!isset($projectBranches[$projectID][$story->product])) $projectBranches[$projectID][$story->product] = array();
  4415. $projectBranches[$projectID][$story->product][$story->branch] = $story->branch;
  4416. }
  4417. }
  4418. foreach($projectBranches as $projectID => $products)
  4419. {
  4420. foreach($products as $productID => $branches)
  4421. {
  4422. foreach($branches as $branchID)
  4423. {
  4424. $data = new stdClass();
  4425. $data->project = $projectID;
  4426. $data->product = $productID;
  4427. $data->branch = $branchID;
  4428. $this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec();
  4429. }
  4430. }
  4431. }
  4432. return true;
  4433. }
  4434. /**
  4435. * Document library for updating documents.
  4436. *
  4437. * @access public
  4438. * @return bool
  4439. */
  4440. public function updateDocField()
  4441. {
  4442. $hasModuleDocs = $this->dao->select('*')->from(TABLE_DOC)->where('module')->ne(0)->fetchAll('id');
  4443. $docModules = $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('doc')->fetchAll('id');
  4444. foreach($hasModuleDocs as $doc)
  4445. {
  4446. $libID = isset($docModules[$doc->module]->root) ? $docModules[$doc->module]->root : 0;
  4447. if(!$libID or $libID == $doc->lib) continue;
  4448. $this->dao->update(TABLE_DOC)->set('lib')->eq($libID)->where('id')->eq($doc->id)->exec();
  4449. }
  4450. return true;
  4451. }
  4452. /**
  4453. * Update activated date by history action.
  4454. *
  4455. * @access public
  4456. * @return bool
  4457. */
  4458. public function updateActivatedDate()
  4459. {
  4460. $actions = $this->dao->select('objectID, objectType, max(date) as date')->from(TABLE_ACTION)->where('action')->eq('activated')->andWhere('objectType')->in('story, task, bug')->groupBy('objectID, objectType')->fetchAll();
  4461. foreach($actions as $action)
  4462. {
  4463. $table = TABLE_BUG;
  4464. if($action->objectType == 'story') $table = TABLE_STORY;
  4465. if($action->objectType == 'task') $table = TABLE_TASK;
  4466. $this->dao->update($table)->set('activatedDate')->eq($action->date)->where('id')->eq($action->objectID)->exec();
  4467. }
  4468. return true;
  4469. }
  4470. /**
  4471. * Update group date for lite.
  4472. *
  4473. * @access public
  4474. * @return bool
  4475. * */
  4476. public function updateGroup4Lite()
  4477. {
  4478. $privTable = $this->config->db->prefix . 'grouppriv';
  4479. $adminPrivs = $this->dao->select('module,method')->from($privTable)->where('`group`')->eq('1')->andWhere('module')->notin($this->config->upgrade->unsetModules)->fetchAll();
  4480. $pmPrivs = $this->dao->select('module,method')->from($privTable)->where('`group`')->eq('4')->andWhere('module')->notin($this->config->upgrade->unsetModules)->fetchAll();
  4481. $topPrivs = $this->dao->select('module,method')->from($privTable)->where('`group`')->eq('9')->andWhere('module')->notin($this->config->upgrade->unsetModules)->fetchAll();
  4482. $liteGroup = $this->dao->select('*')->from(TABLE_GROUP)->where('vision')->eq('lite')->fetchAll();
  4483. $sql = 'REPLACE INTO ' . TABLE_GROUPPRIV . ' VALUES ';
  4484. foreach($liteGroup as $group)
  4485. {
  4486. if($group->role == 'liteAdmin' and !empty($adminPrivs))
  4487. {
  4488. foreach($adminPrivs as $priv)
  4489. {
  4490. $sql .= "($group->id, ";
  4491. $sql .= "'$priv->module', ";
  4492. $sql .= "'$priv->method'), ";
  4493. }
  4494. }
  4495. if($group->role == 'liteProject' and !empty($pmPrivs))
  4496. {
  4497. foreach($pmPrivs as $priv)
  4498. {
  4499. $sql .= "($group->id, ";
  4500. $sql .= "'$priv->module', ";
  4501. $sql .= "'$priv->method'), ";
  4502. }
  4503. }
  4504. if($group->role == 'liteTeam' and !empty($topPrivs))
  4505. {
  4506. foreach($topPrivs as $priv)
  4507. {
  4508. $sql .= "($group->id, ";
  4509. $sql .= "'$priv->module', ";
  4510. $sql .= "'$priv->method'), ";
  4511. }
  4512. }
  4513. }
  4514. $sql = rtrim($sql, ', ') . ';';
  4515. $this->dao->exec($sql);
  4516. return true;
  4517. }
  4518. /**
  4519. * 获取需要移动的扩展文件。
  4520. * Gets the extension file to be moved.
  4521. *
  4522. * @access public
  4523. * @return array
  4524. */
  4525. public function getExtFiles()
  4526. {
  4527. /* Get all modules and skip modules. */
  4528. $files = array();
  4529. $allModules = glob($this->app->moduleRoot . '*');
  4530. $skipModules = $this->getEncryptedModules($allModules);
  4531. foreach($allModules as $modulePath)
  4532. {
  4533. $module = basename($modulePath);
  4534. if(in_array($module, $skipModules)) continue; // If the module is in the skip modules, skip it.
  4535. /* Get directory root path, and directories in the directory root path. */
  4536. $dirRoot = in_array($module, $this->config->upgrade->openModules) ? $modulePath . DS . 'ext' : $modulePath;
  4537. $dirs = glob($dirRoot . DS . '*');
  4538. /* Get plugin files in the directories. */
  4539. foreach($dirs as $dirPath)
  4540. {
  4541. $dir = basename($dirPath);
  4542. $realPath = is_file($dirPath) ? $dirRoot : $dirPath;
  4543. $path = in_array($module, $this->config->upgrade->openModules) ? $module . DS . 'ext' . DS . $dir : $module . DS . $dir;
  4544. if(is_dir($realPath))
  4545. {
  4546. $files += $this->getPluginFiles($module, $dir, $realPath, $path);
  4547. }
  4548. }
  4549. }
  4550. return $files;
  4551. }
  4552. /**
  4553. * 获取非开源版的模块。
  4554. * Get modules that are not open source.
  4555. *
  4556. * @param array $allModules
  4557. * @access public
  4558. * @return array
  4559. */
  4560. public function getEncryptedModules($allModules)
  4561. {
  4562. $encryptModules = array();
  4563. foreach($allModules as $modulePath)
  4564. {
  4565. $customFiles = array();
  4566. $module = basename($modulePath);
  4567. if(in_array($module, $this->config->upgrade->openModules))
  4568. {
  4569. $extRoot = $modulePath . DS . 'ext';
  4570. if(!is_dir($extRoot))
  4571. {
  4572. /* If the ext directory doesn't exist, append it to encryptModules. */
  4573. $encryptModules[] = $module;
  4574. continue;
  4575. }
  4576. else
  4577. {
  4578. /* Get custom files in the control and model directories. */
  4579. foreach(array('control', 'model') as $dir)
  4580. {
  4581. $realPath = $extRoot . DS . $dir;
  4582. $path = $module . DS . 'ext' . DS . $dir;
  4583. if(!is_dir($realPath)) continue;
  4584. $customFiles += $this->getPluginFiles($module, $dir, $realPath, $path);
  4585. }
  4586. }
  4587. }
  4588. else
  4589. {
  4590. /* Check whether control and model are custom files. */
  4591. foreach(array('control.php', 'model.php') as $file)
  4592. {
  4593. $filePath = $modulePath . DS . $file;
  4594. if(!is_file($filePath)) continue;
  4595. $customFiles += $this->getPluginFiles($module, $file, $modulePath, $module);
  4596. }
  4597. }
  4598. /* If the customFiles is empty or the module is owt, append it to encryptModules. */
  4599. if(empty($customFiles) || $module == 'owt') $encryptModules[$module] = $module;
  4600. }
  4601. return $encryptModules;
  4602. }
  4603. /**
  4604. * 获取插件文件.
  4605. * Get plugin files.
  4606. *
  4607. * @param string $module
  4608. * @param string $dir
  4609. * @param string $realPath
  4610. * @param string $path
  4611. * @access public
  4612. * @return array
  4613. */
  4614. public function getPluginFiles($module, $dir, $realPath, $path)
  4615. {
  4616. $pluginFiles = array();
  4617. $files = is_file($realPath . DS . $dir) ? array($dir) : glob($realPath . DS . '*');
  4618. foreach($files as $file)
  4619. {
  4620. $file = basename($file);
  4621. $filePath = $realPath . DS . $file;
  4622. $fileName = is_file($realPath . DS . $dir) ? $path : $path . DS . $file;
  4623. if(is_dir($filePath))
  4624. {
  4625. /* If you are currently pointing to a directory, the files in that directory will be traversed. */
  4626. $pluginFiles += $this->getPluginFiles($module, $dir, $filePath, $fileName);
  4627. }
  4628. else
  4629. {
  4630. /* Get first line in the file. */
  4631. $handle = fopen($filePath, 'r');
  4632. $line = fgets($handle);
  4633. fclose($handle);
  4634. /* If the current file isn't encrypted and the file isn't the system file, append it to pluginFiles. */
  4635. if(is_string($line) && strpos($line, '<?php //') === false)
  4636. {
  4637. $systemFiles = file_get_contents('systemfiles.txt');
  4638. $systemFiles = str_replace('/', DS, $systemFiles);
  4639. if(strpos($systemFiles, ",{$fileName},") !== false) continue;
  4640. $pluginFiles[$fileName] = $fileName;
  4641. }
  4642. }
  4643. }
  4644. return $pluginFiles;
  4645. }
  4646. /**
  4647. * 获取自定义的模块。
  4648. * Get custom modules.
  4649. *
  4650. * @param array $allModules
  4651. * @access public
  4652. * @return array
  4653. */
  4654. public function getCustomModules($allModules)
  4655. {
  4656. /* Get system files. */
  4657. $systemFiles = file_get_contents($this->app->getModuleRoot() . 'upgrade/systemfiles.txt');
  4658. $systemFiles = str_replace('/', DS, $systemFiles);
  4659. $customModules = array();
  4660. foreach($allModules as $modulePath)
  4661. {
  4662. $module = basename($modulePath);
  4663. /* If the module isn't the open module and doesn't exist in the system files, it's the custom module. */
  4664. if(!in_array($module, $this->config->upgrade->openModules) && !preg_match("#{$module}(/[a-z]*)*(/[a-z]+.[a-z]+)+#", $systemFiles)) $customModules[$module] = $module;
  4665. }
  4666. return $customModules;
  4667. }
  4668. /**
  4669. * 移动扩展文件。
  4670. * Move extension files.
  4671. *
  4672. * @access public
  4673. * @return array
  4674. */
  4675. public function moveExtFiles()
  4676. {
  4677. /* Get files and custom root directory, set response. */
  4678. $data = fixer::input('post')->get();
  4679. $customRoot = $this->app->appRoot . 'extension' . DS . 'custom';
  4680. $response = array('result' => 'success');
  4681. foreach($data->files as $file => $check)
  4682. {
  4683. if(!preg_match('/[a-z]/', $file[0])) continue;
  4684. $fromPath = $this->app->getModuleRoot() . $file;
  4685. if(!is_file($fromPath)) continue;
  4686. $dirRoot = $customRoot . DS . dirname($file);
  4687. $toPath = $dirRoot . DS . basename($file);
  4688. /* If the directory doesn't exist and there is no permission to create the directory, set the response and return it. */
  4689. if(!is_dir($dirRoot))
  4690. {
  4691. if(!mkdir($dirRoot, 0777, true))
  4692. {
  4693. $response['result'] = 'fail';
  4694. $response['command'] = 'chmod o=rwx -R '. $this->app->appRoot . 'extension/custom';
  4695. return $response;
  4696. }
  4697. }
  4698. /* Copy from-path to to-path, and replace the load path in the to-path files. */
  4699. copy($fromPath, $toPath);
  4700. $this->replaceIncludePath($toPath);
  4701. }
  4702. return $response;
  4703. }
  4704. /**
  4705. * 移除收费版本的目录。
  4706. * Remove encrypted directories.
  4707. *
  4708. * @param string $script
  4709. * @access public
  4710. * @return array
  4711. */
  4712. public function removeEncryptedDir($script)
  4713. {
  4714. $dir = dirname($script);
  4715. if(!is_writable($dir)) return "chmod 777 {$dir}";
  4716. $command = [];
  4717. $zfile = $this->app->loadClass('zfile');
  4718. $allModules = glob($this->app->moduleRoot . '*');
  4719. $skipModules = $this->getEncryptedModules($allModules);
  4720. $customModules = $this->getCustomModules($allModules);
  4721. $modules = $skipModules + $customModules;
  4722. foreach($modules as $module)
  4723. {
  4724. if(in_array($module, $this->config->upgrade->openModules)) continue; // If the module is open module, skip it.
  4725. $dirPath = $this->app->moduleRoot . $module;
  4726. if(!$zfile->removeDir($dirPath)) $command[] = 'rm -f -r ' . $dirPath; // If the directory can't be removed, append the command for deleting a directory.
  4727. }
  4728. if(!$command) return '';
  4729. asort($command);
  4730. $content = "#!/bin/bash\n" . implode("\n", $command);
  4731. file_put_contents($script, $content);
  4732. return "/bin/bash $script";
  4733. }
  4734. /**
  4735. * 替换文件的加载路径。
  4736. * Replace the load path of the file.
  4737. *
  4738. * @param string $filePath
  4739. * @access public
  4740. * @return void
  4741. */
  4742. public function replaceIncludePath($filePath)
  4743. {
  4744. $content = file_get_contents($filePath); // Get file contents.
  4745. if(strpos(basename($filePath), 'html')) // If the file type is html.
  4746. {
  4747. /**
  4748. * Perform a regular expression search and replace.
  4749. * eg: `include '../../../common/view/header.lite.html.php'` to `include $app->getModuleRoot() . 'common/view/header.lite.html.php'`
  4750. */
  4751. $content = preg_replace('#(include )(\'|")((../){2,})([a-z]+/)(?!ext/)([a-z]+/)#', '$1' . '$app->getModuleRoot() . ' . '$2$5$6', $content);
  4752. /* Get system files. */
  4753. $systemFiles = file_get_contents('systemfiles.txt');
  4754. $systemFiles = str_replace('/', DS, $systemFiles);
  4755. /* Perform a global regular expression match. eg: `include '../../../common/ext/view/calendar.html.php'` */
  4756. preg_match_all('#(include )(\'|")(../){2,}[a-z]+/ext/[a-z]+/(([a-z]+[.]?)+)#', $content, $matches);
  4757. foreach($matches[0] as $fileName)
  4758. {
  4759. /* Get file name. eg: `include '../../../common/ext/view/calendar.html.php'` to `common/ext/view/calendar.html.php` as file name. */
  4760. $fileName = preg_replace("#(include )('|\")((../){2,})#", "", $fileName);
  4761. if(strpos($systemFiles, $fileName) !== false)
  4762. {
  4763. /* If the file is the system file, replace its include path. eg: `include '../../../common/ext/view/calendar.html.php'` to `include $app->appRoot . 'extension/max/common/ext/view/calendar.html.php'`. */
  4764. $fileName = basename($fileName);
  4765. $content = preg_replace('#(include )(\'|")((../){2,})([a-z]+/ext/view/' . $fileName . ')#', '$1' . '$app->appRoot . ' . '$2extension/max/$5', $content);
  4766. }
  4767. else
  4768. {
  4769. /* If the file isn't the system file, replace its include path. eg: `include '../../../common/ext/view/calendar.html.php'` to `include $app->appRoot . 'extension/custom/common/ext/view/calendar.html.php'`. */
  4770. $fileName = basename($fileName);
  4771. $content = preg_replace('#(include )(\'|")((../){2,})([a-z]+/ext/view/' . $fileName . ')#', '$1' . '$app->appRoot . ' . '$2extension/custom/$5', $content);
  4772. }
  4773. }
  4774. }
  4775. else // If the file type is php.
  4776. {
  4777. /* Get module name. */
  4778. $dirPath = dirname($filePath);
  4779. $dir = str_replace($this->app->appRoot . 'extension' . DS . 'custom' .DS , '', $dirPath);
  4780. $dirList = explode(DS, $dir);
  4781. $moduleName = $dirList[0];
  4782. /* Replace the include path of the control. */
  4783. $content = str_replace("include '../../control.php';", "helper::importControl('$moduleName');", $content);
  4784. $content = str_replace("helper::import('../../control.php');", "helper::importControl('$moduleName');", $content);
  4785. $content = str_replace('helper::import(dirname(dirname(dirname(__FILE__))) . "/control.php");', "helper::importControl('$moduleName');", $content);
  4786. }
  4787. file_put_contents($filePath, $content); // Save the file.
  4788. }
  4789. /**
  4790. * Add groups default kanban private
  4791. *
  4792. * @access public
  4793. * @return void
  4794. */
  4795. public function addDefaultKanbanPri()
  4796. {
  4797. /* Fix bug #17954. */
  4798. $hasKanbanPri = $this->dao->select('*')->from(TABLE_GROUPPRIV)->where('module')->eq('kanban')->fetch();
  4799. if(!$hasKanbanPri)
  4800. {
  4801. $this->app->loadLang('group');
  4802. $groups = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->in('admin,pm,po')->fetchPairs('id');
  4803. foreach($groups as $groupID)
  4804. {
  4805. foreach($this->lang->resource->kanban as $method => $name)
  4806. {
  4807. if(stripos($method, 'delete') === false)
  4808. {
  4809. $groupPriv = new stdclass();
  4810. $groupPriv->group = $groupID;
  4811. $groupPriv->module = 'kanban';
  4812. $groupPriv->method = $method;
  4813. $this->dao->insert(TABLE_GROUPPRIV)->data($groupPriv)->exec();
  4814. }
  4815. }
  4816. }
  4817. }
  4818. }
  4819. /**
  4820. * Update the story reviewer when 12 version to 15.
  4821. *
  4822. * @access public
  4823. * @return void
  4824. */
  4825. public function updateStoryReviewer()
  4826. {
  4827. $isOldVersion = false;
  4828. $fromVersion = str_replace('_', '.', $this->fromVersion);
  4829. if(is_numeric($fromVersion[0]) and version_compare($fromVersion, '12.0.0', '<='))
  4830. {
  4831. $isOldVersion = true;
  4832. }
  4833. elseif($fromVersion[0] == 'p' and version_compare($fromVersion, 'pro9.0.3', '<='))
  4834. {
  4835. $isOldVersion = true;
  4836. }
  4837. elseif($fromVersion[0] == 'b' and version_compare($fromVersion, 'biz4.1.3', '<='))
  4838. {
  4839. $isOldVersion = true;
  4840. }
  4841. if(!$isOldVersion) return;
  4842. $stories = $this->dao->select('t1.*,t2.PO,t2.createdBy')->from(TABLE_STORY)->alias('t1')
  4843. ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
  4844. ->where('t1.deleted')->eq('0')
  4845. ->andWhere('t2.deleted')->eq('0')
  4846. ->andWhere('t1.status')->in('draft,changed')
  4847. ->fetchAll('id');
  4848. foreach($stories as $storyID => $story)
  4849. {
  4850. if(!empty($story->assignedTo))
  4851. {
  4852. $story->reviewer = $story->assignedTo;
  4853. }
  4854. elseif(!empty($story->PO))
  4855. {
  4856. $story->reviewer = $story->PO;
  4857. }
  4858. else
  4859. {
  4860. $story->reviewer = $story->createdBy;
  4861. }
  4862. $data = new stdclass();
  4863. $data->story = $storyID;
  4864. $data->version = $story->version;
  4865. $data->reviewer = $story->reviewer;
  4866. $data->result = '';
  4867. $data->reviewDate = '';
  4868. $this->dao->insert(TABLE_STORYREVIEW)->data($data)->exec();
  4869. }
  4870. }
  4871. /**
  4872. * Update the project status.
  4873. *
  4874. * @access public
  4875. * @return void
  4876. */
  4877. public function updateProjectStatus()
  4878. {
  4879. $projects = $this->dao->select('id')->from(TABLE_PROJECT)
  4880. ->where('deleted')->eq('0')
  4881. ->andWhere('status')->eq('doing')
  4882. ->andWhere('type')->eq('project')
  4883. ->andWhere('realBegan')->eq('0000-00-00')
  4884. ->fetchAll('id');
  4885. if(empty($projects)) return;
  4886. $projectIDList = array_keys($projects);
  4887. $executions = $this->dao->select('id,project,min(realBegan) as minBegan')->from(TABLE_EXECUTION)
  4888. ->where('status')->eq('doing')
  4889. ->andWhere('deleted')->eq('0')
  4890. ->andWhere('project')->in($projectIDList)
  4891. ->groupBy('project')
  4892. ->fetchAll();
  4893. if(empty($executions)) return;
  4894. foreach ($executions as $execution) $this->dao->update(TABLE_PROJECT)->set('realBegan')->eq($execution->minBegan)->where('id')->eq($execution->project)->exec();
  4895. }
  4896. /**
  4897. * Change story need Review.
  4898. *
  4899. * @access public
  4900. * @return void
  4901. */
  4902. public function changeStoryNeedReview()
  4903. {
  4904. $this->loadModel('story');
  4905. $this->loadModel('setting');
  4906. $rndNeedReview = $this->setting->getItem('owner=system&vision=rnd&module=story&section=&key=needReview');
  4907. $liteNeedReview = $this->setting->getItem('owner=system&vision=lite&module=story&section=&key=needReview');
  4908. $data = new stdclass();
  4909. $data->forceReview = '';
  4910. $data->forceReviewDepts = '';
  4911. $data->forceReviewRoles = '';
  4912. if(!empty($rndNeedReview)) $this->setting->setItems("system.story@rnd", $data);
  4913. if(!empty($liteNeedReview)) $this->setting->setItems("system.story@lite", $data);
  4914. $this->setting->deleteItems('owner=system&module=story&section=&key=forceReviewAll');
  4915. }
  4916. /**
  4917. * The setlane permission is deleted. We need to replace setlane with editlanename and editlanecolor.
  4918. *
  4919. * @access public
  4920. * @return void
  4921. */
  4922. public function replaceSetLanePriv()
  4923. {
  4924. $groupIDList = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  4925. ->where('module')->eq('kanban')
  4926. ->andWhere('method')->eq('setLane')
  4927. ->fetchAll();
  4928. if(!empty($groupIDList))
  4929. {
  4930. $this->dao->delete()->from(TABLE_GROUPPRIV)
  4931. ->where('module')->eq('kanban')
  4932. ->andWhere('method')->eq('setLane')
  4933. ->exec();
  4934. }
  4935. foreach($groupIDList as $groupID)
  4936. {
  4937. $data = new stdClass();
  4938. $data->group = $groupID->group;
  4939. $data->module = 'kanban';
  4940. $data->method = 'editLaneName';
  4941. $this->dao->insert(TABLE_GROUPPRIV)->data($data)->exec();
  4942. $data->method = 'editLaneColor';
  4943. $this->dao->insert(TABLE_GROUPPRIV)->data($data)->exec();
  4944. }
  4945. return true;
  4946. }
  4947. /**
  4948. * Update path and grade of program, project and execution.
  4949. *
  4950. * @access public
  4951. * @return bool
  4952. */
  4953. public function updateProjectData()
  4954. {
  4955. /* Process programs. */
  4956. $programs = $this->dao->select('id,parent,grade,path')->from(TABLE_PROJECT)->where('type')->eq('program')->orderBy('parent_asc')->fetchAll('id');
  4957. foreach($programs as $program)
  4958. {
  4959. if(!$program->parent)
  4960. {
  4961. $program->grade = 1;
  4962. $program->path = ",$program->id,";
  4963. }
  4964. else
  4965. {
  4966. $program->grade = $programs[$program->parent]->grade + 1;
  4967. $program->path = $programs[$program->parent]->path . "$program->id,";
  4968. }
  4969. $this->dao->update(TABLE_PROGRAM)
  4970. ->set('path')->eq($program->path)
  4971. ->set('grade')->eq($program->grade)
  4972. ->where('id')->eq($program->id)->exec();
  4973. }
  4974. /* Process projects. */
  4975. $projects = $this->dao->select('id,project,parent,grade,path')->from(TABLE_PROJECT)->where('type')->eq('project')->orderBy('parent_asc')->fetchAll('id');
  4976. foreach($projects as $project)
  4977. {
  4978. if(!$project->parent)
  4979. {
  4980. $project->grade = 1;
  4981. $project->path = ",$project->id,";
  4982. }
  4983. else
  4984. {
  4985. $project->grade = $programs[$project->parent]->grade + 1;
  4986. $project->path = $programs[$project->parent]->path . "$project->id,";
  4987. }
  4988. $this->dao->update(TABLE_PROJECT)
  4989. ->set('path')->eq($project->path)
  4990. ->set('grade')->eq($project->grade)
  4991. ->where('id')->eq($project->id)->exec();
  4992. }
  4993. /* Process executions. */
  4994. $sprints = $this->dao->select('id,project,parent,grade,path')->from(TABLE_PROJECT)
  4995. ->where('type')->ne('project')
  4996. ->andWhere('type')->ne('program')
  4997. ->orderBy('parent_asc')->fetchAll('id');
  4998. foreach($sprints as $sprint)
  4999. {
  5000. if($sprint->parent == $sprint->project)
  5001. {
  5002. $sprint->grade = 1;
  5003. $sprint->path = ",$sprint->project,$sprint->id,";
  5004. }
  5005. else
  5006. {
  5007. $sprint->grade = 2;
  5008. $sprint->path = $sprints[$sprint->parent]->path . "$sprint->id,";
  5009. }
  5010. $this->dao->update(TABLE_EXECUTION)
  5011. ->set('path')->eq($sprint->path)
  5012. ->set('grade')->eq($sprint->grade)
  5013. ->where('id')->eq($sprint->id)->exec();
  5014. }
  5015. return true;
  5016. }
  5017. /**
  5018. * Move project admins to new table.
  5019. *
  5020. * @access public
  5021. * @return void
  5022. */
  5023. public function moveProjectAdmins()
  5024. {
  5025. $adminGroupID = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->eq('projectAdmin')->fetch('id');
  5026. $projectAdmins = $this->dao->select('account, project')->from(TABLE_USERGROUP)->where('`group`')->eq($adminGroupID)->fetchPairs();
  5027. $i = 1;
  5028. foreach($projectAdmins as $account => $projects)
  5029. {
  5030. if(!$account or !$projects) continue;
  5031. $data = new stdclass();
  5032. $data->group = $i;
  5033. $data->account = $account;
  5034. $data->projects = $projects;
  5035. $this->dao->replace(TABLE_PROJECTADMIN)->data($data)->exec();
  5036. $i ++;
  5037. }
  5038. $this->dao->delete()->from(TABLE_USERGROUP)->where('`group`')->eq($adminGroupID)->exec();
  5039. }
  5040. /*
  5041. * Insert story view of execution.
  5042. *
  5043. * @access public
  5044. * @return bool
  5045. */
  5046. public function addStoryViewPriv()
  5047. {
  5048. $groupIdList = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  5049. ->where('module')->eq('story')
  5050. ->andWhere('method')->eq('view')
  5051. ->fetchPairs('group');
  5052. foreach($groupIdList as $groupID)
  5053. {
  5054. $this->dao->replace(TABLE_GROUPPRIV)
  5055. ->set('`group`')->eq($groupID)
  5056. ->set('module')->eq('execution')
  5057. ->set('method')->eq('storyView')
  5058. ->exec();
  5059. }
  5060. return true;
  5061. }
  5062. /*
  5063. * Add review issue approval data.
  5064. *
  5065. * @access public
  5066. * @return bool
  5067. */
  5068. public function addReviewIssusApprovalData()
  5069. {
  5070. $reviewIssues = $this->dao->select('id,review,type')->from(TABLE_REVIEWISSUE)
  5071. ->where('type')->eq('review')
  5072. ->andWhere('approval')->eq(0)
  5073. ->andWhere('deleted')->eq('0')
  5074. ->fetchAll('review');
  5075. if(empty($reviewIssues)) return false;
  5076. $reviewIds = array_unique(helper::arrayColumn($reviewIssues, 'review'));
  5077. $approvalsPairs = $this->dao->select('objectID, max(id) as approval')->from(TABLE_APPROVAL)
  5078. ->where('objectID')->in($reviewIds)
  5079. ->andWhere('objectType')->eq('review')
  5080. ->andWhere('result')->eq('fail')
  5081. ->andWhere('deleted')->eq(0)
  5082. ->groupBy('objectID')
  5083. ->fetchAll('objectID');
  5084. /* Add approval data. */
  5085. foreach($reviewIssues as $reviewIssue)
  5086. {
  5087. if(!isset($approvalsPairs[$reviewIssue->review]->approval)) continue;
  5088. $this->dao->update(TABLE_REVIEWISSUE)
  5089. ->set('approval')->eq($approvalsPairs[$reviewIssue->review]->approval)
  5090. ->where('review')->eq($reviewIssue->review)
  5091. ->andWhere('type')->eq('review')
  5092. ->andWhere('approval')->eq(0)
  5093. ->andWhere('deleted')->eq('0')
  5094. ->exec();
  5095. }
  5096. }
  5097. /**
  5098. * Xuan: Add `index` column to all message partition tables.
  5099. *
  5100. * @access public
  5101. * @return bool
  5102. */
  5103. public function xuanAddMessageIndexColumns()
  5104. {
  5105. $prefix = $this->config->db->prefix;
  5106. $tables = $this->dbh->query("SHOW TABLES LIKE '{$prefix}im_message\_%'")->fetchAll();
  5107. $tables = array_filter(array_map(function($table) use ($prefix)
  5108. {
  5109. $tableName = current(array_values((array)$table));
  5110. if(!preg_match("/{$prefix}im_message_[a-z]+/", $tableName)) return $tableName;
  5111. },
  5112. $tables
  5113. ));
  5114. if(empty($tables)) return true;
  5115. $query = '';
  5116. foreach($tables as $table) $query .= "ALTER TABLE `$table` ADD `index` int(11) unsigned DEFAULT 0 AFTER `date`;";
  5117. $this->dbh->query($query);
  5118. return !dao::isError();
  5119. }
  5120. /**
  5121. * Xuan: Re-index messages.
  5122. *
  5123. * @access public
  5124. * @return bool
  5125. */
  5126. public function xuanReindexMessages()
  5127. {
  5128. /** @var array[] $chatTablePairs Associations of chats and partition tables, without main table. */
  5129. $chatTablePairs = array();
  5130. ini_set('memory_limit', '-1');
  5131. set_time_limit(0);
  5132. /* Fetch chat and message partition table associations. */
  5133. $chatTableData = $this->dao->select('gid,tableName')->from(TABLE_IM_CHAT_MESSAGE_INDEX)->orderBy('id_asc')->fetchAll();
  5134. foreach($chatTableData as $chatTable)
  5135. {
  5136. if(isset($chatTablePairs[$chatTable->gid]))
  5137. {
  5138. $chatTablePairs[$chatTable->gid][] = $chatTable->tableName;
  5139. continue;
  5140. }
  5141. $chatTablePairs[$chatTable->gid] = array($chatTable->tableName);
  5142. }
  5143. /* Append all non-partitioned chats. */
  5144. $allChats = $this->dao->select('gid')->from(TABLE_IM_CHAT)->fetchPairs();
  5145. $nonPartitionedChats = array_diff(array_values($allChats), array_keys($chatTablePairs));
  5146. foreach($nonPartitionedChats as $chat) $chatTablePairs[$chat] = array();
  5147. /* Do index. */
  5148. foreach($chatTablePairs as $chat => $tables)
  5149. {
  5150. $result = $this->xuanDoIndex($chat, $tables);
  5151. if(!$result) return false;
  5152. }
  5153. return true;
  5154. }
  5155. /**
  5156. * Xuan: Index messages of chat in partition tables and main table.
  5157. *
  5158. * @param string $chat
  5159. * @param array $tables
  5160. * @return bool
  5161. */
  5162. public function xuanDoIndex($chat, $tables)
  5163. {
  5164. $messageIndex = 0;
  5165. $tables[] = str_replace('`', '', TABLE_IM_MESSAGE);
  5166. foreach($tables as $table)
  5167. {
  5168. $idIndices = array();
  5169. $ids = $this->dao->select('id')->from("`$table`")->where('cgid')->eq($chat)->fetchAll('id');
  5170. $ids = array_keys($ids);
  5171. if(empty($ids)) continue;
  5172. for($index = 1; $index <= count($ids); $index++) $idIndices[$ids[$index - 1]] = $index + $messageIndex;
  5173. $queryData = array();
  5174. foreach($idIndices as $id => $index) $queryData[] = "WHEN $id THEN $index";
  5175. $query = "UPDATE `$table` SET `index` = (CASE `id` " . implode(' ', $queryData) . " END) WHERE `id` IN(" . implode(',', $ids) . ");";
  5176. $this->dao->query($query);
  5177. $messageIndex = max(array_values($idIndices));
  5178. }
  5179. $this->dao->update(TABLE_IM_CHAT)->set('lastMessageIndex')->eq($messageIndex)->where('gid')->eq($chat)->exec();
  5180. return !dao::isError();
  5181. }
  5182. /**
  5183. * Xuan: Set lastReadMessageIndex into table im_chatuser.
  5184. *
  5185. * @access public
  5186. * @return bool
  5187. */
  5188. public function xuanUpdateLastReadMessageIndex()
  5189. {
  5190. $lastReadMessages = $this->dao->select('lastReadMessage')->from(TABLE_IM_CHATUSER)->where('lastReadMessage')->ne(0)->fetchAll('lastReadMessage');
  5191. $lastReadMessages = array_keys($lastReadMessages);
  5192. if(empty($lastReadMessages)) return true;
  5193. ini_set('memory_limit', '1024M');
  5194. set_time_limit(0);
  5195. $messages = $this->loadModel('im')->messageGetList('', $lastReadMessages, null, '', '', false);
  5196. if(empty($messages)) return;
  5197. $queryData = array();
  5198. foreach($messages as $message) $queryData[] = "WHEN {$message->id} THEN {$message->index}";
  5199. $query = "UPDATE " . TABLE_IM_CHATUSER . " SET `lastReadMessageIndex` = (CASE `lastReadMessage` " . implode(' ', $queryData) . " END) WHERE `id` IN(" . implode(',', $lastReadMessages) . ");";
  5200. $this->dao->query($query);
  5201. return !dao::isError();
  5202. }
  5203. /**
  5204. * Xuan: Fix chats without lastReadMessage.
  5205. *
  5206. * @access public
  5207. * @return bool
  5208. */
  5209. public function xuanFixChatsWithoutLastRead()
  5210. {
  5211. $zeroLastReadChats = $this->dao->select('cgid')->from(TABLE_IM_CHATUSER)->where('lastReadMessage')->eq(0)->fetchAll('cgid');
  5212. $zeroLastReadChats = array_keys($zeroLastReadChats);
  5213. if(empty($zeroLastReadChats)) return true;
  5214. ini_set('memory_limit', '1024M');
  5215. set_time_limit(0);
  5216. $lastMessages = $this->dao->select('MAX(`index`), cgid')->from(TABLE_IM_MESSAGE)->where('cgid')->in($zeroLastReadChats)->groupBy('cgid')->fetchAll('cgid');
  5217. if(empty($lastMessages)) return true;
  5218. $maxIndex = 'MAX(`index`)';
  5219. $queryData = array();
  5220. foreach($lastMessages as $cgid => $lastMessage) $queryData[] = "WHEN '{$cgid}' THEN {$lastMessage->$maxIndex}";
  5221. $query = "UPDATE " . TABLE_IM_CHATUSER . " SET `lastReadMessageIndex` = (CASE `cgid` " . implode(' ', $queryData) . " END) WHERE `cgid` IN('" . implode("','", array_keys($lastMessages)) . "');";
  5222. $this->dao->query($query);
  5223. return !dao::isError();
  5224. }
  5225. /**
  5226. * Process bug link bug.
  5227. *
  5228. * @access public
  5229. * @return void
  5230. */
  5231. public function processBugLinkBug()
  5232. {
  5233. $bugs = $this->dao->select('id,linkBug')->from(TABLE_BUG)->where('linkBug')->ne('')->fetchPairs();
  5234. foreach($bugs as $bugID => $linkBugs)
  5235. {
  5236. $linkBugs = explode(',', $linkBugs);
  5237. $this->dao->update(TABLE_BUG)->set("linkBug = TRIM(BOTH ',' from CONCAT(linkBug, ',$bugID'))")->where('id')->in($linkBugs)->andWhere('id')->ne($bugID)->andWhere("CONCAT(',', linkBug, ',')")->notlike("%,$bugID,%")->exec();
  5238. }
  5239. return !dao::isError();
  5240. }
  5241. /**
  5242. * Process created information.
  5243. *
  5244. * @access public
  5245. * @return void
  5246. */
  5247. public function processCreatedInfo()
  5248. {
  5249. $objectTypes = array('productplan', 'release', 'testtask', 'build');
  5250. $actions = $this->dao->select('objectType, objectID, actor, date')->from(TABLE_ACTION)->where('objectType')->in($objectTypes)->andWhere('action')->eq('opened')->fetchGroup('objectType');
  5251. foreach($actions as $objectType => $objectActions)
  5252. {
  5253. foreach($objectActions as $action)
  5254. {
  5255. $this->dao->update($this->config->objectTables[$objectType])->set('createdBy')->eq($action->actor)->set('createdDate')->eq($action->date)->where('id')->eq($action->objectID)->exec();
  5256. }
  5257. }
  5258. return !dao::isError();
  5259. }
  5260. /**
  5261. * Update approval process in Workflow.
  5262. *
  5263. * @access public
  5264. * @return void
  5265. */
  5266. public function updateApproval()
  5267. {
  5268. /* Judge whether the action has opened the approval process before. */
  5269. /* 判断动作 看之前是否开启过审批流 */
  5270. $actions = $this->dao->select('id, module, action, createdDate')->from(TABLE_WORKFLOWACTION)
  5271. ->where('role')->eq('approval')
  5272. ->andWhere('action')->in('submit,cancel,review')
  5273. ->fetchAll('id');
  5274. foreach($actions as $id => $action)
  5275. {
  5276. $module = $action->module;
  5277. $actionCode = $action->action;
  5278. $this->dao->update(TABLE_WORKFLOWACTION)->set('action')->eq('approval' . $action->action)->where('id')->eq($id)->exec();
  5279. /* Change the approval action of the module that has already enabled the approval function */
  5280. /* 改原来已经开启过审批功能的模块的审批动作 */
  5281. if(isset($this->config->upgrade->recoveryActions->{$module}->{$actionCode}))
  5282. {
  5283. $data = array_merge($this->config->upgrade->defaultActions, $this->config->upgrade->recoveryActions->{$module}->{$actionCode});
  5284. if(isset($data['hasLite']) && $data['hasLite'] === true)
  5285. {
  5286. unset($data['hasLite']);
  5287. $liteData = $data;
  5288. $liteData['vision'] = 'lite';
  5289. $this->dao->insert(TABLE_WORKFLOWACTION)->data($liteData)->exec();
  5290. }
  5291. $this->dao->insert(TABLE_WORKFLOWACTION)->data($data)->exec();
  5292. }
  5293. /* Change history */
  5294. /* 改历史记录 */
  5295. $this->dao->update(TABLE_ACTION)->set('action')->eq('approval' . $action->action)->where('objectType')->eq($module)->andWhere('action')->eq($action->action)->andWhere('date')->gt($action->createdDate)->exec();
  5296. /* Change the action field of the workflowlayout table */
  5297. /* 改workflowlayout表的action字段 */
  5298. $this->dao->update(TABLE_WORKFLOWLAYOUT)->set('action')->eq('approval' . $action->action)->where('module')->eq($module)->andWhere('action')->eq($action->action)->exec();
  5299. }
  5300. return !dao::isError();
  5301. }
  5302. /**
  5303. * Process createdBy of conditions.
  5304. *
  5305. * @access public
  5306. * @return bool
  5307. */
  5308. public function processCreatedBy()
  5309. {
  5310. $this->app->loadLang('workflow');
  5311. $this->app->loadModuleConfig('workflow');
  5312. $modules = $this->dao->select('module')->from(TABLE_WORKFLOW)->where('buildin')->eq('1')->andWhere('approval')->eq('enabled')->andWhere('module')->in(array_keys($this->config->workflow->buildin->createdBy))->fetchPairs();
  5313. $actions = $this->dao->select('id, module, conditions')->from(TABLE_WORKFLOWACTION)->where('module')->in($modules)->andWhere('action')->in('submit, cancel, edit, delete')->fetchAll('id');
  5314. foreach($actions as $id => $action)
  5315. {
  5316. $conditions = json_decode($action->conditions);
  5317. if(empty($conditions)) continue;
  5318. foreach($conditions as $index => $condition)
  5319. {
  5320. foreach($condition->fields as $field)
  5321. {
  5322. if($field->field == 'createdBy' && zget($this->config->workflow->buildin->createdBy, $action->module, '')) $field->field = zget($this->config->workflow->buildin->createdBy, $action->module);
  5323. }
  5324. $conditions[$index] = $condition;
  5325. }
  5326. $this->dao->update(TABLE_WORKFLOWACTION)->set('conditions')->eq(json_encode($conditions))->where('id')->eq($id)->exec();
  5327. }
  5328. return !dao::isError();
  5329. }
  5330. /**
  5331. * Update story search index.
  5332. *
  5333. * @access public
  5334. * @return void
  5335. */
  5336. public function updateSearchIndex()
  5337. {
  5338. $requirementIds = $this->dao->select('t1.id')->from(TABLE_SEARCHINDEX)->alias('t1')
  5339. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.objectID = t2.id')
  5340. ->where('t1.objectType')->eq('story')
  5341. ->andWhere('t2.type')->eq('requirement')
  5342. ->fetchPairs('id');
  5343. $this->dao->update(TABLE_SEARCHINDEX)->set('objectType')->eq('requirement')->where('id')->in($requirementIds)->exec();
  5344. }
  5345. /**
  5346. * Add required rule to the built-in workflow status field.
  5347. *
  5348. * @access public
  5349. * @return void
  5350. */
  5351. public function addDefaultRuleToWorkflow()
  5352. {
  5353. $notemptyRule = $this->dao->select('id')->from(TABLE_WORKFLOWRULE)->where('rule')->eq('notempty')->fetch();
  5354. if(empty($notemptyRule)) return false;
  5355. $fields = $this->dao->select('*')->from(TABLE_WORKFLOWFIELD)->where('field')->eq('status')->andWhere('buildin')->eq(1)->fetchAll();
  5356. foreach($fields as $field)
  5357. {
  5358. if(strpos(',' . $field->rules . ',', ',' . $notemptyRule->id . ',') !== false) continue;
  5359. $rules = $notemptyRule->id;
  5360. if(!empty($field->rules)) $rules = $field->rules . ',' . $rules;
  5361. $this->dao->update(TABLE_WORKFLOWFIELD)->set('rules')->eq($rules)->where('id')->eq($field->id)->exec();
  5362. }
  5363. return !dao::isError();
  5364. }
  5365. /**
  5366. * Add workflow actions.
  5367. *
  5368. * @param int $version
  5369. * @access public
  5370. * @return void
  5371. */
  5372. public function addFlowActions($version)
  5373. {
  5374. $this->loadModel('workflow');
  5375. $this->loadModel('workflowaction');
  5376. $upgradeLang = $this->lang->workflowaction->upgrade[$version];
  5377. $upgradeConfig = $this->config->workflowaction->upgrade[$version];
  5378. if(empty($upgradeLang) || empty($upgradeConfig)) return true;
  5379. $this->lang->workflowaction->upgrade = new stdclass();
  5380. $this->config->workflowaction->upgrade = new stdclass();
  5381. foreach($upgradeLang as $module => $labels)
  5382. {
  5383. $this->lang->workflowaction->upgrade->actions = $labels;
  5384. foreach($upgradeConfig[$module] as $code => $config) $this->config->workflowaction->upgrade->$code = $config;
  5385. $flow = $this->dao->select('*')->from(TABLE_WORKFLOW)->where('module')->eq($module)->fetch();
  5386. if(!$flow) continue;
  5387. $this->workflow->createActions($flow, 'upgrade');
  5388. $this->dao->update(TABLE_WORKFLOWACTION)->set('extensionType')->eq('none')->set('role')->eq('buildin')->where('module')->eq($module)->andWhere('action')->in(array_keys($labels))->exec();
  5389. foreach($labels as $method => $label)
  5390. {
  5391. $workflowAction = $this->dao->select('*')->from(TABLE_WORKFLOWACTION)->where('module')->eq($module)->andWhere('action')->eq($method)->fetch();
  5392. if(!$workflowAction) continue;
  5393. unset($workflowAction->id);
  5394. $workflowAction->vision = $workflowAction->vision == 'lite' ? 'rnd' : 'lite';
  5395. $this->dao->replace(TABLE_WORKFLOWACTION)->data($workflowAction)->exec();
  5396. }
  5397. }
  5398. return !dao::isError();
  5399. }
  5400. /**
  5401. * Add flow fields.
  5402. *
  5403. * @param int $version
  5404. * @access public
  5405. * @return bool
  5406. */
  5407. public function addFlowFields($version)
  5408. {
  5409. if($this->config->edition == 'open') return true;
  5410. $this->loadModel('workflowfield');
  5411. $upgradeLang = zget($this->lang->workflowfield->upgrade, $version, array());
  5412. $upgradeConfig = zget($this->config->workflowfield->upgrade, $version, array());
  5413. $workFlowDataSource = $this->dao->select('code,id')->from(TABLE_WORKFLOWDATASOURCE)->where('type')->ne('system')->fetchPairs();
  5414. $field = new stdclass();
  5415. $field->buildin = '1';
  5416. $field->role = 'buildin';
  5417. $field->createdBy = $this->app->user->account;
  5418. $field->createdDate = helper::now();
  5419. foreach($upgradeLang as $module => $fields)
  5420. {
  5421. $field->module = $module;
  5422. foreach($fields as $code => $name)
  5423. {
  5424. $field->field = $code;
  5425. $field->name = $name;
  5426. $fieldConfig = isset($upgradeConfig[$module][$code]) ? $upgradeConfig[$module][$code] : array();
  5427. if(empty($fieldConfig)) continue;
  5428. foreach($fieldConfig as $key => $value) $field->$key = $value;
  5429. if($field->control == 'select' && is_string($field->options) && isset($workFlowDataSource[$field->options])) $field->options = $workFlowDataSource[$field->options];
  5430. $this->dao->delete()->from(TABLE_WORKFLOWFIELD)->where('module')->eq($module)->andWhere('field')->eq($code)->exec();
  5431. $this->dao->insert(TABLE_WORKFLOWFIELD)->data($field)->autoCheck()->exec();
  5432. }
  5433. }
  5434. return !dao::isError();
  5435. }
  5436. /**
  5437. * Process review linkages of approval.
  5438. *
  5439. * @access public
  5440. * @return bool
  5441. */
  5442. public function processReviewLinkages()
  5443. {
  5444. $linkagePairs = $this->dao->select('id, linkages')->from(TABLE_WORKFLOWACTION)->where('action')->eq('approvalreview')->andWhere('role')->eq('approval')->fetchPairs();
  5445. $oldLinkages = array();
  5446. $oldLinkages[0]['sources'][0]['field'] = 'reviewResult';
  5447. $oldLinkages[0]['sources'][0]['operator'] = '==';
  5448. $oldLinkages[0]['sources'][0]['value'] = 'reject';
  5449. $oldLinkages[0]['targets'][0]['field'] = 'reviewOpinion';
  5450. $oldLinkages[0]['targets'][0]['status'] = 'show';
  5451. $newLinkages = array();
  5452. $newLinkages[0]['sources'][0]['field'] = 'reviewResult';
  5453. $newLinkages[0]['sources'][0]['operator'] = '==';
  5454. $newLinkages[0]['sources'][0]['value'] = 'pass';
  5455. $newLinkages[0]['targets'][0]['field'] = 'reviewOpinion';
  5456. $newLinkages[0]['targets'][0]['status'] = 'hide';
  5457. foreach($linkagePairs as $id => $linkages)
  5458. {
  5459. if(helper::jsonEncode($oldLinkages) == $linkages)
  5460. {
  5461. $this->dao->update(TABLE_WORKFLOWACTION)->set('linkages')->eq(helper::jsonEncode($newLinkages))->where('id')->eq($id)->exec();
  5462. }
  5463. }
  5464. return !dao::isError();
  5465. }
  5466. /**
  5467. * Update story status.
  5468. *
  5469. * @access public
  5470. * @return void
  5471. */
  5472. public function updateStoryStatus()
  5473. {
  5474. /* After cancel the review of changed story, the story status should be "changing". */
  5475. $this->dao->update(TABLE_STORY)->set('status')->eq('changing')->where('status')->eq('draft')->andWhere('version')->gt(1)->exec();
  5476. /* The draft story with reviewers should be "reviewing". */
  5477. $reviewingStories = $this->dao->select('story')->from(TABLE_STORYREVIEW)->alias('t1')
  5478. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id and t1.version = t2.version')
  5479. ->where('t2.status')->eq('draft')
  5480. ->andWhere('t2.version')->eq(1)
  5481. ->fetchPairs();
  5482. $this->dao->update(TABLE_STORY)->set('status')->eq('reviewing')->where('id')->in($reviewingStories)->exec();
  5483. return !dao::isError();
  5484. }
  5485. /**
  5486. * Change FULLTEXT index for searchindex table.
  5487. *
  5488. * @access public
  5489. * @return void
  5490. */
  5491. public function rebuildFULLTEXT()
  5492. {
  5493. try
  5494. {
  5495. $table = TABLE_SEARCHINDEX;
  5496. $stmt = $this->dao->query("show index from $table");
  5497. $indexes = array();
  5498. while($index = $stmt->fetch())
  5499. {
  5500. if($index->Index_type != 'FULLTEXT') continue;
  5501. $indexes[$index->Key_name] = $index->Key_name;
  5502. }
  5503. if(!isset($indexes['title_content'])) $this->dao->exec( "ALTER TABLE {$table} ADD FULLTEXT `title_content` (`title`, `content`)");
  5504. if(isset($indexes['title'])) $this->dao->exec( "ALTER TABLE {$table} DROP INDEX `title`");
  5505. if(isset($indexes['content'])) $this->dao->exec( "ALTER TABLE {$table} DROP INDEX `content`");
  5506. }
  5507. catch(PDOException $e){}
  5508. return true;
  5509. }
  5510. /**
  5511. * Check whether the field exists.
  5512. *
  5513. * @param string $table
  5514. * @param string $field
  5515. * @access public
  5516. * @return bool
  5517. */
  5518. public function checkFieldsExists($table, $field)
  5519. {
  5520. $result = $this->dbh->query("show columns from `$table` like '$field'");
  5521. return $result->rowCount() > 0;
  5522. }
  5523. /**
  5524. * Update OS and browser of bug.
  5525. *
  5526. * @access public
  5527. * @return bool
  5528. */
  5529. public function updateOSAndBrowserOfBug()
  5530. {
  5531. $existOSList = $this->dao->select('distinct os')->from(TABLE_BUG)->where('os')->ne('')->fetchPairs();
  5532. $existBrowserList = $this->dao->select('distinct browser')->from(TABLE_BUG)->where('os')->ne('')->fetchPairs();
  5533. $deletedOSList = array('vista', 'win2012', 'win2008', 'win2003', 'win2000', 'wp8', 'wp7', 'symbian', 'freebsd');
  5534. $deletedBrowserList = array('ie7', 'ie6', 'firefox4', 'firefox3', 'firefox2', 'opera11', 'opera10', 'opera9', 'maxthon', 'uc');
  5535. $existList = array_merge($existOSList, $existBrowserList);
  5536. $deletedList = array_merge($deletedOSList, $deletedBrowserList);
  5537. foreach($deletedList as $deletedLang)
  5538. {
  5539. if(in_array($deletedLang, $existList)) continue;
  5540. $this->dao->delete()->from(TABLE_LANG)->where('module')->eq('bug')->andWhere('`key`')->eq($deletedLang)->andWhere('`system`')->eq(1)->andWhere('vision')->eq('rnd')->exec();
  5541. }
  5542. $this->dao->update(TABLE_LANG)->set('value')->eq('Mac OS')->where('module')->eq('bug')->andWhere('`key`')->eq('osx')->andWhere('value')->eq('OS X')->exec();
  5543. $this->dao->update(TABLE_LANG)->set('value')->eq('Opera 系列')->where('module')->eq('bug')->andWhere('`key`')->eq('opera')->andWhere('value')->eq('opera 系列')->exec();
  5544. return true;
  5545. }
  5546. /**
  5547. * Add user requirement privilege when URAndSR is open.
  5548. *
  5549. * @access public
  5550. * @return bool
  5551. */
  5552. public function addURPriv()
  5553. {
  5554. if(empty($this->config->URAndSR)) return true;
  5555. $sql = "REPLACE INTO " . TABLE_GROUPPRIV . " SELECT `group`,'requirement' as 'module',`method` FROM " . TABLE_GROUPPRIV . " WHERE `module` = 'story' AND `method` in ('create', 'batchEdit', 'edit', 'export', 'delete', 'view', 'change', 'review', 'batchReview', 'recall', 'close', 'batchClose', 'assignTo', 'batchAssignTo', 'activate', 'report', 'linkStory', 'batchChangeBranch', 'batchChangeModule', 'linkStories', 'batchEdit', 'import', 'exportTemplate')";
  5556. $this->dbh->exec($sql);
  5557. return true;
  5558. }
  5559. /**
  5560. * Sync case to project|execution if case create from import.
  5561. *
  5562. * @access public
  5563. * @return void
  5564. */
  5565. public function syncCase2Project()
  5566. {
  5567. if(strpos($this->fromVersion, 'max') === false) return;
  5568. $linkStoryCases = $this->dao->select('id, story, version, product')->from(TABLE_CASE)->where('story')->ne('0')->fetchAll('id');
  5569. $linkProjectCases = $this->dao->select('`case`, project')->from(TABLE_PROJECTCASE)->where('`case`')->ne('0')->andWhere('project')->ne('0')->fetchGroup('case', 'project');
  5570. if(empty($linkStoryCases)) return true;
  5571. $projectList = $this->dao->select('t1.project, t1.story, t1.product')->from(TABLE_PROJECTSTORY)->alias('t1')
  5572. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  5573. ->where('t2.status')->ne('closed')
  5574. ->fetchGroup('story', 'project');
  5575. if(empty($projectList)) return true;
  5576. foreach($linkStoryCases as $caseID => $case)
  5577. {
  5578. /* If story unlink project continue. */
  5579. if(!isset($projectList[$case->story])) continue;
  5580. $storyProjects = $projectList[$case->story];
  5581. $lastOrder = 1;
  5582. foreach($storyProjects as $projectID => $value)
  5583. {
  5584. /* If case linked project continue.*/
  5585. if(isset($linkProjectCases[$caseID][$projectID])) continue;
  5586. $data = new stdclass();
  5587. $data->project = $projectID;
  5588. $data->product = $case->product;
  5589. $data->case = $caseID;
  5590. $data->version = $case->version;
  5591. $data->order = $lastOrder ++;
  5592. $this->dao->insert(TABLE_PROJECTCASE)->data($data)->exec();
  5593. }
  5594. }
  5595. }
  5596. /**
  5597. * Update story file version.
  5598. *
  5599. * @access public
  5600. * @return void
  5601. */
  5602. public function updateStoryFile()
  5603. {
  5604. $storyFileList = $this->dao->select('*')->from(TABLE_FILE)->where('objectType')->in('story,requirement')->andWhere('extra')->ne('editor')->fetchAll('id');
  5605. $storyFiles = array();
  5606. foreach($storyFileList as $file)
  5607. {
  5608. if(!is_numeric($file->extra)) continue;
  5609. if(!isset($storyFiles[$file->objectID])) $storyFiles[$file->objectID] = '';
  5610. $storyFiles[$file->objectID] .= "$file->id,";
  5611. }
  5612. foreach($storyFiles as $storyID => $files) $this->dao->update(TABLE_STORYSPEC)->set('files')->eq($files)->where('story')->eq($storyID)->exec();
  5613. return true;
  5614. }
  5615. /*
  5616. * Convert task team to table: zt_taskteam.
  5617. *
  5618. * @access public
  5619. * @return void
  5620. */
  5621. public function convertTaskteam()
  5622. {
  5623. $oldTeamGroup = $this->dao->select('root as task, account, estimate, consumed, `left`')->from(TABLE_TEAM)->where('type')->eq('task')->fetchGroup('task');
  5624. foreach($oldTeamGroup as $taskID => $oldTeams)
  5625. {
  5626. $order = 0;
  5627. foreach($oldTeams as $oldTeam)
  5628. {
  5629. $oldTeam->order = $order;
  5630. $oldTeam->status = 'wait';
  5631. if($oldTeam->consumed > 0 and $oldTeam->left > 0) $oldTeam->status = 'doing';
  5632. if($oldTeam->consumed > 0 and $oldTeam->left == 0) $oldTeam->status = 'done';
  5633. $this->dao->insert(TABLE_TASKTEAM)->data($oldTeam)->exec();
  5634. $this->dao->update(TABLE_TASKESTIMATE)->set('`order`')->eq($order)->where('task')->eq($oldTeam->task)->andWhere('account')->eq($oldTeam->account)->exec();
  5635. $this->dao->update(TABLE_EFFORT)->set('`order`')->eq($order)->where('objectType')->eq('task')->andWhere('objectID')->eq($oldTeam->task)->andWhere('account')->eq($oldTeam->account)->exec();
  5636. $order ++;
  5637. }
  5638. }
  5639. $this->dao->delete()->from(TABLE_TEAM)->where('type')->eq('task')->exec();
  5640. }
  5641. /**
  5642. * Convert estimate to effort.
  5643. *
  5644. * @access public
  5645. * @return void
  5646. */
  5647. public function convertEstToEffort()
  5648. {
  5649. $estimates = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->orderBy('id')->fetchAll('id', false);
  5650. $this->app->loadLang('task');
  5651. $this->loadModel('action');
  5652. foreach($estimates as $estimate)
  5653. {
  5654. $relation = $this->action->getRelatedFields('task', $estimate->task);
  5655. $effort = new stdclass();
  5656. $effort->objectType = 'task';
  5657. $effort->objectID = $estimate->task;
  5658. $effort->product = $relation['product'];
  5659. $effort->project = (int)$relation['project'];
  5660. $effort->execution = (int)$relation['execution'];
  5661. $effort->account = $estimate->account;
  5662. $effort->work = empty($estimate->work) ? $this->lang->task->process : $estimate->work;
  5663. $effort->date = $estimate->date;
  5664. $effort->left = $estimate->left;
  5665. $effort->consumed = $estimate->consumed;
  5666. $effort->vision = $this->config->vision;
  5667. $effort->order = $estimate->order;
  5668. $this->dao->insert(TABLE_EFFORT)->data($effort)->exec();
  5669. $this->dao->delete()->from(TABLE_TASKESTIMATE)->where('id')->eq($estimate->id)->exec();
  5670. }
  5671. return true;
  5672. }
  5673. /**
  5674. * Xuan: Set ownedBy for group chats without it.
  5675. *
  5676. * @access public
  5677. * @return bool
  5678. */
  5679. public function xuanSetOwnedByForGroups()
  5680. {
  5681. $this->dao->update(TABLE_IM_CHAT)->set('ownedBy = createdBy')->where('ownedBy')->eq('')->exec();
  5682. return !dao::isError();
  5683. }
  5684. /**
  5685. * Xuan: Recover created date for chats.
  5686. *
  5687. * @access public
  5688. * @return bool
  5689. */
  5690. public function xuanRecoverCreatedDates()
  5691. {
  5692. $chats = $this->dao->select('gid, id')->from(TABLE_IM_CHAT)
  5693. ->where('createdDate')->eq('0000-00-00 00:00:00')
  5694. ->fetchPairs('gid');
  5695. if(empty($chats)) return true;
  5696. $createdDateData = array();
  5697. /* Try query earliest message date indexed. */
  5698. $indexedMinDates = $this->dao->select('gid, MIN(startDate)')->from(TABLE_IM_CHAT_MESSAGE_INDEX)
  5699. ->where('gid')->in(array_keys($chats))
  5700. ->groupBy('gid')
  5701. ->fetchPairs('gid');
  5702. /* Then try query earliest message date non-indexed from master table. */
  5703. $queryChats = array_diff(array_keys($chats), array_keys($indexedMinDates));
  5704. $minDates = $this->dao->select('cgid, MIN(date)')->from(TABLE_IM_MESSAGE)
  5705. ->where('cgid')->in($queryChats)
  5706. ->groupBy('cgid')
  5707. ->fetchPairs('cgid');
  5708. $knownMinDates = array_merge($indexedMinDates, $minDates);
  5709. $remainingChats = $chats;
  5710. foreach($chats as $cgid => $cid)
  5711. {
  5712. if(isset($knownMinDates[$cgid]))
  5713. {
  5714. $createdDateData[$cid] = $knownMinDates[$cgid];
  5715. unset($remainingChats[$cgid]);
  5716. }
  5717. }
  5718. /* Use other dates for chats without messages. */
  5719. $chatDates = $this->dao->select('id, gid, editedDate, lastActiveTime, dismissDate')->from(TABLE_IM_CHAT)
  5720. ->where('gid')->in(array_keys($remainingChats))
  5721. ->fetchAll('gid');
  5722. $chatDates = array_map(function($chatDate)
  5723. {
  5724. $dates = array_filter(array($chatDate->editedDate, $chatDate->lastActiveTime, $chatDate->dismissDate), function($date)
  5725. {
  5726. return $date != '0000-00-00 00:00:00';
  5727. });
  5728. $minDate = min($dates);
  5729. return $minDate;
  5730. }, $chatDates);
  5731. $knownMinDates = array_merge($knownMinDates, $chatDates);
  5732. if(empty($knownMinDates)) return true;
  5733. $queryData = array();
  5734. foreach($knownMinDates as $gid => $date) $queryData[] = "WHEN {$chats[$gid]} THEN '{$date}'";
  5735. if(empty($queryData)) return true;
  5736. $query = "UPDATE " . TABLE_IM_CHAT . " SET `createdDate` = (CASE `id` " . implode(' ', $queryData) . " END) WHERE `id` IN(" . implode(",", array_values($chats)) . ");";
  5737. $this->dao->query($query);
  5738. return !dao::isError();
  5739. }
  5740. /**
  5741. * Xuan: Set index range for chats in chat partition index table.
  5742. *
  5743. * @access public
  5744. * @return bool
  5745. */
  5746. public function xuanSetPartitionedMessageIndex()
  5747. {
  5748. ini_set('memory_limit', '1024M');
  5749. set_time_limit(0);
  5750. /* Fetch chat and message partition table associations with message range. */
  5751. $chatTableData = $this->dao->select('gid, tableName, start, end')->from(TABLE_IM_CHAT_MESSAGE_INDEX)
  5752. ->where('startIndex')->eq(0)
  5753. ->orWhere('endIndex')->eq(0)
  5754. ->orderBy('id_asc')
  5755. ->fetchAll();
  5756. if(empty($chatTableData)) return true;
  5757. /* Sort ranges by table. */
  5758. $tableRanges = array();
  5759. foreach($chatTableData as $chatTable)
  5760. {
  5761. if(isset($tableRanges[$chatTable->tableName]))
  5762. {
  5763. $tableRanges[$chatTable->tableName]->start[] = $chatTable->start;
  5764. $tableRanges[$chatTable->tableName]->end[] = $chatTable->end;
  5765. continue;
  5766. }
  5767. $tableRanges[$chatTable->tableName] = (object)array('start' => array($chatTable->start), 'end' => array($chatTable->end));
  5768. }
  5769. /* Query corresponding message indice. */
  5770. foreach($tableRanges as $tableName => $tableRange)
  5771. {
  5772. $ids = array_merge($tableRange->start, $tableRange->end);
  5773. $ids = array_unique($ids);
  5774. $indexPairs = $this->dao->select('id, `index`')->from("`$tableName`")
  5775. ->where('id')->in($ids)
  5776. ->fetchPairs('id');
  5777. $tableRanges[$tableName]->indexPairs = $indexPairs;
  5778. }
  5779. /* Set startIndice and endIndice. */
  5780. foreach($tableRanges as $tableRange)
  5781. {
  5782. $queryData = array();
  5783. foreach($tableRange->start as $id) $queryData[] = "WHEN $id THEN {$tableRange->indexPairs[$id]}";
  5784. $query = "UPDATE " . TABLE_IM_CHAT_MESSAGE_INDEX . " SET `startIndex` = (CASE `start` " . implode(' ', $queryData) . " END) WHERE `start` IN(" . implode(',', $tableRange->start) . ");";
  5785. $this->dao->query($query);
  5786. $queryData = array();
  5787. foreach($tableRange->end as $id) $queryData[] = "WHEN $id THEN {$tableRange->indexPairs[$id]}";
  5788. $query = "UPDATE " . TABLE_IM_CHAT_MESSAGE_INDEX . " SET `endIndex` = (CASE `end` " . implode(' ', $queryData) . " END) WHERE `end` IN(" . implode(',', $tableRange->end) . ");";
  5789. $this->dao->query($query);
  5790. }
  5791. }
  5792. /**
  5793. * Fix weekly report.
  5794. *
  5795. * @access public
  5796. * @return bool
  5797. */
  5798. public function fixWeeklyReport()
  5799. {
  5800. if(!isset($this->app->user)) $this->app->user = new stdclass();
  5801. $this->app->user->admin = true;
  5802. $this->loadModel('weekly');
  5803. $projects = $this->dao->select('id,begin,end')->from(TABLE_PROJECT)->where('deleted')->eq('0')->andWhere('model')->eq('waterfall')->fetchAll('id');
  5804. $today = helper::today();
  5805. foreach($projects as $projectID => $project)
  5806. {
  5807. if(helper::isZeroDate($project->begin) or helper::isZeroDate($project->end)) continue;
  5808. $begin = $project->begin;
  5809. $end = $today > $project->end ? $project->end : $today;
  5810. $beginTimestame = strtotime($begin);
  5811. $endTimestame = strtotime($end);
  5812. while($beginTimestame <= $endTimestame)
  5813. {
  5814. $this->weekly->save($projectID, $begin);
  5815. $beginTimestame += 7 * 24 * 3600;
  5816. $begin = date('Y-m-d', $beginTimestame);
  5817. }
  5818. }
  5819. return true;
  5820. }
  5821. /**
  5822. * 将历史的项目作为项目升级。
  5823. * Historical projects are upgraded by project.
  5824. *
  5825. * @param int $programID
  5826. * @param string $fromMode
  5827. * @access public
  5828. * @return bool
  5829. */
  5830. public function upgradeInProjectMode($programID, $fromMode = '')
  5831. {
  5832. $noMergedSprints = $this->upgradeTao->getNoMergedSprints();
  5833. if(!$noMergedSprints) return true;
  5834. $this->loadModel('action');
  5835. $now = helper::now();
  5836. $account = isset($this->app->user->account) ? $this->app->user->account : '';
  5837. foreach($noMergedSprints as $sprint)
  5838. {
  5839. $project = $this->upgradeTao->buildProjectInAutoUpgrade($sprint, $programID, $fromMode, $account, $now);
  5840. $project->days = $this->computeDaysDelta($project->begin, $project->end);
  5841. $this->dao->insert(TABLE_PROJECT)->data($project)->exec();
  5842. if(dao::isError()) return false;
  5843. $projectID = $this->dao->lastInsertID();
  5844. if($project->status == 'closed') $this->action->create('project', $projectID, 'closedbysystem');
  5845. if($fromMode == 'classic')
  5846. {
  5847. $this->dao->update(TABLE_PROJECT)->set('multiple')->eq('0')->where('id')->eq($sprint->id)->exec();
  5848. $this->dao->update(TABLE_DOCLIB)->set('project')->eq($projectID)->set('type')->eq('project')->set('execution')->eq(0)->where('execution')->eq($sprint->id)->andWhere('type')->eq('execution')->exec();
  5849. $this->dao->update(TABLE_DOC)->set('project')->eq($projectID)->set('execution')->eq(0)->where('execution')->eq($sprint->id)->exec();
  5850. $this->dao->update(TABLE_ACTION)->set('objectType')->eq('project')->set('objectID')->eq($projectID)->set('project')->eq($projectID)->set('execution')->eq('0')->where('objectID')->eq($sprint->id)->andWhere('objectType')->eq('execution')->exec();
  5851. $this->dao->update(TABLE_ACTION)->set('project')->eq($projectID)->set('execution')->eq('0')->where('execution')->eq($sprint->id)->exec();
  5852. }
  5853. else
  5854. {
  5855. $this->action->create('project', $projectID, 'openedbysystem');
  5856. $project->id = $projectID;
  5857. $this->upgradeTao->createProjectDocLib($project);
  5858. }
  5859. $productIdList = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($sprint->id)->fetchPairs();
  5860. $this->processMergedData($programID, $projectID, 0, $productIdList, array($sprint->id));
  5861. }
  5862. /* Fix project path and update the module. */
  5863. $this->fixProjectPath($programID);
  5864. $this->dao->update(TABLE_MODULE)->set('root')->eq($programID)->where('type')->eq('line')->andWhere('root')->eq('0')->exec();
  5865. /* Compute product's acl under the program. */
  5866. $productIdList = $this->dao->select('id')->from(TABLE_PRODUCT)->where('program')->eq('0')->fetchPairs();
  5867. $this->computeProductAcl($productIdList, $programID, null);
  5868. return !dao::isError();
  5869. }
  5870. /**
  5871. * 将历史的项目作为执行升级。
  5872. * Historical projects are upgraded by execution.
  5873. *
  5874. * @param int $programID
  5875. * @access public
  5876. * @return bool
  5877. */
  5878. public function upgradeInExecutionMode($programID)
  5879. {
  5880. $this->loadModel('action');
  5881. $now = helper::now();
  5882. $account = isset($this->app->user->account) ? $this->app->user->account : '';
  5883. $noMergedSprints = $this->upgradeTao->getNoMergedSprints();
  5884. if(!$noMergedSprints) return true;
  5885. $projects = array();
  5886. foreach($noMergedSprints as $sprint)
  5887. {
  5888. $year = date('Y', strtotime($sprint->openedDate));
  5889. $projects[$year][$sprint->id] = $sprint;
  5890. }
  5891. foreach($projects as $year => $sprints)
  5892. {
  5893. $project = new stdclass();
  5894. $project->name = $year > 0 ? $year : $this->lang->upgrade->unknownDate;
  5895. $project->type = 'project';
  5896. $project->model = 'scrum';
  5897. $project->parent = $programID;
  5898. $project->team = $project->name;
  5899. $project->auth = 'extend';
  5900. $project->begin = '';
  5901. $project->end = '';
  5902. $project->openedBy = $account;
  5903. $project->openedDate = $now;
  5904. $project->openedVersion = $this->config->version;
  5905. $project->lastEditedBy = $account;
  5906. $project->lastEditedDate = $now;
  5907. $project->grade = 2;
  5908. $project->acl = 'open';
  5909. $project->storyType = 'story';
  5910. $projectStatus = 'closed';
  5911. foreach($sprints as $sprint)
  5912. {
  5913. if(!$project->begin || $sprint->begin < $project->begin) $project->begin = $sprint->begin;
  5914. if(!$project->end || $sprint->end > $project->end) $project->end = $sprint->end;
  5915. if($sprint->status != 'closed') $projectStatus = 'doing';
  5916. }
  5917. $project->status = $projectStatus;
  5918. $project->days = $this->computeDaysDelta($project->begin, $project->end);
  5919. $this->dao->insert(TABLE_PROJECT)->data($project)->exec();
  5920. if(dao::isError()) return false;
  5921. $projectID = $this->dao->lastInsertId();
  5922. $this->action->create('project', $projectID, 'openedbysystem');
  5923. if($project->status == 'closed') $this->action->create('project', $projectID, 'closedbysystem');
  5924. $project->id = $projectID;
  5925. $this->upgradeTao->createProjectDocLib($project);
  5926. $productIdList = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->in(array_keys($sprints))->fetchPairs();
  5927. $this->processMergedData($programID, $projectID, 0, $productIdList, array_keys($sprints));
  5928. }
  5929. $this->fixProjectPath($programID);
  5930. $productIdList = $this->dao->select('id')->from(TABLE_PRODUCT)->where('program')->eq('0')->fetchPairs();
  5931. $this->dao->update(TABLE_MODULE)->set('root')->eq($programID)->where('type')->eq('line')->andWhere('root')->eq('0')->exec();
  5932. $this->computeProductAcl($productIdList, $programID, null);
  5933. if(dao::isError()) return false;
  5934. return true;
  5935. }
  5936. /**
  5937. * 修复项目集下的项目路径。
  5938. * Fix the project path under the program.
  5939. *
  5940. * @param int $programID
  5941. * @access public
  5942. * @return void
  5943. */
  5944. public function fixProjectPath($programID)
  5945. {
  5946. $this->dao->update(TABLE_PROJECT)
  5947. ->set("path = CONCAT(',{$programID},', id, ',')")->set("`order` = `id` * 5")
  5948. ->where('type')->eq('project')
  5949. ->andWhere('parent')->eq($programID)
  5950. ->andWhere('grade')->eq('2')
  5951. ->exec();
  5952. }
  5953. /**
  5954. * Relate default program.
  5955. *
  5956. * @param int $programID
  5957. * @access public
  5958. * @return bool
  5959. */
  5960. public function relateDefaultProgram($programID)
  5961. {
  5962. $this->dao->update(TABLE_PRODUCT)->set('program')->eq($programID)->where('program')->eq(0)->exec();
  5963. $this->dao->update(TABLE_MODULE)->set('root')->eq($programID)->where('type')->eq('line')->andWhere('root')->eq('0')->exec();
  5964. $this->dao->update(TABLE_PROJECT)->set('parent')->eq($programID)->set("path = CONCAT(',{$programID}', path)")->set('grade = grade + 1')->where('type')->eq('project')->andWhere('parent')->eq(0)->andWhere('grade')->eq(1)->exec();
  5965. return !dao::isError();
  5966. }
  5967. /*
  5968. * Update the owner of the program into the product view.
  5969. *
  5970. * @access public
  5971. * @return bool
  5972. */
  5973. public function updateProductView()
  5974. {
  5975. $programs = $this->dao->select('id,PM')->from(TABLE_PROGRAM)->where('type')->eq('program')->andWhere('PM')->ne('')->fetchPairs('id', 'PM');
  5976. if(empty($programs)) return true;
  5977. $productGroup = $this->dao->select('id,program')->from(TABLE_PRODUCT)->where('program')->in(array_keys($programs))->andWhere('acl')->ne('open')->fetchGroup('program', 'id');
  5978. if(empty($productGroup)) return true;
  5979. $userView = $this->dao->select('*')->from(TABLE_USERVIEW)->where('account')->in(array_values($programs))->fetchAll('account');
  5980. foreach($programs as $programID => $programPM)
  5981. {
  5982. if(empty($productGroup[$programID])) continue;
  5983. $canViewProducts = zget($productGroup, $programID);
  5984. $view = $userView[$programPM]->products;
  5985. foreach($canViewProducts as $productID => $product)
  5986. {
  5987. if(strpos(",$view,", ",$productID,") === false) $view .= ',' . $productID;
  5988. }
  5989. $this->dao->update(TABLE_USERVIEW)->set('products')->eq($view)->where('account')->eq($programPM)->exec();
  5990. }
  5991. return true;
  5992. }
  5993. /**
  5994. * Process feedback module
  5995. *
  5996. * @access public
  5997. * @return void
  5998. */
  5999. public function processFeedbackModule()
  6000. {
  6001. $products = $this->dao->select('id, name')->from(TABLE_PRODUCT)->fetchAll();
  6002. $modules = $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('feedback')->andWhere('root')->eq(0)->fetchAll('id', false);
  6003. $feedbacks = $this->dao->select('*')->from(TABLE_FEEDBACK)->fetchAll();
  6004. $allProductRelation = array();
  6005. foreach($products as $product)
  6006. {
  6007. $productID = $product->id;
  6008. $relation = array();
  6009. foreach($modules as $moduleID => $module)
  6010. {
  6011. unset($module->id);
  6012. $module->root = $productID;
  6013. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  6014. $newModuleID = $this->dao->lastInsertID();
  6015. $relation[$moduleID] = $newModuleID;
  6016. $allProductRelation[$productID][$moduleID] = $newModuleID;
  6017. $newPaths = array();
  6018. foreach(explode(',', trim($module->path, ',')) as $path)
  6019. {
  6020. if(isset($relation[$path])) $newPaths[] = $relation[$path];
  6021. }
  6022. $newPaths = implode(',', $newPaths);
  6023. $parent = !empty($module->parent) ? $relation[$module->parent] : 0;
  6024. $this->dao->update(TABLE_MODULE)->set('path')->eq($newPaths)->set('parent')->eq($parent)->where('id')->eq($newModuleID)->exec();
  6025. }
  6026. }
  6027. /* Update feedback module */
  6028. foreach($feedbacks as $feedback)
  6029. {
  6030. $moduleID = $feedback->module;
  6031. $product = $feedback->product;
  6032. if(empty($moduleID)) continue;
  6033. $newModuleID = isset($allProductRelation[$product][$moduleID]) ? $allProductRelation[$product][$moduleID] : 0;
  6034. if(empty($newModuleID)) continue;
  6035. $this->dao->update(TABLE_FEEDBACK)->set('module')->eq($newModuleID)->where('id')->eq($feedback->id)->exec();
  6036. }
  6037. /* Delete history module */
  6038. $this->dao->delete()->from(TABLE_MODULE)->where('type')->eq('feedback')->andWhere('root')->eq(0)->exec();
  6039. }
  6040. /**
  6041. * Add default modules for BI.
  6042. *
  6043. * @param string $type
  6044. * @param int $dimension
  6045. * @access public
  6046. * @return array
  6047. */
  6048. public function addDefaultModules4BI($type = 'report', $dimension = 1)
  6049. {
  6050. $this->app->loadLang('dimension');
  6051. $group = new stdclass();
  6052. $group->root = $dimension;
  6053. $group->grade = 1;
  6054. $group->type = $type;
  6055. $group->owner = 'system';
  6056. $group->order = 0;
  6057. $modules = array();
  6058. foreach($this->lang->dimension->moduleList as $module => $name)
  6059. {
  6060. if(!$module || !$name) continue;
  6061. $group->order += 10;
  6062. $exist = $this->dao->select('id')->from(TABLE_MODULE)
  6063. ->where('root')->eq($dimension)
  6064. ->andWhere('collector')->eq($module)
  6065. ->andWhere('type')->eq($type)
  6066. ->fetchAll();
  6067. if(!empty($exist)) continue;
  6068. $group->name = $name;
  6069. $group->collector = $module;
  6070. $this->dao->insert(TABLE_MODULE)->data($group)->exec();
  6071. $modules[$module] = $this->dao->lastInsertID();
  6072. }
  6073. $this->dao->update(TABLE_MODULE)->set("`path` = CONCAT(',', `id`, ',')")
  6074. ->where('type')->eq($type)
  6075. ->andWhere('grade')->eq('1')
  6076. ->andWhere('path')->eq('')
  6077. ->exec();
  6078. return $modules;
  6079. }
  6080. /**
  6081. * 处理透视表。
  6082. * Process dataset.
  6083. *
  6084. * @access public
  6085. * @return bool
  6086. */
  6087. public function processDataset()
  6088. {
  6089. $dataviewData = $this->dao->select('id')->from(TABLE_DATAVIEW)->fetch();
  6090. if(!empty($dataviewData)) return true;
  6091. $this->loadModel('dataset');
  6092. $this->loadModel('dataview');
  6093. $this->dao->begin();
  6094. $this->upgradeTao->convertBuiltInDataSet();
  6095. if(dao::isError())
  6096. {
  6097. $this->dao->rollback();
  6098. return false;
  6099. }
  6100. $customDataset = $this->dao->select('*')->from(TABLE_DATASET)->fetchAll('id', false);
  6101. if(empty($customDataset)) return $this->dao->commit();
  6102. $this->upgradeTao->convertCustomDataSet($customDataset);
  6103. if(dao::isError())
  6104. {
  6105. $this->dao->rollback();
  6106. return false;
  6107. }
  6108. return $this->dao->commit();
  6109. }
  6110. /**
  6111. * Process report modules.
  6112. *
  6113. * @access public
  6114. * @return bool
  6115. */
  6116. public function createDefaultDimension()
  6117. {
  6118. /* Create default dimension. */
  6119. $this->loadModel('dimension');
  6120. foreach($this->config->dimension->defaultDimension as $dimensionName)
  6121. {
  6122. $dimension = new stdclass();
  6123. $dimension->name = $this->lang->dimension->{$dimensionName};
  6124. $dimension->code = $dimensionName;
  6125. $dimension->desc = '';
  6126. $dimension->createdBy = 'system';
  6127. $dimension->createdDate = helper::now();
  6128. $this->dao->insert(TABLE_DIMENSION)->data($dimension)->exec();
  6129. $dimensionID = $this->dao->lastInsertID();
  6130. $chartModules = $this->addDefaultModules4BI('chart', $dimensionID);
  6131. $this->addSecondModule4BI($dimensionID, $dimensionName, 'chart', $chartModules);
  6132. $pivotModules = $this->addDefaultModules4BI('pivot', $dimensionID);
  6133. $this->addSecondModule4BI($dimensionID, $dimensionName, 'pivot', $pivotModules);
  6134. }
  6135. return !dao::isError();
  6136. }
  6137. /**
  6138. * Add second modules for bi, move buildin chart and pivot to second modules.
  6139. *
  6140. * @access public
  6141. * @return bool
  6142. */
  6143. public function addSecondModule4BI($dimensionID, $dimensionName, $module, $modules)
  6144. {
  6145. $this->loadModel('dimension');
  6146. $listIndex = $module . 'Upgrade';
  6147. $secondModules = $this->config->dimension->secondModuleList[$dimensionName][$module];
  6148. $updateInfos = $this->config->dimension->secondModuleList[$dimensionName][$listIndex];
  6149. foreach($modules as $moduleName => $parentID)
  6150. {
  6151. $insertModules = isset($secondModules[$moduleName]) ? explode(',', $secondModules[$moduleName]) : array();
  6152. if(empty($insertModules)) continue;
  6153. $i = 1;
  6154. foreach($insertModules as $moduleCode)
  6155. {
  6156. $data = new stdclass();
  6157. $data->root = $dimensionID;
  6158. $data->name = $this->lang->dimension->modules[$moduleCode];
  6159. $data->parent = $parentID;
  6160. $data->grade = 2;
  6161. $data->order = 10 * $i;
  6162. $data->type = $module;
  6163. $i ++;
  6164. $this->dao->insert(TABLE_MODULE)->data($data)->exec();
  6165. $lastGroupID = $this->dao->lastInsertID();
  6166. $path = ',' . $parentID . ',' . $lastGroupID . ',';
  6167. $this->dao->update(TABLE_MODULE)->set("`path`")->eq($path)->where('id')->eq($lastGroupID)->exec();
  6168. $updateArr = isset($updateInfos[$moduleName][$moduleCode]) ? $updateInfos[$moduleName][$moduleCode] : array();
  6169. if(!empty($updateArr))
  6170. {
  6171. foreach($updateArr as $type => $idString)
  6172. {
  6173. $table = $type == 'chart' ? TABLE_CHART : TABLE_PIVOT;
  6174. $idList = explode(',', $idString);
  6175. $this->dao->update($table)->set('group')->eq($lastGroupID)->set('dimension')->eq($dimensionID)->where('id')->in($idList)->exec();
  6176. }
  6177. }
  6178. }
  6179. }
  6180. }
  6181. /**
  6182. * Add update2BI mark to zt_config.
  6183. *
  6184. * @access public
  6185. * @return bool
  6186. */
  6187. public function addBIUpdateMark()
  6188. {
  6189. $this->loadModel('setting')->setItem("system.bi.update2BI", 1);
  6190. }
  6191. /**
  6192. * Xuan: Set mute and freeze for hidden groups.
  6193. *
  6194. * @access public
  6195. * @return bool
  6196. */
  6197. public function xuanSetMuteForHiddenGroups()
  6198. {
  6199. $this->dao->update(TABLE_IM_CHATUSER)->set('hide')->eq('0')->set('mute')->eq('1')->set('freeze')->eq('1')->where('hide')->eq('1')->andWhere('quit')->eq('0000-00-00 00:00:00')->exec();
  6200. return !dao::isError();
  6201. }
  6202. /**
  6203. * Xuan: Notify users who have hide the group.
  6204. *
  6205. * @access public
  6206. * @return bool
  6207. */
  6208. public function xuanNotifyGroupHiddenUsers()
  6209. {
  6210. $noticeUsers = $this->dao->select('user')->from(TABLE_IM_CHATUSER)->where('hide')->eq('1')->andWhere('quit')->eq('0000-00-00 00:00:00')->groupBy('user')->fetchAll();
  6211. $sender = new stdClass();
  6212. $sender->avatar = commonModel::getSysURL() . '/www/favicon.ico';
  6213. $sender->id = 'upgradeArchive';
  6214. $sender->realname = $this->lang->upgrade->archiveChangeNoticeTitle;
  6215. $this->loadModel('im')->messageCreateNotify(array_keys($noticeUsers), '', '', $this->lang->upgrade->archiveChangeNoticeContent, 'text', '', array(), $sender);
  6216. return !dao::isError();
  6217. }
  6218. /**
  6219. * Xuan: Notify users for upgrade 9.0 jitsi conference.
  6220. *
  6221. * @access public
  6222. * @return bool
  6223. */
  6224. public function xuanNotifyJitsiConference()
  6225. {
  6226. $users = array_keys($this->dao->select('id')->from(TABLE_USER)->where('deleted')->eq('0')->fetchAll('id'));
  6227. $super = array_values(explode(',', $this->dao->select('admins')->from(TABLE_COMPANY)->where('id')->eq($this->app->company->id)->fetch('admins')));
  6228. $this->loadModel('im');
  6229. $sender = new stdclass();
  6230. $sender->id = 0;
  6231. $sender->displayName = $this->lang->im->bot->commonName;
  6232. $sender->avatar = commonModel::getSysURL() . $this->config->webRoot . 'data/image/xuanbot.png';
  6233. $this->im->messageCreateNotify($super, '', '', $this->lang->upgrade->jitsiConferenceNotice->super, 'text', $this->lang->upgrade->jitsiConferenceNotice->setupLink, array(), $sender);
  6234. $this->im->messageCreateNotify($users, '', '', $this->lang->upgrade->jitsiConferenceNotice->user, 'text', $this->lang->upgrade->jitsiConferenceNotice->manualLink, array(), $sender);
  6235. return !dao::isError();
  6236. }
  6237. /**
  6238. * Init shadow builds.
  6239. *
  6240. * @access public
  6241. * @return bool
  6242. */
  6243. public function initShadowBuilds()
  6244. {
  6245. $releases = $this->dao->select('id,product,shadow,build,name,date,createdBy,createdDate,deleted')->from(TABLE_RELEASE)->where('shadow')->eq(0)->fetchAll();
  6246. foreach($releases as $release)
  6247. {
  6248. $shadowBuild = new stdclass();
  6249. $shadowBuild->product = $release->product;
  6250. $shadowBuild->builds = $release->build;
  6251. $shadowBuild->name = $release->name;
  6252. $shadowBuild->date = $release->date;
  6253. $shadowBuild->createdBy = $release->createdBy;
  6254. $shadowBuild->createdDate = $release->createdDate;
  6255. $shadowBuild->deleted = $release->deleted;
  6256. $this->dao->insert(TABLE_BUILD)->data($shadowBuild)->exec();
  6257. $shadowBuildID = $this->dao->lastInsertID();
  6258. $this->dao->update(TABLE_RELEASE)->set('shadow')->eq($shadowBuildID)->where('id')->eq($release->id)->exec();
  6259. }
  6260. return true;
  6261. }
  6262. /**
  6263. * Init review efforts.
  6264. *
  6265. * @access public
  6266. * @return void
  6267. */
  6268. public function initReviewEfforts()
  6269. {
  6270. $nodes = $this->dao->select('t1.id,t3.id as reviewID,t3.title,t2.id as approvalID,t1.extra as consumed')->from(TABLE_APPROVALNODE)->alias('t1')
  6271. ->leftJoin(TABLE_APPROVAL)->alias('t2')->on("t1.approval=t2.id")
  6272. ->leftJoin(TABLE_REVIEW)->alias('t3')->on("t2.objectID=t3.id")
  6273. ->where('t3.deleted')->eq('0')
  6274. ->andWhere('t2.deleted')->eq('0')
  6275. ->andWhere('t2.objectType')->eq('review')
  6276. ->andWhere('t1.extra')->ne('')
  6277. ->andWhere('t1.extra')->ne(0)
  6278. ->orderBy('t1.approval,t1.id')
  6279. ->fetchAll('id');
  6280. $this->loadModel('effort');
  6281. foreach($nodes as $node)
  6282. {
  6283. $this->dao->delete()->from(TABLE_EFFORT)->where('objectType')->eq('review')->andWhere('objectID')->eq($node->reviewID)->exec();
  6284. $this->effort->create('review', $node->reviewID, (int)$node->consumed, $node->title, $node->approvalID);
  6285. }
  6286. return true;
  6287. }
  6288. /**
  6289. * Update my module blocks.
  6290. *
  6291. * @access public
  6292. * @return bool
  6293. */
  6294. public function updateMyBlocks()
  6295. {
  6296. /* Delete flowchart block. */
  6297. $this->dao->delete()->from(TABLE_BLOCK)->where('module')->eq('my')->andWhere('block')->eq('flowchart')->exec();
  6298. /* Update block order and insert guide block. */
  6299. $visionList = array('rnd', 'lite');
  6300. /* Set guide block data. */
  6301. $guideBlock = new stdclass();
  6302. $guideBlock->module = 'my';
  6303. $guideBlock->title = common::checkNotCN() ? 'Guides' : '使用帮助';
  6304. $guideBlock->block = 'guide';
  6305. $guideBlock->order = 3;
  6306. $guideBlock->grid = 8;
  6307. foreach($visionList as $vision)
  6308. {
  6309. $guideBlock->vision = $vision;
  6310. $this->dao->update(TABLE_BLOCK)
  6311. ->set('`order` = `order` + 1')
  6312. ->where('vision')->eq($vision)
  6313. ->andWhere('module')->eq('my')
  6314. ->andWhere('`order`')->ge(3)
  6315. ->orderBy('`order` desc')
  6316. ->exec();
  6317. $accountList = $this->dao->select('account')->from(TABLE_BLOCK)
  6318. ->where('vision')->eq($vision)
  6319. ->andWhere('module')->eq('my')
  6320. ->fetchPairs('account');
  6321. foreach($accountList as $account)
  6322. {
  6323. if(empty($account)) continue;
  6324. $guideBlock->account = $account;
  6325. $this->dao->insert(TABLE_BLOCK)->data($guideBlock)->exec();
  6326. }
  6327. }
  6328. return true;
  6329. }
  6330. /**
  6331. * Process dashboard to screen.
  6332. *
  6333. * @access public
  6334. * @return void
  6335. */
  6336. public function processDashboard()
  6337. {
  6338. $dashboards = $this->dao->select('*')->from(TABLE_DASHBOARD)->fetchAll('id', false);
  6339. $id = 1002;
  6340. foreach($dashboards as $dashboard)
  6341. {
  6342. $screen = new stdclass();
  6343. $screen->id = $id;
  6344. $screen->name = $dashboard->name;
  6345. $screen->dimension = 1;
  6346. $screen->desc = $dashboard->desc;
  6347. $screen->scheme = $this->processDashboardLayout($dashboard);
  6348. $screen->deleted = $dashboard->deleted;
  6349. $screen->status = 'published';
  6350. $screen->createdBy = $dashboard->createdBy;
  6351. $screen->createdDate = $dashboard->createdDate;
  6352. $this->dao->insert(TABLE_SCREEN)->data($screen)->exec();
  6353. $id++;
  6354. }
  6355. $this->dao->exec("ALTER TABLE " . TABLE_CHART . " DROP `dataset`");
  6356. $this->dao->exec("ALTER TABLE " . TABLE_PIVOT . " DROP `dataset`");
  6357. }
  6358. /**
  6359. * Upgrade dashboard info to screen.
  6360. *
  6361. * @param object $dashboard
  6362. * @access public
  6363. * @return void
  6364. */
  6365. public function processDashboardLayout($dashboard)
  6366. {
  6367. $this->loadModel('screen');
  6368. $scheme = file_get_contents($this->app->getModuleRoot() . DS . 'screen' . DS . 'json' . DS . 'screen.json');
  6369. $scheme = json_decode($scheme);
  6370. $layout = json_decode($dashboard->layout);
  6371. $canvasHeight = $scheme->editCanvasConfig->height;
  6372. $componentList = array();
  6373. foreach($layout as $option)
  6374. {
  6375. $component = new stdclass();
  6376. $component = $this->loadModel('screen')->setComponentDefaults($component);
  6377. $component->id = $option->i->id;
  6378. $component->attr = json_decode('{"offsetX": 0, "offsetY": 0, "lockScale": false, "zIndex": -1}');
  6379. $component->attr->x = round($scheme->editCanvasConfig->width * ($option->x / 12));
  6380. $component->attr->y = round(54 * $option->y);
  6381. $component->attr->w = round($scheme->editCanvasConfig->width * ($option->w / 12));
  6382. $component->attr->h = round(54 * $option->h);
  6383. $type = !empty($option->i->type) ? $option->i->type : 'chart';
  6384. $chart = $this->loadModel($type)->getByID($option->i->id, false, 'published', false);
  6385. if($chart)
  6386. {
  6387. if($chart->sql)
  6388. {
  6389. $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  6390. $stmt = $this->dbh->query($chart->sql);
  6391. if(!$stmt) continue;
  6392. }
  6393. $settings = $chart->settings;
  6394. if($type == 'chart') $chartType = $chart->builtin ? $chart->type : $settings[0]['type'];
  6395. if($type == 'pivot') $chartType = 'table';
  6396. $chartConfig = json_decode(zget($this->config->screen->chartConfig, $chartType));
  6397. $chartConfig->fields = $chart->fieldSettings;
  6398. $chartConfig->sourceID = $option->i->id;
  6399. $component->title = $chart->name;
  6400. $component->type = $chartType;
  6401. $component->sourceID = $option->i->id;
  6402. $component->key = $chartConfig->key;
  6403. $component->chartConfig = $chartConfig;
  6404. $component->option = json_decode(zget($this->config->screen->chartOption, $chartType));
  6405. if(isset($component->option->title->text)) $component->option->title->text = $chart->name;
  6406. $chart->fields = json_encode($chart->fieldSettings);
  6407. $chart->settings = json_encode($chart->settings);
  6408. $chart->langs = json_encode($chart->langs);
  6409. $chartFilters = !empty($chart->filters) ? json_decode($chart->filters, true) : array();
  6410. }
  6411. $componentList[] = $component;
  6412. if($canvasHeight < ($component->attr->y + $component->attr->h)) $canvasHeight = $component->attr->y + $component->attr->h;
  6413. }
  6414. $filters = json_decode($dashboard->filters);
  6415. $globalFilters = array();
  6416. foreach($filters as $filter)
  6417. {
  6418. $globalFilter = new stdclass();
  6419. $globalFilter->name = $filter->name;
  6420. $globalFilter->type = $filter->type;
  6421. $globalFilter->field = $filter->field;
  6422. $globalFilter->defaultValue = zget($filter, 'defaultValue' , '');
  6423. $diagram = array();
  6424. foreach($layout as $option)
  6425. {
  6426. $type = !empty($option->i->type) ? $option->i->type : 'chart';
  6427. $chart = $this->loadModel($type)->getByID($option->i->id);
  6428. $fields = explode('.', $filter->field);
  6429. if($chart->dataset == $fields[0]) $diagram[] = array('id' => "diagram-{$chart->id}", 'field' => $fields[1]);
  6430. }
  6431. $globalFilter->diagram = $diagram;
  6432. $globalFilters[] = $globalFilter;
  6433. }
  6434. $scheme->editCanvasConfig->height = $canvasHeight;
  6435. $scheme->editCanvasConfig->globalFilter = $globalFilters;
  6436. $scheme->componentList = $componentList;
  6437. $scheme = json_encode($scheme);
  6438. return $scheme;
  6439. }
  6440. /**
  6441. * Insert mix stage.
  6442. *
  6443. * @access public
  6444. * @return bool
  6445. */
  6446. public function insertMixStage()
  6447. {
  6448. $typeList = $this->dao->select('*')->from(TABLE_LANG)
  6449. ->where('module')->eq('stage')
  6450. ->andWhere('section')->eq('typeList')
  6451. ->fetchAll('id', false);
  6452. $this->dao->delete()->from(TABLE_LANG)
  6453. ->where('module')->eq('stage')
  6454. ->andWhere('section')->eq('typeList')
  6455. ->exec();
  6456. $mixInserted = array();
  6457. foreach($typeList as $type)
  6458. {
  6459. if(!isset($mixInserted[$type->lang . '-' . $type->vision]))
  6460. {
  6461. $langFile = $this->app->getModuleRoot() . DS . 'stage' . DS . 'lang' . DS . ($type->lang == 'all' ? $this->app->clientLang : $type->lang) . '.php';
  6462. if(!is_file($langFile)) continue;
  6463. $lang = new stdclass();
  6464. $lang->stage = new stdclass();
  6465. include $langFile;
  6466. $this->dao->replace(TABLE_LANG)
  6467. ->set('module')->eq('stage')
  6468. ->set('section')->eq('typeList')
  6469. ->set('lang')->eq($type->lang)
  6470. ->set('vision')->eq($type->vision)
  6471. ->set('key')->eq('mix')
  6472. ->set('value')->eq($lang->stage->typeList['mix'])
  6473. ->exec();
  6474. $mixInserted[$type->lang . '-' . $type->vision] = true;
  6475. }
  6476. if($type->key == 'mix') continue;
  6477. $this->dao->replace(TABLE_LANG)
  6478. ->set('module')->eq('stage')
  6479. ->set('section')->eq('typeList')
  6480. ->set('lang')->eq($type->lang)
  6481. ->set('vision')->eq($type->vision)
  6482. ->set('key')->eq($type->key)
  6483. ->set('value')->eq($type->value)
  6484. ->exec();
  6485. }
  6486. return true;
  6487. }
  6488. /**
  6489. * Change book to custom lib.
  6490. *
  6491. * @access public
  6492. * @return void
  6493. */
  6494. public function changeBookToCustomLib()
  6495. {
  6496. $libs = $this->dao->select('id,id')->from(TABLE_DOCLIB)->where('`type`')->eq('book')->fetchPairs();
  6497. foreach($libs as $libID)
  6498. {
  6499. $chapterModulePairs = array();
  6500. $chapters = $this->dao->select('*')->from(TABLE_DOC)->where('lib')->eq($libID)->andWhere('`type`')->eq('chapter')->orderBy('`grade` asc, `order` asc')->fetchGroup('grade', 'id');
  6501. foreach($chapters as $grade => $gradeChapters)
  6502. {
  6503. foreach($gradeChapters as $id => $chapter)
  6504. {
  6505. $module = new stdclass();
  6506. $module->root = $chapter->lib;
  6507. $module->name = $chapter->title;
  6508. $module->parent = zget($chapterModulePairs, $chapter->parent);
  6509. $module->grade = $chapter->grade;
  6510. $module->order = $chapter->order;
  6511. $module->type = 'doc';
  6512. $module->deleted = $chapter->deleted;
  6513. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  6514. $moduleID = $this->dao->lastInsertID();
  6515. $chapterModulePairs[$id] = $moduleID;
  6516. $path = explode(',', $chapter->path);
  6517. $path = array_filter($path);
  6518. foreach($path as $index => $chapterID) $path[$index] = zget($chapterModulePairs, $chapterID);
  6519. $path = implode(',', $path);
  6520. $this->dao->update(TABLE_MODULE)->set('`path`')->eq(",{$path},")->where('id')->eq($moduleID)->exec();
  6521. $this->dao->update(TABLE_DOC)->set('`module`')->eq($moduleID)->set('path')->eq('')->set('`type`')->eq('text')->where('`parent`')->eq($id)->andWhere('`type`')->eq('article')->exec();
  6522. $this->dao->update(TABLE_DOC)->set('deleted')->eq(1)->where('id')->eq($id)->exec();
  6523. }
  6524. }
  6525. $this->dao->update(TABLE_DOCLIB)->set('`type`')->eq('custom')->where('id')->eq($libID)->exec();
  6526. $this->dao->update(TABLE_DOC)->set('`type`')->eq('text')->where('`lib`')->eq($libID)->andWhere('grade')->eq(1)->andWhere('`type`')->eq('article')->exec();
  6527. }
  6528. $this->dao->update(TABLE_DOC)->set('`type`')->eq('text')->where('`type`')->eq('book')->andWhere('templateType')->eq('')->exec();
  6529. $this->dbh->exec("UPDATE " . TABLE_DOC . " AS t1 LEFT JOIN " . TABLE_EXECUTION . " AS t2 ON t1.`execution` = t2.`id` SET t1.`project` = t2.`project` WHERE t1.`execution` != 0 AND t1.`project` = 0 AND t2.`project` != 0");
  6530. return true;
  6531. }
  6532. /**
  6533. * Create default group.
  6534. *
  6535. * @param string $type
  6536. * @access public
  6537. * @return void
  6538. */
  6539. public function createDefaultGroup($type)
  6540. {
  6541. $firstGroup = new stdclass();
  6542. $firstGroup->root = 1;
  6543. $firstGroup->type = $type;
  6544. $firstGroup->parent = 0;
  6545. $firstGroup->grade = 1;
  6546. $firstGroup->name = $this->lang->upgrade->defaultGroup;
  6547. $this->dao->insert(TABLE_MODULE)->data($firstGroup)->autoCheck()->exec();
  6548. $firstGroupID = $this->dao->lastInsertID();
  6549. $this->dao->update(TABLE_MODULE)->set('path')->eq(",{$firstGroupID},")->where('id')->eq($firstGroupID)->exec();
  6550. $secondGroup = new stdclass();
  6551. $secondGroup->root = 1;
  6552. $secondGroup->type = $type;
  6553. $secondGroup->parent = $firstGroupID;
  6554. $secondGroup->grade = 2;
  6555. $secondGroup->name = $this->lang->upgrade->defaultGroup;
  6556. $this->dao->insert(TABLE_MODULE)->data($secondGroup)->autoCheck()->exec();
  6557. $secondGroupID = $this->dao->lastInsertID();
  6558. $this->dao->update(TABLE_MODULE)->set('path')->eq(",{$firstGroupID},{$secondGroupID},")->where('id')->eq($secondGroupID)->exec();
  6559. return $secondGroupID;
  6560. }
  6561. /**
  6562. * Process chart.
  6563. *
  6564. * @access public
  6565. * @return bool
  6566. */
  6567. public function processChart()
  6568. {
  6569. $charts = $this->dao->select('*')->from(TABLE_CHART)->where('builtin')->eq(0)->fetchAll('id', false);
  6570. $dashboardLayoutPairs = $this->dao->select('id, layout')->from(TABLE_DASHBOARD)->fetchPairs();
  6571. $dataviewList = $this->dao->select('t1.code,t1.view,t1.fields')->from(TABLE_DATAVIEW)->alias('t1')
  6572. ->leftJoin(TABLE_CHART)->alias('t2')->on('t1.code = t2.dataset')
  6573. ->where('t2.id')->in(array_keys($charts))
  6574. ->fetchAll('code');
  6575. $defaultChartGroupID = $this->createDefaultGroup('chart');
  6576. foreach($charts as $chart)
  6577. {
  6578. if($chart->type == 'table')
  6579. {
  6580. $pivotID = $this->upgradeToPivotTable($chart, $dataviewList);
  6581. }
  6582. else
  6583. {
  6584. $data = new stdclass();
  6585. $data->dimension = 1;
  6586. $data->group = $defaultChartGroupID;
  6587. $data->stage = 'published';
  6588. $data->step = 4;
  6589. $data->type = $chart->type == 'bar' ? 'cluBarX' : $chart->type;
  6590. if(isset($dataviewList[$chart->dataset]))
  6591. {
  6592. $dataview = $dataviewList[$chart->dataset];
  6593. $data->sql = 'SELECT * FROM ' . $dataview->view;
  6594. $data->fields = isset($dataview->fields) ? $dataview->fields : '';
  6595. }
  6596. elseif($chart->sql)
  6597. {
  6598. $fieldSettings = $this->getFieldSettings($chart->sql);
  6599. $data->fields = json_encode($fieldSettings);
  6600. }
  6601. $settings = json_decode($chart->settings);
  6602. if($settings && (!empty($settings->group) || !empty($settings->xaxis)))
  6603. {
  6604. $settings->type = $data->type;
  6605. $filters = array();
  6606. if(isset($settings->filter))
  6607. {
  6608. $filters = $settings->filter;
  6609. unset($settings->filter);
  6610. }
  6611. $isQuoteDataview = isset($dataviewList[$chart->dataset]);
  6612. if(isset($settings->xaxis))
  6613. {
  6614. $xaxisFields = $settings->xaxis;
  6615. foreach($xaxisFields as $xaxisIndex => $xaxisField)
  6616. {
  6617. if(isset($xaxisField->dateGroup)) $xaxisField->group = $xaxisField->dateGroup;
  6618. if($isQuoteDataview && strpos($xaxisField->field, '.') !== false) $xaxisField->field = str_replace('.', '_', $xaxisField->field);
  6619. $xaxisFields[$xaxisIndex] = $xaxisField;
  6620. }
  6621. $settings->xaxis = $xaxisFields;
  6622. }
  6623. if(isset($settings->group))
  6624. {
  6625. $groupFields = $settings->group;
  6626. foreach($groupFields as $groupIndex => $groupField)
  6627. {
  6628. if(isset($groupField->dateGroup)) $groupField->group = $groupField->dateGroup;
  6629. if($isQuoteDataview && strpos($groupField->field, '.') !== false) $groupField->field = str_replace('.', '_', $groupField->field);
  6630. $groupFields[$groupIndex] = $groupField;
  6631. }
  6632. $settings->group = $groupFields;
  6633. }
  6634. if(isset($settings->yaxis))
  6635. {
  6636. $yaxisFields = $settings->yaxis;
  6637. foreach($yaxisFields as $yaxisIndex => $yaxisField)
  6638. {
  6639. if($isQuoteDataview && strpos($yaxisField->field, '.') !== false) $yaxisField->field = str_replace('.', '_', $yaxisField->field);
  6640. if($yaxisField->valOrAgg == 'value') $yaxisField->valOrAgg = 'sum';
  6641. if($yaxisField->valOrAgg == 'count_distinct') $yaxisField->valOrAgg = 'distinct';
  6642. if($yaxisField->valOrAgg == 'value_all') $yaxisField->valOrAgg = 'count';
  6643. if($yaxisField->valOrAgg == 'value_distinct') $yaxisField->valOrAgg = 'distinct';
  6644. $yaxisFields[$yaxisIndex] = $yaxisField;
  6645. }
  6646. $settings->yaxis = $yaxisFields;
  6647. }
  6648. if(isset($settings->metric))
  6649. {
  6650. $metricFields = $settings->metric;
  6651. foreach($metricFields as $metricIndex => $metricField)
  6652. {
  6653. if($isQuoteDataview && strpos($metricField->field, '.') !== false) $metricField->field = str_replace('.', '_', $metricField->field);
  6654. if($metricField->valOrAgg == 'value') $metricField->valOrAgg = 'sum';
  6655. if($metricField->valOrAgg == 'count_distinct') $metricField->valOrAgg = 'distinct';
  6656. if($metricField->valOrAgg == 'value_all') $metricField->valOrAgg = 'count';
  6657. if($metricField->valOrAgg == 'value_distinct') $metricField->valOrAgg = 'distinct';
  6658. $metricFields[$metricIndex] = $metricField;
  6659. }
  6660. $settings->metric = $metricFields;
  6661. }
  6662. $data->settings = json_encode(array($settings));
  6663. }
  6664. else
  6665. {
  6666. $data->step = 1;
  6667. $data->stage = 'draft';
  6668. }
  6669. if(!empty($filters))
  6670. {
  6671. $where = array();
  6672. $operatorMap = array('in' => 'IN', 'notin' => 'NOT IN', 'notnull' => 'IS NOT NULL', 'null' => 'IS NULL');
  6673. foreach($filters as $filter)
  6674. {
  6675. $operator = zget($operatorMap, $filter->operator);
  6676. $value = '';
  6677. if(!in_array($filter->operator, array('null', 'notnull')))
  6678. {
  6679. if(!is_array($filter->value))
  6680. {
  6681. $value = "'" . $filter->value . "'";
  6682. }
  6683. else
  6684. {
  6685. $values = array();
  6686. foreach($filter->value as $v) $values[] = $type == 'number' ? $v : "'" . $v . "'";
  6687. $value = '(' . implode(',', $values) . ')';
  6688. }
  6689. }
  6690. $where[] = $filter->field . ' ' . $operator . ' ' . $value;
  6691. }
  6692. if(stripos($data->sql, 'where') === false)
  6693. {
  6694. $data->sql .= ' WHERE ' . implode(' AND ', $where);
  6695. }
  6696. else
  6697. {
  6698. $data->sql .= ' ' . implode(' AND ', $where);
  6699. }
  6700. }
  6701. $this->dao->update(TABLE_CHART)->data($data)->autoCheck()->where('id')->eq($chart->id)->exec();
  6702. }
  6703. /* Update chart id in dashboard. */
  6704. foreach($dashboardLayoutPairs as $dashboardID => $layout)
  6705. {
  6706. $layout = json_decode($layout);
  6707. foreach($layout as $index => $chartLayout)
  6708. {
  6709. if($chartLayout->i->id != $chart->id) continue;
  6710. $chartLayout->i->type = $chart->type == 'table' ? 'pivot' : 'chart';
  6711. if($chart->type == 'table') $chartLayout->i->id = $pivotID;
  6712. $layout[$index] = $chartLayout;
  6713. }
  6714. $dashboardLayoutPairs[$dashboardID] = json_encode($layout);
  6715. $this->dao->update(TABLE_DASHBOARD)->set('layout')->eq(json_encode($layout))->where('id')->eq($dashboardID)->exec();
  6716. }
  6717. }
  6718. if(dao::isError()) return false;
  6719. return !dao::isError();
  6720. }
  6721. /**
  6722. * upgrade to pivot table.
  6723. *
  6724. * @param int $table
  6725. * @access public
  6726. * @return void
  6727. */
  6728. public function upgradeToPivotTable($table, $dataviewList)
  6729. {
  6730. static $defaultPivotGroupID;
  6731. if(!$defaultPivotGroupID) $defaultPivotGroupID = $this->createDefaultGroup('pivot');
  6732. $pivot = new stdclass();
  6733. $pivot->dimension = 1;
  6734. $pivot->group = $defaultPivotGroupID;
  6735. $pivot->stage = 'published';
  6736. $pivot->step = 4;
  6737. $pivot->sql = $table->sql;
  6738. $pivot->createdBy = $table->createdBy;
  6739. $pivot->createdDate = $table->createdDate;
  6740. $pivot->dataset = $table->dataset;
  6741. $name = array();
  6742. $name['zh-cn'] = $table->name;
  6743. $pivot->name = json_encode($name);
  6744. $desc = array();
  6745. $desc['zh-cn'] = $table->desc;
  6746. $pivot->desc = json_encode($desc);
  6747. $pivotSettings = new stdclass();
  6748. $tableSettings = json_decode($table->settings);
  6749. $filters = array();
  6750. if($tableSettings && !empty($tableSettings->column))
  6751. {
  6752. $isQuoteDataview = isset($dataviewList[$table->dataset]);
  6753. $index = 1;
  6754. foreach($tableSettings->group as $group)
  6755. {
  6756. $groupKey = "group{$index}";
  6757. $pivotSettings->$groupKey = ($isQuoteDataview && strpos($group->field, '.') !== false) ? str_replace('.', '_', $group->field) : $group->field;
  6758. $index ++;
  6759. }
  6760. $columns = array();
  6761. foreach($tableSettings->column as $tableColumn)
  6762. {
  6763. $column = new stdclass();
  6764. $column->field = ($isQuoteDataview && strpos($tableColumn->field, '.') !== false) ? str_replace('.', '_', $tableColumn->field) : $tableColumn->field;
  6765. $column->stat = $tableColumn->valOrAgg;
  6766. if($column->stat == 'value')
  6767. {
  6768. $column->stat = 'sum';
  6769. /* 将按值统计的列升级到透视表的分组。*/
  6770. $groupKey = "group{$index}";
  6771. $pivotSettings->$groupKey = $column->field;
  6772. }
  6773. if($column->stat == 'value_all') $column->stat = 'count';
  6774. if($column->stat == 'count_distinct' || $column->stat == 'value_distinct') $column->stat = 'distinct';
  6775. $columns[] = $column;
  6776. }
  6777. $pivotSettings->columns = $columns;
  6778. if(!empty($tableSettings->filter)) $filters = $tableSettings->filter;
  6779. }
  6780. else
  6781. {
  6782. $pivot->stage = 'draft';
  6783. $pivot->step = 1;
  6784. }
  6785. $pivot->settings = json_encode($pivotSettings);
  6786. if(isset($dataviewList[$table->dataset]))
  6787. {
  6788. $dataview = $dataviewList[$table->dataset];
  6789. $pivot->sql = 'SELECT * FROM ' . $dataview->view;
  6790. $pivot->fields = isset($dataview->fields) ? $dataview->fields : '';
  6791. }
  6792. elseif($pivot->sql)
  6793. {
  6794. $fieldSettings = $this->getFieldSettings($table->sql);
  6795. $pivot->fields = json_encode($fieldSettings);
  6796. }
  6797. if(!empty($filters))
  6798. {
  6799. $where = array();
  6800. $operatorMap = array('in' => 'IN', 'notin' => 'NOT IN', 'notnull' => 'IS NOT NULL', 'null' => 'IS NULL');
  6801. foreach($filters as $filter)
  6802. {
  6803. $operator = zget($operatorMap, $filter->operator);
  6804. $value = '';
  6805. if(!in_array($filter->operator, array('null', 'notnull')))
  6806. {
  6807. if(!is_array($filter->value))
  6808. {
  6809. $value = "'" . $filter->value . "'";
  6810. }
  6811. else
  6812. {
  6813. $values = array();
  6814. foreach($filter->value as $v) $values[] = $type == 'number' ? $v : "'" . $v . "'";
  6815. $value = '(' . implode(',', $values) . ')';
  6816. }
  6817. }
  6818. $where[] = $filter->field . ' ' . $operator . ' ' . $value;
  6819. }
  6820. if(stripos($pivot->sql, 'where') === false)
  6821. {
  6822. $pivot->sql .= ' WHERE ' . implode(' AND ', $where);
  6823. }
  6824. else
  6825. {
  6826. $pivot->sql .= ' ' . implode(' AND ', $where);
  6827. }
  6828. }
  6829. /* Process fieldSettings. */
  6830. $this->dao->insert(TABLE_PIVOT)->data($pivot)->autoCheck()->exec();
  6831. $pivotID = $this->dao->lastInsertID();
  6832. if(dao::isError()) return false;
  6833. $this->dao->delete()->from(TABLE_CHART)->where('id')->eq($table->id)->exec();
  6834. return $pivotID;
  6835. }
  6836. /**
  6837. * Process report data.
  6838. *
  6839. * @access public
  6840. * @return bool
  6841. */
  6842. public function processReport()
  6843. {
  6844. $reports = $this->dao->select('*')->from(TABLE_REPORT)->fetchAll('id', false);
  6845. $groupCollectors = $this->dao->select('collector, id')->from(TABLE_MODULE)->where('type')->eq('pivot')->andWhere('root')->eq(1)->andWhere('collector')->ne('')->fetchPairs();
  6846. $this->loadModel('pivot');
  6847. $this->loadModel('dataview');
  6848. foreach($reports as $report)
  6849. {
  6850. $data = new stdclass();
  6851. $data->dimension = 1;
  6852. $data->name = $report->name;
  6853. $data->sql = $this->pivot->replaceTableNames($report->sql);
  6854. $data->vars = $report->vars;
  6855. $data->langs = $report->langs;
  6856. $data->stage = 'published';
  6857. $data->step = 4;
  6858. $data->desc = $report->desc;
  6859. $data->createdBy = $report->addedBy;
  6860. $data->createdDate = $report->addedDate;
  6861. /* Process group. */
  6862. $modules = explode(',', trim($report->module, ','));
  6863. $groups = array();
  6864. foreach($modules as $module)
  6865. {
  6866. if(isset($groupCollectors[$module]))
  6867. {
  6868. $groups[] = $groupCollectors[$module];
  6869. }
  6870. else
  6871. {
  6872. if(!isset($defaultGroupID))
  6873. {
  6874. $defaultGroupID = $this->dao->select('id')->from(TABLE_MODULE)
  6875. ->where('type')->eq('pivot')
  6876. ->andWhere('name')->eq($this->lang->upgrade->defaultGroup)
  6877. ->andWhere('root')->eq(1)
  6878. ->andWhere('parent')->ne(0)
  6879. ->fetch('id');
  6880. if(!$defaultGroupID) $defaultGroupID = $this->createDefaultGroup('pivot');
  6881. }
  6882. $groups[] = $defaultGroupID;
  6883. }
  6884. }
  6885. $data->group = implode(',', $groups);
  6886. /* Process vars. */
  6887. $vars = json_decode($report->vars, true);
  6888. if($vars)
  6889. {
  6890. $filters = array();
  6891. foreach($vars['varName'] as $index => $varName)
  6892. {
  6893. $filter = new stdclass();
  6894. $filter->from = 'query';
  6895. $filter->field = $varName;
  6896. $filter->name = $vars['showName'][$index];
  6897. $filter->type = $vars['requestType'][$index];
  6898. $filter->typeOption = $filter->type == 'select' ? zget($vars['selectList'], $index, '') : '';
  6899. $filter->default = isset($vars['default']) ? zget($vars['default'], $index, '') : '';
  6900. $filters[] = $filter;
  6901. }
  6902. $data->filters = json_encode($filters);
  6903. }
  6904. /* Process settings. */
  6905. $settings = new stdclass();
  6906. $columns = array();
  6907. if($report->params)
  6908. {
  6909. $params = json_decode($report->params);
  6910. $settings->group1 = $params->group1;
  6911. $settings->group2 = $params->group2;
  6912. $settings->columnTotal = 'sum';
  6913. foreach($params->reportField as $index => $field)
  6914. {
  6915. $column = new stdclass();
  6916. $column->field = $field;
  6917. $column->slice = $field;
  6918. $column->stat = isset($params->reportType) ? zget($params->reportType, $index, 'noStat') : 'noStat';
  6919. $column->showTotal = (isset($params->reportTotal) && zget($params->reportTotal, $index, '') == '1') ? 'sum' : 'noShow';
  6920. $column->showMode = (isset($params->percent) && zget($params->percent, $index, '') == '1' && isset($params->contrast) && zget($params->contrast, $index, '') == 'crystalTotal') ? 'total' : 'default';
  6921. $column->monopolize = isset($params->showAlone) ? zget($params->showAlone, $index, '0') : '0';
  6922. if($column->stat == 'sum')
  6923. {
  6924. $sumAppend = isset($params->sumAppend) ? zget($params->sumAppend, $index, $field) : $field;
  6925. if($sumAppend == $field) $column->slice = 'noSlice';
  6926. if($sumAppend != $field) $column->field = $sumAppend;
  6927. }
  6928. $columns[] = $column;
  6929. }
  6930. }
  6931. else
  6932. {
  6933. $column = new stdclass();
  6934. $column->field = '';
  6935. $column->stat = '';
  6936. $column->slice = 'noSlice';
  6937. $column->showMode = 'default';
  6938. $column->showTotal = 'noShow';
  6939. $columns[] = $column;
  6940. $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  6941. $stmt = $this->dbh->query($data->sql);
  6942. if(!$stmt)
  6943. {
  6944. $data->stage = 'draft';
  6945. $data->step = 1;
  6946. }
  6947. else
  6948. {
  6949. $settings->summary = 'notuse';
  6950. }
  6951. }
  6952. $settings->columns = $columns;
  6953. /* Process fieldSettings. */
  6954. $sql = $data->sql;
  6955. if(!empty($filters))
  6956. {
  6957. foreach($filters as $filter)
  6958. {
  6959. $filterDefault = isset($filter->default) ? $filter->default : '';
  6960. if($filter->type == 'date') $filterDefault = $this->pivot->processDateVar($filterDefault);
  6961. $filterDefault = $filterDefault === null ? "NULL" : "'{$filterDefault}'";
  6962. $sql = str_replace('$' . $filter->field, $filterDefault, $sql);
  6963. }
  6964. }
  6965. $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  6966. $stmt = $this->dbh->query($sql);
  6967. if(!$stmt) /* Fix bug #35559. */
  6968. {
  6969. $data->stage = 'draft';
  6970. $fieldSettings = array();
  6971. }
  6972. else
  6973. {
  6974. $fieldSettings = $this->getFieldSettings($sql);
  6975. }
  6976. $data->settings = json_encode($settings);
  6977. $data->fields = json_encode($fieldSettings);
  6978. $this->dao->insert(TABLE_PIVOT)->data($data)->autoCheck()->exec();
  6979. }
  6980. return !dao::isError();
  6981. }
  6982. /**
  6983. * Get field settings.
  6984. *
  6985. * @param string $sql
  6986. * @access public
  6987. * @return array
  6988. */
  6989. public function getFieldSettings($sql)
  6990. {
  6991. if(!$sql) return array();
  6992. $this->loadModel('bi');
  6993. $this->loadModel('dataview');
  6994. $this->loadModel('chart');
  6995. $columns = $this->bi->getColumnsType($sql);
  6996. $columnFields = array();
  6997. foreach($columns as $column => $type) $columnFields[$column] = $column;
  6998. $tableAndFields = $this->bi->getTableAndFields($sql);
  6999. $tables = $tableAndFields['tables'];
  7000. $fields = $tableAndFields['fields'];
  7001. $moduleNames = array();
  7002. if($tables) $moduleNames = $this->dataview->getModuleNames($tables);
  7003. list($fieldPairs, $relatedObject) = $this->dataview->mergeFields($columnFields, $fields, $moduleNames);
  7004. $fieldSettings = array();
  7005. foreach($fieldPairs as $field => $name)
  7006. {
  7007. $relatedTable = zget($relatedObject, $field);
  7008. $fieldSetting = new stdclass();
  7009. $fieldSetting->object = $relatedTable;
  7010. $fieldSetting->field = $field;
  7011. $fieldSetting->type = 'string';
  7012. $fieldSettings[$field] = $fieldSetting;
  7013. }
  7014. return $fieldSettings;
  7015. }
  7016. /**
  7017. * Convert doc collect.
  7018. *
  7019. * @access public
  7020. * @return bool
  7021. */
  7022. public function convertDocCollect()
  7023. {
  7024. $this->saveLogs('Run Method ' . __FUNCTION__);
  7025. $fields = $this->dao->descTable(TABLE_DOC);
  7026. foreach($fields as $field)
  7027. {
  7028. if($field->field == 'collects') return true;
  7029. }
  7030. $users = $this->dao->select('account')->from(TABLE_USER)->fetchPairs();
  7031. $docs = $this->dao->select('id,collector')->from(TABLE_DOC)->where('collector')->ne('')->fetchPairs();
  7032. $this->dao->update(TABLE_DOC)->set('collector')->eq(0)->exec();
  7033. $this->dao->exec("ALTER TABLE " . TABLE_DOC . " CHANGE `collector` `collects` smallint unsigned NOT NULL DEFAULT '0'");
  7034. $data = new stdclass();
  7035. $data->action = 'collect';
  7036. $data->date = helper::now();
  7037. foreach($docs as $id => $collector)
  7038. {
  7039. $collects = 0;
  7040. $data->doc = $id;
  7041. foreach(explode(',', $collector) as $account)
  7042. {
  7043. $account = trim($account);
  7044. if(empty($account)) continue;
  7045. if(!isset($users[$account])) continue;
  7046. $data->actor = $account;
  7047. $this->dao->insert(TABLE_DOCACTION)->data($data)->autoCheck()->exec();
  7048. if(dao::isError()) continue;
  7049. $collects++;
  7050. }
  7051. if($collects) $this->dao->update(TABLE_DOC)->set('collects')->eq($collects)->where('id')->eq($id)->exec();
  7052. }
  7053. return true;
  7054. }
  7055. /**
  7056. * 在功能设置中设置UR开关.
  7057. * Set UR switch status in feature switch.
  7058. *
  7059. * @access public
  7060. * @return bool
  7061. */
  7062. public function setURSwitchStatus()
  7063. {
  7064. if(is_numeric($this->fromVersion[0]) && version_compare($this->fromVersion, '18.2', '>=')) return true;
  7065. if(strpos($this->fromVersion, 'biz') !== false && version_compare($this->fromVersion, 'biz8.2', '>=')) return true;
  7066. if(strpos($this->fromVersion, 'max') !== false && version_compare($this->fromVersion, 'max4.2', '>=')) return true;
  7067. $URSwitchStatus = $this->loadModel('setting')->getItem("owner=system&module=custom&key=URAndSR");
  7068. if(!$URSwitchStatus && $this->config->edition != 'ipd')
  7069. {
  7070. $closedFeatures = $this->setting->getItem('owner=system&module=common&key=closedFeatures');
  7071. if(strpos($closedFeatures, 'productUR') === false) $closedFeatures .= ',productUR';
  7072. $this->setting->setItem('system.common.closedFeatures', trim($closedFeatures, ','));
  7073. }
  7074. return true;
  7075. }
  7076. /**
  7077. * 处理devOps上线步骤的历史记录。
  7078. * Process deploy step action. Delete deploystep action where id not in zt_deploy.
  7079. *
  7080. * @access public
  7081. * @return bool
  7082. */
  7083. public function processDeployStepAction()
  7084. {
  7085. $steps = $this->dao->select('*')->from(TABLE_DEPLOYSTEP)->fetchAll('id');
  7086. if($steps) $this->dao->delete()->from(TABLE_ACTION)->where('objectType')->eq('deploystep')->andWhere('objectID')->notIN(array_keys($steps))->exec();
  7087. }
  7088. /**
  7089. * 更新BI相关zt_pivot表的sql字段。
  7090. * Update BI SQL for 18.4.stable new function: dataview model.php checkUniColumn().
  7091. *
  7092. * @access public
  7093. * @return void
  7094. */
  7095. public function updateBISQL()
  7096. {
  7097. $alpha1File = $this->getUpgradeFile('18.4.alpha1');
  7098. $beta1File = $this->getUpgradeFile('18.4.beta1');
  7099. $alpha1SQL = explode(";", file_get_contents($alpha1File));
  7100. $beta1SQL = explode(";", file_get_contents($beta1File));
  7101. $execSQL = array();
  7102. foreach($alpha1SQL as $sql) if(strpos($sql, '`zt_pivot`') !== false) $execSQL[] = $sql;
  7103. foreach($beta1SQL as $sql) if(strpos($sql, '`zt_pivot`') !== false) $execSQL[] = $sql;
  7104. /* Update stage to published and update sql. */
  7105. foreach($execSQL as $sql)
  7106. {
  7107. $sql = str_replace('zt_', $this->config->db->prefix, $sql);
  7108. $sql = trim($sql);
  7109. $this->dbh->exec($sql);
  7110. }
  7111. }
  7112. /**
  7113. * 更新透视表的阶段。
  7114. * Update pivot stage.
  7115. *
  7116. * @access public
  7117. * @return void
  7118. */
  7119. public function updatePivotStage()
  7120. {
  7121. $this->loadModel('pivot');
  7122. $this->loadModel('chart');
  7123. $this->loadModel('dataview');
  7124. $this->loadModel('bi');
  7125. $pivotList = $this->dao->select('*')->from(TABLE_PIVOT)->where('deleted')->eq(0)->fetchAll('id', false);
  7126. foreach($pivotList as $pivotID => $pivot)
  7127. {
  7128. if($pivot->stage == 'draft' || empty($pivot->sql)) continue;
  7129. $filters = json_decode($pivot->filters, true);
  7130. $filters = $this->pivot->setFilterDefault(empty($filters) ? array() : $filters);
  7131. $sql = trim(str_replace(';', '', $pivot->sql));
  7132. $sql = $this->bi->parseSqlVars($sql, $filters);
  7133. $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  7134. $stmt = $this->dbh->query($sql);
  7135. if(!$stmt || !$this->dataview->checkUniColumn($sql))
  7136. {
  7137. $this->dao->update(TABLE_PIVOT)->set('stage')->eq('draft')->where('id')->eq($pivotID)->exec();
  7138. }
  7139. }
  7140. }
  7141. /**
  7142. * 修复丢失的工作流字段。
  7143. * Fix missed rnd workflow field.
  7144. *
  7145. * @access public
  7146. * @return void
  7147. */
  7148. public function fixMissedFlowField()
  7149. {
  7150. if($this->config->edition == 'open' || !in_array($this->fromVersion, $this->config->upgrade->missedFlowFieldVersions)) return;
  7151. $this->loadModel('workflow');
  7152. $this->loadModel('workflowaction');
  7153. $this->loadModel('workflowlayout');
  7154. $this->upgradeTao->createModules2Workflow();
  7155. $this->upgradeTao->createActions2Workflow();
  7156. $this->upgradeTao->createLayouts2Workflow();
  7157. }
  7158. /**
  7159. * 更新透视表中字段的类型。
  7160. * Update pivot fields type, pivotID maybe is 1008,1012,1013,1015,1020,1027.
  7161. *
  7162. * @access public
  7163. * @return void
  7164. */
  7165. public function updatePivotFieldsType()
  7166. {
  7167. $fieldsPairs = array();
  7168. $fieldsPairs[] = array('maybeID' => 1008, 'beforefields' => '"execution":{"object":"project","field":"execution","type":"string"},"type":{"object":"project","field":"type","type":"string"}',
  7169. 'fields' => '{"id":{"object":"project","field":"id","type":"string"},"project":{"object":"project","field":"project","type":"string"},"execution":{"object":"project","field":"execution","type":"string"},"type":{"object":"task","field":"type","type":"option"},"taskID":{"object":"task","field":"taskID","type":"string"},"projectstatus":{"object":"task","field":"projectstatus","type":"string"}}',
  7170. 'langs' => '{"project":{"zh-cn":"\\u9879\\u76ee\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"execution":{"zh-cn":"\\u6267\\u884c\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"id":{"zh-cn":"\\u9879\\u76eeID","zh-tw":"","en":"","de":"","fr":""},"type":{"zh-cn":"\\u4efb\\u52a1\\u7c7b\\u578b","zh-tw":"","en":"","de":"","fr":""},"taskID":{"zh-cn":"taskID","zh-tw":"","en":"","de":"","fr":""},"projectstatus":{"zh-cn":"projectstatus","zh-tw":"","en":"","de":"","fr":""}}');
  7171. $fieldsPairs[] = array('maybeID' => 1012, 'beforefields' => '"project":{"object":"projectstory","field":"project","type":"string"},"execution":{"object":"project","field":"execution","type":"string"},"status":{"object":"project","field":"status","type":"string"}',
  7172. 'fields' => '{"id":{"object":"project","field":"id","type":"string"},"project":{"object":"projectstory","field":"project","type":"string"},"execution":{"object":"project","field":"execution","type":"string"},"status":{"object":"story","field":"status","type":"option"}}',
  7173. );
  7174. $fieldsPairs[] = array('maybeID' => 1013, 'beforefields' => '"execution":{"object":"project","field":"execution","type":"string"},"stage":{"object":"project","field":"stage","type":"string"}}',
  7175. 'fields' => '{"id":{"object":"project","field":"id","type":"string"},"project":{"object":"projectstory","field":"project","type":"string"},"execution":{"object":"project","field":"execution","type":"string"},"stage":{"object":"story","field":"stage","type":"option"}}',
  7176. 'langs' => '{"project":{"zh-cn":"\\u9879\\u76ee\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"execution":{"zh-cn":"\\u6267\\u884c\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"id":{"zh-cn":"\\u9879\\u76eeID","zh-tw":"","en":"","de":"","fr":""},"stage":{"zh-cn":"\\u9636\\u6bb5","zh-tw":"","en":"","de":"","fr":""}}');
  7177. $fieldsPairs[] = array('maybeID' => 1015, 'beforefields' => '"bugID":{"object":"bug","field":"bugID","type":"string"},"status":{"object":"project","field":"status","type":"string"}',
  7178. 'fields' => '{"id":{"object":"project","field":"id","type":"string"},"project":{"object":"project","field":"project","type":"string"},"t3id":{"object":"project","field":"t3id","type":"string"},"execution":{"object":"project","field":"execution","type":"string"},"bugID":{"object":"bug","field":"bugID","type":"string"},"status":{"object":"bug","field":"status","type":"option"}}',
  7179. );
  7180. $fieldsPairs[] = array('maybeID' => 1020, 'beforefields' => '"bugID":{"object":"project","field":"bugID","type":"string"},"type":{"object":"product","field":"type","type":"string"}',
  7181. 'fields' => '{"id":{"object":"product","field":"id","type":"string"},"name":{"object":"product","field":"name","type":"string"},"bugID":{"object":"project","field":"bugID","type":"string"},"type":{"object":"bug","field":"type","type":"option"}}',
  7182. 'langs' => '{"count":{"zh-cn":"\\u9700\\u6c42\\u6570","zh-tw":"\\u9700\\u6c42\\u6570","en":"Stories"},"done":{"zh-cn":"\\u5b8c\\u6210\\u6570","zh-tw":"\\u5b8c\\u6210\\u6570","en":"Done"},"id":{"zh-cn":"\\u7f16\\u53f7","zh-tw":"","en":"","de":"","fr":""},"name":{"zh-cn":"\\u4ea7\\u54c1\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"bugID":{"zh-cn":"bugID","zh-tw":"","en":"","de":"","fr":""},"type":{"zh-cn":"Bug\\u7c7b\\u578b","zh-tw":"","en":"","de":"","fr":""}}');
  7183. $fieldsPairs[] = array('maybeID' => 1027, 'beforefields' => '"bugID":{"object":"bug","field":"bugID","type":"string"},"type":{"object":"project","field":"type","type":"string"}',
  7184. 'fields' => '{"id":{"object":"project","field":"id","type":"string"},"project":{"object":"project","field":"project","type":"string"},"t3id":{"object":"project","field":"t3id","type":"string"},"execution":{"object":"project","field":"execution","type":"string"},"bugID":{"object":"bug","field":"bugID","type":"string"},"type":{"object":"bug","field":"type","type":"option"}}',
  7185. 'langs' => '{"stories":{"zh-cn":"\\u9700\\u6c42\\u6570","zh-tw":"\\u9700\\u6c42\\u6570","en":"Stories"},"tasks":{"zh-cn":"\\u4efb\\u52a1\\u6570","zh-tw":"\\u4efb\\u52a1\\u6570","en":"Tasks"},"undoneStory":{"zh-cn":"\\u5269\\u4f59\\u9700\\u6c42\\u6570","zh-tw":"\\u5269\\u4f59\\u9700\\u6c42\\u6570","en":"Undone Story"},"undoneTask":{"zh-cn":"\\u5269\\u4f59\\u4efb\\u52a1\\u6570","zh-tw":"\\u5269\\u4f59\\u4efb\\u52a1\\u6570","en":"Undone Task"},"consumed":{"zh-cn":"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6","zh-tw":"\\u5df2\\u6d88\\u8017\\u5de5\\u65f6","en":"Cost(h)"},"left":{"zh-cn":"\\u5269\\u4f59\\u5de5\\u65f6","zh-tw":"\\u5269\\u4f59\\u5de5\\u65f6","en":"Left(h)"},"consumedPercent":{"zh-cn":"\\u8fdb\\u5ea6","zh-tw":"\\u8fdb\\u5ea6","en":"Process"},"execution":{"zh-cn":"\\u6267\\u884c\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"id":{"zh-cn":"\\u9879\\u76eeID","zh-tw":"","en":"","de":"","fr":""},"project":{"zh-cn":"\\u9879\\u76ee\\u540d\\u79f0","zh-tw":"","en":"","de":"","fr":""},"bugID":{"zh-cn":"bugID","zh-tw":"","en":"","de":"","fr":""},"type":{"zh-cn":"Bug\\u7c7b\\u578b","zh-tw":"","en":"","de":"","fr":""}}');
  7186. foreach($fieldsPairs as $field)
  7187. {
  7188. $pivotID = $this->dao->select('id')->from(TABLE_PIVOT)->where('fields')->like("%{$field['beforefields']}%")->fetch('id');
  7189. if(empty($pivotID)) continue;
  7190. $data = new stdclass();
  7191. if(isset($field['fields'])) $data->fields = $field['fields'];
  7192. if(isset($field['langs'])) $data->langs = $field['langs'];
  7193. $this->dao->update(TABLE_PIVOT)->data($data)->where('id')->eq($pivotID)->exec();
  7194. }
  7195. }
  7196. /**
  7197. * 为18_5, biz8_5, max4_5这三个版本的用户故事添加创建历史记录。
  7198. * Add create action for story when version is 18_5, biz8_5, max4_5.
  7199. *
  7200. * @access public
  7201. * @return void
  7202. */
  7203. public function addCreateAction4Story()
  7204. {
  7205. if(!in_array($this->fromVersion, array('18_5', 'biz8_5', 'max4_5'))) return;
  7206. $stories = $this->dao->select('id,product,openedBy,openedDate,vision')->from(TABLE_STORY)->where('openedDate')->ge('2023-07-12')->fetchAll('id');
  7207. foreach($stories as $story)
  7208. {
  7209. $firstAction = $this->dao->select('*')->from(TABLE_ACTION)->where('objectType')->eq('story')->andWhere('objectID')->eq($story->id)->orderBy('date,id')->fetch();
  7210. if(empty($firstAction) || $firstAction->action != 'opened')
  7211. {
  7212. $actionDate = $story->openedDate;
  7213. if($firstAction->date <= $actionDate) $actionDate = date('Y-m-d H:i:s', strtotime($firstAction->date) - 1);
  7214. $action = new stdclass();
  7215. $action->objectType = 'story';
  7216. $action->objectID = $story->id;
  7217. $action->product = ',' . $story->product . ',';
  7218. $action->project = 0;
  7219. $action->execution = 0;
  7220. $action->actor = $story->openedBy;
  7221. $action->action = 'opened';
  7222. $action->date = $actionDate;
  7223. $action->comment = '';
  7224. $action->extra = '';
  7225. $action->read = 0;
  7226. $action->vision = $story->vision;
  7227. $action->efforted = 0;
  7228. $this->dao->insert(TABLE_ACTION)->data($action)->exec();
  7229. }
  7230. }
  7231. }
  7232. /**
  7233. * 当从12.x升级到18.x时,删除产品线必填字段。
  7234. * Remove product line required fields when 12.x upgrade to 18.x.
  7235. *
  7236. * @access public
  7237. * @return bool
  7238. */
  7239. public function removeProductLineRequired()
  7240. {
  7241. $createRequired = $this->loadModel('setting')->getItem('owner=system&module=product&section=create&key=requiredFields');
  7242. $editRequired = $this->setting->getItem('owner=system&module=product&section=edit&key=requiredFields');
  7243. $createRequired = str_replace(',line,', ',', ",$createRequired,");
  7244. $editRequired = str_replace(',line,', ',', ",$editRequired,");
  7245. if(!empty(trim($createRequired, ','))) $this->setting->setItem('system.product.create.requiredFields', trim($createRequired, ','));
  7246. if(!empty(trim($editRequired, ','))) $this->setting->setItem('system.product.edit.requiredFields', trim($editRequired, ','));
  7247. return true;
  7248. }
  7249. /**
  7250. * 将旧的度量指标转换为新的度量指标。
  7251. * Convert old metrics to new metrics.
  7252. *
  7253. * @access public
  7254. * @return bool
  7255. */
  7256. public function processOldMetrics()
  7257. {
  7258. $this->loadModel('metric');
  7259. $scopeMap = $this->config->metric->oldScopeMap;
  7260. $purposeMap = $this->config->metric->oldPurposeMap;
  7261. $objectMap = $this->config->metric->oldObjectMap;
  7262. $oldMetrics = $this->dao->select('*')->from(TABLE_BASICMEAS)->where('deleted')->eq('0')->orderBy('order_asc')->fetchAll('id', false);
  7263. foreach($oldMetrics as $oldMetric)
  7264. {
  7265. $metric = new stdclass();
  7266. $metric->scope = zget($scopeMap, $oldMetric->scope, 'other');
  7267. $metric->purpose = zget($purposeMap, $oldMetric->purpose, 'other');
  7268. $metric->object = zget($objectMap, $oldMetric->object, 'other');
  7269. $metric->stage = 'wait';
  7270. $metric->type = 'sql';
  7271. $metric->name = $oldMetric->name;
  7272. $metric->code = $oldMetric->code;
  7273. $metric->desc = '';
  7274. $metric->definition = $oldMetric->definition;
  7275. $metric->createdBy = $oldMetric->createdBy;
  7276. $metric->createdDate = helper::isZeroDate($oldMetric->createdDate) ? null : $oldMetric->createdDate;
  7277. $metric->editedBy = $oldMetric->editedBy;
  7278. $metric->editedDate = helper::isZeroDate($oldMetric->editedDate) ? null : $oldMetric->editedDate;
  7279. $metric->builtin = '1';
  7280. $metric->fromID = $oldMetric->id;
  7281. $metric->order = 0;
  7282. $metric->deleted = $oldMetric->deleted;
  7283. $this->dao->insert(TABLE_METRIC)->data($metric)->exec();
  7284. $metricID = $this->dao->lastInsertID();
  7285. $this->loadModel('action')->create('metric', $metricID, 'created', '', '', 'system');
  7286. }
  7287. return !dao::isError();
  7288. }
  7289. /**
  7290. * processHistoryOfStory方法的别名。
  7291. * Alias for processHistoryOfStory.
  7292. *
  7293. * @access public
  7294. * @return void
  7295. */
  7296. public function processHistoryDataForMetric()
  7297. {
  7298. $this->processHistoryOfStory();
  7299. }
  7300. /**
  7301. * 构建用户需求的历史记录。
  7302. * Process history of story.
  7303. *
  7304. * @access public
  7305. * @return void
  7306. */
  7307. public function processHistoryOfStory()
  7308. {
  7309. $releasedStorys = $this->upgradeTao->getReleasedStorys();
  7310. foreach($releasedStorys as $releasedStory) $this->dao->update(TABLE_STORY)->set('releasedDate')->eq($releasedStory->date)->where('id')->eq($releasedStory->id)->exec();
  7311. }
  7312. /**
  7313. * Upgrade testtask members.
  7314. *
  7315. * @access public
  7316. * @return void
  7317. */
  7318. public function upgradeTesttaskMembers()
  7319. {
  7320. $memberGroup = $this->dao->select('task,lastRunner')->from(TABLE_TESTRUN)
  7321. ->where('lastRunner')->ne('')
  7322. ->fetchGroup('task', 'lastRunner');
  7323. foreach($memberGroup as $taskID => $members)
  7324. {
  7325. $members = implode(',', array_keys($members));
  7326. $this->dao->update(TABLE_TESTTASK)->set('members')->eq($members)->where('id')->eq($taskID)->exec();
  7327. }
  7328. }
  7329. /**
  7330. * 删除瀑布流通用报表。
  7331. * Delete waterfall general report block.
  7332. *
  7333. * @access public
  7334. * @return true
  7335. */
  7336. public function deleteGeneralReportBlock()
  7337. {
  7338. $closedBlocks = $this->loadModel('setting')->getItem('owner=system&module=block&key=closed');
  7339. if(strpos($closedBlocks, 'waterfallgeneralreport') !== false)
  7340. {
  7341. $closedBlocks = str_replace(',project|waterfallgeneralreport', '', $closedBlocks);
  7342. $this->setting->setItem('system.block.closed', $closedBlocks);
  7343. }
  7344. $fields = $this->dao->descTable(TABLE_BLOCK);
  7345. foreach($fields as $field)
  7346. {
  7347. if($field->field == 'block') $this->dao->delete()->from(TABLE_BLOCK)->where('block')->eq('waterfallgeneralreport')->exec();
  7348. }
  7349. return true;
  7350. }
  7351. /**
  7352. * Sync xuanxuan https config.
  7353. *
  7354. * @access public
  7355. * @return void
  7356. */
  7357. public function syncXXHttpsConfig()
  7358. {
  7359. $this->dao->update(TABLE_CONFIG)->set('value')->eq('off')->where('`key`')->eq('isHttps')->andWhere('`section`')->eq('xuanxuan')->andWhere('`value`')->eq('0')->exec();
  7360. $this->dao->update(TABLE_CONFIG)->set('value')->eq('on')->where('`key`')->eq('isHttps')->andWhere('`section`')->eq('xuanxuan')->andWhere('`value`')->eq('1')->exec();
  7361. return true;
  7362. }
  7363. /**
  7364. * Create demo API.
  7365. *
  7366. * @access public
  7367. * @return void
  7368. */
  7369. public function createDemoAPI()
  7370. {
  7371. $this->loadModel('api')->createDemoData($this->lang->api->zentaoAPI, commonModel::getSysURL() . $this->app->config->webRoot . 'api.php/v1', '16.0');
  7372. return true;
  7373. }
  7374. /**
  7375. * Complete the database for different edition.
  7376. *
  7377. * @access public
  7378. * @return void
  7379. */
  7380. public function completionAllSQL()
  7381. {
  7382. switch($this->fromEdition)
  7383. {
  7384. case 'open':
  7385. $this->execSQL($this->getUpgradeFile('proinstall'));
  7386. case 'pro':
  7387. $this->execSQL($this->getUpgradeFile('bizinstall'));
  7388. case 'biz':
  7389. $this->execSQL($this->getUpgradeFile('maxinstall'));
  7390. $this->execSQL($this->getUpgradeFile('functionss'));
  7391. }
  7392. }
  7393. /**
  7394. * 为喧喧表添加adminInvite字段。
  7395. * Add adminInvite field for xuanxuan.
  7396. *
  7397. * @access public
  7398. * @return void
  7399. */
  7400. public function addAdminInviteField()
  7401. {
  7402. $table = $this->config->db->prefix . 'im_chat';
  7403. $exists = $this->checkFieldsExists($table, 'adminInvite');
  7404. if(!$exists) $this->dbh->exec("ALTER TABLE {$table} ADD `adminInvite` enum('0','1') NOT NULL DEFAULT '0' AFTER `mergedChats`");
  7405. }
  7406. /**
  7407. * 安装IPD。
  7408. * Insetall IPD.
  7409. *
  7410. * @access public
  7411. * @return void
  7412. */
  7413. public function installIPD()
  7414. {
  7415. $fromVersion = $this->fromVersion;
  7416. $ipdinstall = false;
  7417. if(is_numeric($fromVersion[0]) && version_compare($fromVersion, '18.5', '<=')) $ipdinstall = true;
  7418. if(strpos($fromVersion, 'pro') !== false) $ipdinstall = true;
  7419. if(strpos($fromVersion, 'biz') !== false && version_compare($fromVersion, 'biz8.5', '<=')) $ipdinstall = true;
  7420. if(strpos($fromVersion, 'max') !== false && version_compare($fromVersion, 'max4.5', '<=')) $ipdinstall = true;
  7421. if($ipdinstall) $this->execSQL($this->getUpgradeFile('ipdinstall'));
  7422. if($fromVersion == 'ipd1.0.beta1') $this->execSQL($this->getUpgradeFile('ipd1.0.beta1'));
  7423. }
  7424. /**
  7425. * 停止旧的计划任务。
  7426. * Stop old cron.
  7427. *
  7428. * @access public
  7429. * @return void
  7430. */
  7431. public function stopOldCron()
  7432. {
  7433. touch($this->app->getCacheRoot() . 'restartcron');
  7434. }
  7435. /**
  7436. * 检查是否包含一致性错误。
  7437. * Check has consistency error or not.
  7438. *
  7439. * @access public
  7440. * @return bool
  7441. */
  7442. public function hasConsistencyError()
  7443. {
  7444. $logFile = $this->getConsistencyLogFile();
  7445. if(!file_exists($logFile)) return false;
  7446. $lastTwoLines = array(0 => '', 1 => '');
  7447. $fh = fopen($logFile, 'r');
  7448. while(!feof($fh))
  7449. {
  7450. $lastTwoLines[0] = $lastTwoLines[1];
  7451. $lastTwoLines[1] = fgets($fh);
  7452. }
  7453. fclose($fh);
  7454. if(trim($lastTwoLines[0]) == 'HasError') return true;
  7455. return false;
  7456. }
  7457. /**
  7458. * 获取没有合并的产品数量。
  7459. * Get no merged product count.
  7460. *
  7461. * @access public
  7462. * @return int
  7463. */
  7464. public function getNoMergedProductCount()
  7465. {
  7466. return (int)$this->dao->select('COUNT(1) AS count')->from(TABLE_PRODUCT)->where('program')->eq(0)->andWhere('vision')->eq('rnd')->fetch('count');
  7467. }
  7468. /**
  7469. * 获取没有合并的迭代数量。
  7470. * Get no merged sprint count.
  7471. *
  7472. * @access public
  7473. * @return int
  7474. */
  7475. public function getNoMergedSprintCount()
  7476. {
  7477. return (int)$this->dao->select('COUNT(1) AS count')->from(TABLE_PROJECT)->where('vision')->eq('rnd')->andWhere('project')->eq(0)->andWhere('type')->eq('sprint')->andWhere('deleted')->eq('0')->fetch('count');
  7478. }
  7479. /**
  7480. * Add default traincourse priv to each group.
  7481. *
  7482. * @access public
  7483. * @return bool
  7484. */
  7485. public function addDefaultTraincoursePriv()
  7486. {
  7487. $groups = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->ne('limited')->andWhere('vision')->eq('rnd')->fetchPairs();
  7488. $sqlTemplate = 'REPLACE INTO ' . TABLE_GROUPPRIV . " (`group`, `module`, `method`) VALUES ('%s', 'traincourse', 'browse'), ('%s', 'traincourse', 'viewCourse'), ('%s', 'traincourse', 'viewChapter'), ('%s', 'traincourse', 'practice'), ('%s', 'traincourse', 'practiceBrowse'), ('%s', 'traincourse', 'practiceView'), ('%s', 'traincourse', 'updatePractice');";
  7489. foreach($groups as $groupID)
  7490. {
  7491. $sql = str_replace('%s', (string)$groupID, $sqlTemplate);
  7492. $this->dbh->exec($sql);
  7493. }
  7494. return true;
  7495. }
  7496. /**
  7497. * Rename BI default module name.
  7498. *
  7499. * @access public
  7500. * @return bool
  7501. */
  7502. public function renameBIModule()
  7503. {
  7504. $moduleNames = array('zh' => '默认分组', 'en' => 'Default');
  7505. $replaceNames = array('zh' => '未分组', 'en' => 'Ungrouped');
  7506. $BIModules = array('pivot', 'chart');
  7507. foreach($BIModules as $BImodule)
  7508. {
  7509. foreach($moduleNames as $lang => $moduleName)
  7510. {
  7511. $modules = $this->dao->select('*')->from(TABLE_MODULE)->where('name')->eq($moduleName)->andWhere('type')->eq($BImodule)->fetchAll();
  7512. if(count($modules) == 2)
  7513. {
  7514. $data = new stdclass();
  7515. $data->name = $replaceNames[$lang];
  7516. $data->order = 1000;
  7517. foreach($modules as $module) $this->dao->update(TABLE_MODULE)->data($data)->where('id')->eq($module->id)->exec();
  7518. break;
  7519. }
  7520. }
  7521. }
  7522. return true;
  7523. }
  7524. /**
  7525. * Migrate xuanxuan client settings, make owners user accounts.
  7526. *
  7527. * @access public
  7528. * @return void
  7529. */
  7530. public function migrateXuanClientSettings()
  7531. {
  7532. $existingSettings = $this->dao->select('`key`, `value`')->from(TABLE_CONFIG)
  7533. ->where('owner')->eq('system')
  7534. ->andWhere('module')->eq('chat')
  7535. ->andWhere('section')->eq('settings')
  7536. ->fetchAll();
  7537. if(empty($existingSettings)) return true;
  7538. foreach($existingSettings as $setting)
  7539. {
  7540. $migratedSetting = array('owner' => $setting->key, 'module' => 'chat', 'section' => 'clientSettings', 'key' => 'settings', 'value' => $setting->value);
  7541. $this->dao->insert(TABLE_CONFIG)->data($migratedSetting)->exec();
  7542. }
  7543. $this->dao->delete()->from(TABLE_CONFIG)->where('owner')->eq('system')->andWhere('module')->eq('chat')->andWhere('section')->eq('settings')->exec();
  7544. return true;
  7545. }
  7546. /**
  7547. * Revert story custom fields.
  7548. *
  7549. * @access public
  7550. * @return void
  7551. */
  7552. public function revertStoryCustomFields()
  7553. {
  7554. $this->dao->delete()->from(TABLE_CONFIG)->where('section')->eq('productBrowse')->andWhere('module')->eq('datatable')->andWhere('key')->eq('cols')->exec();
  7555. $this->dao->delete()->from(TABLE_CONFIG)->where('section')->eq('executionStory')->andWhere('module')->eq('datatable')->andWhere('key')->eq('cols')->exec();
  7556. return true;
  7557. }
  7558. /**
  7559. * Update metric datetype.
  7560. *
  7561. * @access public
  7562. * @return void
  7563. */
  7564. public function updateMetricDateType()
  7565. {
  7566. $this->loadModel('metric');
  7567. foreach($this->config->metric->dateType as $code => $dateType)
  7568. {
  7569. $this->dao->update(TABLE_METRIC)->set('dateType')->eq($dateType)->where('code')->eq($code)->exec();
  7570. }
  7571. $this->dao->update(TABLE_METRIC)->set('dateType')->eq('nodate')->where('dateType')->eq('')->exec();
  7572. }
  7573. /**
  7574. * Migrate configured AI model (from pre 18.11) into table.
  7575. *
  7576. * @access public
  7577. * @return void
  7578. */
  7579. public function migrateAIModelConfig()
  7580. {
  7581. $aiSettings = $this->loadModel('setting')->getItems('owner=system&module=ai');
  7582. if(empty($aiSettings)) return;
  7583. $aiConfig = new stdclass();
  7584. foreach($aiSettings as $item) $aiConfig->{$item->key} = $item->value;
  7585. $model = new stdclass();
  7586. $model->type = $aiConfig->type;
  7587. $model->vendor = $aiConfig->vendor ?: '';
  7588. $model->enabled = $aiConfig->status == 'on' ? '1' : '0';
  7589. $model->desc = $aiConfig->description ?: '';
  7590. $model->createdDate = helper::now();
  7591. $model->createdBy = 'system';
  7592. if(empty($model->type)) return;
  7593. $credentials = new stdclass();
  7594. $credentials->key = $aiConfig->key ?: '';
  7595. if($model->type == 'baidu-ernie')
  7596. {
  7597. $credentials->secret = $aiConfig->secret ?: '';
  7598. }
  7599. elseif($aiConfig->vendor == 'azure')
  7600. {
  7601. $credentials->resource = $aiConfig->resource ?: '';
  7602. $credentials->deployment = $aiConfig->deployment ?: '';
  7603. }
  7604. $model->credentials = json_encode($credentials);
  7605. $model->proxy = $aiConfig->proxyType == 'socks5' ? json_encode((object)array('type' => 'socks5', 'addr' => $aiConfig->proxyAddr)) : null;
  7606. $this->dao->insert(TABLE_AI_MODEL)->data($model)->exec();
  7607. if(!dao::isError()) $this->setting->deleteItems('owner=system&module=ai');
  7608. }
  7609. /**
  7610. * 检查办公应用是否有数据,没有就隐藏。
  7611. * Check if the office app has data, if not, hide it.
  7612. *
  7613. * @access public
  7614. * @return void
  7615. */
  7616. public function hideOA()
  7617. {
  7618. $closedFeatures = $this->loadModel('setting')->getItem('owner=system&module=common&section=&key=closedFeatures');
  7619. if(strpos($closedFeatures, 'otherOA') !== false) return;
  7620. $attends = $this->dao->select('id')->from(TABLE_ATTEND)->count();
  7621. if($attends) return;
  7622. $leaves = $this->dao->select('id')->from(TABLE_LEAVE)->count();
  7623. if($leaves) return;
  7624. $lieus = $this->dao->select('id')->from(TABLE_LIEU)->count();
  7625. if($lieus) return;
  7626. $overtimes = $this->dao->select('id')->from(TABLE_OVERTIME)->count();
  7627. if($overtimes) return;
  7628. $this->setting->setItem('system.common.closedFeatures', trim($closedFeatures . ',otherOA', ','));
  7629. }
  7630. /**
  7631. * 检查是否添加了18.10.1版本的 AI 表,如果没有就添加。
  7632. * Check if the AI table of version 18.10.1 is added, if not, add it.
  7633. *
  7634. * @access public
  7635. * @return void
  7636. */
  7637. public function update18101()
  7638. {
  7639. $count = $this->dao->select('COUNT(1) AS count')->from('information_schema.TABLES')->where('TABLE_SCHEMA')->eq($this->config->db->name)->andWhere('TABLE_NAME')->eq(str_replace('`', '', TABLE_AI_MODEL))->fetch('count');
  7640. if($count) return;
  7641. /* Execute open edition. */
  7642. $this->saveLogs('Execute 18_10_1');
  7643. $this->execSQL($this->getUpgradeFile('18.10.1'));
  7644. }
  7645. /**
  7646. * 检查是否执行了18.11版本的SQL。
  7647. * Exec 18.11 sql file.
  7648. *
  7649. * @access public
  7650. * @return void
  7651. */
  7652. public function update1811()
  7653. {
  7654. /* Execute open edition. */
  7655. $this->saveLogs('Execute 18_11');
  7656. $this->execSQL($this->getUpgradeFile('18.11'));
  7657. }
  7658. /**
  7659. * 更新通过工作流添加的字段的默认值。
  7660. * Update the default value of the fields added through the workflow.
  7661. *
  7662. * @access public
  7663. * @return true
  7664. */
  7665. public function updateWorkflowFieldDefaultValue()
  7666. {
  7667. if($this->config->edition == 'open') return false;
  7668. $this->loadModel('workflowfield');
  7669. $tables = $this->dao->select('module, `table`')->from(TABLE_WORKFLOW)->fetchPairs();
  7670. $nullFields = $this->dao->select('module, field, type')->from(TABLE_WORKFLOWFIELD)
  7671. ->where('buildin')->eq(0)
  7672. ->andWhere('type')->in('date, datetime, text')
  7673. ->andWhere('field')->notin('id, subStatus, deleted')
  7674. ->fetchAll();
  7675. $notNullFields = $this->dao->select('module, field, type, length, `default`')->from(TABLE_WORKFLOWFIELD)
  7676. ->where('buildin')->eq(0)
  7677. ->andWhere('type')->notin('date, datetime, text')
  7678. ->andWhere('field')->notin('id, subStatus, deleted')
  7679. ->fetchAll();
  7680. if(!$nullFields && !$notNullFields) return true;
  7681. $sqls = array();
  7682. foreach($nullFields as $field)
  7683. {
  7684. if(!isset($tables[$field->module])) continue;
  7685. $table = $tables[$field->module];
  7686. $sqls[] = "ALTER TABLE `$table` MODIFY `$field->field` $field->type NULL;";
  7687. }
  7688. foreach($notNullFields as $field)
  7689. {
  7690. if(!isset($tables[$field->module])) continue;
  7691. $table = $tables[$field->module];
  7692. $isNumber = in_array($field->type, $this->config->workflowfield->numberTypes);
  7693. if($field->length)
  7694. {
  7695. if($field->type == 'decimal')
  7696. {
  7697. list($integerDigits, $decimalDigits) = explode(',', $field->length);
  7698. $field->length = $integerDigits + $decimalDigits . ',' . $decimalDigits;
  7699. }
  7700. $field->type .= "($field->length)";
  7701. }
  7702. $sql = "ALTER TABLE `$table` MODIFY `$field->field` $field->type NOT NULL";
  7703. if($field->default)
  7704. {
  7705. $sql .= ' DEFAULT ' . $this->dbh->quote($field->default) . ';';
  7706. }
  7707. else
  7708. {
  7709. $sql .= $isNumber ? ' DEFAULT 0;' : " DEFAULT '';";
  7710. }
  7711. $sqls[] = $sql;
  7712. }
  7713. if(!$sqls) return true;
  7714. try
  7715. {
  7716. foreach($sqls as $sql)
  7717. {
  7718. $this->saveLogs($sql);
  7719. $this->dbh->query($sql);
  7720. }
  7721. }
  7722. catch(PDOException $exception)
  7723. {
  7724. static::$errors[] = $exception->getMessage();
  7725. }
  7726. return true;
  7727. }
  7728. /**
  7729. * 将值为'0000-00-00'的日期类型字段值更新为NULL。
  7730. * Update the default value of the fields.
  7731. *
  7732. * @access public
  7733. * @return true
  7734. */
  7735. public function updateZeroDateToNull()
  7736. {
  7737. set_time_limit(0);
  7738. if(in_array($this->config->db->driver, $this->config->mysqlDriverList))
  7739. {
  7740. try
  7741. {
  7742. $this->dao->exec("SET @@sql_mode=''");
  7743. $tables = $this->dao->select("TABLE_NAME, COLUMN_NAME, DATA_TYPE")->from('information_schema.COLUMNS')
  7744. ->where("TABLE_SCHEMA")->eq($this->config->db->name)
  7745. ->andWhere("TABLE_NAME")->like($this->config->db->prefix . '%')
  7746. ->andWhere("DATA_TYPE")->in('date,datetime')
  7747. ->fetchAll();
  7748. foreach($tables as $table)
  7749. {
  7750. $tableName = $table->TABLE_NAME;
  7751. $columnName = $table->COLUMN_NAME;
  7752. $columnType = $table->DATA_TYPE;
  7753. if(strpos($tableName, $this->config->db->prefix) !== 0) continue;
  7754. if($columnType == 'date')
  7755. {
  7756. $this->dbh->exec("UPDATE `$tableName` SET `$columnName` = NULL WHERE `$columnName` = '0000-00-00'");
  7757. }
  7758. elseif($columnType == 'datetime')
  7759. {
  7760. $this->dbh->exec("UPDATE `$tableName` SET `$columnName` = NULL WHERE `$columnName` = '0000-00-00 00:00:00'");
  7761. }
  7762. }
  7763. }
  7764. catch(PDOException $e)
  7765. {
  7766. $this->saveLogs($e->getMessage());
  7767. }
  7768. }
  7769. }
  7770. /**
  7771. * 将设置阶段时自定义字段更改为按项目模型保存。
  7772. * Set stage custom fields by project model.
  7773. *
  7774. * @access public
  7775. * @return true
  7776. */
  7777. public function updateProgramplanCustom()
  7778. {
  7779. $createFields = $this->dao->select('*')->from(TABLE_CONFIG)
  7780. ->where('module')->eq('programplan')
  7781. ->andWhere('section')->eq('custom')
  7782. ->andWhere('`key`')->eq('createFields')
  7783. ->fetchAll('id', false);
  7784. foreach($createFields as $createField)
  7785. {
  7786. unset($createField->id);
  7787. $createField->key = 'createWaterfallFields'; //瀑布模型
  7788. $this->dao->insert(TABLE_CONFIG)->data($createField)->exec();
  7789. $createField->key = 'createWaterfallplusFields'; //融合瀑布模型
  7790. $this->dao->insert(TABLE_CONFIG)->data($createField)->exec();
  7791. }
  7792. /* 删除旧的config。*/
  7793. $this->dao->delete()->from(TABLE_CONFIG)->where('module')->eq('programplan')->andWhere('section')->eq('custom')->andWhere('`key`')->eq('createFields')->exec();
  7794. return true;
  7795. }
  7796. /**
  7797. * 为自定义过的需求阶段增加条目。
  7798. * Add items for custom story stage.
  7799. *
  7800. * @access public
  7801. * @return bool
  7802. */
  7803. public function changeCustomStoryStage()
  7804. {
  7805. $clientLang = $this->app->getClientLang();
  7806. $customStagePairs = $this->dao->select('`key`, value')->from(TABLE_LANG)
  7807. ->where('module')->eq('story')
  7808. ->andWhere('section')->eq('stageList')
  7809. ->andWhere('lang')->eq($clientLang)
  7810. ->fetchPairs();
  7811. if(empty($customStagePairs))
  7812. {
  7813. $clientLang = 'all';
  7814. $customStagePairs = $this->dao->select('`key`, value')->from(TABLE_LANG)
  7815. ->where('module')->eq('story')
  7816. ->andWhere('section')->eq('stageList')
  7817. ->andWhere('lang')->eq($clientLang)
  7818. ->fetchPairs();
  7819. }
  7820. if(empty($customStagePairs)) return false;
  7821. $this->dao->delete()->from(TABLE_LANG)
  7822. ->where('module')->eq('story')
  7823. ->andWhere('section')->eq('stageList')
  7824. ->andWhere('lang')->eq($clientLang)
  7825. ->exec();
  7826. foreach($this->lang->upgrade->storyStageList as $key => $value)
  7827. {
  7828. $item = new stdclass();
  7829. $item->lang = $clientLang;
  7830. $item->module = 'story';
  7831. $item->section = 'stageList';
  7832. $item->key = $key;
  7833. $item->value = isset($customStagePairs[$key]) ? $customStagePairs[$key] : $value;
  7834. $item->system = '1';
  7835. $item->vision = 'rnd';
  7836. $this->dao->insert(TABLE_LANG)->data($item)->exec();
  7837. }
  7838. return true;
  7839. }
  7840. /**
  7841. * 禅道20版本之前,用需和软需是关联关系,多对多,现改为父子关系,一对多。
  7842. * Before ZenTao 20, the relationship between use requirements and story is a many-to-many relationship, now changed to a parent-child relationship, a one-to-many relationship.
  7843. *
  7844. * @access public
  7845. * @return void
  7846. */
  7847. public function processStoryRelation()
  7848. {
  7849. $this->loadModel('story');
  7850. $relations = $this->dao->select('t1.*, t2.parent as BParent, t2.isParent')->from(TABLE_RELATION)->alias('t1')
  7851. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.BID = t2.id')
  7852. ->where('AType')->eq('requirement')
  7853. ->andWhere('BType')->eq('story')
  7854. ->andWhere('relation')->eq('subdivideinto')
  7855. ->orderBy('AID asc')
  7856. ->fetchGroup('AID', 'BID');
  7857. $requirementRoadmaps = $this->dao->select('id,roadmap')->from(TABLE_STORY)
  7858. ->where('id')->notin(array_keys($relations))
  7859. ->andWhere('roadmap')->ne(0)
  7860. ->andWhere('type')->eq('requirement')
  7861. ->andWhere('stage')->eq('wait')
  7862. ->andWhere('linkStories')->eq('')
  7863. ->andWhere('linkRequirements')->eq('')
  7864. ->fetchPairs('id');
  7865. $processd = array(); // 记录已处理的软件需求,一个软件需求只能有一个父需求。
  7866. foreach($relations as $AID => $storyList)
  7867. {
  7868. $hasChild = false;
  7869. $lastChildID = 0;
  7870. foreach($storyList as $BID => $story)
  7871. {
  7872. if($story->BParent > 0) continue; // 如果已经是子需求,无需处理。
  7873. if(!isset($processd[$BID])) // 没有被其它用需变为子需求,才处理。
  7874. {
  7875. $processd[$BID] = $BID;
  7876. $hasChild = true;
  7877. $lastChildID = $BID;
  7878. /* 当软件需求变为子需求后,需要更改这些字段。 */
  7879. $child = new stdclass();
  7880. $child->parent = $AID;
  7881. $child->parentVersion = $story->AVersion;
  7882. $child->path = ',' . $AID . ',' . $BID . ',';
  7883. $child->root = $AID;
  7884. $this->dao->update(TABLE_STORY)->data($child)->where('id')->eq($BID)->exec();
  7885. /* 如果该软件需求本身就是父需求,则将root和path同步给它的子需求。 */
  7886. if($story->isParent == '1')
  7887. {
  7888. $this->dao->update(TABLE_STORY)
  7889. ->set('root')->eq($AID)
  7890. ->set("path = concat(',', {$AID}, path)")
  7891. ->where('parent')->eq($BID)
  7892. ->exec();
  7893. }
  7894. /* 将用需软需的关联关系去掉,它们现在是父子关系了。 */
  7895. $this->dao->delete()->from(TABLE_RELATION)->where('AID')->eq($BID)->andWhere('BID')->eq($AID)->exec();
  7896. $this->dao->delete()->from(TABLE_RELATION)->where('AID')->eq($AID)->andWhere('BID')->eq($BID)->exec();
  7897. }
  7898. }
  7899. if($hasChild)
  7900. {
  7901. $this->dao->update(TABLE_STORY)->set('isParent')->eq('1')->where('id')->eq($AID)->exec();
  7902. $status = $this->dao->select('status')->from(TABLE_STORY)->where('id')->eq($AID)->fetch('status');
  7903. if($status == 'closed')
  7904. {
  7905. $this->dao->update(TABLE_STORY)->set('stage')->eq('closed')->where('id')->eq($AID)->exec();
  7906. continue;
  7907. }
  7908. /* 如果用需成为了父需求且状态不是关闭,则需要通过它最后一个子需求计算它的阶段、工时、状态等信息。 */
  7909. $this->story->setStage($lastChildID);
  7910. $this->story->updateParentStatus($lastChildID, $AID, false);
  7911. }
  7912. }
  7913. $roadmapList = $this->dao->select('id,status')->from(TABLE_ROADMAP)->where('id')->in(array_values($requirementRoadmaps))->fetchPairs('id');
  7914. foreach($requirementRoadmaps as $requirementID => $roadmapID)
  7915. {
  7916. if(!isset($roadmapList[$roadmapID])) continue;
  7917. $roadmapStatus = $roadmapList[$roadmapID];
  7918. $requirementStage = $roadmapStatus == 'launched' ? 'incharter' : 'inroadmap';
  7919. $this->dao->update(TABLE_STORY)->set('stage')->eq($requirementStage)->where('id')->eq($requirementID)->exec();
  7920. }
  7921. /* 其它没被处理成父子关系的细分关系,变成关联关系。 */
  7922. $this->dao->update(TABLE_RELATION)->set('relation')->eq('linkedto')->where('relation')->eq('subdivideinto')->exec();
  7923. $this->dao->update(TABLE_RELATION)->set('relation')->eq('linkedfrom')->where('relation')->eq('subdividedfrom')->exec();
  7924. }
  7925. /**
  7926. * 禅道20版本前,软需关联软需,用需关联用需,是以字符串形式记录在story表的linkStories字段中,现迁移到relation表中。
  7927. * Before ZenTao 20, the relationship between stories and stories, and the relationship between requirements and requirements, are recorded in the linkStories field of the story table in string form, and now migrated to the relation table.
  7928. *
  7929. * @access public
  7930. * @return void
  7931. */
  7932. public function processLinkStories()
  7933. {
  7934. $doProcess = function($stories, $storyType)
  7935. {
  7936. $relation = new stdclass();
  7937. $relation->product = 0;
  7938. foreach($stories as $storyID => $linkStories)
  7939. {
  7940. $this->dao->delete()->from(TABLE_RELATION)->where('product')->eq(0)->andWhere('relation')->eq('linkedto')->andWhere('AType')->eq($storyType)->andWhere('BType')->eq($storyType)->andWhere('AID')->eq($storyID)->andWhere('BID')->in($linkStories)->exec();
  7941. $this->dao->delete()->from(TABLE_RELATION)->where('product')->eq(0)->andWhere('relation')->eq('linkedfrom')->andWhere('AType')->eq($storyType)->andWhere('BType')->eq($storyType)->andWhere('AID')->in($linkStories)->andWhere('BID')->eq($storyID)->exec();
  7942. $linkStories = explode(',', trim($linkStories, ','));
  7943. foreach($linkStories as $linkStoryID)
  7944. {
  7945. if(!$linkStoryID) continue;
  7946. $relation->AType = $storyType;
  7947. $relation->AID = $storyID;
  7948. $relation->BType = $storyType;
  7949. $relation->BID = $linkStoryID;
  7950. $relation->relation = 'linkedto';
  7951. $this->dao->insert(TABLE_RELATION)->data($relation)->exec();
  7952. $relation->AType = $storyType;
  7953. $relation->AID = $linkStoryID;
  7954. $relation->BType = $storyType;
  7955. $relation->BID = $storyID;
  7956. $relation->relation = 'linkedfrom';
  7957. $this->dao->insert(TABLE_RELATION)->data($relation)->exec();
  7958. }
  7959. }
  7960. };
  7961. $stories = $this->dao->select('id, linkStories')->from(TABLE_STORY)
  7962. ->where('linkStories')->ne('')
  7963. ->andWhere('deleted')->eq('0')
  7964. ->fetchPairs();
  7965. $requirements = $this->dao->select('id, linkRequirements')->from(TABLE_STORY)
  7966. ->where('linkRequirements')->ne('')
  7967. ->andWhere('deleted')->eq('0')
  7968. ->fetchPairs();
  7969. $doProcess($stories, 'story');
  7970. $doProcess($requirements, 'requirement');
  7971. }
  7972. /**
  7973. * 如果用户自定义过需求概念,则追加默认的业需概念。
  7974. * If the user has customized the concept of demand, append the default concept of business demand.
  7975. *
  7976. * @access public
  7977. * @return void
  7978. */
  7979. public function addERName()
  7980. {
  7981. /* If the mode is light, disable the epic story. */
  7982. /* 如果是轻量模式,禁用业务需求和用户需求。 */
  7983. if(zget($this->config->global, 'mode', 'light') == 'light')
  7984. {
  7985. $this->loadModel('setting')->setItem('system.custom.enableER', '0');
  7986. }
  7987. $closedFeatures = $this->loadModel('setting')->getItem('owner=system&module=common&key=closedFeatures');
  7988. if(strpos($closedFeatures, 'productUR') !== false) $closedFeatures .= ',productER,';
  7989. $this->setting->setItem('system.common.closedFeatures', trim($closedFeatures, ','));
  7990. $lang = $this->app->getClientLang();
  7991. $URSRList = $this->dao->select('*')->from(TABLE_LANG)
  7992. ->where('lang')->eq($lang)
  7993. ->andWhere('module')->eq('custom')
  7994. ->andWhere('section')->eq('URSRList')
  7995. ->fetchAll('id', false);
  7996. foreach($URSRList as $URSR)
  7997. {
  7998. $value = json_decode($URSR->value);
  7999. if(isset($value->ERName)) continue;
  8000. $value->ERName = $this->lang->defaultERName;
  8001. $this->dao->update(TABLE_LANG)
  8002. ->set('value')->eq(json_encode($value))
  8003. ->where('id')->eq($URSR->id)
  8004. ->exec();
  8005. }
  8006. }
  8007. /**
  8008. * 为IPD项目开发阶段追加TR4A评审点
  8009. * Add TR4A points to the development phase of IPD project
  8010. *
  8011. * @access public
  8012. * @return bool
  8013. */
  8014. public function addTR4APoint()
  8015. {
  8016. if($this->config->edition != 'ipd') return true;
  8017. $this->loadModel('review');
  8018. /* 获取所有已经设置过阶段的IPD项目。*/
  8019. /* Get all IPD projects that have been set up to phase. */
  8020. $projects = $this->dao->select('t1.project')->from(TABLE_OBJECT)->alias('t1')->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')->where('t2.model')->eq('ipd')->fetchPairs('project');
  8021. $data = new stdclass();
  8022. $data->title = $this->lang->review->reviewPoint->titleList['TR4A'];
  8023. $data->category = 'TR4A';
  8024. $data->type = 'reviewed';
  8025. $data->enabled = 0;
  8026. $data->range = 'all';
  8027. foreach($projects as $projectID)
  8028. {
  8029. $data->project = $projectID;
  8030. $this->dao->insert(TABLE_OBJECT)->data($data)->exec();
  8031. }
  8032. return true;
  8033. }
  8034. /**
  8035. * 更新升级大屏和度量项数据。
  8036. * Import screen and metric data.
  8037. *
  8038. * @access public
  8039. * @return bool
  8040. */
  8041. public function upgradeScreenAndMetricData()
  8042. {
  8043. $this->saveLogs('Run Method ' . __FUNCTION__);
  8044. $this->dao->clearTablesDescCache();
  8045. $metricSQLs = $this->loadModel('bi')->prepareBuiltinMetricSQL('update');
  8046. $screenSQLs = $this->bi->prepareBuiltinScreenSQL('update');
  8047. $upgradeSqls = array_merge($metricSQLs, $screenSQLs);
  8048. $this->dbh->beginTransaction();
  8049. try
  8050. {
  8051. foreach($upgradeSqls as $sql)
  8052. {
  8053. $sql = trim($sql);
  8054. if(empty($sql)) continue;
  8055. $this->saveLogs($sql);
  8056. $prefix = in_array($this->config->db->driver, $this->config->pgsqlDriverList) ? 'public' : $this->config->db->name;
  8057. $sql = str_replace('`zt_', $prefix . '.`zt_', $sql);
  8058. $sql = str_replace('zt_', $this->config->db->prefix, $sql);
  8059. $this->dbh->exec($sql);
  8060. }
  8061. }
  8062. catch(Error $e)
  8063. {
  8064. static::$errors[] = $e->getMessage();
  8065. $this->dbh->rollBack();
  8066. return false;
  8067. }
  8068. return $this->dbh->commit();
  8069. }
  8070. /**
  8071. * 更新升级BI内置数据。
  8072. * Import BI data.
  8073. *
  8074. * @access public
  8075. * @return bool
  8076. */
  8077. public function upgradeBIData()
  8078. {
  8079. $this->loadModel('bi');
  8080. $this->saveLogs('Run Method ' . __FUNCTION__);
  8081. $this->dao->clearTablesDescCache();
  8082. /* Prepare built-in sqls of bi. */
  8083. $chartSQLs = $this->bi->prepareBuiltinChartSQL('update');
  8084. $pivotSQLs = $this->bi->prepareBuiltinPivotSQL('update');
  8085. $upgradeSqls = array_merge($chartSQLs, $pivotSQLs);
  8086. $this->dbh->beginTransaction();
  8087. try
  8088. {
  8089. foreach($upgradeSqls as $sql)
  8090. {
  8091. $sql = trim($sql);
  8092. if(empty($sql)) continue;
  8093. $this->saveLogs($sql);
  8094. $prefix = in_array($this->config->db->driver, $this->config->pgsqlDriverList) ? 'public' : $this->config->db->name;
  8095. $sql = str_replace('`zt_', $prefix . '.`zt_', $sql);
  8096. $sql = str_replace('zt_', $this->config->db->prefix, $sql);
  8097. $this->dbh->exec($sql);
  8098. }
  8099. }
  8100. catch(Error $e)
  8101. {
  8102. static::$errors[] = $e->getMessage();
  8103. $this->dbh->rollBack();
  8104. return false;
  8105. }
  8106. return $this->dbh->commit();
  8107. }
  8108. /**
  8109. * 根据是否加载 APCu 扩展,开启缓存。
  8110. * Open cache by load APCu or not.
  8111. *
  8112. * @access public
  8113. * @return bool
  8114. */
  8115. public function openCacheByAPCu()
  8116. {
  8117. return $this->loadModel('install')->enableCache();
  8118. }
  8119. /**
  8120. * 补充分类项语言项。
  8121. * Complete classify lang.
  8122. *
  8123. * @access public
  8124. * @return bool
  8125. */
  8126. public function completeClassifyLang()
  8127. {
  8128. $this->app->loadLang('install');
  8129. $classifyLang = $this->dao->select('*')->from(TABLE_LANG)
  8130. ->where('lang')->eq('all')
  8131. ->andWhere('module')->eq('process')
  8132. ->andWhere('section')->in(array('scrumClassify', 'agileplusClassify', 'waterfallplusClassify'))
  8133. ->andWhere('`key`')->in(array('support', 'engineering', 'project'))
  8134. ->andWhere('vision')->eq('rnd')
  8135. ->fetchGroup('section', 'key');
  8136. $classifyData = new stdclass();
  8137. $classifyData->lang = 'all';
  8138. $classifyData->module = 'process';
  8139. $classifyData->vision = 'rnd';
  8140. $classifyData->system = 1;
  8141. foreach(array('scrumClassify', 'agileplusClassify', 'waterfallplusClassify') as $section)
  8142. {
  8143. $classifyData->section = $section;
  8144. foreach($this->lang->install->langList as $langInfo)
  8145. {
  8146. if($langInfo['module'] != 'process') continue;
  8147. if(isset($classifyLang[$section][$langInfo['key']])) continue;
  8148. $classifyData->key = $langInfo['key'];
  8149. $classifyData->value = $langInfo['value'];
  8150. $this->dao->insert(TABLE_LANG)->data($classifyData)->exec();
  8151. }
  8152. }
  8153. return true;
  8154. }
  8155. /**
  8156. * 新增反馈的数据源,并且更新到工单的由反馈转化字段。
  8157. * Add feedback datasource and set options of ticket feedback.
  8158. *
  8159. * @access public
  8160. * @return bool
  8161. */
  8162. public function addFeedbackDatasource()
  8163. {
  8164. $this->loadModel('feedback');
  8165. $datasource = new stdclass();
  8166. $datasource->type = 'sql';
  8167. $datasource->name = $this->lang->feedback->common;
  8168. $datasource->code = 'feedbacks';
  8169. $datasource->datasource = "select id,title from zt_feedback where deleted = '0'";
  8170. $datasource->keyField = 'id';
  8171. $datasource->valueField = 'title';
  8172. $datasource->buildin = '1';
  8173. $datasource->vision = 'rnd';
  8174. $datasource->vision = 'rnd';
  8175. $datasource->createdBy = 'system';
  8176. $datasource->createdDate = helper::now();
  8177. $this->dao->insert(TABLE_WORKFLOWDATASOURCE)->data($datasource)->autoCheck()->exec();
  8178. $datasourceID = $this->dao->lastInsertID();
  8179. $view = "view_datasource_$datasourceID";
  8180. $sql = "CREATE OR REPLACE VIEW $view (`id`, `title`) AS $datasource->datasource";
  8181. $this->dbh->query($sql);
  8182. $this->dao->update(TABLE_WORKFLOWDATASOURCE)->set('view')->eq($view)->where('id')->eq($datasourceID)->exec();
  8183. $this->dao->update(TABLE_WORKFLOWFIELD)->set('options')->eq($datasourceID)->where('module')->eq('ticket')->andWhere('field')->eq('feedback')->exec();
  8184. return !dao::isError();
  8185. }
  8186. /**
  8187. * 将瀑布项目的活动和文档同步到融合瀑。
  8188. * Synchronise the activities and zoutputs of the Waterfall project with the Waterfallplus project.
  8189. *
  8190. * @access public
  8191. * @return bool
  8192. */
  8193. public function syncActivityAndOutput()
  8194. {
  8195. $models = array('waterfall' => 'waterfallplus', 'scrum' => 'agileplus');
  8196. $acitivies = $this->dao->select('*')->from(TABLE_ACTIVITY)->where('deleted')->eq(0)->orderBy('order_asc')->fetchGroup('process', 'id');
  8197. $zoutputs = $this->dao->select('*')->from(TABLE_ZOUTPUT)->where('deleted')->eq(0)->orderBy('order_asc')->fetchGroup('activity', 'id');
  8198. foreach($models as $model => $plusModel)
  8199. {
  8200. $plusProcesses = $this->dao->select('*')->from(TABLE_PROCESS)->where('deleted')->eq(0)->andWhere('model')->eq($plusModel)->orderBy('order_asc')->fetchAll('id', false);
  8201. if(!empty($plusProcesses))
  8202. {
  8203. $plusActivities = $this->dao->select('*')->from(TABLE_ACTIVITY)->where('deleted')->eq(0)->andWhere('process')->in(array_keys($plusProcesses))->orderBy('order_asc')->fetchAll();
  8204. if(!empty($plusActivities)) continue;
  8205. }
  8206. $processes = $this->dao->select('*')->from(TABLE_PROCESS)->where('deleted')->eq(0)->andWhere('model')->eq($model)->orderBy('order_asc')->fetchAll('id', false);
  8207. foreach($processes as $process)
  8208. {
  8209. if(!isset($acitivies[$process->id])) continue;
  8210. foreach($plusProcesses as $plusProcess)
  8211. {
  8212. if($plusProcess->name != $process->name || $plusProcess->type != $process->type || $plusProcess->abbr != $process->abbr) continue;
  8213. foreach($acitivies[$process->id] as $activity)
  8214. {
  8215. $data = clone $activity;
  8216. $data->process = $plusProcess->id;
  8217. $data->editedDate = $data->assignedDate = null;
  8218. unset($data->id);
  8219. $this->dao->insert(TABLE_ACTIVITY)->data($data)->autoCheck()->exec();
  8220. $newActivityID = $this->dao->lastInsertID();
  8221. if(!isset($zoutputs[$activity->id])) continue;
  8222. foreach($zoutputs[$activity->id] as $zoutput)
  8223. {
  8224. $data = clone $zoutput;
  8225. $data->activity = $newActivityID;
  8226. $data->editedDate = null;
  8227. unset($data->id);
  8228. $this->dao->insert(TABLE_ZOUTPUT)->data($data)->autoCheck()->exec();
  8229. }
  8230. }
  8231. }
  8232. }
  8233. }
  8234. return !dao::isError();
  8235. }
  8236. /**
  8237. * 升级推算已关闭需求池需求阶段。
  8238. * Process demand stage.
  8239. *
  8240. * @access public
  8241. * @return bool
  8242. */
  8243. public function processDemandStage()
  8244. {
  8245. $demands = $this->dao->select('id')->from(TABLE_DEMAND)->fetchPairs('id');
  8246. if(empty($demands)) return true;
  8247. $this->dao->update(TABLE_DEMAND)->set('stage')->eq('closed')->where('status')->eq('closed')->exec();
  8248. $this->loadModel('demand')->updateDemandStage($demands);
  8249. return true;
  8250. }
  8251. /**
  8252. * 导入内置工作流。
  8253. * Import buildin workflow.
  8254. *
  8255. * @param string $vision
  8256. * @param string $importModule 指定导入的模块
  8257. * @access public
  8258. * @return void
  8259. */
  8260. public function importBuildinWorkflow($vision = 'all', $importModule = '')
  8261. {
  8262. $this->loadModel('workflow');
  8263. $this->loadModel('workflowaction');
  8264. $this->loadModel('workflowfield');
  8265. $this->loadModel('workflowlayout');
  8266. $modules = $this->config->workflow->buildin->modules;
  8267. $visions = $this->config->workflow->buildin->visions;
  8268. $actions = $this->config->workflowaction->buildin->actions;
  8269. $actionTypes = $this->config->workflowaction->buildin->types;
  8270. $actionMethods = $this->config->workflowaction->buildin->methods;
  8271. $actionOpens = $this->config->workflowaction->buildin->opens;
  8272. $actionLayouts = $this->config->workflowaction->buildin->layouts;
  8273. $actionPositions = $this->config->workflowaction->buildin->positions;
  8274. $actionShows = $this->config->workflowaction->buildin->shows;
  8275. $fields = $this->config->workflowfield->buildin->fields;
  8276. $layouts = $this->config->workflowlayout->buildin->layouts;
  8277. $account = isset($this->app->user->account) ? $this->app->user->account : 'admin';
  8278. $now = helper::now();
  8279. /* Insert buildin modules to TABLE_WORKFLOW. */
  8280. $data = new stdclass();
  8281. $data->buildin = 1;
  8282. $data->createdBy = $account;
  8283. $data->createdDate = $now;
  8284. $data->status = 'normal';
  8285. foreach($modules as $app => $appModules)
  8286. {
  8287. $data->app = $app;
  8288. foreach($appModules as $module => $options)
  8289. {
  8290. if($importModule && strpos(",$importModule,", ",$module,") === false) continue;
  8291. $this->app->loadLang($module);
  8292. $data->vision = 'rnd';
  8293. $data->module = $module;
  8294. $data->name = isset($this->lang->$module->common) ? $this->lang->$module->common : $module;
  8295. $data->table = str_replace('`', '', zget($options, 'table', ''));
  8296. $data->navigator = zget($options, 'navigator', 'secondary');
  8297. if(!empty($visions[$module])) $data->vision = $visions[$module];
  8298. if($module == 'execution') $data->name = $this->lang->workflow->execution;
  8299. if($module == 'story') $data->name = $this->lang->SRCommon;
  8300. if($module == 'epic') $data->name = $this->lang->ERCommon;
  8301. if($module == 'requirement') $data->name = $this->lang->URCommon;
  8302. if($vision != 'all' && $vision != $data->vision) continue;
  8303. $this->dao->delete()->from(TABLE_WORKFLOW)->where('app')->eq($app)->andWhere('module')->eq($module)->andWhere('vision')->eq($data->vision)->exec();
  8304. $this->dao->insert(TABLE_WORKFLOW)->data($data)->exec();
  8305. }
  8306. }
  8307. /* Insert actions of buildin modules to TABLE_WORKFLOWACTION. */
  8308. $data = new stdclass();
  8309. $data->buildin = 1;
  8310. $data->role = 'buildin';
  8311. $data->extensionType = 'none';
  8312. $data->createdBy = $account;
  8313. $data->createdDate = $now;
  8314. foreach($actions as $module => $moduleActions)
  8315. {
  8316. if($importModule && strpos(",$importModule,", ",$module,") === false) continue;
  8317. $data->module = $module;
  8318. foreach($moduleActions as $action)
  8319. {
  8320. $data->action = $action;
  8321. /* Use default action name if not set flow action name. */
  8322. $name = '';
  8323. if(isset($this->lang->$module->$action)) $name = $this->lang->$module->$action;
  8324. if(empty($name) && in_array($action, array_keys($this->config->flowAction)))
  8325. {
  8326. $methodName = $this->config->flowAction[$action];
  8327. if(isset($this->lang->$module->$methodName)) $name = $this->lang->$module->$methodName;
  8328. }
  8329. if(empty($name) && isset($this->lang->workflowaction->default->actions[$action])) $name = $this->lang->workflowaction->default->actions[$action];
  8330. if(empty($name)) $name = $action;
  8331. $data->name = $name;
  8332. $data->method = $data->action;
  8333. $data->open = 'normal';
  8334. $data->layout = 'normal';
  8335. $data->type = 'single';
  8336. $data->position = 'browseandview';
  8337. $data->show = 'direct';
  8338. $data->vision = 'rnd';
  8339. if(isset($actionMethods[$module][$action])) $data->method = $actionMethods[$module][$action];
  8340. if(isset($actionOpens[$module][$action])) $data->open = $actionOpens[$module][$action];
  8341. if(isset($actionLayouts[$module][$action])) $data->layout = $actionLayouts[$module][$action];
  8342. if(isset($actionTypes[$module][$action])) $data->type = $actionTypes[$module][$action];
  8343. if(isset($actionPositions[$module][$action])) $data->position = $actionPositions[$module][$action];
  8344. if(isset($actionShows[$module][$action])) $data->show = $actionShows[$module][$action];
  8345. if(!empty($visions[$module])) $data->vision = $visions[$module];
  8346. if($vision != 'all' && $vision != $data->vision) continue;
  8347. $this->dao->delete()->from(TABLE_WORKFLOWACTION)->where('module')->eq($module)->andWhere('action')->eq($action)->andWhere('vision')->eq($data->vision)->exec();
  8348. $this->dao->insert(TABLE_WORKFLOWACTION)->data($data)->exec();
  8349. }
  8350. }
  8351. /* Insert fields of buildin modules to TABLE_WORKFLOWFIELD. */
  8352. $data = new stdclass();
  8353. $data->role = 'buildin';
  8354. $data->createdBy = $account;
  8355. $data->createdDate = $now;
  8356. foreach($fields as $module => $moduleFields)
  8357. {
  8358. if($importModule && strpos(",$importModule,", ",$module,") === false) continue;
  8359. $order = 1;
  8360. $data->module = $module;
  8361. $currentVision = 'rnd';
  8362. if(!empty($visions[$module])) $currentVision = $visions[$module];
  8363. if($vision != 'all' && $vision != $currentVision) continue;
  8364. foreach($moduleFields as $field => $options)
  8365. {
  8366. $data->field = $field;
  8367. $data->name = isset($this->lang->$module->$field) ? $this->lang->$module->$field : $field;
  8368. $data->type = zget($options, 'type', 'varchar');
  8369. $data->length = zget($options, 'length', '');
  8370. $data->control = zget($options, 'control', 'input');
  8371. $data->options = zget($options, 'options', '[]');
  8372. $data->default = zget($options, 'default', '');
  8373. $data->buildin = zget($options, 'buildin', 1);
  8374. $data->order = $order;
  8375. $data->readonly = ($field == 'subStatus') ? '0' : '1';
  8376. $data->rules = zget($options, 'rules', '');
  8377. $order++;
  8378. if($module == 'execution')
  8379. {
  8380. $execField = 'exec' . ucfirst($field);
  8381. if(isset($this->lang->$module->$execField)) $data->name = $this->lang->$module->$execField;
  8382. }
  8383. if(is_object($data->options) or is_array($data->options)) $data->options = helper::jsonEncode($data->options);
  8384. $this->dao->delete()->from(TABLE_WORKFLOWFIELD)->where('module')->eq($module)->andWhere('field')->eq($field)->exec();
  8385. $this->dao->insert(TABLE_WORKFLOWFIELD)->data($data)->exec();
  8386. }
  8387. }
  8388. /* Insert layouts of buildin modules to TABLE_WORKFLOWLAYOUT. */
  8389. $data = new stdclass();
  8390. foreach($layouts as $module => $moduleLayouts)
  8391. {
  8392. if($importModule && strpos(",$importModule,", ",$module,") === false) continue;
  8393. $data->module = $module;
  8394. foreach($moduleLayouts as $action => $layoutFields)
  8395. {
  8396. $order = 1;
  8397. $data->action = $action;
  8398. foreach($layoutFields as $field => $options)
  8399. {
  8400. $data->field = $field;
  8401. $data->width = zget($options, 'width', 0);
  8402. $data->mobileShow = zget($options, 'mobileShow', 0);
  8403. $data->order = $order;
  8404. $data->vision = 'rnd';
  8405. $order++;
  8406. if(!empty($visions[$module])) $data->vision = $visions[$module];
  8407. if($data->width == 'auto') $data->width = 0;
  8408. if($vision != 'all' && $vision != $data->vision) continue;
  8409. $this->dao->delete()->from(TABLE_WORKFLOWLAYOUT)->where('module')->eq($module)->andWhere('action')->eq($action)->andWhere('field')->eq($field)->andWhere('vision')->eq($data->vision)->exec();
  8410. $this->dao->insert(TABLE_WORKFLOWLAYOUT)->data($data)->exec();
  8411. }
  8412. }
  8413. }
  8414. /* Insert labels of buildin modules to TABLE_WORKFLOWLABEL. */
  8415. $data = new stdclass();
  8416. $data->buildin = 1;
  8417. $data->role = 'buildin';
  8418. $data->params = '[]';
  8419. $data->createdBy = $account;
  8420. $data->createdDate = $now;
  8421. foreach($modules as $app => $appModules)
  8422. {
  8423. foreach($appModules as $module => $options)
  8424. {
  8425. if($importModule && strpos(",$importModule,", ",$module,") === false) continue;
  8426. $labels = array();
  8427. if($module == 'product')
  8428. {
  8429. if(isset($this->lang->product->featureBar['all'])) $labels = $this->lang->product->featureBar['all'];
  8430. }
  8431. elseif($module == 'story')
  8432. {
  8433. if(isset($this->lang->product->featureBar['browse'])) $labels = $this->lang->product->featureBar['browse'];
  8434. }
  8435. elseif($module == 'execution')
  8436. {
  8437. if(isset($this->lang->execution->featureBar['all'])) $labels = $this->lang->execution->featureBar['all'];
  8438. }
  8439. elseif($module == 'task')
  8440. {
  8441. if(isset($this->lang->execution->featureBar['task'])) $labels = $this->lang->execution->featureBar['task'];
  8442. }
  8443. elseif($module == 'bug')
  8444. {
  8445. if(isset($this->lang->bug->featureBar['browse'])) $labels = $this->lang->bug->featureBar['browse'];
  8446. }
  8447. elseif($module == 'feedback')
  8448. {
  8449. if(isset($this->lang->feedback->menu) && (is_object($this->lang->feedback->menu) || is_array($this->lang->feedback->menu)))
  8450. {
  8451. foreach($this->lang->feedback->menu as $key => $menuItem)
  8452. {
  8453. $menus = explode('|', zget($menuItem, 'link', $menuItem));
  8454. $labels[$key] = zget($menus, 0, $menuItem);
  8455. }
  8456. }
  8457. }
  8458. else
  8459. {
  8460. if(isset($this->lang->$module->featureBar['browse'])) $labels = $this->lang->$module->featureBar['browse'];
  8461. }
  8462. $order = 1;
  8463. $data->module = $module;
  8464. $currentVision = 'rnd';
  8465. if(!empty($visions[$module])) $currentVision = $visions[$module];
  8466. if($vision != 'all' && $vision != $currentVision) continue;
  8467. foreach($labels as $key => $label)
  8468. {
  8469. $data->code = $key;
  8470. $data->label = trim(strip_tags($label));
  8471. $data->order = $order;
  8472. $order++;
  8473. $this->dao->delete()->from(TABLE_WORKFLOWLABEL)->where('module')->eq($module)->andWhere('code')->eq($key)->exec();
  8474. $this->dao->insert(TABLE_WORKFLOWLABEL)->data($data)->exec();
  8475. }
  8476. }
  8477. }
  8478. }
  8479. /**
  8480. * 将项目关联的Charter的路标放到projectproduct表中。
  8481. * Process project roadmap by charter.
  8482. *
  8483. * @access public
  8484. * @return bool
  8485. */
  8486. public function processProjectRoadmapsByCharter()
  8487. {
  8488. $projectCharters = $this->dao->select('id,charter')->from(TABLE_PROJECT)->where('charter')->ne('0')->fetchPairs('id', 'charter');
  8489. if(empty($projectCharters)) return true;
  8490. $charterGroups = $this->dao->select('t1.*,t2.id as charterID')->from(TABLE_ROADMAP)->alias('t1')
  8491. ->leftJoin(TABLE_CHARTER)->alias('t2')->on('FIND_IN_SET(t1.id, t2.roadmap)')
  8492. ->where('t2.id')->in($projectCharters)
  8493. ->fetchGroup('charterID');
  8494. $productRoadmap = new stdclass();
  8495. foreach($projectCharters as $projectID => $charter)
  8496. {
  8497. $productRoadmap->project = $projectID;
  8498. foreach($charterGroups[$charter] as $roadmap)
  8499. {
  8500. $productRoadmap->product = $roadmap->product;
  8501. $productRoadmap->branch = $roadmap->branch;
  8502. $productRoadmap->roadmap = ",$roadmap->id,";
  8503. $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->andWhere('product')->eq($roadmap->product)->andWhere('branch')->eq($roadmap->branch)->exec();
  8504. $this->dao->insert(TABLE_PROJECTPRODUCT)->data($productRoadmap)->exec();
  8505. }
  8506. }
  8507. return true;
  8508. }
  8509. /**
  8510. * 为文档库的团队空间创建默认空间。
  8511. * Create default doclib space for team space.
  8512. *
  8513. * @access public
  8514. * @return void
  8515. */
  8516. public function createDefaultDoclibSpace()
  8517. {
  8518. $this->app->loadLang('doc');
  8519. $doclib = new stdclass();
  8520. $doclib->type = 'custom';
  8521. $doclib->name = $this->lang->doclib->defaultSpace;
  8522. $doclib->acl = 'open';
  8523. $doclib->addedBy = 'admin';
  8524. $doclib->addedDate = helper::now();
  8525. foreach(array('or', 'lite', 'rnd') as $vision)
  8526. {
  8527. $hasDocLib = $this->dao->select('id')->from(TABLE_DOCLIB)->where('vision')->eq($vision)->andWhere('type')->eq('custom')->fetch('id');
  8528. if(!$hasDocLib) continue;
  8529. $doclib->vision = $vision;
  8530. $this->dao->insert(TABLE_DOCLIB)->data($doclib)->exec();
  8531. $doclibID = $this->dao->lastInsertID();
  8532. $this->dao->update(TABLE_DOCLIB)
  8533. ->set('parent')->eq($doclibID)
  8534. ->where('vision')->eq($vision)
  8535. ->andWhere('type')->eq('custom')
  8536. ->andWhere('id')->ne($doclibID)
  8537. ->exec();
  8538. }
  8539. }
  8540. /*
  8541. * Update the priv of task relation.
  8542. *
  8543. * @access public
  8544. * @return bool
  8545. */
  8546. public function updateTaskRelationPriv()
  8547. {
  8548. $groupIdList = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  8549. ->where('module')->eq('execution')
  8550. ->andWhere('method')->eq('maintainrelation')
  8551. ->fetchPairs();
  8552. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('`group`')->in($groupIdList)->andWhere('module')->eq('execution')->andWhere('method')->in('editrelation,batcheditrelation')->exec();
  8553. foreach($groupIdList as $groupID)
  8554. {
  8555. $this->dao->update(TABLE_GROUPPRIV)
  8556. ->set('method')->eq('createrelation')
  8557. ->where('`group`')->eq($groupID)
  8558. ->andWhere('module')->eq('execution')
  8559. ->andWhere('method')->eq('maintainrelation')
  8560. ->exec();
  8561. $this->dao->insert(TABLE_GROUPPRIV)->set('group')->eq($groupID)->set('module')->eq('execution')->set('method')->eq('editrelation')->exec();
  8562. $this->dao->insert(TABLE_GROUPPRIV)->set('group')->eq($groupID)->set('module')->eq('execution')->set('method')->eq('batcheditrelation')->exec();
  8563. }
  8564. return true;
  8565. }
  8566. /**
  8567. * 更新需求的验收时间。
  8568. * Update verifiedDate for story.
  8569. *
  8570. * @access public
  8571. * @return void
  8572. */
  8573. public function updateStoryVerifiedDate()
  8574. {
  8575. $verifiedDatePairs = $this->dao->select('t1.objectID, t1.date')->from(TABLE_ACTION)->alias('t1')
  8576. ->leftJoin(TABLE_HISTORY)->alias('t2')->on('t1.id = t2.action')
  8577. ->where('t1.objectType')->eq('story')
  8578. ->andWhere('t1.action')->eq('edited')
  8579. ->andWhere('t2.field')->eq('stage')
  8580. ->andWhere('t2.new')->eq('verified')
  8581. ->orderBy('date_desc')
  8582. ->fetchPairs();
  8583. foreach($verifiedDatePairs as $storyID => $verifiedDate)
  8584. {
  8585. $this->dao->update(TABLE_STORY)->set('verifiedDate')->eq($verifiedDate)->where('id')->eq($storyID)->andWhere('stage')->eq('verified')->exec();
  8586. }
  8587. return !dao::isError();
  8588. }
  8589. /**
  8590. * 更新审批流的条件分支数据结构。
  8591. * Update the condition branch data structure of the approval flow.
  8592. *
  8593. * @access public
  8594. * @return void
  8595. */
  8596. public function updateApprovalCondition()
  8597. {
  8598. $specs = $this->dao->select('id, nodes')->from(TABLE_APPROVALFLOWSPEC)->fetchPairs();
  8599. foreach($specs as $specID => $spec)
  8600. {
  8601. $nodes = json_decode($spec);
  8602. $hasBranch = false;
  8603. foreach($nodes as $id => $node)
  8604. {
  8605. if($node->type != 'branch') continue;
  8606. $hasBranch = true;
  8607. $nodes[$id] = $this->transferCondition($node);
  8608. }
  8609. if($hasBranch) $this->dao->update(TABLE_APPROVALFLOWSPEC)->set('nodes')->eq(json_encode($nodes))->where('id')->eq($specID)->exec();
  8610. }
  8611. }
  8612. /**
  8613. * 递归转换条件分支的数据结构。
  8614. * Transfer branch conditions.
  8615. *
  8616. * @param object $node
  8617. * @access public
  8618. * @return object
  8619. */
  8620. public function transferCondition($node)
  8621. {
  8622. foreach($node->branches as $index => $branch)
  8623. {
  8624. if(isset($branch->conditions))
  8625. {
  8626. $newConditions = array();
  8627. $newCondition = new stdClass();
  8628. $newCondition->conditionOperator = 'equal';
  8629. $newCondition->conditionLogical = 'or';
  8630. foreach($branch->conditions as $condition)
  8631. {
  8632. if($condition->selectType == 'account')
  8633. {
  8634. foreach($condition->users as $account)
  8635. {
  8636. $newCondition->conditionField = 'submitUsers';
  8637. $newCondition->conditionValue = $account;
  8638. $newConditions[] = clone $newCondition;
  8639. }
  8640. }
  8641. elseif($condition->selectType == 'role')
  8642. {
  8643. foreach($condition->roles as $role)
  8644. {
  8645. $newCondition->conditionField = 'submitRoles';
  8646. $newCondition->conditionValue = $role;
  8647. $newConditions[] = clone $newCondition;
  8648. }
  8649. }
  8650. elseif($condition->selectType == 'position')
  8651. {
  8652. foreach($condition->positions as $position)
  8653. {
  8654. $newCondition->conditionField = 'submitPositions';
  8655. $newCondition->conditionValue = $position;
  8656. $newConditions[] = clone $newCondition;
  8657. }
  8658. }
  8659. elseif($condition->selectType == 'dept')
  8660. {
  8661. foreach($condition->depts as $dept)
  8662. {
  8663. $newCondition->conditionField = 'submitDepts';
  8664. $newCondition->conditionValue = $dept;
  8665. $newConditions[] = clone $newCondition;
  8666. }
  8667. }
  8668. }
  8669. $node->branches[$index]->conditions = $newConditions;
  8670. }
  8671. if(isset($branch->nodes))
  8672. {
  8673. foreach($branch->nodes as $branchID => $branchNode)
  8674. {
  8675. if($branchNode->type != 'branch') continue;
  8676. $node->branches[$index]->nodes[$branchID] = $this->transferCondition($branchNode);
  8677. if(isset($branchNode->default->nodes))
  8678. {
  8679. foreach($branchNode->default->nodes as $defaultID => $defaultNode)
  8680. {
  8681. if($defaultNode->type != 'branch') continue;
  8682. $node->branches[$index]->nodes[$branchID]->default->nodes[$defaultID] = $this->transferCondition($defaultNode);
  8683. }
  8684. }
  8685. }
  8686. }
  8687. }
  8688. return $node;
  8689. }
  8690. /**
  8691. * 修正工作流字段选项。
  8692. * Fix workflowfield options.
  8693. *
  8694. * @access public
  8695. * @return bool
  8696. */
  8697. public function fixWorkflowFieldOptions()
  8698. {
  8699. $datasourceID = $this->dao->select('*')->from(TABLE_WORKFLOWDATASOURCE)->where('code')->eq('requirements')->fetch('id');
  8700. if($datasourceID != 5) return true;
  8701. $fieldOptions = $this->dao->select('*')->from(TABLE_WORKFLOWFIELD)->where('options')->ne('')->fetchPairs('id', 'options');
  8702. foreach($fieldOptions as $id => $options)
  8703. {
  8704. if(!is_numeric($options)) continue;
  8705. if($options < 5) continue;
  8706. $this->dao->update(TABLE_WORKFLOWFIELD)->set('options')->eq($options + 2)->where('id')->eq($id)->exec();
  8707. }
  8708. return true;
  8709. }
  8710. /**
  8711. * Append flow fields for belong.
  8712. *
  8713. * @access public
  8714. * @return void
  8715. */
  8716. public function appendFlowFieldsForBelong()
  8717. {
  8718. $flows = $this->dao->select('*')->from(TABLE_WORKFLOW)->where('buildin')->eq('0')->fetchAll('id', false);
  8719. if(empty($flows)) return true;
  8720. $tableFields = array();
  8721. $tables = $this->dao->showTables();
  8722. foreach($tables as $table)
  8723. {
  8724. $table = reset($table);
  8725. if(strpos($table, $this->config->db->prefix . 'flow_') !== 0) continue;
  8726. $desc = $this->dao->descTable($table);
  8727. $tableFields[$table] = array_column($desc, 'field', 'field');
  8728. }
  8729. $defaultData = array('type' => 'mediumint', 'length' => '8', 'control' => 'select', 'readonly' => 1, 'buildin' => 1, 'role' => 'default');
  8730. $orders = array('program' => '1', 'product' => '2', 'project' => '3', 'execution' => '4');
  8731. $fields = array();
  8732. $fields['execution'] = "ALTER TABLE %table% ADD `execution` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `id`";
  8733. $fields['project'] = "ALTER TABLE %table% ADD `project` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `id`";
  8734. $fields['product'] = "ALTER TABLE %table% ADD `product` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `id`";
  8735. $fields['program'] = "ALTER TABLE %table% ADD `program` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `id`";
  8736. $oldFields = $this->dao->select('*')->from(TABLE_WORKFLOWFIELD)->fetchGroup('module', 'field');
  8737. foreach($flows as $flow)
  8738. {
  8739. if(!isset($tableFields[$flow->table])) continue;
  8740. if($flow->vision == 'lite') continue;
  8741. foreach($fields as $field => $addSQL)
  8742. {
  8743. if($flow->vision == 'or' && $field != 'product') continue;
  8744. $searchedField = '';
  8745. foreach($tableFields[$flow->table] as $tableField)
  8746. {
  8747. if($field == strtolower($tableField))
  8748. {
  8749. $searchedField = $tableField;
  8750. break;
  8751. }
  8752. }
  8753. if($searchedField)
  8754. {
  8755. $newFieldName = "{$searchedField}_1";
  8756. $oldField = isset($oldFields[$flow->module][$searchedField]) ? $oldFields[$flow->module][$searchedField] : null;
  8757. if(isset($tableFields[$flow->table][$newFieldName]) || ($oldField && ($oldField->buildin || $oldField->readonly)))
  8758. {
  8759. $this->dao->exec("ALTER TABLE {$flow->table} DROP `{$searchedField}`");
  8760. $this->dao->delete()->from(TABLE_WORKFLOWFIELD)->where('module')->eq($flow->module)->andWhere('field')->eq("{$searchedField}")->exec();
  8761. }
  8762. elseif($oldField)
  8763. {
  8764. $this->dao->update(TABLE_WORKFLOWFIELD)->set('field')->eq("{$newFieldName}")->where('module')->eq($flow->module)->andWhere('field')->eq($searchedField)->exec();
  8765. $this->dao->update(TABLE_WORKFLOWSQL)->set('field')->eq("{$newFieldName}")->where('module')->eq($flow->module)->andWhere('field')->eq($searchedField)->exec();
  8766. $newField = clone $oldField;
  8767. $newField->field = $newFieldName;
  8768. $this->loadModel('workflowfield');
  8769. $this->workflowfield->processTable($flow->table, $oldField, $newField);
  8770. $this->workflowfield->updateRelated($flow, $oldField, $newFieldName);
  8771. }
  8772. }
  8773. $this->dao->exec(str_replace('%table%', $flow->table, $addSQL));
  8774. $data = $defaultData;
  8775. $data['module'] = $flow->module;
  8776. $data['field'] = $field;
  8777. $data['name'] = $this->lang->upgrade->flowFields[$field];
  8778. $data['options'] = $field;
  8779. $data['order'] = $orders[$field];
  8780. $this->dao->insert(TABLE_WORKFLOWFIELD)->data($data)->exec();
  8781. }
  8782. }
  8783. return true;
  8784. }
  8785. /**
  8786. * Process demand files.
  8787. *
  8788. * @access public
  8789. * @return bool
  8790. */
  8791. public function processDemandFiles()
  8792. {
  8793. $demandIdList = $this->dao->select('objectID')->from(TABLE_FILE)->where('extra')->eq('')->andWhere('objectType')->eq('demand')->fetchPairs('objectID', 'objectID');
  8794. $latestVersion = $this->dao->select('id,version')->from(TABLE_DEMAND)->where('id')->in($demandIdList)->fetchPairs('id', 'version');
  8795. foreach($demandIdList as $demandID)
  8796. {
  8797. if(!isset($latestVersion[$demandID])) continue;
  8798. $this->dao->update(TABLE_FILE)->set('extra')->eq($latestVersion[$demandID])->where('objectID')->eq($demandID)->andWhere('objectType')->eq('demand')->exec();
  8799. }
  8800. return true;
  8801. }
  8802. /**
  8803. * Process tables of sql builder.
  8804. *
  8805. * @access public
  8806. * @return void
  8807. */
  8808. public function processSqlbuilderTables()
  8809. {
  8810. $prefix = $this->config->db->prefix;
  8811. $sqlbuilderSettings = $this->dao->select('id,setting')->from(TABLE_SQLBUILDER)->fetchPairs();
  8812. foreach($sqlbuilderSettings as $id => $setting)
  8813. {
  8814. $setting = json_decode($setting, true);
  8815. if($setting['from']['table'] && strpos($setting['from']['table'], 'zt_') === false) $setting['from']['table'] = $prefix . $setting['from']['table'];
  8816. foreach($setting['joins'] as $index => $join)
  8817. {
  8818. if($setting['joins'][$index]['table'] && strpos($setting['joins'][$index]['table'], 'zt_') === false) $setting['joins'][$index]['table'] = $prefix . $join['table'];
  8819. }
  8820. $this->dao->update(TABLE_SQLBUILDER)->set('setting')->eq(json_encode($setting))->where('id')->eq($id)->exec();
  8821. }
  8822. return !dao::isError();
  8823. }
  8824. /**
  8825. * Upgrade my doc space.
  8826. *
  8827. * @access public
  8828. * @return bool
  8829. */
  8830. public function upgradeMyDocSpace()
  8831. {
  8832. $this->loadModel('doc');
  8833. $this->app->loadLang('doclib');
  8834. $spacesGroup = $this->dao->select('*')->from(TABLE_DOCLIB)
  8835. ->where('deleted')->eq(0)
  8836. ->andWhere('type')->eq('mine')
  8837. ->orderBy('`order` asc, id_asc')
  8838. ->fetchGroup('addedBy', 'id');
  8839. foreach($spacesGroup as $account => $allSpaces)
  8840. {
  8841. $visionSpaces = array('rnd' => array(), 'lite' => array(), 'or' => array());
  8842. foreach($allSpaces as $space) $visionSpaces[$space->vision][$space->id] = $space;
  8843. foreach($visionSpaces as $vision => $spaces)
  8844. {
  8845. if(empty($spaces)) continue;
  8846. $space = new stdclass();
  8847. $space->type = 'mine';
  8848. $space->vision = $vision;
  8849. $space->parent = 0;
  8850. $space->name = $this->lang->doclib->defaultSpace;
  8851. $space->main = '1';
  8852. $space->acl = 'private';
  8853. $space->addedBy = $account;
  8854. $space->addedDate = helper::now();
  8855. $spaceID = $this->doc->doInsertLib($space);
  8856. $this->dao->update(TABLE_DOCLIB)->set('parent')->eq($spaceID)->where('id')->in(array_keys($spaces))->exec();
  8857. $this->dao->update(TABLE_DOCLIB)->set('main')->eq(0)->where('id')->in(array_keys($spaces))->exec();
  8858. }
  8859. }
  8860. }
  8861. /**
  8862. * 历史产品、项目绑定默认工作流模板。
  8863. *
  8864. * @access public
  8865. * @return void
  8866. */
  8867. public function processWorkflowGroups()
  8868. {
  8869. $workflowGroups = $this->dao->select('code, id')->from(TABLE_WORKFLOWGROUP)->where('main')->eq('1')->fetchPairs();
  8870. foreach($workflowGroups as $code => $groupID)
  8871. {
  8872. if($code == 'productproject')
  8873. {
  8874. $this->dao->update(TABLE_PRODUCT)->set('workflowGroup')->eq($groupID)->exec();
  8875. }
  8876. else
  8877. {
  8878. $this->dao->update(TABLE_PROJECT)
  8879. ->set('workflowGroup')->eq($groupID)
  8880. ->where('type')->eq('project')
  8881. ->beginIF($code == 'scrumproduct')->andWhere('model')->in('scrum,agileplus')->andWhere('hasProduct')->eq('1')->fi()
  8882. ->beginIF($code == 'scrumproject')->andWhere('model')->in('scrum,agileplus')->andWhere('hasProduct')->eq('0')->fi()
  8883. ->beginIF($code == 'waterfallproduct')->andWhere('model')->in('waterfall,waterfallplus,ipd')->andWhere('hasProduct')->eq('1')->fi()
  8884. ->beginIF($code == 'waterfallproject')->andWhere('model')->in('waterfall,waterfallplus,ipd')->andWhere('hasProduct')->eq('0')->fi()
  8885. ->exec();
  8886. }
  8887. }
  8888. }
  8889. /**
  8890. * Process object relations.
  8891. * 将系统内原有关联关系合并到relation表中:需求关联需求、bug关联bug、用例关联用例、问题关联风险、风险关联问题.
  8892. *
  8893. * @access public
  8894. * @return bool
  8895. */
  8896. public function processObjectRelation()
  8897. {
  8898. if($this->config->edition == 'open') return true;
  8899. $relation = new stdClass();
  8900. $relation->product = 0;
  8901. /* Process story link story. */
  8902. $relation->relation = 1;
  8903. $linkedtoStories = $this->dao->select('*')->from(TABLE_RELATION)
  8904. ->where('AType')->in('story,requirement,epic')
  8905. ->andWhere('relation')->eq('linkedto')
  8906. ->andWhere('BType')->in('story,requirement,epic')
  8907. ->fetchAll('id', false);
  8908. foreach($linkedtoStories as $story)
  8909. {
  8910. $relation->AType = $story->AType;
  8911. $relation->AID = $story->AID;
  8912. $relation->BType = $story->BType;
  8913. $relation->BID = $story->BID;
  8914. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8915. }
  8916. /* Process bug link bug. */
  8917. $bugList = $this->dao->select('id,story,relatedBug,task,`case`')->from(TABLE_BUG)
  8918. ->where('relatedBug')->ne('')
  8919. ->orWhere('story')->ne(0)
  8920. ->orWhere('task')->ne(0)
  8921. ->orWhere('case')->ne(0)
  8922. ->fetchAll('id');
  8923. foreach($bugList as $bugID => $bug)
  8924. {
  8925. if(!empty($bug->relatedBug))
  8926. {
  8927. foreach(explode(',', ",{$bug->relatedBug},") as $relatedBugID)
  8928. {
  8929. if(empty($relatedBugID)) continue;
  8930. $relation->AType = 'bug';
  8931. $relation->AID = $bugID;
  8932. $relation->relation = 1;
  8933. $relation->BType = 'bug';
  8934. $relation->BID = $relatedBugID;
  8935. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8936. }
  8937. }
  8938. if(!empty($bug->story))
  8939. {
  8940. $relation->AType = 'story';
  8941. $relation->AID = $bug->story;
  8942. $relation->relation = 'generated';
  8943. $relation->BType = 'bug';
  8944. $relation->BID = $bugID;
  8945. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8946. }
  8947. if(!empty($bug->task))
  8948. {
  8949. $relation->AType = 'task';
  8950. $relation->AID = $bug->task;
  8951. $relation->relation = 'generated';
  8952. $relation->BType = 'bug';
  8953. $relation->BID = $bugID;
  8954. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8955. }
  8956. if(!empty($bug->case))
  8957. {
  8958. $relation->AType = 'testcase';
  8959. $relation->AID = $bug->case;
  8960. $relation->relation = 'generated';
  8961. $relation->BType = 'bug';
  8962. $relation->BID = $bugID;
  8963. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8964. }
  8965. }
  8966. /* Process case link case. */
  8967. $caseList = $this->dao->select('id,story,linkCase,fromBug')->from(TABLE_CASE)->where('linkCase')->ne('')->orWhere('story')->ne(0)->orWhere('fromBug')->ne(0)->fetchAll('id');
  8968. foreach($caseList as $caseID => $case)
  8969. {
  8970. foreach(explode(',', ",{$case->linkCase},") as $relatedCaseID)
  8971. {
  8972. if(empty($relatedCaseID)) continue;
  8973. $relation->AType = 'testcase';
  8974. $relation->AID = $caseID;
  8975. $relation->relation = 1;
  8976. $relation->BType = 'testcase';
  8977. $relation->BID = $relatedCaseID;
  8978. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8979. }
  8980. if(!empty($case->story))
  8981. {
  8982. $relation->AType = 'story';
  8983. $relation->AID = $case->story;
  8984. $relation->relation = 'generated';
  8985. $relation->BType = 'testcase';
  8986. $relation->BID = $caseID;
  8987. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8988. }
  8989. if(!empty($case->fromBug))
  8990. {
  8991. $relation->AType = 'bug';
  8992. $relation->AID = $case->fromBug;
  8993. $relation->relation = 'generated';
  8994. $relation->BType = 'testcase';
  8995. $relation->BID = $caseID;
  8996. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  8997. }
  8998. }
  8999. /* Process bug transferred to task. */
  9000. $relation->BType = 'task';
  9001. $taskList = $this->dao->select('id,story,fromBug,design')->from(TABLE_TASK)->where('fromBug')->ne(0)->orWhere('story')->ne(0)->orWhere('design')->ne(0)->fetchAll('id');
  9002. foreach($taskList as $taskID => $task)
  9003. {
  9004. $relation->BID = $taskID;
  9005. if(!empty($task->fromBug))
  9006. {
  9007. $relation->relation = 'transferredto';
  9008. $relation->AType = 'bug';
  9009. $relation->AID = $task->fromBug;
  9010. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9011. }
  9012. if(!empty($task->story))
  9013. {
  9014. $relation->relation = 'generated';
  9015. $relation->AType = 'story';
  9016. $relation->AID = $task->story;
  9017. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9018. }
  9019. if(!empty($task->design))
  9020. {
  9021. $relation->relation = 'generated';
  9022. $relation->AType = 'design';
  9023. $relation->AID = $task->design;
  9024. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9025. }
  9026. }
  9027. /* Process bug transferred to story. */
  9028. $relation->AType = 'bug';
  9029. $relation->relation = 'transferredto';
  9030. $relation->BType = 'story';
  9031. $bugTransferredToStory = $this->dao->select('id,fromBug')->from(TABLE_STORY)->where('fromBug')->ne(0)->fetchPairs();
  9032. foreach($bugTransferredToStory as $storyID => $bugID)
  9033. {
  9034. $relation->AID = $bugID;
  9035. $relation->BID = $storyID;
  9036. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9037. }
  9038. /* Process release. */
  9039. $relation->AType = 'story';
  9040. $relation->relation = 'interrated';
  9041. $relation->BType = 'release';
  9042. $releaseLinkedStories = $this->dao->select('id,stories')->from(TABLE_RELEASE)->where('stories')->ne('')->fetchPairs();
  9043. foreach($releaseLinkedStories as $releaseID => $storyIdList)
  9044. {
  9045. $relation->BID = $releaseID;
  9046. foreach(explode(',', trim($storyIdList, ',')) as $storyID)
  9047. {
  9048. if(empty($storyID)) continue;
  9049. $relation->AID = $storyID;
  9050. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9051. }
  9052. }
  9053. /* Process build. */
  9054. $relation->AType = 'story';
  9055. $relation->relation = 'interrated';
  9056. $relation->BType = 'build';
  9057. $buildLinkedStories = $this->dao->select('id,stories')->from(TABLE_BUILD)->where('stories')->ne('')->fetchPairs();
  9058. foreach($buildLinkedStories as $buildID => $storyIdList)
  9059. {
  9060. $relation->BID = $buildID;
  9061. foreach(explode(',', trim($storyIdList, ',')) as $storyID)
  9062. {
  9063. if(empty($storyID)) continue;
  9064. $relation->AID = $storyID;
  9065. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9066. }
  9067. }
  9068. /* Process story generated design. */
  9069. $relation->relation = 'generated';
  9070. $relation->BType = 'design';
  9071. $designStories = $this->dao->select('id,story')->from(TABLE_DESIGN)->where('story')->ne(0)->fetchPairs();
  9072. $storyTypeList = $this->dao->select('id,type')->from(TABLE_STORY)->where('id')->in(array_values($designStories))->fetchPairs();
  9073. foreach($designStories as $designID => $storyID)
  9074. {
  9075. $relation->AType = $storyTypeList[$storyID];
  9076. $relation->AID = $storyID;
  9077. $relation->BID = $designID;
  9078. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9079. }
  9080. /* Process feedback transferred to story. */
  9081. $relation->AType = 'feedback';
  9082. $relation->relation = 'transferredto';
  9083. $feedbackTransferredToStories = $this->dao->select('id,type,feedback')->from(TABLE_STORY)->where('feedback')->ne(0)->fetchAll();
  9084. foreach($feedbackTransferredToStories as $story)
  9085. {
  9086. $relation->AID = $story->feedback;
  9087. $relation->BType = $story->type;
  9088. $relation->BID = $story->id;
  9089. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9090. }
  9091. /* Process feedback transferredto to bug, task, ticket, demand. */
  9092. $relation->AType = 'feedback';
  9093. $relation->relation = 'transferredto';
  9094. foreach(array('bug', 'task', 'ticket', 'demand') as $feedbackTransferredToType)
  9095. {
  9096. $relation->BType = $feedbackTransferredToType;
  9097. $feedbackTransferredTo = $this->dao->select('id,feedback')->from($this->config->objectTables[$feedbackTransferredToType])->where('feedback')->ne(0)->fetchPairs();
  9098. foreach($feedbackTransferredTo as $id => $feedbackID)
  9099. {
  9100. $relation->AID = $feedbackID;
  9101. $relation->BID = $id;
  9102. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9103. }
  9104. }
  9105. /* Process ticket transferred to story and bug. */
  9106. $relation->AType = 'ticket';
  9107. $relation->relation = 'transferredto';
  9108. $ticketTransferred = $this->dao->select('*')->from(TABLE_TICKETRELATION)->fetchAll('id', false);
  9109. foreach($ticketTransferred as $transferredObject)
  9110. {
  9111. $relation->AID = $transferredObject->ticketId;
  9112. $relation->BType = $transferredObject->objectType;
  9113. $relation->BID = $transferredObject->objectId;
  9114. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9115. }
  9116. /* Process twins. */
  9117. $relation->AType = 'story';
  9118. $relation->relation = 'twin';
  9119. $relation->BType = 'story';
  9120. $twins = $this->dao->select('id,twins')->from(TABLE_STORY)->where('twins')->ne('')->fetchPairs();
  9121. foreach($twins as $storyID => $twinsID)
  9122. {
  9123. $relation->AID = $storyID;
  9124. foreach(explode(',', trim($twinsID, ',')) as $twinID)
  9125. {
  9126. if(empty($twinID)) continue;
  9127. $relation->BID = $twinID;
  9128. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9129. }
  9130. }
  9131. /* Process child story. */
  9132. $relation->relation = 'subdivideinto';
  9133. $parentStories = array();
  9134. $childStories = $this->dao->select('id,type,parent,demand')->from(TABLE_STORY)->where('parent')->gt(0)->orWhere('demand')->gt(0)->fetchAll();
  9135. foreach($childStories as $childStory) $parentStories[$childStory->parent] = $childStory->parent;
  9136. $parentStoryType = $this->dao->select('id,type')->from(TABLE_STORY)->where('id')->in($parentStories)->fetchPairs();
  9137. foreach($childStories as $childStory)
  9138. {
  9139. $relation->BType = $childStory->type;
  9140. $relation->BID = $childStory->id;
  9141. if(!empty($childStory->parent))
  9142. {
  9143. $relation->AType = $parentStoryType[$childStory->parent];
  9144. $relation->AID = $childStory->parent;
  9145. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9146. }
  9147. if(!empty($childStory->demand))
  9148. {
  9149. $relation->AType = 'demand';
  9150. $relation->AID = $childStory->demand;
  9151. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9152. }
  9153. }
  9154. /* Process issue link risk. */
  9155. $relation->AType = 'issue';
  9156. $relation->relation = 1;
  9157. $relation->BType = 'risk';
  9158. $riskIssues = $this->dao->select('issue,risk')->from(TABLE_RISKISSUE)->fetchAll();
  9159. foreach($riskIssues as $riskIssueList)
  9160. {
  9161. $relation->AID = $riskIssueList->issue;
  9162. $relation->BID = $riskIssueList->risk;
  9163. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9164. }
  9165. /* Process child demand. */
  9166. $relation->AType = 'demand';
  9167. $relation->relation = 'subdivideinto';
  9168. $relation->BType = 'demand';
  9169. $childDemands = $this->dao->select('id,parent')->from(TABLE_DEMAND)->where('parent')->gt(0)->fetchPairs();
  9170. foreach($childDemands as $childDemandID => $parentDemandID)
  9171. {
  9172. $relation->AID = $parentDemandID;
  9173. $relation->BID = $childDemandID;
  9174. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  9175. }
  9176. if(!dao::isError())
  9177. {
  9178. $this->dao->delete()->from(TABLE_CRON)
  9179. ->where('`command`')->eq('moduleName=upgrade&methodName=ajaxProcessObjectRelation')
  9180. ->andWhere('type')->eq('zentao')
  9181. ->andWhere('status')->eq('normal')
  9182. ->exec();
  9183. }
  9184. return true;
  9185. }
  9186. /**
  9187. * 处理上线步骤数据。
  9188. * Process deploy step.
  9189. *
  9190. * @access public
  9191. * @return void
  9192. */
  9193. public function processDeployStep()
  9194. {
  9195. $this->app->loadLang('deploy');
  9196. if(empty($this->lang->deploy->stageList)) return;
  9197. $deploySteps = $this->dao->select('*')->from(TABLE_DEPLOYSTEP)->fetchGroup('deploy');
  9198. $deployData = array();
  9199. foreach($deploySteps as $deployID => $steps)
  9200. {
  9201. foreach($steps as $step)
  9202. {
  9203. if($step->parent || $step->stage == '') return;
  9204. $deployData[$deployID][$step->stage][] = $step->id;
  9205. }
  9206. }
  9207. $this->loadModel('action');
  9208. foreach($deployData as $deployID => $steps)
  9209. {
  9210. foreach($steps as $stage => $childs)
  9211. {
  9212. $parent = new stdclass();
  9213. $parent->title = zget($this->lang->deploy->stageList, $stage, '');
  9214. $parent->status = 'wait';
  9215. $parent->deploy = $deployID;
  9216. $parent->createdBy = $this->app->user->account;
  9217. $parent->createdDate = helper::now();
  9218. $this->dao->insert(TABLE_DEPLOYSTEP)->data($parent)->autoCheck()->exec();
  9219. $parentID = $this->dao->lastInsertID();
  9220. $this->action->create('deploystep', $parentID, 'created');
  9221. $this->dao->update(TABLE_DEPLOYSTEP)->set('parent')->eq($parentID)->where('id')->in($childs)->exec();
  9222. }
  9223. }
  9224. }
  9225. /**
  9226. * 根据旧的缓存配置生成新的缓存配置。
  9227. * Generate new cache config.
  9228. *
  9229. * @access public
  9230. * @return void
  9231. */
  9232. public function processCacheConfig()
  9233. {
  9234. $cache = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=cache');
  9235. if($cache == '{"dao":{"enable":"1"}}') $this->loadModel('install')->enableCache();
  9236. $this->setting->deleteItems('owner=system&module=common&section=global&key=cache');
  9237. }
  9238. /**
  9239. * 初始化任务关联。
  9240. * Init task relation.
  9241. *
  9242. * @access public
  9243. * @return bool
  9244. */
  9245. public function initTaskRelation()
  9246. {
  9247. $childTasks = $this->dao->select('id,parent')->from(TABLE_TASK)->where('parent')->gt(0)->fetchPairs('id', 'parent');
  9248. if(empty($childTasks)) return true;
  9249. $this->dao->begin();
  9250. $childIdList = array_keys($childTasks);
  9251. $this->dao->delete()->from(TABLE_RELATION)->where('BID')->in($childIdList)->andWhere('relation')->eq('subdivideinto')->andWhere('AType')->eq('task')->andWhere('BType')->eq('task')->exec();
  9252. $this->dao->delete()->from(TABLE_RELATION)->where('AID')->in($childIdList)->andWhere('relation')->eq('subdividefrom')->andWhere('AType')->eq('task')->andWhere('BType')->eq('task')->exec();
  9253. $data = new stdclass();
  9254. $data->AType = 'task';
  9255. $data->BType = 'task';
  9256. foreach($childTasks as $taskID => $parentID)
  9257. {
  9258. $data->AID = $taskID;
  9259. $data->BID = $parentID;
  9260. $data->relation = 'subdividefrom';
  9261. $this->dao->insert(TABLE_RELATION)->data($data)->exec();
  9262. $data->AID = $parentID;
  9263. $data->BID = $taskID;
  9264. $data->relation = 'subdivideinto';
  9265. $this->dao->insert(TABLE_RELATION)->data($data)->exec();
  9266. }
  9267. $this->dao->delete()->from(TABLE_CRON)
  9268. ->where('`command`')->eq('moduleName=upgrade&methodName=ajaxInitTaskRelation')
  9269. ->andWhere('type')->eq('zentao')
  9270. ->andWhere('status')->eq('normal')
  9271. ->exec();
  9272. if(dao::isError())
  9273. {
  9274. $this->dao->rollBack();
  9275. return false;
  9276. }
  9277. return $this->dao->commit();
  9278. }
  9279. /**
  9280. * 创建内置立项审批流。
  9281. * Add default charter approval flow.
  9282. *
  9283. * @access public
  9284. * @return bool
  9285. */
  9286. public function addCharterApprovalFlow()
  9287. {
  9288. foreach($this->lang->upgrade->defaultCharterApprovalFlow as $approvalType => $approval)
  9289. {
  9290. $approvalflow = new stdclass();
  9291. $approvalflow->name = $approval->title;
  9292. $approvalflow->code = $approvalType;
  9293. $approvalflow->desc = $approval->desc;
  9294. $approvalflow->version = 1;
  9295. $approvalflow->createdBy = 'system';
  9296. $approvalflow->createdDate = helper::now();
  9297. $approvalflow->workflow = 'charter';
  9298. $approvalflow->deleted = 0;
  9299. $this->dao->insert(TABLE_APPROVALFLOW)->data($approvalflow)->exec();
  9300. if(dao::isError()) return false;
  9301. $approvalflowID = $this->dao->lastInsertId();
  9302. $approvalflowSpec = new stdclass();
  9303. $approvalflowSpec->flow = $approvalflowID;
  9304. $approvalflowSpec->version = 1;
  9305. $approvalflowSpec->nodes = '[{"type":"start","ccs":[]},{"id":"3ewcj92p55e","type":"approval","title":"审批","reviewType":"manual","multiple":"and","percent":"50","commentType":"noRequired","agentType":"pass","selfType":"selfReview","deletedType":"setAdmin","reviewers":[{"type":"select","users":[],"roles":[""],"depts":[""],"positions":[""],"userRange":"all","required":"yes"}],"ccs":[{"type":"select","users":[],"roles":[""],"depts":[""],"positions":[""],"userRange":"all"}]},{"type":"end","ccs":[]}]';
  9306. $approvalflowSpec->createdBy = 'system';
  9307. $approvalflowSpec->createdDate = helper::now();
  9308. $this->dao->insert(TABLE_APPROVALFLOWSPEC)->data($approvalflowSpec)->exec();
  9309. if(dao::isError()) return false;
  9310. $approvalflowobject = new stdclass();
  9311. $approvalflowobject->flow = $approvalflowID;
  9312. $approvalflowobject->objectType = 'charter';
  9313. $approvalflowobject->extra = $approvalType;
  9314. $this->dao->insert(TABLE_APPROVALFLOWOBJECT)->data($approvalflowobject)->exec();
  9315. if(dao::isError()) return false;
  9316. }
  9317. return true;
  9318. }
  9319. /**
  9320. * 对立项附件配置做升级处理。
  9321. * Process charter file config.
  9322. *
  9323. * @access public
  9324. * @return bool
  9325. */
  9326. public function processCharterFileConfig()
  9327. {
  9328. $oldCharterFileConfig = $this->loadModel('custom')->getItems('owner=system&module=charter&section=charterList&vision=or');
  9329. if(!$oldCharterFileConfig) return true;
  9330. $projectApprovalFiles = array();
  9331. foreach($oldCharterFileConfig as $oldCharterFile) $projectApprovalFiles[] = array('index' => $oldCharterFile->key, 'name' => $oldCharterFile->value);
  9332. $charterFiles = json_decode($this->config->custom->charterFiles, true);
  9333. foreach($charterFiles as $index => $charterFile)
  9334. {
  9335. $charterFiles[$index]['type'] = 'roadmap';
  9336. $charterFiles[$index]['projectApproval'] = $projectApprovalFiles;
  9337. }
  9338. $this->loadModel('setting')->setItem('system.custom.charterFiles', json_encode($charterFiles));
  9339. return true;
  9340. }
  9341. /**
  9342. * 处理立项状态及评审状态。
  9343. * Process charter status.
  9344. *
  9345. * @access public
  9346. * @return bool
  9347. */
  9348. public function processCharterStatus()
  9349. {
  9350. /* Update roadmap status. */
  9351. $doneCharterRoadmaps = $this->dao->select('id,roadmap')->from(TABLE_CHARTER)->where('status')->eq('closed')->andWhere('closedReason')->eq('done')->fetchPairs();
  9352. $canceledCharterRoadmaps = $this->dao->select('id,roadmap')->from(TABLE_CHARTER)->where('status')->eq('closed')->andWhere('closedReason')->eq('canceled')->fetchPairs();
  9353. $this->dao->update(TABLE_ROADMAP)->set('status')->eq('launched')->where('id')->in(array_filter(explode(',', implode(',', $doneCharterRoadmaps))))->exec();
  9354. $this->dao->update(TABLE_ROADMAP)->set('status')->eq('wait')->where('id')->in(array_filter(explode(',', implode(',', $canceledCharterRoadmaps))))->exec();
  9355. $this->dao->update(TABLE_CHARTER)
  9356. ->set('status')->eq('wait')
  9357. ->set('reviewStatus')->eq('projectReject')
  9358. ->where('status')->eq('failed')
  9359. ->andWhere('reviewedResult')->eq('failed')
  9360. ->exec();
  9361. $this->dao->update(TABLE_CHARTER)
  9362. ->set('reviewStatus')->eq('projectPass')
  9363. ->where('status')->eq('launched')
  9364. ->exec();
  9365. $this->dao->update(TABLE_CHARTER)
  9366. ->set('reviewStatus')->eq('completionPass')
  9367. ->where('status')->eq('closed')
  9368. ->andWhere('closedReason')->eq('done')
  9369. ->exec();
  9370. $this->dao->update(TABLE_CHARTER)
  9371. ->set('prevCanceledStatus')->eq('wait')
  9372. ->set('reviewStatus')->eq('cancelPass')
  9373. ->where('status')->eq('closed')
  9374. ->andWhere('closedReason')->eq('canceled')
  9375. ->exec();
  9376. return true;
  9377. }
  9378. /**
  9379. * 处理devops的图表模块数据。
  9380. * Process devops chart modules.
  9381. *
  9382. * @access public
  9383. * @return bool
  9384. */
  9385. public function createDevOpsChartModule()
  9386. {
  9387. $chartModules = $this->addDefaultModules4BI('chart');
  9388. $this->addSecondModule4BI(1, 'macro', 'chart', $chartModules);
  9389. return !dao::isError();
  9390. }
  9391. /**
  9392. * 获取需要升级的文档。
  9393. * Get docs need to upgrade.
  9394. *
  9395. * @access public
  9396. * @return array
  9397. */
  9398. public function getUpgradeDocs()
  9399. {
  9400. $docs = $this->dao->select('t1.*,t2.title,t2.content,t2.type AS contentType,t2.rawContent,t1.version')->from(TABLE_DOC)->alias('t1')
  9401. ->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id=t2.doc && t1.version=t2.version')
  9402. ->where('t2.type')->in(array('doc', 'html'))
  9403. ->andWhere('t1.status')->ne('draft')
  9404. ->andWhere('t2.rawContent')->in(null)
  9405. ->andWhere('t1.templateType')->eq('')
  9406. ->andWhere('t1.template')->eq('')
  9407. ->andWhere('t2.fromVersion')->eq(0)
  9408. ->fetchAll('id', false);
  9409. $newDocs = array();
  9410. $oldDocs = array();
  9411. foreach($docs as $doc)
  9412. {
  9413. if($doc->contentType == 'doc' && empty($doc->rawContent) && !empty($doc->content)) $newDocs[] = $doc->id;
  9414. if(($doc->contentType == 'html') && !empty($doc->content)) $oldDocs[] = $doc->id;
  9415. }
  9416. $wikis = $this->dao->select('*')->from(TABLE_DOCLIB)
  9417. ->where('type')->eq('book')
  9418. ->andWhere('parent')->eq(0)
  9419. ->fetchAll('id', false);
  9420. if(empty($newDocs) && empty($oldDocs) && empty($wikis)) return array();
  9421. return array('doc' => $newDocs, 'html' => $oldDocs, 'wiki' => array_keys($wikis));
  9422. }
  9423. /**
  9424. * 升级文档,设置新编辑器文档的 html,以及转换老文档为新文档。
  9425. * Upgrade doc, set doc html generated by new editor, and convert old doc to new doc.
  9426. *
  9427. * @param int $docID
  9428. * @param int $version
  9429. * @param string $content
  9430. * @return bool
  9431. */
  9432. public function upgradeDoc($docID, $version, $content)
  9433. {
  9434. $docContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($docID)->andWhere('version')->eq($version)->fetch();
  9435. if(empty($docContent)) return false;
  9436. $this->app->loadLang('doc');
  9437. if($docContent->type == 'html')
  9438. {
  9439. $newDocContent = clone $docContent;
  9440. $newDocContent->version = $docContent->version + 1;
  9441. $newDocContent->type = 'doc';
  9442. $newDocContent->rawContent = empty($content) ? json_encode(array('$migrate' => 'html', '$data' => $docContent->content)) : $content;
  9443. unset($newDocContent->id);
  9444. $newDocContent->addedBy = 'system';
  9445. $newDocContent->addedDate = helper::now();
  9446. $newDocContent->editedBy = 'system';
  9447. $newDocContent->editedDate = helper::now();
  9448. $this->dao->insert(TABLE_DOCCONTENT)->data($newDocContent)->exec();
  9449. $this->dao->update(TABLE_DOC)->set('version')->eq($newDocContent->version)->where('id')->eq($docID)->exec();
  9450. $this->loadModel('action')->create('doc', $docID, 'convertDoc', sprintf($this->lang->doc->docConvertComment, "#$docContent->version", ''), '', 'system');
  9451. }
  9452. elseif($docContent->type == 'doc')
  9453. {
  9454. if(empty($content))
  9455. {
  9456. $link = helper::createLink('doc', 'view', "docID=$docContent->doc");
  9457. $content = sprintf($this->lang->doc->previewNotAvailable, "<a href='$link' target='_blank'>#$docContent->doc $docContent->title</a>");
  9458. }
  9459. $rawContent = empty($docContent->rawContent) ? $docContent->content : $docContent->rawContent;
  9460. $this->dao->update(TABLE_DOCCONTENT)
  9461. ->set('rawContent')->eq($rawContent)
  9462. ->set('content')->eq($content)
  9463. ->where('id')->eq($docContent->id)
  9464. ->exec();
  9465. }
  9466. if(dao::isError()) return false;
  9467. return true;
  9468. }
  9469. /**
  9470. * 升级老版手册
  9471. * Migrate old wiki books.
  9472. *
  9473. * @param array $wikis
  9474. * @access public
  9475. * @return bool
  9476. */
  9477. public function upgradeWikis($wikis)
  9478. {
  9479. if(empty($wikis)) return true;
  9480. $this->app->loadLang('doc');
  9481. $wikiSpace = $this->dao->select('*')->from(TABLE_DOCLIB)
  9482. ->where('type')->eq('custom')
  9483. ->andWhere('name')->eq($this->lang->doclib->migratedWiki)
  9484. ->andWhere('parent')->eq(0)
  9485. ->andWhere('deleted')->eq(0)
  9486. ->fetch();
  9487. if(!$wikiSpace)
  9488. {
  9489. $wikiSpace = new stdClass();
  9490. $wikiSpace->name = $this->lang->doclib->migratedWiki;
  9491. $wikiSpace->parent = 0;
  9492. $wikiSpace->type = 'custom';
  9493. $wikiSpace->acl = 'open';
  9494. $wikiSpace->deleted = 0;
  9495. $this->dao->insert(TABLE_DOCLIB)->data($wikiSpace)->exec();
  9496. if(dao::isError()) return false;
  9497. $wikiSpace->id = $this->dao->lastInsertID();
  9498. }
  9499. /* Move wiki libs to the migrated wiki space. */
  9500. foreach($wikis as $wiki)
  9501. {
  9502. $this->dao->update(TABLE_DOCLIB)
  9503. ->set('parent')->eq($wikiSpace->id)
  9504. ->set('type')->eq('custom')
  9505. ->where('id')->eq($wiki)
  9506. ->exec();
  9507. }
  9508. if(dao::isError()) return false;
  9509. return true;
  9510. }
  9511. /**
  9512. * 初始化发布关联数据。
  9513. * Init release related data.
  9514. *
  9515. * @access public
  9516. * @return bool
  9517. */
  9518. public function initReleaseRelated()
  9519. {
  9520. $releaseID = $this->config->lastReleaseID ?? 0;
  9521. $releases = $this->dao->select('id, project, build, branch, releases, stories, bugs, leftBugs')->from(TABLE_RELEASE)->where('id')->gt($releaseID)->orderBy('id')->limit(100)->fetchAll('id');
  9522. if(!$releases)
  9523. {
  9524. $this->dao->delete()->from(TABLE_CRON)
  9525. ->where('`command`')->eq('moduleName=upgrade&methodName=ajaxInitReleaseRelated')
  9526. ->andWhere('type')->eq('zentao')
  9527. ->andWhere('status')->eq('normal')
  9528. ->exec();
  9529. $this->loadModel('setting')->deleteItem('owner=system&module=common&key=lastReleaseID');
  9530. return !dao::isError();
  9531. }
  9532. $this->dao->begin();
  9533. $this->dao->delete()->from(TABLE_RELEASERELATED)->where('release')->in(array_keys($releases))->exec();
  9534. $fieldPairs = ['project' => 'project', 'build' => 'build', 'branch' => 'branch', 'release' => 'releases', 'story' => 'stories', 'bug' => 'bugs', 'leftBug' => 'leftBugs'];
  9535. $related = new stdClass();
  9536. foreach($releases as $release)
  9537. {
  9538. $related->release = $release->id;
  9539. foreach($fieldPairs as $objectType => $field)
  9540. {
  9541. $related->objectType = $objectType;
  9542. if(!$release->$field) continue;
  9543. $objectIdList = array_unique(array_filter(explode(',', $release->$field)));
  9544. foreach($objectIdList as $objectID)
  9545. {
  9546. if(!preg_match('/^\d+$/', $objectID)) continue; // Filter invalid objectID.
  9547. $related->objectID = $objectID;
  9548. $this->dao->insert(TABLE_RELEASERELATED)->data($related)->exec();
  9549. }
  9550. }
  9551. $releaseID = $release->id;
  9552. }
  9553. $this->loadModel('setting')->setItem('system.common.lastReleaseID', $releaseID);
  9554. if(dao::isError())
  9555. {
  9556. $this->dao->rollBack();
  9557. return false;
  9558. }
  9559. return $this->dao->commit();
  9560. }
  9561. /**
  9562. * 转换数据库的字符集。
  9563. * Convert database charset.
  9564. *
  9565. * @access public
  9566. * @return bool
  9567. */
  9568. public function convertCharset()
  9569. {
  9570. if($this->config->db->driver != 'mysql') return true;
  9571. $dbVersion = $this->loadModel('install')->getDatabaseVersion();
  9572. if(version_compare($dbVersion, '5.6', '<')) return true;
  9573. /* 获取服务器的字符集和排序规则。Get server charset and collation. */
  9574. $result = $this->dbh->getServerCharsetAndCollation($this->config->db->name);
  9575. $serverCharset = $result['charset'];
  9576. $serverCollation = $result['collation'];
  9577. /* 如果服务器的字符集不支持配置的字符集,则不转换。If server charset does not support configured charset, do not convert. */
  9578. if($this->config->db->encoding != $serverCharset) return true;
  9579. /* 获取当前数据库的字符集和排序规则。Get current database charset and collation. */
  9580. $result = $this->dbh->getDatabaseCharsetAndCollation($this->config->db->name);
  9581. $dbCharset = $result['charset'];
  9582. $dbCollation = $result['collation'];
  9583. if($dbCharset != $serverCharset || $dbCollation != $serverCollation)
  9584. {
  9585. /* 转换数据库的字符集和排序规则。Convert database charset and collation. */
  9586. $this->dbh->exec("ALTER DATABASE `{$this->config->db->name}` CHARACTER SET={$serverCharset} COLLATE={$serverCollation}");
  9587. }
  9588. /* 获取当前数据库中所有表的排序规则。Get all tables collation in current database. */
  9589. $tableCollations = $this->dao->select('TABLE_NAME AS name, TABLE_COLLATION AS collation')->from('information_schema.TABLES')
  9590. ->where('TABLE_SCHEMA')->eq($this->config->db->name)
  9591. ->andWhere('TABLE_TYPE')->eq('BASE TABLE')
  9592. ->fetchPairs();
  9593. foreach($tableCollations as $tableName => $tableCollation)
  9594. {
  9595. if(strpos($tableName, $this->config->db->prefix) !== 0) continue;
  9596. if($tableCollation == $serverCollation) continue;
  9597. $tableName = '`' . $tableName . '`';
  9598. if($tableName == TABLE_METRICLIB || $tableName == TABLE_ACTION || $tableName == TABLE_HISTORY) continue;
  9599. /* 转换表的字符集和排序规则。Convert table charset and collation. */
  9600. $this->dbh->exec("ALTER TABLE {$tableName} CONVERT TO CHARACTER SET {$serverCharset} COLLATE {$serverCollation}");
  9601. }
  9602. $this->loadModel('setting')->setItems('system.common.global', ['dbConvertedTime' => helper::now(), 'dbCharset' => $serverCharset, 'dbCollation' => $serverCollation]);
  9603. return true;
  9604. }
  9605. /**
  9606. * 计算立项的分支。
  9607. * Process charter branch.
  9608. *
  9609. * @access public
  9610. * @return bool
  9611. */
  9612. public function processCharterBranch()
  9613. {
  9614. /* 根据当前版本获取立项关联的产品ID和对象ID。 */
  9615. $objectType = $this->config->edition == 'ipd' ? 'roadmap' : 'plan';
  9616. $charterObjects = $this->dao->select("id,product,{$objectType}")->from(TABLE_CHARTER)->fetchAll('id');
  9617. /* 获取立项关联的对象ID列表。 */
  9618. $objectIdList = array_column($charterObjects, $objectType);
  9619. $objectIdList = implode(',', $objectIdList);
  9620. $objectIdList = explode(',', $objectIdList);
  9621. $objectIdList = array_filter($objectIdList);
  9622. $objectIdList = array_unique($objectIdList);
  9623. $objectIdList = implode(',', $objectIdList);
  9624. /* 获取立项关联对象的分支。 */
  9625. $objects = $this->dao->select('id,product,branch')->from($this->config->edition == 'ipd' ? TABLE_ROADMAP : TABLE_PRODUCTPLAN)->where('id')->in($objectIdList)->fetchGroup('product', 'id');
  9626. $charterProduct = new stdclass();
  9627. foreach($charterObjects as $charterID => $charterObject)
  9628. {
  9629. $charterProduct->charter = $charterID;
  9630. $productIdList = explode(',', $charterObject->product);
  9631. foreach($productIdList as $productID)
  9632. {
  9633. if($productID == '') continue;
  9634. $charterProduct->product = $productID;
  9635. if(!isset($objects[$productID]))
  9636. {
  9637. $charterProduct->branch = '0';
  9638. $charterProduct->{$objectType} = '';
  9639. $this->dao->replace(TABLE_CHARTERPRODUCT)->data($charterProduct)->exec();
  9640. }
  9641. else
  9642. {
  9643. foreach($objects[$productID] as $objectID => $object)
  9644. {
  9645. if(strpos(',' . $charterObject->{$objectType} . ',', ",{$objectID},") === false) continue;
  9646. $branches = explode(',', (string)$object->branch);
  9647. foreach($branches as $branch)
  9648. {
  9649. $charterProduct->branch = $branch;
  9650. $charterProduct->{$objectType} = $objectID;
  9651. $this->dao->replace(TABLE_CHARTERPRODUCT)->data($charterProduct)->exec();
  9652. }
  9653. }
  9654. }
  9655. }
  9656. }
  9657. return true;
  9658. }
  9659. /**
  9660. * 处理action表的product字段,将数据转移到actionproduct表。
  9661. * Process product field of action table, move to actionproduct table.
  9662. *
  9663. * @access public
  9664. * @return bool
  9665. */
  9666. public function processActionProduct()
  9667. {
  9668. $this->dao->exec('TRUNCATE TABLE ' . TABLE_ACTIONPRODUCT);
  9669. $this->dao->exec('TRUNCATE TABLE ' . TABLE_ACTIONRECENT);
  9670. $stmt = $this->dao->select('id,product')->from(TABLE_ACTION)->where('product')->ne('')->andWhere('product')->ne(',0,')->query();
  9671. $sql = 'INSERT INTO ' . TABLE_ACTIONPRODUCT . '(`action`, `product`) VALUES';
  9672. $values = array();
  9673. while($action = $stmt->fetch())
  9674. {
  9675. $productIdList = array_unique(array_filter(explode(',', $action->product)));
  9676. foreach($productIdList as $productID)
  9677. {
  9678. if(!is_numeric($productID)) continue;
  9679. $values[] = "({$action->id}, {$productID})";
  9680. }
  9681. if(count($values) >= 5000)
  9682. {
  9683. $this->dao->exec($sql . implode(',', $values));
  9684. $values = array();
  9685. }
  9686. }
  9687. if($values) $this->dao->exec($sql . implode(',', $values));
  9688. $fields = "`id`,`objectType`,`objectID`,`product`,`project`,`execution`,`actor`,`action`,`date`,`comment`,`files`,`extra`,`read`,`vision`,`efforted`";
  9689. $this->dao->exec('INSERT INTO ' . TABLE_ACTIONRECENT . "({$fields}) SELECT {$fields} FROM " . TABLE_ACTION . " WHERE `date` >= '" . date('Y-m-d', strtotime('-1 month')) . "'");
  9690. return true;
  9691. }
  9692. /**
  9693. * 修改执行模块工作流动作名称
  9694. * Fix workflow action and field name for execution.
  9695. *
  9696. * @access public
  9697. * @return bool
  9698. */
  9699. public function fixWorkflowNameForExecution()
  9700. {
  9701. $this->app->loadLang('execution');
  9702. $this->dao->update(TABLE_WORKFLOWACTION)->set('name')->eq($this->lang->execution->allExecutionAB)->where('module')->eq('execution')->andWhere('action')->eq('all')->exec();
  9703. $this->dao->update(TABLE_WORKFLOWACTION)->set('name')->eq($this->lang->execution->create)->where('module')->eq('execution')->andWhere('action')->eq('create')->exec();
  9704. $this->dao->update(TABLE_WORKFLOWACTION)->set('name')->eq($this->lang->execution->delete)->where('module')->eq('execution')->andWhere('action')->eq('delete')->exec();
  9705. $this->dao->update(TABLE_WORKFLOWACTION)->set('name')->eq($this->lang->execution->edit)->where('module')->eq('execution')->andWhere('action')->eq('edit')->exec();
  9706. $this->dao->update(TABLE_WORKFLOWACTION)->set('name')->eq($this->lang->execution->view)->where('module')->eq('execution')->andWhere('action')->eq('view')->exec();
  9707. $this->dao->update(TABLE_WORKFLOWACTION)->set('name')->eq($this->lang->execution->batchEdit)->where('module')->eq('execution')->andWhere('action')->eq('batchedit')->exec();
  9708. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->id)->where('module')->eq('execution')->andWhere('field')->eq('id')->exec();
  9709. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->type)->where('module')->eq('execution')->andWhere('field')->eq('type')->exec();
  9710. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->name)->where('module')->eq('execution')->andWhere('field')->eq('name')->exec();
  9711. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->code)->where('module')->eq('execution')->andWhere('field')->eq('code')->exec();
  9712. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->status)->where('module')->eq('execution')->andWhere('field')->eq('status')->exec();
  9713. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->desc)->where('module')->eq('execution')->andWhere('field')->eq('desc')->exec();
  9714. $this->dao->update(TABLE_WORKFLOWFIELD)->set('name')->eq($this->lang->execution->PM)->where('module')->eq('execution')->andWhere('field')->eq('PM')->exec();
  9715. return true;
  9716. }
  9717. /**
  9718. * 获取需要升级的文档模板。
  9719. * Get doc templates need to upgrade.
  9720. *
  9721. * @access public
  9722. * @return array
  9723. */
  9724. public function getUpgradeDocTemplates()
  9725. {
  9726. $templateList = $this->dao->select('t1.*, t2.title, t2.content, t2.type AS contentType, t1.version')->from(TABLE_DOC)->alias('t1')
  9727. ->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id = t2.doc && t1.version = t2.version')
  9728. ->where('t1.deleted')->eq(0)
  9729. ->andWhere('t1.templateType')->notIn(array('', 'reportTemplate', 'projectReport'))
  9730. ->andWhere('t1.lib')->eq(0)
  9731. ->andWhere('t1.module')->eq('')
  9732. ->fetchAll('id', false);
  9733. if(empty($templateList)) return array();
  9734. $htmlTemplates = array_filter($templateList, function($template) {return $template->type == 'html' || $template->type == 'markdown';});
  9735. $wikiTemplates = array_filter($templateList, function($template) {return $template->type == 'book';});
  9736. return array('all' => array_keys($templateList), 'html' => array_keys($htmlTemplates), 'wiki' => array_keys($wikiTemplates));
  9737. }
  9738. /**
  9739. * 升级文档模板,设置新编辑器文档的 html。
  9740. * Upgrade doc template, set doc html generated by new editor.
  9741. *
  9742. * @param int $docID
  9743. * @param int $version
  9744. * @param string $content
  9745. * @return bool
  9746. */
  9747. public function upgradeDocTemplate($docID, $version)
  9748. {
  9749. $docContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($docID)->andWhere('version')->eq($version)->fetch();
  9750. if(empty($docContent)) return false;
  9751. $this->app->loadLang('doc');
  9752. $newDocContent = clone $docContent;
  9753. $newDocContent->version = $docContent->version + 1;
  9754. $newDocContent->type = 'doc';
  9755. $newDocContent->rawContent = json_encode(array('$migrate' => $docContent->type == 'markdown' ? 'markdown' : 'html', '$data' => $docContent->content));
  9756. $newDocContent->addedBy = 'system';
  9757. $newDocContent->addedDate = helper::now();
  9758. $newDocContent->editedBy = 'system';
  9759. $newDocContent->editedDate = helper::now();
  9760. unset($newDocContent->id);
  9761. $this->dao->insert(TABLE_DOCCONTENT)->data($newDocContent)->exec();
  9762. $this->dao->update(TABLE_DOC)->set('version')->eq($newDocContent->version)->where('id')->eq($docID)->exec();
  9763. $this->loadModel('action')->create('docTemplate', $docID, 'convertDocTemplate', sprintf($this->lang->doc->docTemplateConvertComment, "#$docContent->version"), '', 'system');
  9764. if(dao::isError()) return false;
  9765. return true;
  9766. }
  9767. /**
  9768. * 升级wiki类型的文档模板。
  9769. * Upgrade templates of wiki.
  9770. *
  9771. * @param array $wikis
  9772. * @return bool
  9773. */
  9774. public function upgradeWikiTemplates($wikis = array())
  9775. {
  9776. /* 读取实际的requestType后续用来创建链接。*/
  9777. /* Read the actual requestType and use it to create a link. */
  9778. global $config, $filter;
  9779. $myConfig = $this->app->getConfigRoot() . 'my.php';
  9780. if(file_exists($myConfig)) include $myConfig;
  9781. $this->app->loadLang('doc');
  9782. $wikiList = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->in($wikis)->fetchAll('doc');
  9783. foreach($wikis as $bookID)
  9784. {
  9785. /* 历史版本的文档模板更新content内容,文章显示为链接。*/
  9786. /* The historical version of the template updates the content, and the article is displayed as a link. */
  9787. $oldContent = $wikiList[$bookID];
  9788. $oldTemplate = $this->convertWiki2Html((int)$bookID, 0, true);
  9789. $this->dao->update(TABLE_DOCCONTENT)
  9790. ->set('type')->eq('doc')
  9791. ->set('content')->eq($oldTemplate)
  9792. ->set('rawContent')->eq(json_encode(array('$migrate' => 'html', '$data' => $oldTemplate)))
  9793. ->where('doc')->eq($bookID)
  9794. ->andWhere('version')->eq($oldContent->version)
  9795. ->exec();
  9796. /* 添加新的版本显示转换后的wiki模板。*/
  9797. /* Add a new version to display the converted wiki template. */
  9798. $templateHtml = $this->convertWiki2Html((int)$bookID);
  9799. $newContent = clone $oldContent;
  9800. $newContent->version = $oldContent->version + 1;
  9801. $newContent->type = 'doc';
  9802. $newContent->content = $templateHtml;
  9803. $newContent->rawContent = json_encode(array('$migrate' => 'html', '$data' => $templateHtml));
  9804. $newContent->addedBy = 'system';
  9805. $newContent->addedDate = helper::now();
  9806. $newContent->editedBy = 'system';
  9807. $newContent->editedDate = helper::now();
  9808. unset($newContent->id);
  9809. $this->dao->insert(TABLE_DOCCONTENT)->data($newContent)->exec();
  9810. $this->dao->update(TABLE_DOC)->set('version')->eq($newContent->version)->where('id')->eq($bookID)->exec();
  9811. $this->loadModel('action')->create('docTemplate', (int)$bookID, 'convertDocTemplate', sprintf($this->lang->doc->docTemplateConvertComment, "#$oldContent->version"), '', 'system');
  9812. }
  9813. /* 还原requestType配置。*/
  9814. /* Restore the config of requestType. */
  9815. $this->config->set('requestType', 'GET');
  9816. if(dao::isError()) return false;
  9817. return true;
  9818. }
  9819. /**
  9820. * 将wiki下的章节和文章合并为一个html。
  9821. * Merge chapters and articles from the wiki into HTML.
  9822. *
  9823. * @param int $bookID
  9824. * @param int $nodeID
  9825. * @param bool $showLink 文章是否显示为链接
  9826. * @return string
  9827. */
  9828. public function convertWiki2Html($bookID = 0, $nodeID = 0, $showLink = false)
  9829. {
  9830. $template = $this->dao->select('t1.*, t2.content AS content')->from(TABLE_DOC)->alias('t1')
  9831. ->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id = t2.doc')
  9832. ->where('t1.id')->eq($nodeID ? $nodeID : $bookID)
  9833. ->fetch();
  9834. $children = $this->dao->select('*')->from(TABLE_DOC)
  9835. ->where('template')->eq($bookID)
  9836. ->andWhere('deleted')->eq(0)
  9837. ->andWhere('parent')->eq($nodeID ? $nodeID : $bookID)
  9838. ->orderBy('`order`, id')
  9839. ->fetchAll('id');
  9840. $childrenHtml = '';
  9841. if($children)
  9842. {
  9843. foreach($children as $child) $childrenHtml .= $this->convertWiki2Html($bookID, $child->id, $showLink);
  9844. }
  9845. $templateHtml = '';
  9846. $levelMark = $template->grade > 0 && $template->grade < 7 ? "h{$template->grade}" : 'p';
  9847. if($template->type == 'book') $templateHtml .= $childrenHtml;
  9848. if(!empty($template->chapterType) && strpos('input,system', $template->chapterType) !== false)
  9849. {
  9850. $templateHtml .= "<{$levelMark}>" . $template->title . "</{$levelMark}>";
  9851. if(!$showLink && $template->chapterType == 'system')
  9852. {
  9853. /* 当模板章节使用系统数据时,将系统数据章节转换为动态区块。*/
  9854. /* When chapters use system data, convert chapters into dynamic blocks. */
  9855. $templateBlock = $this->getTemplateBlock((int)$template->template);
  9856. $templateHtml .= empty($templateBlock) ? '' : "<div class='tml-zentaolist' data-title='{$templateBlock['blockTitle']}' data-export-url='{$templateBlock['exportUrl']}' data-fetcher='{$templateBlock['fetcherUrl']}'></div>";
  9857. }
  9858. $templateHtml .= $childrenHtml;
  9859. }
  9860. if($template->type == 'article')
  9861. {
  9862. if($showLink)
  9863. {
  9864. /* 历史版本的文档模板中,文章显示链接,点击链接查看文章的具体内容。*/
  9865. /* In the historical version of the template, the article displays a link. Click on the link to view the content of the article. */
  9866. $articleLink = helper::createLink('baseline', 'view', "articleID={$template->id}");
  9867. $articleLink = $this->config->requestType == 'GET' ? str_replace('upgrade.php?', 'index.php?', $articleLink) : $articleLink;
  9868. $templateHtml .= "<a class='iframe' href='{$articleLink}' data-toggle='modal' data-size='lg'>{$template->title}</a><br>";
  9869. }
  9870. else
  9871. {
  9872. $templateHtml .= "<{$levelMark}>" . $template->title . "</{$levelMark}>" . $template->content . $childrenHtml;
  9873. }
  9874. }
  9875. return $templateHtml;
  9876. }
  9877. /**
  9878. * 获取文档模板的动态区块。
  9879. * Get the block of doc template.
  9880. *
  9881. * @param int $templateID
  9882. * @return array
  9883. */
  9884. public function getTemplateBlock($templateID)
  9885. {
  9886. $templateType = $this->dao->findByID($templateID)->from(TABLE_DOC)->fetch('templateType');
  9887. if(strpos(',HLDS,DDS,DBDS,ADS,ITTC,STTC,SRS,PP,', ",{$templateType},") === false) return array();
  9888. $blockType = $templateType;
  9889. if($templateType == 'SRS') $blockType = 'projectStory';
  9890. if($templateType == 'PP') $blockType = 'gantt';
  9891. if(strpos(',ITTC,STTC,', $templateType) !== false) $blockType = 'projectCase';
  9892. $blockTitle = $exportUrl = $fetcherUrl = '';
  9893. $searchTabList = $blockType == 'gantt' ? array('all' => '') : $this->lang->docTemplate->searchTabList[$blockType];
  9894. $blockTitle = ($blockType == 'projectStory' ? $searchTabList['allstory'] : $searchTabList['all']) . $this->lang->docTemplate->of . $this->lang->docTemplate->zentaoList[$blockType];
  9895. $settingUrl = helper::createLink('doc', 'buildZentaoConfig', "type={$blockType}&oldBlockID={blockID}&isTemplate=1");
  9896. $blockContent = array('searchTab' => $blockType == 'projectStory' ? 'allstory' : 'all');
  9897. if($templateType == 'ITTC') $blockContent = array('searchTab' => 'all', 'caseStage' => 'intergrate');
  9898. if($templateType == 'STTC') $blockContent = array('searchTab' => 'all', 'caseStage' => 'system');
  9899. if($templateType == 'PP') $blockContent = array();
  9900. $templateBlock = new stdClass();
  9901. $templateBlock->doc = $templateID;
  9902. $templateBlock->type = $blockType;
  9903. $templateBlock->settings = $this->config->requestType == 'GET' ? str_replace(array('upgrade.php?', 'install.php?'), 'index.php?', $settingUrl) : $settingUrl;
  9904. $templateBlock->content = json_encode($blockContent);
  9905. $this->dao->insert(TABLE_DOCBLOCK)->data($templateBlock)->exec();
  9906. $templateBlockID = $this->dao->lastInsertId();
  9907. $exportUrl = helper::createLink('doc', 'ajaxExportZentaoList', "blockID=__TML_ZENTAOLIST__{$templateBlockID}");
  9908. $fetcherUrl = helper::createLink('doc', 'zentaoList', "type={$blockType}&blockID=__TML_ZENTAOLIST__{$templateBlockID}");
  9909. if($this->config->requestType == 'GET') list($exportUrl, $fetcherUrl) = str_replace(array('upgrade.php?', 'install.php?'), 'index.php?', array($exportUrl, $fetcherUrl));
  9910. return array('blockTitle' => $blockTitle, 'exportUrl' => $exportUrl, 'fetcherUrl' => $fetcherUrl);
  9911. }
  9912. /**
  9913. *
  9914. * 为项目流程内置其他活动。
  9915. * Add other activity.
  9916. *
  9917. * @access public
  9918. * @return void
  9919. */
  9920. public function addWorkflowGroupOtherActivity()
  9921. {
  9922. $projectModules = $this->dao->select('root,id')->from(TABLE_MODULE)->where('type')->eq('process')->andWhere('extra')->eq('project')->fetchPairs();
  9923. $groupList = $this->dao->select('*')->from(TABLE_WORKFLOWGROUP)->where('type')->eq('project')->andWhere('projectModel')->ne('kanban')->fetchAll();
  9924. foreach($groupList as $group)
  9925. {
  9926. if($this->config->edition != 'open' && $group->projectModel == 'ipd')
  9927. {
  9928. $this->loadModel('workflowGroup')->addProcessAndActivity($group, 'ipd');
  9929. }
  9930. elseif(!empty($projectModules[$group->id]))
  9931. {
  9932. $this->createOtherActivity($projectModules[$group->id], $group->id);
  9933. }
  9934. }
  9935. }
  9936. /**
  9937. * 为项目流程内置默认分类。
  9938. * Add deliverable default modules.
  9939. *
  9940. * @access public
  9941. * @return void
  9942. */
  9943. public function addDefaultDeliverableModule()
  9944. {
  9945. $groupList = $this->dao->select('id')->from(TABLE_WORKFLOWGROUP)->where('type')->eq('project')->andWhere('projectModel')->ne('kanban')->fetchAll();
  9946. foreach($groupList as $group)
  9947. {
  9948. foreach($this->lang->upgrade->deliverableModule as $code => $name) $this->createDeliverableModule($group->id, $name, $code);
  9949. }
  9950. }
  9951. /**
  9952. * 升级设计类型为交付物。
  9953. * Upgrade design type to deliverable.
  9954. *
  9955. * @access public
  9956. * @return void
  9957. */
  9958. public function upgradeDesignToDeliverable()
  9959. {
  9960. $this->app->loadLang('design');
  9961. $clientLang = $this->app->getClientLang();
  9962. $typeList = $this->dao->select('`key`, value')->from(TABLE_LANG)
  9963. ->where('module')->eq('design')
  9964. ->andWhere('section')->eq('typeList')
  9965. ->andWhere('lang')->in(array($clientLang, 'all'))
  9966. ->orderBy('id_asc')
  9967. ->fetchPairs();
  9968. $plusTypeList = $this->dao->select('`key`, value')->from(TABLE_LANG)
  9969. ->where('module')->eq('design')
  9970. ->andWhere('section')->eq('plusTypeList')
  9971. ->andWhere('lang')->in(array($clientLang, 'all'))
  9972. ->orderBy('id_asc')
  9973. ->fetchPairs();
  9974. if($typeList) $this->lang->design->typeList = $typeList;
  9975. if($plusTypeList) $this->lang->design->plusTypeList = $plusTypeList;
  9976. $modelList = array('waterfall', 'waterfallplus', 'ipd');
  9977. $moduleList = $this->dao->select('t1.id,t1.name,t2.projectModel,t2.id as workflowGroup')->from(TABLE_MODULE)->alias('t1')
  9978. ->leftJoin(TABLE_WORKFLOWGROUP)->alias('t2')->on('t1.root=t2.id')
  9979. ->where('t1.type')->eq('deliverable')
  9980. ->andWhere('t1.extra')->eq('design')
  9981. ->andWhere('t2.projectModel')->in($modelList)
  9982. ->fetchAll();
  9983. $activityList = $this->dao->select('t1.*')->from(TABLE_ACTIVITY)->alias('t1')->leftJoin(TABLE_PROCESS)->alias('t2')->on('t1.process=t2.id')
  9984. ->where('t1.name')->eq($this->lang->other)
  9985. ->andWhere('t2.name')->eq($this->lang->other)
  9986. ->fetchGroup('workflowGroup');
  9987. $projectWorkflowGroup = $this->dao->select('id,workflowGroup')->from(TABLE_PROJECT)->where('type')->eq('project')->fetchGroup('workflowGroup', 'id');
  9988. $deliverable = new stdClass();
  9989. $deliverable->status = 'enabled';
  9990. $deliverable->createdBy = 'system';
  9991. $deliverable->createdDate = helper::now();
  9992. $deliverable->systemList = 1;
  9993. $deliverable->trimmable = '1';
  9994. $deliverable->template = '[]';
  9995. $deliverableStage = new stdClass();
  9996. $deliverableStage->required = '0';
  9997. $deliverableStage->stage = 'project';
  9998. foreach($moduleList as $module)
  9999. {
  10000. if(empty($activityList[$module->workflowGroup])) continue;
  10001. $otherActivity = reset($activityList[$module->workflowGroup]);
  10002. $otherActivityID = $otherActivity->id;
  10003. $nameFilter = array();
  10004. $deliverable->workflowGroup = $module->workflowGroup;
  10005. $deliverable->module = $module->id;
  10006. $deliverable->activity = $otherActivityID;
  10007. foreach(array_filter($this->lang->design->typeList) as $key => $value)
  10008. {
  10009. if(empty($value) || !in_array($module->projectModel, array('waterfall', 'ipd'))) continue;
  10010. $deliverable->category = $key; // 将设计的类型转换为交付物的类型。
  10011. $deliverableID = $this->addDeliverable((string)$value, $deliverable, $deliverableStage, $nameFilter);
  10012. /* 将历史设计的设计类型替换为交付物ID。 */
  10013. if(!empty($projectWorkflowGroup[$module->workflowGroup]))
  10014. {
  10015. $projects = array_keys($projectWorkflowGroup[$module->workflowGroup]);
  10016. $this->dao->update(TABLE_DESIGN)->set('type')->eq($deliverableID)->where('type')->eq($key)->andWhere('project')->in($projects)->exec();
  10017. }
  10018. }
  10019. foreach(array_filter($this->lang->design->plusTypeList) as $key => $value)
  10020. {
  10021. if(empty($value) || $module->projectModel != 'waterfallplus') continue;
  10022. $deliverable->category = $key; // 将设计的类型转换为交付物的类型。
  10023. $deliverableID = $this->addDeliverable((string)$value, $deliverable, $deliverableStage, $nameFilter);
  10024. /* 将历史设计的设计类型替换为交付物ID。 */
  10025. if(!empty($projectWorkflowGroup[$module->workflowGroup]))
  10026. {
  10027. $projects = array_keys($projectWorkflowGroup[$module->workflowGroup]);
  10028. $this->dao->update(TABLE_DESIGN)->set('type')->eq($deliverableID)->where('type')->eq($key)->andWhere('project')->in($projects)->exec();
  10029. }
  10030. }
  10031. }
  10032. }
  10033. /**
  10034. * 创建交付物。
  10035. * Add deliverable.
  10036. *
  10037. * @param string $name
  10038. * @param object $deliverable
  10039. * @param object $deliverableStage
  10040. * @param array $nameFilter
  10041. * @access public
  10042. * @return int
  10043. */
  10044. public function addDeliverable($name, $deliverable, $deliverableStage, &$nameFilter = array())
  10045. {
  10046. /* 重名的交付物名称后面加数字。 */
  10047. if(!empty($nameFilter[$name]))
  10048. {
  10049. $deliverable->name = $name . $nameFilter[$name];
  10050. $nameFilter[$name] ++;
  10051. }
  10052. else
  10053. {
  10054. $deliverable->name = $name;
  10055. $nameFilter[$name] = 1;
  10056. }
  10057. $this->dao->insert(TABLE_DELIVERABLE)->data($deliverable)->exec();
  10058. $deliverableID = $this->dao->lastInsertID();
  10059. $deliverableStage->deliverable = $deliverableID;
  10060. $this->dao->insert(TABLE_DELIVERABLESTAGE)->data($deliverableStage)->exec();
  10061. return $deliverableID;
  10062. }
  10063. /**
  10064. * 将通用交付物升级为项目流程下的交付物。
  10065. * Upgrade deliverable.
  10066. *
  10067. * @access public
  10068. * @return void
  10069. */
  10070. public function upgradeDeliverable()
  10071. {
  10072. $this->app->loadLang('tree');
  10073. $deliverable = new stdClass();
  10074. $deliverable->status = 'disabled';
  10075. $deliverable->trimmable = '1';
  10076. $deliverableStage = new stdClass();
  10077. $deliverableStage->stage = 'project';
  10078. $deliverableStage->required = '0';
  10079. $deliverableList = array();
  10080. $nameFilter = array(); // 过滤重名交付物。
  10081. $workflowGroups = $this->dao->select('id,deliverable,projectModel,projectType')->from(TABLE_WORKFLOWGROUP)->where('type')->eq('project')->andWhere('projectModel')->ne('kanban')->fetchAll();
  10082. $deliverables = $this->dao->select('id,name,model,`desc`,createdBy,createdDate')->from(TABLE_DELIVERABLE)->where('deleted')->eq('0')->andWhere('model')->ne('')->fetchAll('id');
  10083. $fileList = $this->dao->select('id,title,objectType,objectID')->from(TABLE_FILE)->where('objectType')->eq('deliverable')->fetchAll('objectID');
  10084. $otherModules = $this->dao->select('root,id')->from(TABLE_MODULE)->where('type')->eq('deliverable')->andWhere('extra')->eq('other')->fetchPairs();
  10085. $activityList = $this->dao->select('t1.*')->from(TABLE_ACTIVITY)->alias('t1')->leftJoin(TABLE_PROCESS)->alias('t2')->on('t1.process=t2.id')
  10086. ->where('t1.name')->eq($this->lang->other)
  10087. ->andWhere('t2.name')->eq($this->lang->other)
  10088. ->fetchGroup('workflowGroup');
  10089. /* 将旧的交付物按照适用范围升级到各个项目流程中。 */
  10090. foreach($deliverables as $oldDeliverable)
  10091. {
  10092. $models = explode(',', $oldDeliverable->model);
  10093. /* 原瀑布、敏捷追加融合模式。 */
  10094. foreach($models as $model)
  10095. {
  10096. if(strpos($model, 'product_waterfall') !== false) $models[] = str_replace('product_waterfall', 'product_waterfallplus', $model);
  10097. if(strpos($model, 'project_waterfall') !== false) $models[] = str_replace('project_waterfall', 'project_waterfallplus', $model);
  10098. if(strpos($model, 'product_scrum') !== false) $models[] = str_replace('product_scrum', 'product_agileplus', $model);
  10099. if(strpos($model, 'project_scrum') !== false) $models[] = str_replace('project_scrum', 'project_agileplus', $model);
  10100. }
  10101. foreach($models as $model)
  10102. {
  10103. foreach($workflowGroups as $workflowGroup)
  10104. {
  10105. if($workflowGroup->projectModel == 'kanban') $workflowGroup->projectModel = 'scrum_kanban';
  10106. if(!empty($deliverableList[$workflowGroup->id][$oldDeliverable->id])) continue;
  10107. if(strpos($model, "{$workflowGroup->projectType}_{$workflowGroup->projectModel}") === false) continue;
  10108. if(empty($activityList[$workflowGroup->id])) continue;
  10109. $otherActivity = reset($activityList[$workflowGroup->id]);
  10110. $otherActivityID = $otherActivity->id;
  10111. $deliverableFile = $fileList[$oldDeliverable->id]; // 原交付物只会上传一个附件。
  10112. $deliverable->workflowGroup = $workflowGroup->id;
  10113. $deliverable->desc = $oldDeliverable->desc;
  10114. $deliverable->module = isset($otherModules[$workflowGroup->id]) ? $otherModules[$workflowGroup->id] : '0';
  10115. $deliverable->activity = $otherActivityID;
  10116. $deliverable->template = $deliverableFile ? '{"new_0":{"name":"' . $deliverableFile->title . '","doc":"","fileID":"' . $deliverableFile->id . '"}}' : '[]';
  10117. $deliverable->createdBy = $oldDeliverable->createdBy;
  10118. $deliverable->createdDate = !empty($oldDeliverable->createdDate) ? $oldDeliverable->createdDate : null;
  10119. /* 重名的交付物名称后面加数字。 */
  10120. if(!empty($nameFilter[$workflowGroup->id][$oldDeliverable->name]))
  10121. {
  10122. $deliverable->name = $oldDeliverable->name . $nameFilter[$workflowGroup->id][$oldDeliverable->name];
  10123. $nameFilter[$workflowGroup->id][$oldDeliverable->name] ++;
  10124. }
  10125. else
  10126. {
  10127. $deliverable->name = $oldDeliverable->name;
  10128. $nameFilter[$workflowGroup->id][$oldDeliverable->name] = 1;
  10129. }
  10130. $this->dao->insert(TABLE_DELIVERABLE)->data($deliverable)->exec();
  10131. $deliverableID = $this->dao->lastInsertID();
  10132. $deliverableList[$workflowGroup->id][$oldDeliverable->id] = $deliverableID;
  10133. $deliverableStage->deliverable = $deliverableID;
  10134. $this->dao->insert(TABLE_DELIVERABLESTAGE)->data($deliverableStage)->exec();
  10135. }
  10136. }
  10137. }
  10138. $sprintFilter = array(); // 过滤重复交付物检查规则。
  10139. /* 按照项目流程下的交付物配置设置各个交付物的检查规则。 */
  10140. foreach($workflowGroups as $workflowGroup)
  10141. {
  10142. /* 解析项目流程的交付物配置。 */
  10143. $groupDeliverable = !empty($workflowGroup->deliverable) ? json_decode($workflowGroup->deliverable, true) : array();
  10144. foreach($groupDeliverable as $stageCode => $methodDeliverable)
  10145. {
  10146. foreach($methodDeliverable as $deliverableConfigs)
  10147. {
  10148. foreach($deliverableConfigs as $config)
  10149. {
  10150. if(empty($deliverableList[$workflowGroup->id][$config['deliverable']])) continue;
  10151. $projectModel = $workflowGroup->projectModel;
  10152. if($projectModel == 'waterfallplus') $projectModel = 'waterfall';
  10153. if($projectModel == 'agileplus') $projectModel = 'scrum';
  10154. $deliverableID = $deliverableList[$workflowGroup->id][$config['deliverable']];
  10155. $workflowGroupModel = "{$workflowGroup->projectType}_{$projectModel}";
  10156. $deliverableStage->stage = $stageCode == $workflowGroupModel ? 'project' : str_replace("{$workflowGroupModel}_", '', $stageCode);
  10157. $deliverableStage->required = !empty($config['required']) ? '1' : '0';
  10158. $deliverableStage->deliverable = $deliverableID;
  10159. /* 如果这个交付物在项目流程下有交付物配置,则启用交付物并删除默认的交付物检查规则。 */
  10160. if(!isset($sprintFilter[$deliverableID]))
  10161. {
  10162. $this->dao->update(TABLE_DELIVERABLE)->set('status')->eq('enabled')->where('id')->eq($deliverableID)->exec();
  10163. $this->dao->delete()->from(TABLE_DELIVERABLESTAGE)->where('deliverable')->eq($deliverableID)->andWhere('stage')->eq('project')->exec();
  10164. }
  10165. /* 按照交付物配置生成交付物检查规则。 */
  10166. if(in_array($deliverableStage->stage, array('short', 'long', 'ops')))
  10167. {
  10168. /* 将原来不同类型的迭代合并成一个迭代。 */
  10169. $deliverableStage->stage = 'sprint';
  10170. /* 其中有一个类型是必填的合并后的结果就是必填。 */
  10171. if(isset($sprintFilter[$deliverableID]['sprint']) && empty($sprintFilter[$deliverableID]['sprint']) && !empty($deliverableStage->required))
  10172. {
  10173. $this->dao->update(TABLE_DELIVERABLESTAGE)->set('required')->eq('1')->where('deliverable')->eq($deliverableID)->andWhere('stage')->eq('sprint')->exec();
  10174. }
  10175. if(!isset($sprintFilter[$deliverableID]['sprint']))
  10176. {
  10177. $this->dao->insert(TABLE_DELIVERABLESTAGE)->data($deliverableStage)->exec();
  10178. }
  10179. }
  10180. elseif(!isset($sprintFilter[$deliverableID][$deliverableStage->stage]))
  10181. {
  10182. $this->dao->insert(TABLE_DELIVERABLESTAGE)->data($deliverableStage)->exec();
  10183. }
  10184. $sprintFilter[$deliverableID][$deliverableStage->stage] = $deliverableStage->required;
  10185. }
  10186. }
  10187. }
  10188. $this->createOtherDeliverable($workflowGroup->id); // 创建名为其他的交付物类型。
  10189. }
  10190. /* 将已升级的历史交付物数据删掉。 */
  10191. $this->dao->delete()->from(TABLE_DELIVERABLE)->where('id')->in(array_keys($deliverables))->andWhere('workflowGroup')->eq('0')->exec();
  10192. /* 升级完后把没用的字段删掉。 */
  10193. $this->dao->exec("ALTER TABLE " . TABLE_DELIVERABLE . " CHANGE `module` `module` mediumint(8) unsigned NOT NULL DEFAULT '0';");
  10194. if($deliverableList) $this->upgradeProjectDeliverable($deliverableList);
  10195. }
  10196. /**
  10197. * 内置交付物其他分类。
  10198. * Create other module.
  10199. *
  10200. * @param int $workflowGroupID
  10201. * @param string $name
  10202. * @param string $extra
  10203. * @access public
  10204. * @return int
  10205. */
  10206. public function createDeliverableModule($workflowGroupID, $name, $extra = '')
  10207. {
  10208. $module = new stdclass();
  10209. $module->root = $workflowGroupID;
  10210. $module->branch = '0';
  10211. $module->name = $name;
  10212. $module->parent = '0';
  10213. $module->path = '';
  10214. $module->grade = '1';
  10215. $module->order = '0';
  10216. $module->type = 'deliverable';
  10217. $module->from = '0';
  10218. $module->owner = '';
  10219. $module->collector = null;
  10220. $module->short = '';
  10221. $module->extra = $extra;
  10222. $module->deleted = '0';
  10223. $this->dao->insert(TABLE_MODULE)->data($module)->exec();
  10224. $moduleID = $this->dao->lastInsertID();
  10225. $this->dao->update(TABLE_MODULE)->set('path')->eq(",$moduleID,")->where('id')->eq($moduleID)->exec();
  10226. return $moduleID;
  10227. }
  10228. /**
  10229. * 创建名为其他的交付物类型。
  10230. * Create other deliverable.
  10231. *
  10232. * @param int $workflowGroupID
  10233. * @param int $moduleID
  10234. * @access public
  10235. * @return void
  10236. */
  10237. public function createOtherDeliverable($workflowGroupID)
  10238. {
  10239. $moduleID = $this->dao->select('id')->from(TABLE_MODULE)->where('type')->eq('deliverable')->andWhere('root')->eq($workflowGroupID)->andWhere('extra')->eq('other')->fetch('id');
  10240. $activityID = $this->dao->select('id')->from(TABLE_ACTIVITY)->where('name')->eq($this->lang->other)->andWhere('workflowGroup')->eq($workflowGroupID)->fetch('id');
  10241. /* 创建名为其他的交付物类型。 */
  10242. $deliverable = new stdclass();
  10243. $deliverable->name = $this->lang->other;
  10244. $deliverable->category = 'other';
  10245. $deliverable->module = $moduleID;
  10246. $deliverable->activity = $activityID ? $activityID : 0;
  10247. $deliverable->workflowGroup = $workflowGroupID;
  10248. $deliverable->template = '[]';
  10249. $deliverable->trimmable = '1';
  10250. $deliverable->builtin = '1';
  10251. $deliverable->status = 'enabled';
  10252. $deliverable->createdBy = 'system';
  10253. $deliverable->createdDate = helper::now();
  10254. $this->dao->insert(TABLE_DELIVERABLE)->data($deliverable)->exec();
  10255. $deliverableID = $this->dao->lastInsertID();
  10256. $stageList = $this->config->edition != 'biz' ? $this->loadModel('deliverable')->buildStageList($workflowGroupID) : array();
  10257. foreach($stageList as $key => $stage)
  10258. {
  10259. $deliverableStage = new stdclass();
  10260. $deliverableStage->deliverable = $deliverableID;
  10261. $deliverableStage->stage = $key;
  10262. $deliverableStage->required = '0';
  10263. $this->dao->insert(TABLE_DELIVERABLESTAGE)->data($deliverableStage)->exec();
  10264. }
  10265. }
  10266. /**
  10267. * 内置默认的过程活动。
  10268. * Create other process and activity.
  10269. *
  10270. * @param int $moduleID
  10271. * @param int $workflowGroupID
  10272. * @access public
  10273. * @return int
  10274. */
  10275. public function createOtherActivity($moduleID, $workflowGroupID)
  10276. {
  10277. $existsProcess = $this->dao->select('id')->from(TABLE_PROCESS)->where('workflowGroup')->eq($workflowGroupID)->andWhere('name')->eq($this->lang->other)->fetch('id');
  10278. if($existsProcess) return $existsProcess;
  10279. $process = new stdclass();
  10280. $process->workflowGroup = $workflowGroupID;
  10281. $process->name = $this->lang->other;
  10282. $process->module = $moduleID;
  10283. $process->createdBy = 'system';
  10284. $process->createdDate = '2020-01-09 00:00:00';
  10285. $process->editedDate = null;
  10286. $process->assignedDate = null;
  10287. $process->deleted = '0';
  10288. $this->dao->insert(TABLE_PROCESS)->data($process)->exec();
  10289. $processID = $this->dao->lastInsertID();
  10290. $this->dao->update(TABLE_PROCESS)->set('`order`')->eq($processID * 5)->where('id')->eq($processID)->exec();
  10291. $activity = new stdclass();
  10292. $activity->workflowGroup = $workflowGroupID;
  10293. $activity->process = $processID;
  10294. $activity->name = $this->lang->other;
  10295. $activity->optional = 'no';
  10296. $activity->createdBy = 'system';
  10297. $activity->createdDate = '2020-01-09 00:00:00';
  10298. $activity->editedDate = null;
  10299. $activity->assignedDate = null;
  10300. $activity->deleted = '0';
  10301. $this->dao->insert(TABLE_ACTIVITY)->data($activity)->exec();
  10302. $activityID = $this->dao->lastInsertID();
  10303. $this->dao->update(TABLE_ACTIVITY)->set('`order`')->eq($activityID * 5)->where('id')->eq($activityID)->exec();
  10304. return $activityID;
  10305. }
  10306. /**
  10307. * 内置测试环节列表交付物。
  10308. * Buildin testcase stage deliverable.
  10309. *
  10310. * @access public
  10311. * @return bool
  10312. */
  10313. public function buildinTestcaseStageDeliverable()
  10314. {
  10315. $this->app->loadLang('testcase');
  10316. $clientLang = $this->app->getClientLang();
  10317. $testcaseStageList = $this->dao->select('`key`, value')->from(TABLE_LANG)
  10318. ->where('module')->eq('testcase')
  10319. ->andWhere('section')->eq('stageList')
  10320. ->andWhere('lang')->in(array($clientLang, 'all'))
  10321. ->orderBy('id_asc')
  10322. ->fetchPairs();
  10323. if(empty($testcaseStageList)) $testcaseStageList = $this->lang->testcase->stageList;
  10324. $this->createTestcaseDeliverable($testcaseStageList);
  10325. return true;
  10326. }
  10327. /**
  10328. * 创建测试用例阶段类型交付物。
  10329. * Create testcase deliverable.
  10330. *
  10331. * @param array $modules
  10332. * @access public
  10333. * @return bool
  10334. */
  10335. public function createTestcaseDeliverable($modules)
  10336. {
  10337. $testModules = $this->dao->select('root,id')->from(TABLE_MODULE)
  10338. ->where('type')->eq('deliverable')
  10339. ->andWhere('extra')->eq('test')
  10340. ->fetchPairs();
  10341. $testDeliverable = $this->dao->select('category')->from(TABLE_DELIVERABLE)
  10342. ->where('builtin')->eq('0')
  10343. ->andWhere('module')->in($testModules)
  10344. ->fetchPairs();
  10345. /* 已经存在但是被删除的交付物进行还原操作。 */
  10346. foreach($modules as $key => $name)
  10347. {
  10348. if(empty($name)) continue;
  10349. if(!empty($testDeliverable[$key]))
  10350. {
  10351. $this->dao->update(TABLE_DELIVERABLE)->set('deleted')->eq('0')->set('name')->eq($name . $this->lang->upgrade->list)->where('category')->eq($key)->andWhere('builtin')->eq('0')->exec();
  10352. unset($modules[$key]);
  10353. }
  10354. }
  10355. $activityList = $this->dao->select('t1.*')->from(TABLE_ACTIVITY)->alias('t1')->leftJoin(TABLE_PROCESS)->alias('t2')->on('t1.process=t2.id')
  10356. ->where('t1.name')->eq($this->lang->other)
  10357. ->andWhere('t2.name')->eq($this->lang->other)
  10358. ->fetchGroup('workflowGroup');
  10359. if(empty($modules)) return true;
  10360. $deliverable = new stdClass();
  10361. $deliverable->status = 'enabled';
  10362. $deliverable->createdBy = 'system';
  10363. $deliverable->createdDate = helper::now();
  10364. $deliverable->template = '[]';
  10365. $deliverable->trimmable = '1';
  10366. $deliverable->builtin = '0';
  10367. $deliverable->systemList = 1;
  10368. $deliverableStage = new stdClass();
  10369. $deliverableStage->required = '0';
  10370. $deliverableStage->stage = 'project';
  10371. $workflows = $this->dao->select('id,projectModel')->from(TABLE_WORKFLOWGROUP)->where('type')->eq('project')->andWhere('projectModel')->ne('kanban')->fetchAll('id');
  10372. foreach($workflows as $workflow)
  10373. {
  10374. $groupID = $workflow->id;
  10375. if(empty($activityList[$groupID])) continue;
  10376. $otherActivity = reset($activityList[$groupID]);
  10377. $otherActivityID = $otherActivity->id;
  10378. $deliverable->module = zget($testModules, $workflow->id, 0);
  10379. $deliverable->workflowGroup = $workflow->id;
  10380. $deliverable->activity = $otherActivityID;
  10381. foreach($modules as $key => $name)
  10382. {
  10383. if(empty($name)) continue;
  10384. $deliverable->category = $key; // 标记交付物的类型。
  10385. $this->addDeliverable($name . $this->lang->upgrade->list, $deliverable, $deliverableStage);
  10386. }
  10387. }
  10388. return true;
  10389. }
  10390. /**
  10391. * 删除测试用例阶段交付物。
  10392. * Delete testcase deliverable.
  10393. *
  10394. * @param array $modules
  10395. * @access public
  10396. * @return bool
  10397. */
  10398. public function deleteTestcaseDeliverable($modules)
  10399. {
  10400. foreach($modules as $key => $name)
  10401. {
  10402. $this->dao->update(TABLE_DELIVERABLE)->set('deleted')->eq('1')->where('category')->eq($key)->andWhere('builtin')->eq('0')->exec();
  10403. }
  10404. return true;
  10405. }
  10406. /**
  10407. * 变更测试用例阶段交付物名称。
  10408. * Change testcase deliverable.
  10409. *
  10410. * @param array $modules
  10411. * @access public
  10412. * @return bool
  10413. */
  10414. public function changeTestcaseDeliverable($modules)
  10415. {
  10416. foreach($modules as $key => $name)
  10417. {
  10418. $this->dao->update(TABLE_DELIVERABLE)->set('name')->eq($name . $this->lang->upgrade->list)->where('category')->eq($key)->andWhere('builtin')->eq('0')->exec();
  10419. }
  10420. return true;
  10421. }
  10422. /**
  10423. * 升级项目和迭代的交付物配置。
  10424. * Upgrade project deliverable.
  10425. *
  10426. * @param array $deliverableList
  10427. * @access public
  10428. * @return void
  10429. */
  10430. public function upgradeProjectDeliverable($deliverableList)
  10431. {
  10432. $projectList = $this->dao->select('id,deliverable,workflowGroup,type,project')->from(TABLE_PROJECT)->where('deliverable')->ne('')->fetchAll('id');
  10433. $projectMainLibPairs = $this->dao->select('project, id')->from(TABLE_DOCLIB)->where('main')->eq('1')->andWhere('type')->eq('project')->andWhere('project')->in(array_keys($projectList))->fetchPairs();
  10434. $executionMainLibPairs = $this->dao->select('execution, id')->from(TABLE_DOCLIB)->where('main')->eq('1')->andWhere('type')->eq('execution')->andWhere('execution')->in(array_keys($projectList))->fetchPairs();
  10435. $fileList = $this->dao->select('*')->from(TABLE_FILE)->where('deleted')->eq('0')->andWhere('extra')->like('deliverable%')->fetchAll('id');
  10436. $otherDeliverablePairs = $this->dao->select('workflowGroup, id')->from(TABLE_DELIVERABLE)->where('category')->eq('other')->andWhere('builtin')->eq('1')->fetchPairs();
  10437. foreach($projectList as $project)
  10438. {
  10439. $oldProjectDeliverable = !empty($project->deliverable) ? json_decode($project->deliverable, true) : array();
  10440. if(empty($oldProjectDeliverable)) continue;
  10441. if($project->type != 'project' && isset($projectList[$project->project])) $project->workflowGroup = $projectList[$project->project]->workflowGroup; // 执行用项目的项目流程
  10442. /* 解析项目的交付物配置。 */
  10443. foreach($oldProjectDeliverable as $methodDeliverable)
  10444. {
  10445. foreach($methodDeliverable as $deliverableConfigs)
  10446. {
  10447. foreach($deliverableConfigs as $config)
  10448. {
  10449. if(empty($config['doc']) && empty($config['file'])) continue;
  10450. $oldDeliverableID = $config['deliverable'];
  10451. if(strpos($oldDeliverableID, 'new_') !== false)
  10452. {
  10453. $deliverableID = $otherDeliverablePairs[$project->workflowGroup]; // 其他类型的交付物。
  10454. }
  10455. else
  10456. {
  10457. $deliverableID = !empty($deliverableList[$project->workflowGroup][$oldDeliverableID]) ? $deliverableList[$project->workflowGroup][$oldDeliverableID] : $oldDeliverableID;
  10458. }
  10459. $newProjectDeliverable = new stdclass();
  10460. $newProjectDeliverable->deliverable = $deliverableID;
  10461. $newProjectDeliverable->project = $project->type == 'project' ? $project->id : $project->project;
  10462. $newProjectDeliverable->submitFrom = $project->id;
  10463. $newProjectDeliverable->status = 'draft';
  10464. /* 将原来的附件类型的交付物变成附件类型的文档并关联到交付物。 */
  10465. if(!empty($config['file']) && !empty($fileList[$config['file']]))
  10466. {
  10467. $file = $fileList[$config['file']];
  10468. $newProjectDeliverable->doc = $this->moveFileToDoc($project, $projectMainLibPairs, $executionMainLibPairs, $file);
  10469. $newProjectDeliverable->createdBy = $file->addedBy;
  10470. $newProjectDeliverable->createdDate = substr($file->addedDate, 0, 10);
  10471. }
  10472. else
  10473. {
  10474. $newProjectDeliverable->doc = $config['doc'];
  10475. }
  10476. $this->dao->insert(TABLE_PROJECTDELIVERABLE)->data($newProjectDeliverable)->exec();
  10477. }
  10478. }
  10479. }
  10480. }
  10481. /* 之前交付物没存文档名称、版本,升级的时候补上。 */
  10482. $emptyNameDocs = $this->dao->select('t1.id,t2.title,t2.version,t2.addedBy,t2.addedDate')->from(TABLE_PROJECTDELIVERABLE)->alias('t1')
  10483. ->leftJoin(TABLE_DOC)->alias('t2')->on('t1.doc=t2.id')
  10484. ->where('t1.name')->eq('')
  10485. ->andWhere('t2.deleted')->eq('0')
  10486. ->fetchAll('id');
  10487. foreach($emptyNameDocs as $deliverableID => $doc)
  10488. {
  10489. $deliverableCreatedDate = date('Y-m-d', strtotime($doc->addedDate));
  10490. $this->dao->update(TABLE_PROJECTDELIVERABLE)
  10491. ->set('name')->eq($doc->title)
  10492. ->set('docVersion')->eq($doc->version)
  10493. ->set('createdBy')->eq($doc->addedBy)
  10494. ->set('createdDate')->eq($deliverableCreatedDate)
  10495. ->where('id')->eq($deliverableID)
  10496. ->exec();
  10497. }
  10498. $this->dao->query("ALTER TABLE " . TABLE_PROJECT . " DROP COLUMN `deliverable`;");
  10499. }
  10500. /**
  10501. * 将附件类型的交付物变成附件类型的文档并关联到交付物。
  10502. * Move file to doc.
  10503. *
  10504. * @param object $project
  10505. * @param array $projectMainLibPairs
  10506. * @param array $executionMainLibPairs
  10507. * @param array $files
  10508. * @access public
  10509. * @return int
  10510. * @param object $file
  10511. */
  10512. public function moveFileToDoc($project, $projectMainLibPairs, $executionMainLibPairs, $file)
  10513. {
  10514. $projectType = $project->type == 'project' ? 'project' : 'execution';
  10515. $title = $file->title;
  10516. $position = strrpos($title, '.');
  10517. if($position > 0) $title = substr($title, 0, $position);
  10518. $doc = new stdclass();
  10519. $doc->title = $title;
  10520. $doc->lib = $projectType == 'project' ? $projectMainLibPairs[$project->id] : $executionMainLibPairs[$project->id];
  10521. $doc->project = $projectType == 'project' ? $project->id : 0;
  10522. $doc->execution = $projectType == 'execution' ? $project->id : 0;
  10523. $doc->addedBy = 'admin';
  10524. $doc->addedDate = helper::today();
  10525. $doc->acl = 'open';
  10526. $doc->vision = 'rnd';
  10527. $doc->version = 1;
  10528. $doc->type = 'attachment';
  10529. $this->dao->insert(TABLE_DOC)->data($doc)->exec();
  10530. $docID = $this->dao->lastInsertID();
  10531. $this->dao->update(TABLE_FILE)->set('objectID')->eq($docID)->set('objectType')->eq('doc')->where('id')->eq($file->id)->exec(); // 将附件的对象变成文档。
  10532. $docContent = new stdclass();
  10533. $docContent->doc = $docID;
  10534. $docContent->title = $title;
  10535. $docContent->content = '';
  10536. $docContent->type = 'attachment';
  10537. $docContent->digest = '';
  10538. $docContent->version = 1;
  10539. $docContent->files = ",$file->id";
  10540. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  10541. return $docID;
  10542. }
  10543. /**
  10544. * 将检查单迁移到内置项目流程中。
  10545. * Migrate auditcl to built-in project workflow.
  10546. *
  10547. * @access public
  10548. * @return void
  10549. */
  10550. public function upgradeAuditcl()
  10551. {
  10552. $workflows = $this->dao->select('id,projectModel,projectType,main')->from(TABLE_WORKFLOWGROUP)->where('type')->eq('project')->andWhere('projectModel')->in('scrum,agileplus,waterfall,waterfallplus')->fetchAll('id');
  10553. foreach($workflows as $id => $workflow)
  10554. {
  10555. if($workflow->projectType == 'product' && $workflow->main == '1')
  10556. {
  10557. $this->dao->update(TABLE_AUDITCL)->set('workflowGroup')->eq($id)->where('model')->eq($workflow->projectModel)->exec();
  10558. }
  10559. else
  10560. {
  10561. /* 复制一份检查单放到项目型项目流程中。*/
  10562. $idMap = array();
  10563. $auditclList = $this->dao->select('*')->from(TABLE_AUDITCL)->where('model')->eq($workflow->projectModel)->fetchAll('id', false);
  10564. foreach($auditclList as $auditcl)
  10565. {
  10566. $oldID = $auditcl->id;
  10567. unset($auditcl->id);
  10568. $auditcl->workflowGroup = $id;
  10569. $auditcl->model = '';
  10570. if(helper::isZeroDate($auditcl->editedDate)) $auditcl->editedDate = null;
  10571. if(helper::isZeroDate($auditcl->assignedDate)) $auditcl->assignedDate = null;
  10572. $this->dao->insert(TABLE_AUDITCL)->data($auditcl)->exec();
  10573. $idMap[$oldID] = $this->dao->lastInsertID();
  10574. }
  10575. $projectIdList = $this->dao->select('id')->from(TABLE_PROJECT)->where('workflowGroup')->eq($id)->fetchPairs();
  10576. foreach($idMap as $oldID => $newID)
  10577. {
  10578. $this->dao->update(TABLE_NC)->set('listID')->eq($newID)->where('listID')->eq($oldID)->andWhere('project')->in($projectIdList)->exec();
  10579. }
  10580. }
  10581. }
  10582. /* 将auditplan中的execution迁移到nc中。 */
  10583. $ncs = $this->dao->select('t1.id, t2.execution')->from(TABLE_NC)->alias('t1')
  10584. ->leftJoin(TABLE_AUDITPLAN)->alias('t2')->on('t1.auditplan=t2.id')
  10585. ->where('t2.execution')->ne('0')
  10586. ->fetchPairs();
  10587. foreach($ncs as $ncID => $executionID)
  10588. {
  10589. $this->dao->update(TABLE_NC)->set('execution')->eq($executionID)->where('id')->eq($ncID)->exec();
  10590. }
  10591. $this->dao->exec("ALTER TABLE " . TABLE_AUDITCL . " DROP `model`;");
  10592. }
  10593. /**
  10594. * 之前IPD、融合瀑布使用的是瀑布项目流程,融合敏捷使用的是敏捷项目流程。现在统一使用自己的项目流程。
  10595. * Before IPD, fusion waterfall uses the waterfall project workflow, and fusion agile uses the agile project workflow. Now use the own project workflow.
  10596. *
  10597. * @access public
  10598. * @return void
  10599. */
  10600. public function modifyProjectWorkflowGroup()
  10601. {
  10602. if(!in_array($this->config->edition, array('ipd', 'max'))) return; // 开源版、企业版没有项目流程
  10603. $projectGroup = $this->dao->select('id,workflowGroup,model,category,hasProduct')->from(TABLE_PROJECT)->where('model')->in('ipd,waterfallplus,agileplus')->andWhere('deleted')->eq('0')->fetchGroup('workflowGroup', 'id');
  10604. $workflows = $this->dao->select('*')->from(TABLE_WORKFLOWGROUP)->where('id')->in(array_keys($projectGroup))->fetchAll('id', false);
  10605. $tableList = array(
  10606. TABLE_WORKFLOW,
  10607. TABLE_WORKFLOWFIELD,
  10608. TABLE_WORKFLOWACTION,
  10609. TABLE_WORKFLOWLABEL,
  10610. TABLE_WORKFLOWLAYOUT,
  10611. TABLE_WORKFLOWUI
  10612. );
  10613. $this->loadModel('project');
  10614. $codeWorkflowPairs = array();
  10615. $oldNewFlowPairs = array();
  10616. foreach($workflows as $workflow)
  10617. {
  10618. $projects = $projectGroup[$workflow->id];
  10619. $index = 1;
  10620. $name = $workflow->name;
  10621. foreach($projects as $project)
  10622. {
  10623. $oldWorkflowGroupID = $project->workflowGroup;
  10624. $hasProduct = $project->hasProduct ? 'product' : 'project';
  10625. $code = $project->model == 'ipd' ? strtolower($project->category) . $hasProduct : $project->model . $hasProduct;
  10626. $projectType = $project->model == 'ipd' ? strtolower($project->category) : $hasProduct;
  10627. if($projectType == 'cpd') $projectType = 'cpd' . $hasProduct;
  10628. unset($workflow->id);
  10629. if(isset($codeWorkflowPairs[$code][$oldWorkflowGroupID]))
  10630. {
  10631. $this->dao->update(TABLE_PROJECT)->set('workflowGroup')->eq($codeWorkflowPairs[$code][$oldWorkflowGroupID])->where('id')->eq($project->id)->exec();
  10632. continue;
  10633. }
  10634. $workflow->name = $name . $index;
  10635. $workflow->createdDate = helper::now();
  10636. $workflow->projectModel = $project->model;
  10637. $workflow->projectType = $projectType;
  10638. $workflow->code = $code;
  10639. $workflow->editedDate = null;
  10640. $workflow->main = '0';
  10641. $this->dao->insert(TABLE_WORKFLOWGROUP)->data($workflow)->exec();
  10642. $newWorkflowGroupID = $this->dao->lastInsertId();
  10643. $this->dao->update(TABLE_PROJECT)->set('workflowGroup')->eq($newWorkflowGroupID)->where('id')->eq($project->id)->exec();
  10644. foreach($tableList as $table)
  10645. {
  10646. $this->project->copyWorkflow($project->workflowGroup, $newWorkflowGroupID, $table);
  10647. }
  10648. if(!isset($oldNewFlowPairs[$oldWorkflowGroupID])) $oldNewFlowPairs[$oldWorkflowGroupID] = array();
  10649. $oldNewFlowPairs[$oldWorkflowGroupID][] = $newWorkflowGroupID;
  10650. $codeWorkflowPairs[$code][$oldWorkflowGroupID] = $newWorkflowGroupID;
  10651. $index++;
  10652. }
  10653. }
  10654. /* 处理报告模板的适用流程。*/
  10655. $reportTemplateList = $this->dao->select('id,objects')->from(TABLE_DOC)->where('templateType')->eq('reportTemplate')->fetchPairs('id');
  10656. foreach($reportTemplateList as $templateID => $objects)
  10657. {
  10658. $newObjects = array();
  10659. $objectIdList = array_filter(explode(',', $objects));
  10660. foreach($objectIdList as $objectID)
  10661. {
  10662. $newObjects[] = $objectID;
  10663. if(!isset($oldNewFlowPairs[$objectID])) continue;
  10664. $newObjects = array_merge($newObjects, $oldNewFlowPairs[$objectID]);
  10665. }
  10666. if(!empty($newObjects))
  10667. {
  10668. $newObjectList = implode(',', array_filter(array_unique($newObjects)));
  10669. $this->dao->update(TABLE_DOC)->set('objects')->eq($newObjectList)->where('id')->eq($templateID)->exec();
  10670. }
  10671. }
  10672. }
  10673. /**
  10674. * 升级流程和活动, 将流程和活动中的数据迁移到流程中。
  10675. * Upgrade process and activity.
  10676. *
  10677. * @access public
  10678. * @return void
  10679. */
  10680. public function upgradeProcessAndActivity()
  10681. {
  10682. $this->loadModel('workflowgroup');
  10683. $this->app->loadLang('process');
  10684. $groupList = $this->upgradeTao->getWorkflowGroupForProcess();
  10685. $classifyModule = array();
  10686. foreach($groupList as $groupID => $group)
  10687. {
  10688. $projectModel = $group->projectModel;
  10689. if($projectModel == 'agileplus') $projectModel = 'scrum';
  10690. if($projectModel == 'waterfallplus') $projectModel = 'waterfall';
  10691. if(in_array($group->projectModel, array('scrum', 'agileplus', 'waterfall', 'waterfallplus')))
  10692. {
  10693. $projectActivity = $this->dao->select('t1.id')->from(TABLE_PROGRAMACTIVITY)->alias('t1')
  10694. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  10695. ->leftJoin(TABLE_WORKFLOWGROUP)->alias('t3')->on('t2.workflowGroup=t3.id')
  10696. ->where('t3.projectModel')->eq($group->projectModel)
  10697. ->limit(1)
  10698. ->fetch('id');
  10699. if($projectActivity)
  10700. {
  10701. /* 更新groupID到过程表,获取旧分类和新模块的对应关系。 */
  10702. if($group->projectType == 'product' && $group->main == '1')
  10703. {
  10704. $classifyModule = $this->upgradeTao->handleBuildinWorkflowGroup($group, $groupID, $classifyModule);
  10705. }
  10706. else
  10707. {
  10708. $classifyModule = $this->upgradeTao->handleNeedCopyWorkflowGroup($group, $groupID, $classifyModule);
  10709. $this->upgradeTao->migrateOutputToDeliverable($group);
  10710. }
  10711. }
  10712. else
  10713. {
  10714. $this->workflowgroup->addProcessAndActivity($group, $projectModel);
  10715. $this->dao->delete()->from(TABLE_AUDITCL)->where('workflowGroup')->eq($groupID)->exec(); // 新增活动删除旧的检查单
  10716. }
  10717. }
  10718. else
  10719. {
  10720. $this->workflowgroup->addProcessAndActivity($group, $projectModel);
  10721. $this->dao->delete()->from(TABLE_AUDITCL)->where('workflowGroup')->eq($groupID)->exec(); // 新增活动删除旧的检查单
  10722. }
  10723. }
  10724. /* 迁移过程分类到模块表。 */
  10725. $this->upgradeTao->updateProcessModules($classifyModule);
  10726. /* 删除旧分类。 */
  10727. $this->dao->delete()->from(TABLE_PROCESS)->where('workflowGroup')->eq(0)->exec();
  10728. $this->dao->delete()->from(TABLE_ACTIVITY)->where('workflowGroup')->eq(0)->exec();
  10729. $this->dao->delete()->from(TABLE_LANG)->where('module')->eq('process')->exec();
  10730. }
  10731. /**
  10732. * 获取需要更新的项目报告数据。
  10733. * Get project reports that need to be updated.
  10734. *
  10735. * @access public
  10736. * @return array
  10737. */
  10738. public function getUpgradeProjectReports()
  10739. {
  10740. $reports = $this->dao->select('t1.id,t1.project,t1.weekStart,t2.model AS projectModel,t2.status AS projectStatus,t2.realBegan,t2.realEnd,t2.suspendedDate')->from(TABLE_WEEKLYREPORT)->alias('t1')
  10741. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
  10742. ->where('t2.status')->ne('wait')
  10743. ->orderBy('t1.project asc, t1.weekStart asc')
  10744. ->fetchAll();
  10745. $projectReports = $this->dao->select('id')->from(TABLE_PROJECT)->where('type')->eq('project')->andWhere('model')->in('waterfall,waterfallplus,ipd')->fetchAll();
  10746. if(empty($reports) && empty($projectReports)) return array();
  10747. $thisSunday = date('Y-m-d', strtotime('this Sunday'));
  10748. foreach($reports as $report)
  10749. {
  10750. if(in_array($report->projectModel, array('scrum', 'agileplus', 'kanban'))) continue;
  10751. if($report->projectStatus == 'doing') $report->realBegan = !helper::isZeroDate($report->realBegan) ? $report->realBegan : $report->weekStart;
  10752. if(helper::isZeroDate($report->realBegan)) continue;
  10753. $beginTimestame = strtotime($report->realBegan);
  10754. $day = date('w', $beginTimestame);
  10755. if($day == 0) $day = 7;
  10756. $report->projectBegin = date('Y-m-d', $beginTimestame - (($day - 1) * 24 * 3600));
  10757. /* Filter date < project begin date report. */
  10758. if($report->weekStart < $report->projectBegin) continue;
  10759. if($report->projectStatus == 'doing') $report->projectEnd = $thisSunday;
  10760. if($report->projectStatus == 'suspended') $report->projectEnd = $report->suspendedDate;
  10761. if($report->projectStatus == 'closed') $report->projectEnd = $report->realEnd;
  10762. /* Filter date > project end date report. */
  10763. $report->projectEnd = helper::isZeroDate($report->projectEnd) ? '' : date('Y-m-d', strtotime($report->projectEnd));
  10764. if(!$report->projectEnd || $report->weekStart > $report->projectEnd) continue;
  10765. $projectReports[] = $report;
  10766. }
  10767. return $projectReports;
  10768. }
  10769. /**
  10770. * 升级项目报告数据。
  10771. * Upgrade project report data.
  10772. *
  10773. * @param array $data
  10774. * @access public
  10775. * @return bool
  10776. */
  10777. public function upgradeProjectReport($data)
  10778. {
  10779. $report = new stdclass();
  10780. if(!empty($data['weekStart']))
  10781. {
  10782. $weekNumber = ceil(helper::diffDate($data['weekStart'], $data['projectBegin']) / 7) + 1;
  10783. $weekEnd = date('Y-m-d', strtotime('+6 day', strtotime($data['weekStart'])));
  10784. $report->project = $data['project'];
  10785. $report->title = sprintf($this->lang->upgrade->weeklyReportTitle, $weekNumber, $data['weekStart'], $weekEnd);
  10786. $report->module = 'week';
  10787. $report->addedDate = $data['weekStart'] . ' 00:00:00';
  10788. $report->weeklyDate = str_replace('-', '', $data['weekStart']);
  10789. }
  10790. else
  10791. {
  10792. $report->title = $this->lang->upgrade->milestoneTitle;
  10793. $report->module = 'milestone';
  10794. $report->project = $data['id'];
  10795. $report->addedDate = helper::now();
  10796. }
  10797. $report->templateType = 'projectReport';
  10798. $report->addedBy = 'system';
  10799. $this->dao->insert(TABLE_DOC)->data($report)->exec();
  10800. return !dao::isError();
  10801. }
  10802. /**
  10803. * 内置基线评审到项目流程中。
  10804. * Buildin baseline review flow.
  10805. *
  10806. * @access public
  10807. * @return bool
  10808. */
  10809. public function buildinBaselineReview()
  10810. {
  10811. $reviewFlow = new stdclass();
  10812. $reviewFlow->flow = '1';
  10813. $reviewFlow->objectID = '0';
  10814. $reviewFlow->relatedBy = 'system';
  10815. $reviewFlow->relatedDate = helper::now();
  10816. $reviewFlow->extra = 'baseline';
  10817. $workflows = $this->dao->select('id')->from(TABLE_WORKFLOWGROUP)->where('projectModel')->notin('scrum,projectModel')->fetchAll('id');
  10818. foreach($workflows as $workflow)
  10819. {
  10820. foreach($this->lang->upgrade->baselineReview as $key => $name)
  10821. {
  10822. $reviewFlow->root = $workflow->id;
  10823. $reviewFlow->objectType = $key;
  10824. $this->dao->insert(TABLE_APPROVALFLOWOBJECT)->data($reviewFlow)->exec();
  10825. }
  10826. }
  10827. return true;
  10828. }
  10829. /**
  10830. * 升级检查单分类到项目流程下。
  10831. * Upgrade reviewcl category.
  10832. *
  10833. * @access public
  10834. * @return bool
  10835. */
  10836. public function upgradeReviewclCategory()
  10837. {
  10838. $this->loadModel('custom');
  10839. $clientLang = $this->app->getClientLang();
  10840. $categoryList = $this->dao->select('`key`, value')->from(TABLE_LANG)
  10841. ->where('module')->eq('reviewcl')
  10842. ->andWhere('section')->eq('waterfallCategoryList')
  10843. ->andWhere('lang')->in(array($clientLang, 'all'))
  10844. ->orderBy('id_asc')
  10845. ->fetchPairs();
  10846. $categoryPlusList = $this->dao->select('`key`, value')->from(TABLE_LANG)
  10847. ->where('module')->eq('reviewcl')
  10848. ->andWhere('section')->eq('waterfallplusCategoryList')
  10849. ->andWhere('lang')->in(array($clientLang, 'all'))
  10850. ->orderBy('id_asc')
  10851. ->fetchPairs();
  10852. if(empty($categoryList) && empty($categoryPlusList)) return true;
  10853. $workflows = $this->dao->select('id,projectModel')->from(TABLE_WORKFLOWGROUP)->where('projectModel')->in('waterfall,waterfallplus')->fetchAll('id');
  10854. foreach($workflows as $workflow)
  10855. {
  10856. if($workflow->projectModel == 'waterfall' && !empty($categoryList))
  10857. {
  10858. foreach($categoryList as $key => $value) $this->custom->setItem("{$clientLang}.reviewcl.{$workflow->id}CategoryList.{$key}.1", $value);
  10859. }
  10860. if($workflow->projectModel == 'waterfallplus' && !empty($categoryPlusList))
  10861. {
  10862. foreach($categoryPlusList as $key => $value) $this->custom->setItem("{$clientLang}.reviewcl.{$workflow->id}CategoryList.{$key}.1", $value);
  10863. }
  10864. }
  10865. $this->dao->delete()->from(TABLE_LANG)->where('section')->in('waterfallCategoryList,waterfallplusCategoryList')->exec();
  10866. return true;
  10867. }
  10868. /**
  10869. * 升级项目评审对象到项目流程中。
  10870. * Upgrade baseline objects.
  10871. *
  10872. * @access public
  10873. * @return void
  10874. */
  10875. public function upgradeBaselineObjects()
  10876. {
  10877. $this->app->loadLang('baseline');
  10878. $clientLang = $this->app->getClientLang();
  10879. $objectList = $this->dao->select('`key`, value')->from(TABLE_LANG)
  10880. ->where('module')->eq('baseline')
  10881. ->andWhere('section')->eq('objectList')
  10882. ->andWhere('lang')->in(array($clientLang, 'all'))
  10883. ->orderBy('id_asc')
  10884. ->fetchPairs();
  10885. if(empty($objectList) && !empty($this->lang->baseline->objectList)) $objectList = $this->lang->baseline->objectList;
  10886. if(empty($objectList)) $objectList = $this->lang->upgrade->reviewObjectList;
  10887. $workflowGroups = $this->dao->select('id, projectModel, projectType')->from(TABLE_WORKFLOWGROUP)->where('type')->eq('project')->fetchAll('id');
  10888. $moduleGroup = $this->dao->select('id, root, extra')->from(TABLE_MODULE)
  10889. ->where('type')->eq('deliverable')
  10890. ->andWhere('root')->in(array_keys($workflowGroups))
  10891. ->fetchGroup('root', 'extra');
  10892. $reviewclList = $this->dao->select('*')->from(TABLE_REVIEWCL)->where('type')->ne('')->fetchGroup('type');
  10893. $activityList = $this->dao->select('t1.*')->from(TABLE_ACTIVITY)->alias('t1')->leftJoin(TABLE_PROCESS)->alias('t2')->on('t1.process=t2.id')
  10894. ->where('t1.name')->eq($this->lang->other)
  10895. ->andWhere('t2.name')->eq($this->lang->other)
  10896. ->fetchGroup('workflowGroup');
  10897. $actionIdList = array();
  10898. $reviewclActions = $this->dao->select('*')->from(TABLE_ACTION)->where('objectType')->eq('reviewcl')->fetchGroup('objectID', 'id');
  10899. foreach($reviewclActions as $actions) $actionIdList = arrayUnion($actionIdList, $actions);
  10900. $reviewclHistory = $this->dao->select('*')->from(TABLE_HISTORY)->where('action')->in(array_keys($actionIdList))->fetchGroup('action', 'id');
  10901. $deliverable = new stdClass();
  10902. $deliverable->status = 'enabled';
  10903. $deliverable->createdBy = 'system';
  10904. $deliverable->createdDate = helper::now();
  10905. $deliverable->trimmable = '1';
  10906. $deliverable->template = '[]';
  10907. $deliverableStage = new stdClass();
  10908. $deliverableStage->stage = 'project';
  10909. $deliverableStage->required = '0';
  10910. $reviewFlow = new stdclass();
  10911. $reviewFlow->flow = 1;
  10912. $reviewFlow->objectType = 'deliverable';
  10913. $reviewFlow->relatedBy = 'system';
  10914. $reviewFlow->relatedDate = helper::now();
  10915. $reviewFlow->extra = 'review';
  10916. $upgradeReviewcls = array();
  10917. $categoryModuleMap = array('PP' => 'plan', 'SRS' => 'story');
  10918. foreach($workflowGroups as $groupID => $workflowGroup)
  10919. {
  10920. $projectModel = $workflowGroup->projectModel;
  10921. if(empty($activityList[$groupID])) continue;
  10922. $otherActivity = reset($activityList[$groupID]);
  10923. $otherActivityID = $otherActivity->id;
  10924. $nameFilter = array();
  10925. $deliverable->workflowGroup = $groupID;
  10926. $deliverable->activity = $otherActivityID;
  10927. foreach(array_filter($objectList) as $key => $value)
  10928. {
  10929. if(empty($value)) continue;
  10930. if(in_array($key, array('HLDS', 'DDS', 'ADS', 'DBDS', 'ITTC', 'STTC', 'intergrate', 'system'))) continue; // 设计类型和测试类型的上面处理过了,跳过。
  10931. if($key == 'PP' && in_array($projectModel, array('scrum', 'agileplus'))) continue; // 敏捷、融合敏捷没有项目计划
  10932. if($key == 'SRS') $value = $this->lang->upgrade->reviewObjectList['SRS']; // 将软件需求规格说明书改成项目需求规格说明书
  10933. $deliverable->category = $key; // 标记交付物的类型。
  10934. $deliverable->systemList = (int)in_array($key, ['PP', 'SRS']);
  10935. /* 将原来的评审对象放到新交付物的模块下:计划类、需求类、设计类、测试类。其他类放到其他模块下。 */
  10936. $moduleKey = isset($categoryModuleMap[$key]) ? $categoryModuleMap[$key] : 'other';
  10937. $deliverable->module = isset($moduleGroup[$groupID][$moduleKey]) ? $moduleGroup[$groupID][$moduleKey]->id : 0;
  10938. $deliverableID = $this->addDeliverable((string)$value, $deliverable, $deliverableStage, $nameFilter);
  10939. $reviewFlow->root = $groupID;
  10940. $reviewFlow->objectID = $deliverableID;
  10941. $this->dao->insert(TABLE_APPROVALFLOWOBJECT)->data($reviewFlow)->exec();
  10942. $flowID = $this->dao->lastInsertID();
  10943. /* 迁移评审对象对应的检查清单。 */
  10944. if(empty($reviewclList[$projectModel])) continue;
  10945. foreach($reviewclList[$projectModel] as $reviewcl)
  10946. {
  10947. if($key != $reviewcl->object) continue;
  10948. $upgradeReviewcls[$reviewcl->id] = $reviewcl->id;
  10949. $reviewclData = clone $reviewcl;
  10950. unset($reviewclData->id);
  10951. unset($reviewclData->editedDate);
  10952. unset($reviewclData->assignedDate);
  10953. $reviewclData->workflowGroup = $groupID;
  10954. $reviewclData->object = $flowID;
  10955. $this->dao->insert(TABLE_REVIEWCL)->data($reviewclData)->exec();
  10956. $reviewclID = $this->dao->lastInsertID();
  10957. $this->dao->update(TABLE_REVIEWISSUE)->set('listID')->eq($reviewclID)->where('listID')->eq($reviewcl->id)->exec();
  10958. /* 迁移检查清单历史记录。 */
  10959. if(empty($reviewclActions[$reviewcl->id])) continue;
  10960. foreach($reviewclActions[$reviewcl->id] as $action)
  10961. {
  10962. $actionData = clone $action;
  10963. unset($actionData->id);
  10964. $actionData->objectID = $reviewclID;
  10965. $this->dao->insert(TABLE_ACTION)->data($actionData)->exec();
  10966. if(empty($reviewclHistory[$action->id])) continue;
  10967. $actionID = $this->dao->lastInsertID();
  10968. foreach($reviewclHistory[$action->id] as $history)
  10969. {
  10970. unset($history->id);
  10971. $history->action = $actionID;
  10972. $this->dao->insert(TABLE_HISTORY)->data($history)->exec();
  10973. }
  10974. }
  10975. }
  10976. }
  10977. }
  10978. /* 将已升级的历史检查清单数据删掉。 */
  10979. $this->dao->delete()->from(TABLE_REVIEWCL)->where('id')->in($upgradeReviewcls)->exec();
  10980. $this->dao->exec("ALTER TABLE " . TABLE_REVIEWCL . " CHANGE `object` `object` mediumint(8) unsigned NOT NULL DEFAULT '0';");
  10981. $this->dao->exec("ALTER TABLE " . TABLE_REVIEWCL . " DROP `type`;");
  10982. $this->dao->exec('ALTER TABLE ' . TABLE_PROCESS . ' DROP `model`');
  10983. $this->dao->exec('ALTER TABLE ' . TABLE_PROCESS . ' DROP `type`');
  10984. }
  10985. /**
  10986. * 升级项目评审对象到项目交付物。
  10987. * Upgrade review to deliverable.
  10988. *
  10989. * @access public
  10990. * @return void
  10991. */
  10992. public function upgradeReviewToDeliverable()
  10993. {
  10994. if(!in_array($this->config->edition, array('ipd', 'max'))) return; // 开源版、企业版没有评审功能。
  10995. $this->loadModel('review');
  10996. $this->loadModel('baseline');
  10997. $reviewPointList = $this->config->edition == 'ipd' ? $this->lang->review->reviewPoint->titleList : array();
  10998. $reviews = $this->dao->select('t1.*, t2.category, t2.version')->from(TABLE_REVIEW)->alias('t1')
  10999. ->leftJoin(TABLE_OBJECT)->alias('t2')->on('t1.object=t2.id')
  11000. ->where('t1.deleted')->eq('0')
  11001. ->beginIF($reviewPointList)->andWhere('t2.category')->notin(array_keys($reviewPointList))->fi() // IPD 模式下,只升级非评审点的评审。
  11002. ->orderBy('t1.id_asc')
  11003. ->fetchAll('id');
  11004. $projectDeliverables = $this->dao->select('t1.id, t2.id as deliverable, t2.category')->from(TABLE_PROJECT)->alias('t1')
  11005. ->leftJoin(TABLE_DELIVERABLE)->alias('t2')->on('t1.workflowGroup=t2.workflowGroup')
  11006. ->where('t2.category')->ne('')
  11007. ->fetchGroup('id', 'category');
  11008. $docIdList = array_column($reviews, 'doc');
  11009. $docIdList = array_filter($docIdList);
  11010. $docList = $this->dao->select('id, title')->from(TABLE_DOC)->where('id')->in($docIdList)->fetchPairs();
  11011. $projectMainLibPairs = $this->dao->select('project, id')->from(TABLE_DOCLIB)->where('main')->eq('1')->andWhere('type')->eq('project')->fetchPairs();
  11012. $fileGroup = $this->dao->select('id, objectID')->from(TABLE_FILE)->where('deleted')->eq('0')->andWhere('objectType')->eq('review')->fetchGroup('objectID', 'id');
  11013. $doc = new stdclass();
  11014. $doc->version = 1;
  11015. $doc->type = 'text';
  11016. $doc->acl = 'open';
  11017. $doc->addedBy = 'system';
  11018. $doc->addedDate = helper::now();
  11019. foreach($reviews as $review)
  11020. {
  11021. if(!isset($projectDeliverables[$review->project][$review->category])) continue;
  11022. $files = '';
  11023. if(!empty($fileGroup[$review->id]))
  11024. {
  11025. $fileIdList = array_keys($fileGroup[$review->id]);
  11026. $files = implode(',', $fileIdList);
  11027. }
  11028. /* 不是系统模板生成、也没选文档的,自动生成文档。 */
  11029. if(!$review->template && !$review->doc)
  11030. {
  11031. $doc->title = $review->title;
  11032. $doc->lib = zget($projectMainLibPairs, $review->project, 0);
  11033. $doc->project = $review->project;
  11034. $doc->addedBy = $review->createdBy;
  11035. $doc->addedDate = $review->createdDate;
  11036. $doc->version = 1;
  11037. $doc->type = $files ? 'attachment' : 'text';
  11038. $this->dao->insert(TABLE_DOC)->data($doc)->exec();
  11039. $review->doc = $this->dao->lastInsertID();
  11040. $review->docVersion = 1;
  11041. $docContent = new stdclass();
  11042. $docContent->type = 'doc';
  11043. $docContent->doc = $review->doc;
  11044. $docContent->title = $review->title;
  11045. $docContent->files = $files;
  11046. $docContent->version = 1;
  11047. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  11048. $this->dao->update(TABLE_FILE)->set('objectType')->eq('doc')->set('objectID')->eq($review->doc)->where('id')->in($files)->exec();
  11049. }
  11050. elseif($review->doc && $files)
  11051. {
  11052. /* 原来有文档也有附件,将附件关联到文档。 */
  11053. $maxVersion = $this->dao->select('max(version) as version')->from(TABLE_DOCCONTENT)->where('doc')->eq($review->doc)->fetch('version');
  11054. $docContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($review->doc)->andWhere('version')->eq($review->docVersion)->fetch();
  11055. unset($docContent->id);
  11056. $docContent->version = $maxVersion + 1;
  11057. $docContent->files = $files;
  11058. $review->docVersion = $docContent->version;
  11059. $this->dao->insert(TABLE_DOCCONTENT)->data($docContent)->exec();
  11060. $this->dao->update(TABLE_FILE)->set('objectType')->eq('doc')->set('objectID')->eq($review->doc)->where('id')->in($files)->exec();
  11061. $this->dao->update(TABLE_DOC)->set('version')->eq($review->docVersion)->where('id')->eq($review->doc)->exec();
  11062. $this->dao->update(TABLE_REVIEW)->set('docVersion')->eq($review->docVersion)->where('id')->eq($review->id)->exec();
  11063. }
  11064. $deliverable = new stdclass();
  11065. $deliverable->name = (!empty($docList[$review->doc]) && $docList[$review->doc] != $review->title) ? $docList[$review->doc] : $review->title; // 优先使用文档的标题
  11066. $deliverable->project = $review->project;
  11067. $deliverable->submitFrom = $review->project;
  11068. $deliverable->review = $review->id;
  11069. $deliverable->deliverable = $projectDeliverables[$review->project][$review->category]->deliverable;
  11070. $deliverable->doc = $review->doc;
  11071. $deliverable->docVersion = $review->docVersion;
  11072. $deliverable->createdBy = $review->createdBy;
  11073. $deliverable->createdDate = $review->createdDate;
  11074. $deliverable->status = $review->status;
  11075. $deliverable->version = $review->version;
  11076. $deliverableID = $this->dao->select('id')->from(TABLE_PROJECTDELIVERABLE)
  11077. ->where('project')->eq($deliverable->project)
  11078. ->andWhere('deliverable')->eq($deliverable->deliverable)
  11079. ->andWhere('doc')->eq($deliverable->doc)
  11080. ->fetch('id');
  11081. /* 保证交付物在项目下的唯一性,如果不存在,则插入。 */
  11082. if(!$deliverableID)
  11083. {
  11084. $this->dao->insert(TABLE_PROJECTDELIVERABLE)->data($deliverable)->exec();
  11085. $deliverableID = $this->dao->lastInsertID();
  11086. }
  11087. else
  11088. {
  11089. $this->dao->update(TABLE_PROJECTDELIVERABLE)->set('docVersion')->eq($deliverable->docVersion)->set('review')->eq($deliverable->review)->where('id')->eq($deliverableID)->exec();
  11090. }
  11091. $this->dao->update(TABLE_REVIEW)->set('deliverable')->eq($deliverableID)->set('title')->eq($deliverable->name)->where('id')->eq($review->id)->exec();
  11092. $this->dao->update(TABLE_OBJECT)->set('category')->eq($deliverable->deliverable)->where('id')->eq($review->object)->exec(); // 将评审对象改成交付物ID
  11093. /* 如果是系统模板类型、但没有选系统模板生成,则复制一份交付物,让用户升级上来后可以继续使用系统模板。 */
  11094. if(in_array($review->category, array('PP', 'SRS', 'HLDS', 'DDS', 'ADS', 'DBDS', 'intergrate', 'system')) && !$review->template)
  11095. {
  11096. $deliverable->name = zget($this->lang->upgrade->reviewObjectList, $review->category);
  11097. $deliverable->doc = 0;
  11098. $deliverable->docVersion = 0;
  11099. $deliverable->status = 'draft';
  11100. $deliverable->version = '';
  11101. $deliverable->review = 0;
  11102. $deliverableID = $this->dao->select('id')->from(TABLE_PROJECTDELIVERABLE)
  11103. ->where('project')->eq($deliverable->project)
  11104. ->andWhere('deliverable')->eq($deliverable->deliverable)
  11105. ->andWhere('doc')->eq($deliverable->doc)
  11106. ->fetch('id');
  11107. if(!$deliverableID) $this->dao->insert(TABLE_PROJECTDELIVERABLE)->data($deliverable)->exec();
  11108. }
  11109. }
  11110. $this->dao->exec('ALTER TABLE ' . TABLE_REVIEW . ' DROP `doc`');
  11111. }
  11112. /**
  11113. * 升级历史基线。
  11114. * Upgrade baseline.
  11115. *
  11116. * @access public
  11117. * @return void
  11118. */
  11119. public function upgradeBaseline()
  11120. {
  11121. $baselines = $this->dao->select('id,`from`')->from(TABLE_OBJECT)->where('type')->eq('taged')->fetchAll();
  11122. $reviews = $this->dao->select('id,object,deliverable')->from(TABLE_REVIEW)->where('status')->eq('pass')->fetchAll('object');
  11123. foreach($baselines as $baseline)
  11124. {
  11125. if(empty($reviews[$baseline->from])) continue;
  11126. $review = $reviews[$baseline->from];
  11127. $data = new stdclass();
  11128. $data->category = $review->deliverable;
  11129. $data->categoryVersion = json_encode(array($review->deliverable => $review->id));
  11130. $data->status = 'pass';
  11131. $this->dao->update(TABLE_OBJECT)->data($data)->where('id')->eq($baseline->id)->exec();
  11132. $this->dao->update(TABLE_REVIEW)->set('isBaseline')->eq('1')->where('id')->eq($review->id)->exec();
  11133. }
  11134. return true;
  11135. }
  11136. /**
  11137. * 新增交付物权限。
  11138. * Add deliverable privs for all groups.
  11139. *
  11140. * @access public
  11141. * @return void
  11142. */
  11143. public function addDeliverablePrivs()
  11144. {
  11145. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('project')->andWhere('method')->eq('deliverable')->fetchPairs();
  11146. $group = new stdclass();
  11147. $group->module = 'project';
  11148. foreach($groups as $groupID)
  11149. {
  11150. foreach(array('createDeliverable', 'deleteDeliverable', 'viewDeliverable') as $method)
  11151. {
  11152. $group->method = $method;
  11153. $group->group = $groupID;
  11154. $this->dao->insert(TABLE_GROUPPRIV)->data($group)->exec();
  11155. }
  11156. }
  11157. }
  11158. /**
  11159. * 升级开源版的阶段。
  11160. * Update stage for PMS.
  11161. *
  11162. * @access public
  11163. * @return void
  11164. */
  11165. public function upgradeStage4PMS()
  11166. {
  11167. if($this->config->edition != 'open') return true;
  11168. $stageGroup = $this->dao->select('*')->from(TABLE_STAGE)->where('workflowGroup')->eq(0)->fetchGroup('projectType', 'id');
  11169. if(empty($stageGroup)) return true;
  11170. $typeNamePairs = array();
  11171. $needDeleteStage = array();
  11172. foreach($stageGroup as $projectModel => $stageList)
  11173. {
  11174. if($projectModel == 'ipd') continue;
  11175. foreach($stageList as $stageInfo)
  11176. {
  11177. if(isset($typeNamePairs[$stageInfo->type]) && in_array($stageInfo->name, $typeNamePairs[$stageInfo->type]))
  11178. {
  11179. $needDeleteStage[$stageInfo->id] = $stageInfo->id;
  11180. continue;
  11181. }
  11182. if(!isset($typeNamePairs[$stageInfo->type])) $typeNamePairs[$stageInfo->type] = array();
  11183. $typeNamePairs[$stageInfo->type][] = $stageInfo->name;
  11184. }
  11185. }
  11186. if(!empty($needDeleteStage)) $this->dao->delete()->from(TABLE_STAGE)->where('id')->in($needDeleteStage)->exec();
  11187. return true;
  11188. }
  11189. /**
  11190. * 升级阶段列表及评审点。
  11191. * Update stage and point.
  11192. *
  11193. * @access public
  11194. * @return void
  11195. */
  11196. public function upgradeStageAndPoint()
  11197. {
  11198. $stageGroup = $this->dao->select('*')->from(TABLE_STAGE)->where('workflowGroup')->eq(0)->fetchGroup('projectType', 'id');
  11199. if(empty($stageGroup)) return true;
  11200. $this->app->loadConfig('project');
  11201. $this->app->loadConfig('stage');
  11202. $workflowList = $this->dao->select('projectModel, code, id')->from(TABLE_WORKFLOWGROUP)->where('projectModel')->in('waterfall,waterfallplus,ipd')->fetchAll('id');
  11203. foreach($workflowList as $flow)
  11204. {
  11205. $projectModel = $flow->projectModel;
  11206. if(!isset($stageGroup[$projectModel])) continue;
  11207. $flowCode = $flow->code;
  11208. foreach($stageGroup[$projectModel] as $stage)
  11209. {
  11210. if($flowCode == 'tpdproduct' && !in_array($stage->type, $this->config->project->categoryStages['TPD'])) continue;
  11211. if($flowCode == 'cbbproduct' && !in_array($stage->type, $this->config->project->categoryStages['CBB'])) continue;
  11212. if(in_array($flowCode, array('cpdproduct', 'cpdproject')) && !in_array($stage->type, $this->config->project->categoryStages['CPD'])) continue;
  11213. unset($stage->id);
  11214. $stage->workflowGroup = $flow->id;
  11215. if(empty($stage->editedDate)) $stage->editedDate = null;
  11216. $this->dao->insert(TABLE_STAGE)->data($stage)->exec();
  11217. $stageID = $this->dao->lastInsertID();
  11218. /* 添加IPD项目流程阶段的评审点。*/
  11219. if($projectModel == 'ipd')
  11220. {
  11221. $decision = new stdClass();
  11222. $decision->builtin = '1';
  11223. $decision->createdBy = 'system';
  11224. $decision->createdDate = helper::now();
  11225. $decisionFlow = new stdClass();
  11226. $decisionFlow->flow = 1;
  11227. $decisionFlow->objectType = 'decision';
  11228. $decisionFlow->relatedBy = 'system';
  11229. $decisionFlow->relatedDate = helper::now();
  11230. foreach($this->config->stage->ipdReviewPoint->{$stage->type} as $index => $point)
  11231. {
  11232. $decision->workflowGroup = $stage->workflowGroup;
  11233. $decision->stage = $stageID;
  11234. $decision->order = $index + 1;
  11235. $decision->title = $point;
  11236. $decision->type = strpos($point, 'TR') !== false ? 'TR' : 'DCP';
  11237. $decision->category = $point;
  11238. $this->dao->insert(TABLE_DECISION)->data($decision)->exec();
  11239. $decisionID = $this->dao->lastInsertID();
  11240. $decisionFlow->root = $stage->workflowGroup;
  11241. $decisionFlow->objectID = $decisionID;
  11242. $decisionFlow->extra = $decision->type;
  11243. $this->dao->insert(TABLE_APPROVALFLOWOBJECT)->data($decisionFlow)->exec();
  11244. }
  11245. }
  11246. }
  11247. }
  11248. return true;
  11249. }
  11250. /**
  11251. * 升级评审点评审。
  11252. * Upgrade object of decision.
  11253. *
  11254. * @access public
  11255. * @return void
  11256. */
  11257. public function upgradeObjectOfDecision()
  11258. {
  11259. $ipdProject = $this->dao->select('id,workflowGroup')->from(TABLE_PROJECT)->where('model')->eq('ipd')->andWhere('type')->eq('project')->fetchPairs('id');
  11260. if(empty($ipdProject)) return true;
  11261. $this->app->loadConfig('review');
  11262. $stagePointGroup = array();
  11263. $ipdProjectStage = $this->dao->select('id,project,attribute')->from(TABLE_PROJECT)->where('project')->in(array_keys($ipdProject))->andWhere('type')->eq('stage')->fetchGroup('project', 'attribute');
  11264. foreach($ipdProjectStage as $projectID => $stageList)
  11265. {
  11266. if(!isset($stagePointGroup[$projectID])) $stagePointGroup[$projectID] = array();
  11267. foreach($stageList as $attribute => $stage)
  11268. {
  11269. foreach($this->config->review->ipdReviewPoint->$attribute as $pointCategory)
  11270. {
  11271. $stagePointGroup[$projectID][$pointCategory] = $stage->id;
  11272. }
  11273. }
  11274. }
  11275. $decisionGroup = $this->dao->select('id,workflowGroup,category')->from(TABLE_DECISION)->fetchGroup('workflowGroup', 'category');
  11276. $ipdStagePoints = $this->dao->select('*')->from(TABLE_OBJECT)->where('project')->in(array_keys($ipdProject))->andWhere('category')->in($this->config->review->ipdPointOrder)->fetchAll();
  11277. foreach($ipdStagePoints as $point)
  11278. {
  11279. $category = $point->category;
  11280. $projectID = $point->project;
  11281. $workflowGroup = $ipdProject[$projectID];
  11282. $this->dao->update(TABLE_OBJECT)
  11283. ->set('type')->eq('decision')
  11284. ->set('categoryTitle')->eq($category)
  11285. ->set('execution')->eq(!empty($stagePointGroup[$projectID][$category]) ? $stagePointGroup[$projectID][$category] : 0)
  11286. ->set('category')->eq(!empty($decisionGroup[$workflowGroup][$category]->id) ? $decisionGroup[$workflowGroup][$category]->id : 0)
  11287. ->where('id')->eq($point->id)
  11288. ->exec();
  11289. }
  11290. return true;
  11291. }
  11292. /**
  11293. * 调整 21.7.5 版本的权限
  11294. * Adjust priv 21.7.5.
  11295. *
  11296. * @access public
  11297. * @return bool
  11298. */
  11299. public function adjustPriv21_7_5()
  11300. {
  11301. $weeklyMethods = array('browse', 'create', 'edit', 'delete', 'view', 'exportReport', 'manageCategroy');
  11302. $templateMethods = array('browse', 'create', 'edit', 'delete', 'view', 'pause', 'cron', 'addCategory', 'editCategory', 'deleteCategory');
  11303. $groups = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)->where('module')->eq('weekly')->fetchPairs('group', 'group');
  11304. $data = array();
  11305. foreach($groups as $groupID)
  11306. {
  11307. foreach($weeklyMethods as $method) $data[] = "('{$groupID}', 'weekly', '{$method}')";
  11308. $data[] = "('{$groupID}', 'milestone', 'index')";
  11309. $data[] = "('{$groupID}', 'milestone', 'saveOtherProblem')";
  11310. }
  11311. foreach($templateMethods as $method) $data[] = "('1', 'reporttemplate', '{$method}')";
  11312. foreach($weeklyMethods as $method) $data[] = "('1', 'weekly', '{$method}')";
  11313. $data[] = "('1', 'milestone', 'index')";
  11314. $data[] = "('1', 'milestone', 'saveOtherProblem')";
  11315. $sql = 'REPLACE INTO ' . TABLE_GROUPPRIV . '(`group`, `module`, `method`) VALUES ' . implode(',', $data);
  11316. $this->dao->exec($sql);
  11317. return true;
  11318. }
  11319. /**
  11320. * 四个项目模型的功能开关合并成一个。
  11321. * Merge four project model feature switches into one.
  11322. * 内置AI禅道智能体。
  11323. * Initialize the AI prompts.
  11324. *
  11325. * @access public
  11326. * @return bool
  11327. */
  11328. public function upgradeClosedFeature()
  11329. {
  11330. $disabledFeatures = $this->dao->select('value')->from(TABLE_CONFIG)->where('`key`')->eq('closedFeatures')->andWhere('owner')->eq('system')->fetch('value');
  11331. if(empty($disabledFeatures)) return true;
  11332. $disabledFeatures = explode(',', $disabledFeatures);
  11333. $countFeature = array();
  11334. foreach($this->config->featureGroup->project as $newFeature)
  11335. {
  11336. $countFeature[$newFeature] = 0;
  11337. foreach($disabledFeatures as $key => $feature)
  11338. {
  11339. $feature = strtolower(trim($feature));
  11340. if(strpos($feature, $newFeature) !== false)
  11341. {
  11342. $countFeature[$newFeature]++;
  11343. unset($disabledFeatures[$key]);
  11344. }
  11345. }
  11346. }
  11347. /* 之前在所有模型下都关闭的功能升级上来才关闭,否则开启。*/
  11348. $disabledFeatures = array_values($disabledFeatures);
  11349. foreach($countFeature as $feature => $count)
  11350. {
  11351. if($count == 4 || ($count == 2 && in_array($feature, array('gapanalysis', 'researchplan', 'track'))))
  11352. {
  11353. $disabledFeatures[] = 'project' . ucfirst($feature);
  11354. }
  11355. }
  11356. $disabledFeatures = implode(',', array_unique($disabledFeatures));
  11357. $this->dao->update(TABLE_CONFIG)->set('value')->eq($disabledFeatures)->where('`key`')->eq('closedFeatures')->andWhere('owner')->eq('system')->exec();
  11358. }
  11359. /**
  11360. * 之前object表中data字段直接存入URL,如果改变请求方式则无法使用,现在升级为模块名、方法名、参数的形式。
  11361. * Before the object table directly stores the URL in the data field, if the request method changes, it cannot be used, now upgrade to the form of module name, method name, and parameters.
  11362. *
  11363. * @access public
  11364. * @return bool
  11365. */
  11366. public function parseDocFetcherURL()
  11367. {
  11368. $objects = $this->dao->select('id,data')->from(TABLE_OBJECT)->where('data')->like('%tml-zentaolist%')->fetchPairs();
  11369. $cleanValue = function($value)
  11370. {
  11371. if(preg_match('/__(\w+)__([0-9]+)/', $value, $m)) return $m[2];
  11372. return $value;
  11373. };
  11374. $parsePathInfoUrl = function($url) use ($cleanValue)
  11375. {
  11376. $url = ltrim($url, '/');
  11377. $url = preg_replace('/\.html$/', '', $url);
  11378. $parts = explode('-', $url);
  11379. array_shift($parts); // 删掉模块名
  11380. array_shift($parts); // 删掉方法名
  11381. $params = [];
  11382. foreach($parts as $index => $value)
  11383. {
  11384. $index ++;
  11385. $key = "param{$index}";
  11386. $value = $cleanValue($value);
  11387. $params[$key] = $value;
  11388. }
  11389. return $params;
  11390. };
  11391. $parseGetUrl = function($url) use ($cleanValue)
  11392. {
  11393. $parts = parse_url($url);
  11394. parse_str($parts['query'] ?? '', $query);
  11395. unset($query['m'], $query['f']);
  11396. $params = [];
  11397. $index = 1;
  11398. foreach($query as $key => $value)
  11399. {
  11400. $key = 'param' . $index;
  11401. $value = $cleanValue($value);
  11402. $params[$key] = $value;
  11403. $index++;
  11404. }
  11405. return $params;
  11406. };
  11407. foreach($objects as $id => $data)
  11408. {
  11409. $data = json_decode($data, true);
  11410. $html = $data['$data'];
  11411. preg_match('/data-export-url=\'([^\']+)\'/', $html, $exportMatch);
  11412. preg_match('/data-fetcher=\'([^\']+)\'/', $html, $fetcherMatch);
  11413. $exportUrl = $exportMatch[1] ?? null;
  11414. $fetcherUrl = $fetcherMatch[1] ?? null;
  11415. if(!$exportUrl && !$fetcherUrl) continue;
  11416. if(strpos($exportUrl, '?') !== false)
  11417. {
  11418. $data['exportParams'] = $parseGetUrl($exportUrl);
  11419. }
  11420. else
  11421. {
  11422. $data['exportParams'] = $parsePathInfoUrl($exportUrl);
  11423. }
  11424. if(strpos($fetcherUrl, '?') !== false)
  11425. {
  11426. $data['fetcherParams'] = $parseGetUrl($fetcherUrl);
  11427. }
  11428. else
  11429. {
  11430. $data['fetcherParams'] = $parsePathInfoUrl($fetcherUrl);
  11431. }
  11432. $data['$data'] = str_replace($exportMatch[1], '', $data['$data']);
  11433. $data['$data'] = str_replace($fetcherMatch[1], '', $data['$data']);
  11434. $data = json_encode($data);
  11435. $this->dao->update(TABLE_OBJECT)->set('data')->eq($data)->where('id')->eq($id)->exec();
  11436. }
  11437. }
  11438. /**
  11439. * 处理升级新增的工作流数据源。
  11440. * Process workflow datasource.
  11441. *
  11442. * @param array $codeList
  11443. * @access public
  11444. * @return void
  11445. */
  11446. public function processWorkflowDatasource($codeList)
  11447. {
  11448. $datasources = $this->dao->select('id,code')->from(TABLE_WORKFLOWDATASOURCE)->where('code')->in(array_keys($codeList))->fetchAll();
  11449. foreach($datasources as $datasource)
  11450. {
  11451. list($module, $field) = explode('_', $codeList[$datasource->code]);
  11452. $this->dao->update(TABLE_WORKFLOWFIELD)->set('options')->eq($datasource->id)->where('module')->eq($module)->andWhere('field')->eq($field)->exec();
  11453. }
  11454. }
  11455. /**
  11456. * 内置AI禅道智能体。
  11457. * Initialize the AI prompts.
  11458. *
  11459. * @access public
  11460. * @return bool
  11461. */
  11462. public function initAIPrompts()
  11463. {
  11464. $this->app->loadConfig('ai');
  11465. foreach($this->config->ai->initAIPrompts as $aiPrompt)
  11466. {
  11467. if(!$this->checkExistAIPrompt($aiPrompt)) return;
  11468. $index = $aiPrompt->id;
  11469. unset($aiPrompt->id);
  11470. $aiPrompt->createdBy = 'system';
  11471. $aiPrompt->createdDate = helper::now();
  11472. $this->dao->insert(TABLE_AI_AGENT)->data($aiPrompt)->autoCheck()->exec();
  11473. $promptID = $this->dao->lastInsertID();
  11474. if(!empty($this->config->ai->initAIPromptFields[$index])) $this->initAIPromptFields($this->config->ai->initAIPromptFields[$index], $promptID);
  11475. }
  11476. }
  11477. /**
  11478. * 检查AI禅道智能体唯一性。
  11479. * Check AI zentao agent unique.
  11480. *
  11481. * @param object $aiPrompt
  11482. * @access public
  11483. * @return bool
  11484. */
  11485. public function checkExistAIPrompt($aiPrompt)
  11486. {
  11487. $count = $this->dao->select('COUNT(1) AS count')->from(TABLE_AI_AGENT)
  11488. ->where('name')->eq($aiPrompt->name)
  11489. ->fetch('count');
  11490. return $count == 0;
  11491. }
  11492. /**
  11493. * 修改用户表的last字段类型。
  11494. * Alter user table field last type.
  11495. *
  11496. * @access public
  11497. * @return bool
  11498. */
  11499. public function alterUserTableFields()
  11500. {
  11501. $tableDesc = $this->dao->descTable(TABLE_USER);
  11502. if(isset($tableDesc['last']) && stripos($tableDesc['last']->type, 'datetime') !== false) return true;
  11503. $sql = 'ALTER TABLE ' . TABLE_USER . ' ADD `last_tmp` datetime DEFAULT NULL AFTER `last`';
  11504. $this->dao->exec($sql);
  11505. $users = $this->dao->select('id, last')->from(TABLE_USER)->where('last')->ne(0)->fetchPairs();
  11506. foreach($users as $id => $last)
  11507. {
  11508. $last = date('Y-m-d H:i:s', $last);
  11509. $this->dao->update(TABLE_USER)->set('last_tmp')->eq($last)->where('id')->eq($id)->exec();
  11510. }
  11511. $sql = 'ALTER TABLE ' . TABLE_USER . ' DROP `last`';
  11512. $this->dao->exec($sql);
  11513. $sql = 'ALTER TABLE ' . TABLE_USER . ' CHANGE COLUMN `last_tmp` `last` datetime DEFAULT NULL';
  11514. $this->dao->exec($sql);
  11515. return !dao::isError();
  11516. }
  11517. /**
  11518. * 内置AI禅道智能体和相关字段。
  11519. * Initialize the AI prompts and fields.
  11520. *
  11521. * @param array $aiPromptFields
  11522. * @param int $promptID
  11523. * @access public
  11524. * @return bool
  11525. */
  11526. public function initAIPromptFields($aiPromptFields, $promptID)
  11527. {
  11528. foreach($aiPromptFields as $field)
  11529. {
  11530. $field->appID = $promptID;
  11531. $this->dao->insert(TABLE_AI_AGENTFIELD)->data($field, 'id')->exec();
  11532. }
  11533. }
  11534. /*
  11535. * 升级项目流程相关的权限。
  11536. * Update the priv of workflow group.
  11537. *
  11538. * @access public
  11539. * @return bool
  11540. */
  11541. public function updateWorkflowGroupPriv()
  11542. {
  11543. $designFlowGroupIdList = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  11544. ->where('module')->eq('workflowfield')
  11545. ->andWhere('method')->eq('browse')
  11546. ->fetchPairs();
  11547. foreach($designFlowGroupIdList as $groupID) $this->dao->insert(TABLE_GROUPPRIV)->set('group')->eq($groupID)->set('module')->eq('workflowgroup')->set('method')->eq('designFlow')->exec();
  11548. $flowReportGroupIdList = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  11549. ->where('module')->eq('workflowgroup')
  11550. ->andWhere('method')->eq('design')
  11551. ->fetchPairs();
  11552. foreach($flowReportGroupIdList as $groupID) $this->dao->insert(TABLE_GROUPPRIV)->set('group')->eq($groupID)->set('module')->eq('workflowgroup')->set('method')->eq('report')->exec();
  11553. $setDelieverableGroupIdList = $this->dao->select('`group`')->from(TABLE_GROUPPRIV)
  11554. ->where('module')->eq('workflowgroup')
  11555. ->andWhere('method')->eq('deliverable')
  11556. ->fetchPairs();
  11557. foreach($setDelieverableGroupIdList as $groupID)
  11558. {
  11559. $this->dao->insert(TABLE_GROUPPRIV)->set('group')->eq($groupID)->set('module')->eq('deliverable')->set('method')->eq('enable')->exec();
  11560. $this->dao->insert(TABLE_GROUPPRIV)->set('group')->eq($groupID)->set('module')->eq('deliverable')->set('method')->eq('disable')->exec();
  11561. }
  11562. $this->dao->delete()->from(TABLE_GROUPPRIV)->where('module')->eq('workflowgroup')->andWhere('method')->eq('deliverable')->exec();
  11563. return true;
  11564. }
  11565. /**
  11566. * 处理报告模板中内置的适用流程。
  11567. * Upgrade report template objects.
  11568. *
  11569. * @access public
  11570. * @return bool
  11571. */
  11572. public function upgradeReportTemplateObjects()
  11573. {
  11574. $templateList = $this->dao->select('id,objects')->from(TABLE_DOC)->where('templateType')->eq('reportTemplate')->fetchPairs('id');
  11575. $workflowList = $this->dao->select('id,projectModel,projectType')->from(TABLE_WORKFLOWGROUP)->where('main')->eq('1')->fetchAll('id');
  11576. $flowGroup = array();
  11577. foreach($workflowList as $flowID => $flow)
  11578. {
  11579. if(!isset($flowGroup[$flow->projectModel])) $flowGroup[$flow->projectModel] = array();
  11580. if(!isset($flowGroup[$flow->projectModel][$flow->projectType])) $flowGroup[$flow->projectModel][$flow->projectType] = array();
  11581. $flowGroup[$flow->projectModel][$flow->projectType][] = $flowID;
  11582. }
  11583. foreach($templateList as $templateID => $objects)
  11584. {
  11585. $newObjects = array();
  11586. $objectIdList = array_filter(explode(',', $objects));
  11587. foreach($objectIdList as $objectID)
  11588. {
  11589. $newObjects[] = $objectID;
  11590. if(!isset($workflowList[$objectID])) continue;
  11591. $projectModel = $workflowList[$objectID]->projectModel;
  11592. $projectType = $workflowList[$objectID]->projectType;
  11593. if($projectModel == 'scrum' && $projectType == 'product') $newObjects = array_merge($newObjects, $flowGroup['agileplus']['product']);
  11594. if($projectModel == 'scrum' && $projectType == 'project') $newObjects = array_merge($newObjects, $flowGroup['agileplus']['project']);
  11595. if($projectModel == 'waterfall' && $projectType == 'product') $newObjects = array_merge($newObjects, $flowGroup['waterfallplus']['product'], $flowGroup['ipd']['ipd'], $flowGroup['ipd']['tpd'], $flowGroup['ipd']['cbb'], $flowGroup['ipd']['cpdproduct']);
  11596. if($projectModel == 'waterfall' && $projectType == 'project') $newObjects = array_merge($newObjects, $flowGroup['waterfallplus']['project'], $flowGroup['ipd']['cpdproject']);
  11597. }
  11598. if(!empty($newObjects))
  11599. {
  11600. $newObjectList = implode(',', array_filter(array_unique($newObjects)));
  11601. $this->dao->update(TABLE_DOC)->set('objects')->eq($newObjectList)->where('id')->eq($templateID)->exec();
  11602. }
  11603. }
  11604. return true;
  11605. }
  11606. /**
  11607. * 从轻量模式升级后,禁用新增的功能。
  11608. * Disable new features after upgrading from light mode.
  11609. *
  11610. * @access public
  11611. * @return bool
  11612. */
  11613. public function disableFeaturesByMode()
  11614. {
  11615. if($this->config->systemMode != 'light') return true;
  11616. if($this->config->edition == 'ipd')
  11617. {
  11618. $this->loadModel('custom')->disableFeaturesByMode('PLM');
  11619. }
  11620. else
  11621. {
  11622. $this->loadModel('custom')->disableFeaturesByMode('light');
  11623. }
  11624. return true;
  11625. }
  11626. }