attributedef.class.inc.php 182 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522
  1. <?php
  2. // Copyright (C) 2010-2016 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Typology for the attributes
  20. *
  21. * @copyright Copyright (C) 2010-2016 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. require_once('MyHelpers.class.inc.php');
  25. require_once('ormdocument.class.inc.php');
  26. require_once('ormstopwatch.class.inc.php');
  27. require_once('ormpassword.class.inc.php');
  28. require_once('ormcaselog.class.inc.php');
  29. require_once('htmlsanitizer.class.inc.php');
  30. require_once(APPROOT.'sources/autoload.php');
  31. require_once('customfieldshandler.class.inc.php');
  32. require_once('ormcustomfieldsvalue.class.inc.php');
  33. /**
  34. * MissingColumnException - sent if an attribute is being created but the column is missing in the row
  35. *
  36. * @package iTopORM
  37. */
  38. class MissingColumnException extends Exception
  39. {}
  40. /**
  41. * add some description here...
  42. *
  43. * @package iTopORM
  44. */
  45. define('EXTKEY_RELATIVE', 1);
  46. /**
  47. * add some description here...
  48. *
  49. * @package iTopORM
  50. */
  51. define('EXTKEY_ABSOLUTE', 2);
  52. /**
  53. * Propagation of the deletion through an external key - ask the user to delete the referencing object
  54. *
  55. * @package iTopORM
  56. */
  57. define('DEL_MANUAL', 1);
  58. /**
  59. * Propagation of the deletion through an external key - ask the user to delete the referencing object
  60. *
  61. * @package iTopORM
  62. */
  63. define('DEL_AUTO', 2);
  64. /**
  65. * Fully silent delete... not yet implemented
  66. */
  67. define('DEL_SILENT', 2);
  68. /**
  69. * For HierarchicalKeys only: move all the children up one level automatically
  70. */
  71. define('DEL_MOVEUP', 3);
  72. /**
  73. * For Link sets: tracking_level
  74. *
  75. * @package iTopORM
  76. */
  77. define('ATTRIBUTE_TRACKING_NONE', 0); // Do not track changes of the attribute
  78. define('ATTRIBUTE_TRACKING_ALL', 3); // Do track all changes of the attribute
  79. define('LINKSET_TRACKING_NONE', 0); // Do not track changes in the link set
  80. define('LINKSET_TRACKING_LIST', 1); // Do track added/removed items
  81. define('LINKSET_TRACKING_DETAILS', 2); // Do track modified items
  82. define('LINKSET_TRACKING_ALL', 3); // Do track added/removed/modified items
  83. define('LINKSET_EDITMODE_NONE', 0); // The linkset cannot be edited at all from inside this object
  84. define('LINKSET_EDITMODE_ADDONLY', 1); // The only possible action is to open a new window to create a new object
  85. define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
  86. define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
  87. define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
  88. /**
  89. * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
  90. *
  91. * @package iTopORM
  92. */
  93. abstract class AttributeDefinition
  94. {
  95. public function GetType()
  96. {
  97. return Dict::S('Core:'.get_class($this));
  98. }
  99. public function GetTypeDesc()
  100. {
  101. return Dict::S('Core:'.get_class($this).'+');
  102. }
  103. abstract public function GetEditClass();
  104. protected $m_sCode;
  105. private $m_aParams = array();
  106. protected $m_sHostClass = '!undefined!';
  107. public function Get($sParamName) {return $this->m_aParams[$sParamName];}
  108. protected function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));}
  109. protected function GetOptional($sParamName, $default)
  110. {
  111. if (array_key_exists($sParamName, $this->m_aParams))
  112. {
  113. return $this->m_aParams[$sParamName];
  114. }
  115. else
  116. {
  117. return $default;
  118. }
  119. }
  120. public function __construct($sCode, $aParams)
  121. {
  122. $this->m_sCode = $sCode;
  123. $this->m_aParams = $aParams;
  124. $this->ConsistencyCheck();
  125. }
  126. public function GetParams()
  127. {
  128. return $this->m_aParams;
  129. }
  130. public function SetHostClass($sHostClass)
  131. {
  132. $this->m_sHostClass = $sHostClass;
  133. }
  134. public function GetHostClass()
  135. {
  136. return $this->m_sHostClass;
  137. }
  138. public function ListSubItems()
  139. {
  140. $aSubItems = array();
  141. foreach(MetaModel::ListAttributeDefs($this->m_sHostClass) as $sAttCode => $oAttDef)
  142. {
  143. if ($oAttDef instanceof AttributeSubItem)
  144. {
  145. if ($oAttDef->Get('target_attcode') == $this->m_sCode)
  146. {
  147. $aSubItems[$sAttCode] = $oAttDef;
  148. }
  149. }
  150. }
  151. return $aSubItems;
  152. }
  153. // Note: I could factorize this code with the parameter management made for the AttributeDef class
  154. // to be overloaded
  155. static public function ListExpectedParams()
  156. {
  157. return array();
  158. }
  159. private function ConsistencyCheck()
  160. {
  161. // Check that any mandatory param has been specified
  162. //
  163. $aExpectedParams = $this->ListExpectedParams();
  164. foreach($aExpectedParams as $sParamName)
  165. {
  166. if (!array_key_exists($sParamName, $this->m_aParams))
  167. {
  168. $aBacktrace = debug_backtrace();
  169. $sTargetClass = $aBacktrace[2]["class"];
  170. $sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"];
  171. throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)");
  172. }
  173. }
  174. }
  175. /**
  176. * Check the validity of the given value
  177. * @param DBObject $oHostObject
  178. * @param string An error if any, null otherwise
  179. */
  180. public function CheckValue(DBObject $oHostObject, $value)
  181. {
  182. // todo: factorize here the cases implemented into DBObject
  183. return true;
  184. }
  185. // table, key field, name field
  186. public function ListDBJoins()
  187. {
  188. return "";
  189. // e.g: return array("Site", "infrid", "name");
  190. }
  191. public function GetFinalAttDef()
  192. {
  193. return $this;
  194. }
  195. public function IsDirectField() {return false;}
  196. public function IsScalar() {return false;}
  197. public function IsLinkSet() {return false;}
  198. public function IsExternalKey($iType = EXTKEY_RELATIVE) {return false;}
  199. public function IsHierarchicalKey() {return false;}
  200. public function IsExternalField() {return false;}
  201. public function IsWritable() {return false;}
  202. public function LoadInObject() {return true;}
  203. public function LoadFromDB() {return true;}
  204. public function AlwaysLoadInTables() {return $this->GetOptional('always_load_in_tables', false);}
  205. public function GetValue($oHostObject, $bOriginal = false){return null;} // must return the value if LoadInObject returns false
  206. public function IsNullAllowed() {return true;}
  207. public function GetCode() {return $this->m_sCode;}
  208. public function GetMirrorLinkAttribute() {return null;}
  209. /**
  210. * Helper to browse the hierarchy of classes, searching for a label
  211. */
  212. protected function SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly)
  213. {
  214. $sLabel = Dict::S('Class:'.$this->m_sHostClass.$sDictEntrySuffix, '', $bUserLanguageOnly);
  215. if (strlen($sLabel) == 0)
  216. {
  217. // Nothing found: go higher in the hierarchy (if possible)
  218. //
  219. $sLabel = $sDefault;
  220. $sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
  221. if ($sParentClass)
  222. {
  223. if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
  224. {
  225. $oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
  226. $sLabel = $oAttDef->SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly);
  227. }
  228. }
  229. }
  230. return $sLabel;
  231. }
  232. public function GetLabel($sDefault = null)
  233. {
  234. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, null, true /*user lang*/);
  235. if (is_null($sLabel))
  236. {
  237. // If no default value is specified, let's define the most relevant one for developping purposes
  238. if (is_null($sDefault))
  239. {
  240. $sDefault = str_replace('_', ' ', $this->m_sCode);
  241. }
  242. // Browse the hierarchy again, accepting default (english) translations
  243. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, $sDefault, false);
  244. }
  245. return $sLabel;
  246. }
  247. /**
  248. * Get the label corresponding to the given value (in plain text)
  249. * To be overloaded for localized enums
  250. */
  251. public function GetValueLabel($sValue)
  252. {
  253. return $sValue;
  254. }
  255. /**
  256. * Get the value from a given string (plain text, CSV import)
  257. * Return null if no match could be found
  258. */
  259. public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
  260. {
  261. return $this->MakeRealValue($sProposedValue, null);
  262. }
  263. public function GetLabel_Obsolete()
  264. {
  265. // Written for compatibility with a data model written prior to version 0.9.1
  266. if (array_key_exists('label', $this->m_aParams))
  267. {
  268. return $this->m_aParams['label'];
  269. }
  270. else
  271. {
  272. return $this->GetLabel();
  273. }
  274. }
  275. public function GetDescription($sDefault = null)
  276. {
  277. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', null, true /*user lang*/);
  278. if (is_null($sLabel))
  279. {
  280. // If no default value is specified, let's define the most relevant one for developping purposes
  281. if (is_null($sDefault))
  282. {
  283. $sDefault = '';
  284. }
  285. // Browse the hierarchy again, accepting default (english) translations
  286. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', $sDefault, false);
  287. }
  288. return $sLabel;
  289. }
  290. public function GetHelpOnEdition($sDefault = null)
  291. {
  292. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', null, true /*user lang*/);
  293. if (is_null($sLabel))
  294. {
  295. // If no default value is specified, let's define the most relevant one for developping purposes
  296. if (is_null($sDefault))
  297. {
  298. $sDefault = '';
  299. }
  300. // Browse the hierarchy again, accepting default (english) translations
  301. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', $sDefault, false);
  302. }
  303. return $sLabel;
  304. }
  305. public function GetHelpOnSmartSearch()
  306. {
  307. $aParents = array_merge(array(get_class($this) => get_class($this)), class_parents($this));
  308. foreach ($aParents as $sClass)
  309. {
  310. $sHelp = Dict::S("Core:$sClass?SmartSearch", '-missing-');
  311. if ($sHelp != '-missing-')
  312. {
  313. return $sHelp;
  314. }
  315. }
  316. return '';
  317. }
  318. public function GetDescription_Obsolete()
  319. {
  320. // Written for compatibility with a data model written prior to version 0.9.1
  321. if (array_key_exists('description', $this->m_aParams))
  322. {
  323. return $this->m_aParams['description'];
  324. }
  325. else
  326. {
  327. return $this->GetDescription();
  328. }
  329. }
  330. public function GetTrackingLevel()
  331. {
  332. return $this->GetOptional('tracking_level', ATTRIBUTE_TRACKING_ALL);
  333. }
  334. public function GetValuesDef() {return null;}
  335. public function GetPrerequisiteAttributes($sClass = null) {return array();}
  336. public function GetNullValue() {return null;}
  337. public function IsNull($proposedValue) {return is_null($proposedValue);}
  338. public function MakeRealValue($proposedValue, $oHostObj) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!)
  339. public function Equals($val1, $val2) {return ($val1 == $val2);}
  340. public function GetSQLExpressions($sPrefix = '') {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select)
  341. public function FromSQLToValue($aCols, $sPrefix = '') {return null;} // returns a value out of suffix/value pairs, for SELECT result interpretation
  342. public function GetSQLColumns($bFullSpec = false) {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation)
  343. public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update)
  344. public function RequiresIndex() {return false;}
  345. public function GetOrderBySQLExpressions($sClassAlias)
  346. {
  347. // Note: This is the responsibility of this function to place backticks around column aliases
  348. return array('`'.$sClassAlias.$this->GetCode().'`');
  349. }
  350. public function GetOrderByHint()
  351. {
  352. return '';
  353. }
  354. // Import - differs slightly from SQL input, but identical in most cases
  355. //
  356. public function GetImportColumns() {return $this->GetSQLColumns();}
  357. public function FromImportToValue($aCols, $sPrefix = '')
  358. {
  359. $aValues = array();
  360. foreach ($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr)
  361. {
  362. // This is working, based on the assumption that importable fields
  363. // are not computed fields => the expression is the name of a column
  364. $aValues[$sPrefix.$sAlias] = $aCols[$sExpr];
  365. }
  366. return $this->FromSQLToValue($aValues, $sPrefix);
  367. }
  368. public function GetValidationPattern()
  369. {
  370. return '';
  371. }
  372. public function CheckFormat($value)
  373. {
  374. return true;
  375. }
  376. public function GetMaxSize()
  377. {
  378. return null;
  379. }
  380. public function MakeValue()
  381. {
  382. $sComputeFunc = $this->Get("compute_func");
  383. if (empty($sComputeFunc)) return null;
  384. return call_user_func($sComputeFunc);
  385. }
  386. abstract public function GetDefaultValue(DBObject $oHostObject = null);
  387. //
  388. // To be overloaded in subclasses
  389. //
  390. abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description"
  391. abstract public function GetBasicFilterLooseOperator(); // returns an "opCode"
  392. //abstract protected GetBasicFilterHTMLInput();
  393. abstract public function GetBasicFilterSQLExpr($sOpCode, $value);
  394. public function GetFilterDefinitions()
  395. {
  396. return array();
  397. }
  398. public function GetEditValue($sValue, $oHostObj = null)
  399. {
  400. return (string)$sValue;
  401. }
  402. /**
  403. * For fields containing a potential markup, return the value without this markup
  404. * @return string
  405. */
  406. public function GetAsPlainText($sValue, $oHostObj = null)
  407. {
  408. return (string) $this->GetEditValue($sValue, $oHostObj);
  409. }
  410. /**
  411. * Helper to get a value that will be JSON encoded
  412. * The operation is the opposite to FromJSONToValue
  413. */
  414. public function GetForJSON($value)
  415. {
  416. // In most of the cases, that will be the expected behavior...
  417. return $this->GetEditValue($value);
  418. }
  419. /**
  420. * Helper to form a value, given JSON decoded data
  421. * The operation is the opposite to GetForJSON
  422. */
  423. public function FromJSONToValue($json)
  424. {
  425. // Passthrough in most of the cases
  426. return $json;
  427. }
  428. /**
  429. * Override to display the value in the GUI
  430. */
  431. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  432. {
  433. return Str::pure2html((string)$sValue);
  434. }
  435. /**
  436. * Override to export the value in XML
  437. */
  438. public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
  439. {
  440. return Str::pure2xml((string)$sValue);
  441. }
  442. /**
  443. * Override to escape the value when read by DBObject::GetAsCSV()
  444. */
  445. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  446. {
  447. return (string)$sValue;
  448. }
  449. /**
  450. * Override to differentiate a value displayed in the UI or in the history
  451. */
  452. public function GetAsHTMLForHistory($sValue, $oHostObject = null, $bLocalize = true)
  453. {
  454. return $this->GetAsHTML($sValue, $oHostObject, $bLocalize);
  455. }
  456. /**
  457. * List the available verbs for 'GetForTemplate'
  458. */
  459. public function EnumTemplateVerbs()
  460. {
  461. return array(
  462. '' => 'Plain text (unlocalized) representation',
  463. 'html' => 'HTML representation',
  464. 'label' => 'Localized representation',
  465. 'text' => 'Plain text representation (without any markup)',
  466. );
  467. }
  468. /**
  469. * Get various representations of the value, for insertion into a template (e.g. in Notifications)
  470. * @param $value mixed The current value of the field
  471. * @param $sVerb string The verb specifying the representation of the value
  472. * @param $oHostObject DBObject The object
  473. * @param $bLocalize bool Whether or not to localize the value
  474. */
  475. public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
  476. {
  477. if ($this->IsScalar())
  478. {
  479. switch ($sVerb)
  480. {
  481. case '':
  482. return $value;
  483. case 'html':
  484. return $this->GetAsHtml($value, $oHostObject, $bLocalize);
  485. case 'label':
  486. return $this->GetEditValue($value);
  487. case 'text':
  488. return $this->GetAsPlainText($value);
  489. break;
  490. default:
  491. throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
  492. }
  493. }
  494. return null;
  495. }
  496. public function GetAllowedValues($aArgs = array(), $sContains = '')
  497. {
  498. $oValSetDef = $this->GetValuesDef();
  499. if (!$oValSetDef) return null;
  500. return $oValSetDef->GetValues($aArgs, $sContains);
  501. }
  502. /**
  503. * Explain the change of the attribute (history)
  504. */
  505. public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
  506. {
  507. if (is_null($sLabel))
  508. {
  509. $sLabel = $this->GetLabel();
  510. }
  511. $sNewValueHtml = $this->GetAsHTMLForHistory($sNewValue);
  512. $sOldValueHtml = $this->GetAsHTMLForHistory($sOldValue);
  513. if($this->IsExternalKey())
  514. {
  515. $sTargetClass = $this->GetTargetClass();
  516. $sOldValueHtml = (int)$sOldValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sOldValue) : null;
  517. $sNewValueHtml = (int)$sNewValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sNewValue) : null;
  518. }
  519. if ( (($this->GetType() == 'String') || ($this->GetType() == 'Text')) &&
  520. (strlen($sNewValue) > strlen($sOldValue)) )
  521. {
  522. // Check if some text was not appended to the field
  523. if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end
  524. {
  525. $sDelta = $this->GetAsHTML(substr($sNewValue, strlen($sOldValue)));
  526. $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel);
  527. }
  528. else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning
  529. {
  530. $sDelta = $this->GetAsHTML(substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue)));
  531. $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel);
  532. }
  533. else
  534. {
  535. if (strlen($sOldValue) == 0)
  536. {
  537. $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml);
  538. }
  539. else
  540. {
  541. if (is_null($sNewValue))
  542. {
  543. $sNewValueHtml = Dict::S('UI:UndefinedObject');
  544. }
  545. $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml);
  546. }
  547. }
  548. }
  549. else
  550. {
  551. if (strlen($sOldValue) == 0)
  552. {
  553. $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml);
  554. }
  555. else
  556. {
  557. if (is_null($sNewValue))
  558. {
  559. $sNewValueHtml = Dict::S('UI:UndefinedObject');
  560. }
  561. $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml);
  562. }
  563. }
  564. return $sResult;
  565. }
  566. /**
  567. * Parses a string to find some smart search patterns and build the corresponding search/OQL condition
  568. * Each derived class is reponsible for defining and processing their own smart patterns, the base class
  569. * does nothing special, and just calls the default (loose) operator
  570. * @param string $sSearchText The search string to analyze for smart patterns
  571. * @param FieldExpression The FieldExpression representing the atttribute code in this OQL query
  572. * @param Hash $aParams Values of the query parameters
  573. * @return Expression The search condition to be added (AND) to the current search
  574. */
  575. public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams)
  576. {
  577. $sParamName = $oField->GetParent().'_'.$oField->GetName();
  578. $oRightExpr = new VariableExpression($sParamName);
  579. $sOperator = $this->GetBasicFilterLooseOperator();
  580. switch ($sOperator)
  581. {
  582. case 'Contains':
  583. $aParams[$sParamName] = "%$sSearchText%";
  584. $sSQLOperator = 'LIKE';
  585. break;
  586. default:
  587. $sSQLOperator = $sOperator;
  588. $aParams[$sParamName] = $sSearchText;
  589. }
  590. $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr);
  591. return $oNewCondition;
  592. }
  593. /**
  594. * Tells if an attribute is part of the unique fingerprint of the object (used for comparing two objects)
  595. * All attributes which value is not based on a value from the object itself (like ExternalFields or LinkedSet)
  596. * must be excluded from the object's signature
  597. * @return boolean
  598. */
  599. public function IsPartOfFingerprint()
  600. {
  601. return true;
  602. }
  603. /**
  604. * The part of the current attribute in the object's signature, for the supplied value
  605. * @param unknown $value The value of this attribute for the object
  606. * @return string The "signature" for this field/attribute
  607. */
  608. public function Fingerprint($value)
  609. {
  610. return (string)$value;
  611. }
  612. }
  613. /**
  614. * Set of objects directly linked to an object, and being part of its definition
  615. *
  616. * @package iTopORM
  617. */
  618. class AttributeLinkedSet extends AttributeDefinition
  619. {
  620. static public function ListExpectedParams()
  621. {
  622. return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max"));
  623. }
  624. public function GetEditClass() {return "LinkedSet";}
  625. public function IsWritable() {return true;}
  626. public function IsLinkSet() {return true;}
  627. public function IsIndirect() {return false;}
  628. public function GetValuesDef() {return $this->Get("allowed_values");}
  629. public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");}
  630. public function GetDefaultValue(DBObject $oHostObject = null)
  631. {
  632. return DBObjectSet::FromScratch($this->Get('linked_class'));
  633. }
  634. public function GetTrackingLevel()
  635. {
  636. return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default'));
  637. }
  638. public function GetEditMode()
  639. {
  640. return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
  641. }
  642. public function GetLinkedClass() {return $this->Get('linked_class');}
  643. public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');}
  644. public function GetBasicFilterOperators() {return array();}
  645. public function GetBasicFilterLooseOperator() {return '';}
  646. public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';}
  647. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  648. {
  649. if (is_object($sValue) && ($sValue instanceof DBObjectSet))
  650. {
  651. $sValue->Rewind();
  652. $aItems = array();
  653. while ($oObj = $sValue->Fetch())
  654. {
  655. // Show only relevant information (hide the external key to the current object)
  656. $aAttributes = array();
  657. foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
  658. {
  659. if ($sAttCode == $this->GetExtKeyToMe()) continue;
  660. if ($oAttDef->IsExternalField()) continue;
  661. $sAttValue = $oObj->GetAsHTML($sAttCode);
  662. if (strlen($sAttValue) > 0)
  663. {
  664. $aAttributes[] = $sAttValue;
  665. }
  666. }
  667. $sAttributes = implode(', ', $aAttributes);
  668. $aItems[] = $sAttributes;
  669. }
  670. return implode('<br/>', $aItems);
  671. }
  672. return null;
  673. }
  674. public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
  675. {
  676. if (is_object($sValue) && ($sValue instanceof DBObjectSet))
  677. {
  678. $sValue->Rewind();
  679. $sRes = "<Set>\n";
  680. while ($oObj = $sValue->Fetch())
  681. {
  682. $sObjClass = get_class($oObj);
  683. $sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n";
  684. // Show only relevant information (hide the external key to the current object)
  685. $aAttributes = array();
  686. foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
  687. {
  688. if ($sAttCode == 'finalclass')
  689. {
  690. if ($sObjClass == $this->GetLinkedClass())
  691. {
  692. // Simplify the output if the exact class could be determined implicitely
  693. continue;
  694. }
  695. }
  696. if ($sAttCode == $this->GetExtKeyToMe()) continue;
  697. if ($oAttDef->IsExternalField() && ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe())) continue;
  698. if (($oAttDef instanceof AttributeFriendlyName) && ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe())) continue;
  699. if (($oAttDef instanceof AttributeFriendlyName) && ($oAttDef->GetKeyAttCode() == 'id')) continue;
  700. if (!$oAttDef->IsScalar()) continue;
  701. $sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize);
  702. $sRes .= "<$sAttCode>$sAttValue</$sAttCode>\n";
  703. }
  704. $sRes .= "</$sObjClass>\n";
  705. }
  706. $sRes .= "</Set>\n";
  707. }
  708. else
  709. {
  710. $sRes = '';
  711. }
  712. return $sRes;
  713. }
  714. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  715. {
  716. $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
  717. $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
  718. $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator');
  719. $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier');
  720. if (is_object($sValue) && ($sValue instanceof DBObjectSet))
  721. {
  722. $sValue->Rewind();
  723. $aItems = array();
  724. while ($oObj = $sValue->Fetch())
  725. {
  726. $sObjClass = get_class($oObj);
  727. // Show only relevant information (hide the external key to the current object)
  728. $aAttributes = array();
  729. foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
  730. {
  731. if ($sAttCode == 'finalclass')
  732. {
  733. if ($sObjClass == $this->GetLinkedClass())
  734. {
  735. // Simplify the output if the exact class could be determined implicitely
  736. continue;
  737. }
  738. }
  739. if ($sAttCode == $this->GetExtKeyToMe()) continue;
  740. if ($oAttDef->IsExternalField()) continue;
  741. if (!$oAttDef->IsDirectField()) continue;
  742. if (!$oAttDef->IsScalar()) continue;
  743. $sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize);
  744. if (strlen($sAttValue) > 0)
  745. {
  746. $sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, $sAttCode.$sSepValue.$sAttValue);
  747. $aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier;
  748. }
  749. }
  750. $sAttributes = implode($sSepAttribute, $aAttributes);
  751. $aItems[] = $sAttributes;
  752. }
  753. $sRes = implode($sSepItem, $aItems);
  754. }
  755. else
  756. {
  757. $sRes = '';
  758. }
  759. $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes);
  760. $sRes = $sTextQualifier.$sRes.$sTextQualifier;
  761. return $sRes;
  762. }
  763. /**
  764. * List the available verbs for 'GetForTemplate'
  765. */
  766. public function EnumTemplateVerbs()
  767. {
  768. return array(
  769. '' => 'Plain text (unlocalized) representation',
  770. 'html' => 'HTML representation (unordered list)',
  771. );
  772. }
  773. /**
  774. * Get various representations of the value, for insertion into a template (e.g. in Notifications)
  775. * @param $value mixed The current value of the field
  776. * @param $sVerb string The verb specifying the representation of the value
  777. * @param $oHostObject DBObject The object
  778. * @param $bLocalize bool Whether or not to localize the value
  779. */
  780. public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
  781. {
  782. $sRemoteName = $this->IsIndirect() ? $this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
  783. $oLinkSet = clone $value; // Workaround/Safety net for Trac #887
  784. $iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
  785. if ($iLimit > 0)
  786. {
  787. $oLinkSet->SetLimit($iLimit);
  788. }
  789. $aNames = $oLinkSet->GetColumnAsArray($sRemoteName);
  790. if ($iLimit > 0)
  791. {
  792. $iTotal = $oLinkSet->Count();
  793. if ($iTotal > count($aNames))
  794. {
  795. $aNames[] = '... '.Dict::Format('UI:TruncatedResults', count($aNames), $iTotal);
  796. }
  797. }
  798. switch($sVerb)
  799. {
  800. case '':
  801. return implode("\n", $aNames);
  802. case 'html':
  803. return '<ul><li>'.implode("</li><li>", $aNames).'</li></ul>';
  804. default:
  805. throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
  806. }
  807. }
  808. public function DuplicatesAllowed() {return false;} // No duplicates for 1:n links, never
  809. public function GetImportColumns()
  810. {
  811. $aColumns = array();
  812. $aColumns[$this->GetCode()] = 'TEXT';
  813. return $aColumns;
  814. }
  815. public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
  816. {
  817. if (is_null($sSepItem) || empty($sSepItem))
  818. {
  819. $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
  820. }
  821. if (is_null($sSepAttribute) || empty($sSepAttribute))
  822. {
  823. $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
  824. }
  825. if (is_null($sSepValue) || empty($sSepValue))
  826. {
  827. $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator');
  828. }
  829. if (is_null($sAttributeQualifier) || empty($sAttributeQualifier))
  830. {
  831. $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier');
  832. }
  833. $sTargetClass = $this->Get('linked_class');
  834. $sInput = str_replace($sSepItem, "\n", $sProposedValue);
  835. $oCSVParser = new CSVParser($sInput, $sSepAttribute, $sAttributeQualifier);
  836. $aInput = $oCSVParser->ToArray(0 /* do not skip lines */);
  837. $aLinks = array();
  838. foreach($aInput as $aRow)
  839. {
  840. // 1st - get the values, split the extkey->searchkey specs, and eventually get the finalclass value
  841. $aExtKeys = array();
  842. $aValues = array();
  843. foreach($aRow as $sCell)
  844. {
  845. $iSepPos = strpos($sCell, $sSepValue);
  846. if ($iSepPos === false)
  847. {
  848. // Houston...
  849. throw new CoreException('Wrong format for link attribute specification', array('value' => $sCell));
  850. }
  851. $sAttCode = trim(substr($sCell, 0, $iSepPos));
  852. $sValue = substr($sCell, $iSepPos + strlen($sSepValue));
  853. if (preg_match('/^(.+)->(.+)$/', $sAttCode, $aMatches))
  854. {
  855. $sKeyAttCode = $aMatches[1];
  856. $sRemoteAttCode = $aMatches[2];
  857. $aExtKeys[$sKeyAttCode][$sRemoteAttCode] = $sValue;
  858. if (!MetaModel::IsValidAttCode($sTargetClass, $sKeyAttCode))
  859. {
  860. throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sKeyAttCode));
  861. }
  862. $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
  863. $sRemoteClass = $oKeyAttDef->GetTargetClass();
  864. if (!MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode))
  865. {
  866. throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode));
  867. }
  868. }
  869. else
  870. {
  871. if(!MetaModel::IsValidAttCode($sTargetClass, $sAttCode))
  872. {
  873. throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sAttCode));
  874. }
  875. $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode);
  876. $aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier);
  877. }
  878. }
  879. // 2nd - Instanciate the object and set the value
  880. if (isset($aValues['finalclass']))
  881. {
  882. $sLinkClass = $aValues['finalclass'];
  883. if (!is_subclass_of($sLinkClass, $sTargetClass))
  884. {
  885. throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
  886. }
  887. }
  888. elseif (MetaModel::IsAbstract($sTargetClass))
  889. {
  890. throw new CoreException('Missing finalclass for link attribute specification');
  891. }
  892. else
  893. {
  894. $sLinkClass = $sTargetClass;
  895. }
  896. $oLink = MetaModel::NewObject($sLinkClass);
  897. foreach ($aValues as $sAttCode => $sValue)
  898. {
  899. $oLink->Set($sAttCode, $sValue);
  900. }
  901. // 3rd - Set external keys from search conditions
  902. foreach ($aExtKeys as $sKeyAttCode => $aReconciliation)
  903. {
  904. $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
  905. $sKeyClass = $oKeyAttDef->GetTargetClass();
  906. $oExtKeyFilter = new DBObjectSearch($sKeyClass);
  907. $aReconciliationDesc = array();
  908. foreach($aReconciliation as $sRemoteAttCode => $sValue)
  909. {
  910. $oExtKeyFilter->AddCondition($sRemoteAttCode, $sValue, '=');
  911. $aReconciliationDesc[] = "$sRemoteAttCode=$sValue";
  912. }
  913. $oExtKeySet = new CMDBObjectSet($oExtKeyFilter);
  914. switch($oExtKeySet->Count())
  915. {
  916. case 0:
  917. $sReconciliationDesc = implode(', ', $aReconciliationDesc);
  918. throw new CoreException("Found no match", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc));
  919. break;
  920. case 1:
  921. $oRemoteObj = $oExtKeySet->Fetch();
  922. $oLink->Set($sKeyAttCode, $oRemoteObj->GetKey());
  923. break;
  924. default:
  925. $sReconciliationDesc = implode(', ', $aReconciliationDesc);
  926. throw new CoreException("Found several matches", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc));
  927. // Found several matches, ambiguous
  928. }
  929. }
  930. // Check (roughly) if such a link is valid
  931. $aErrors = array();
  932. foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef)
  933. {
  934. if ($oAttDef->IsExternalKey())
  935. {
  936. if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass())))
  937. {
  938. continue; // Don't check the key to self
  939. }
  940. }
  941. if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed())
  942. {
  943. $aErrors[] = $sAttCode;
  944. }
  945. }
  946. if (count($aErrors) > 0)
  947. {
  948. throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
  949. }
  950. $aLinks[] = $oLink;
  951. }
  952. $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
  953. return $oSet;
  954. }
  955. /**
  956. * Helper to get a value that will be JSON encoded
  957. * The operation is the opposite to FromJSONToValue
  958. */
  959. public function GetForJSON($value)
  960. {
  961. $aRet = array();
  962. if (is_object($value) && ($value instanceof DBObjectSet))
  963. {
  964. $value->Rewind();
  965. while ($oObj = $value->Fetch())
  966. {
  967. $sObjClass = get_class($oObj);
  968. // Show only relevant information (hide the external key to the current object)
  969. $aAttributes = array();
  970. foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
  971. {
  972. if ($sAttCode == 'finalclass')
  973. {
  974. if ($sObjClass == $this->GetLinkedClass())
  975. {
  976. // Simplify the output if the exact class could be determined implicitely
  977. continue;
  978. }
  979. }
  980. if ($sAttCode == $this->GetExtKeyToMe()) continue;
  981. if ($oAttDef->IsExternalField()) continue;
  982. if (!$oAttDef->IsDirectField()) continue;
  983. if (!$oAttDef->IsScalar()) continue;
  984. $attValue = $oObj->Get($sAttCode);
  985. $aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue);
  986. }
  987. $aRet[] = $aAttributes;
  988. }
  989. }
  990. return $aRet;
  991. }
  992. /**
  993. * Helper to form a value, given JSON decoded data
  994. * The operation is the opposite to GetForJSON
  995. */
  996. public function FromJSONToValue($json)
  997. {
  998. $sTargetClass = $this->Get('linked_class');
  999. $aLinks = array();
  1000. foreach($json as $aValues)
  1001. {
  1002. if (isset($aValues['finalclass']))
  1003. {
  1004. $sLinkClass = $aValues['finalclass'];
  1005. if (!is_subclass_of($sLinkClass, $sTargetClass))
  1006. {
  1007. throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
  1008. }
  1009. }
  1010. elseif (MetaModel::IsAbstract($sTargetClass))
  1011. {
  1012. throw new CoreException('Missing finalclass for link attribute specification');
  1013. }
  1014. else
  1015. {
  1016. $sLinkClass = $sTargetClass;
  1017. }
  1018. $oLink = MetaModel::NewObject($sLinkClass);
  1019. foreach ($aValues as $sAttCode => $sValue)
  1020. {
  1021. $oLink->Set($sAttCode, $sValue);
  1022. }
  1023. // Check (roughly) if such a link is valid
  1024. $aErrors = array();
  1025. foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef)
  1026. {
  1027. if ($oAttDef->IsExternalKey())
  1028. {
  1029. if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass())))
  1030. {
  1031. continue; // Don't check the key to self
  1032. }
  1033. }
  1034. if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed())
  1035. {
  1036. $aErrors[] = $sAttCode;
  1037. }
  1038. }
  1039. if (count($aErrors) > 0)
  1040. {
  1041. throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
  1042. }
  1043. $aLinks[] = $oLink;
  1044. }
  1045. $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
  1046. return $oSet;
  1047. }
  1048. public function Equals($val1, $val2)
  1049. {
  1050. if ($val1 === $val2) return true;
  1051. if (is_object($val1) != is_object($val2))
  1052. {
  1053. return false;
  1054. }
  1055. if (!is_object($val1))
  1056. {
  1057. // string ?
  1058. // todo = implement this case ?
  1059. return false;
  1060. }
  1061. // Note: maintain this algorithm so as to make sure it is strictly equivalent to the one used within DBObject::DBWriteLinks()
  1062. $sExtKeyToMe = $this->GetExtKeyToMe();
  1063. $sAdditionalKey = null;
  1064. if ($this->IsIndirect() && !$this->DuplicatesAllowed())
  1065. {
  1066. $sAdditionalKey = $this->GetExtKeyToRemote();
  1067. }
  1068. $oComparator = new DBObjectSetComparator($val1, $val2, array($sExtKeyToMe), $sAdditionalKey);
  1069. $aChanges = $oComparator->GetDifferences();
  1070. $bAreEquivalent = (count($aChanges['added']) == 0) && (count($aChanges['removed']) == 0) && (count($aChanges['modified']) == 0);
  1071. return $bAreEquivalent;
  1072. }
  1073. /**
  1074. * Find the corresponding "link" attribute on the target class
  1075. *
  1076. * @return string The attribute code on the target class, or null if none has been found
  1077. */
  1078. public function GetMirrorLinkAttribute()
  1079. {
  1080. $oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe());
  1081. return $oRemoteAtt;
  1082. }
  1083. public function IsPartOfFingerprint() { return false; }
  1084. }
  1085. /**
  1086. * Set of objects linked to an object (n-n), and being part of its definition
  1087. *
  1088. * @package iTopORM
  1089. */
  1090. class AttributeLinkedSetIndirect extends AttributeLinkedSet
  1091. {
  1092. static public function ListExpectedParams()
  1093. {
  1094. return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote"));
  1095. }
  1096. public function IsIndirect() {return true;}
  1097. public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); }
  1098. public function GetEditClass() {return "LinkedSet";}
  1099. public function DuplicatesAllowed() {return $this->GetOptional("duplicates", false);} // The same object may be linked several times... or not...
  1100. public function GetTrackingLevel()
  1101. {
  1102. return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default'));
  1103. }
  1104. /**
  1105. * Find the corresponding "link" attribute on the target class
  1106. *
  1107. * @return string The attribute code on the target class, or null if none has been found
  1108. */
  1109. public function GetMirrorLinkAttribute()
  1110. {
  1111. $oRet = null;
  1112. $oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
  1113. $sRemoteClass = $oExtKeyToRemote->GetTargetClass();
  1114. foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef)
  1115. {
  1116. if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) continue;
  1117. if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) continue;
  1118. if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) continue;
  1119. if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) continue;
  1120. $oRet = $oRemoteAttDef;
  1121. break;
  1122. }
  1123. return $oRet;
  1124. }
  1125. }
  1126. /**
  1127. * Abstract class implementing default filters for a DB column
  1128. *
  1129. * @package iTopORM
  1130. */
  1131. class AttributeDBFieldVoid extends AttributeDefinition
  1132. {
  1133. static public function ListExpectedParams()
  1134. {
  1135. return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql"));
  1136. }
  1137. // To be overriden, used in GetSQLColumns
  1138. protected function GetSQLCol($bFullSpec = false)
  1139. {
  1140. return "VARCHAR(255)".($bFullSpec ? $this->GetSQLColSpec() : '');
  1141. }
  1142. protected function GetSQLColSpec()
  1143. {
  1144. $default = $this->ScalarToSQL($this->GetDefaultValue());
  1145. if (is_null($default))
  1146. {
  1147. $sRet = '';
  1148. }
  1149. else
  1150. {
  1151. if (is_numeric($default))
  1152. {
  1153. // Though it is a string in PHP, it will be considered as a numeric value in MySQL
  1154. // Then it must not be quoted here, to preserve the compatibility with the value returned by CMDBSource::GetFieldSpec
  1155. $sRet = " DEFAULT $default";
  1156. }
  1157. else
  1158. {
  1159. $sRet = " DEFAULT ".CMDBSource::Quote($default);
  1160. }
  1161. }
  1162. return $sRet;
  1163. }
  1164. public function GetEditClass() {return "String";}
  1165. public function GetValuesDef() {return $this->Get("allowed_values");}
  1166. public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");}
  1167. public function IsDirectField() {return true;}
  1168. public function IsScalar() {return true;}
  1169. public function IsWritable() {return true;}
  1170. public function GetSQLExpr() {return $this->Get("sql");}
  1171. public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);}
  1172. public function IsNullAllowed() {return false;}
  1173. //
  1174. protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
  1175. public function GetSQLExpressions($sPrefix = '')
  1176. {
  1177. $aColumns = array();
  1178. // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
  1179. $aColumns[''] = $this->Get("sql");
  1180. return $aColumns;
  1181. }
  1182. public function FromSQLToValue($aCols, $sPrefix = '')
  1183. {
  1184. $value = $this->MakeRealValue($aCols[$sPrefix.''], null);
  1185. return $value;
  1186. }
  1187. public function GetSQLValues($value)
  1188. {
  1189. $aValues = array();
  1190. $aValues[$this->Get("sql")] = $this->ScalarToSQL($value);
  1191. return $aValues;
  1192. }
  1193. public function GetSQLColumns($bFullSpec = false)
  1194. {
  1195. $aColumns = array();
  1196. $aColumns[$this->Get("sql")] = $this->GetSQLCol($bFullSpec);
  1197. return $aColumns;
  1198. }
  1199. public function GetFilterDefinitions()
  1200. {
  1201. return array($this->GetCode() => new FilterFromAttribute($this));
  1202. }
  1203. public function GetBasicFilterOperators()
  1204. {
  1205. return array("="=>"equals", "!="=>"differs from");
  1206. }
  1207. public function GetBasicFilterLooseOperator()
  1208. {
  1209. return "=";
  1210. }
  1211. public function GetBasicFilterSQLExpr($sOpCode, $value)
  1212. {
  1213. $sQValue = CMDBSource::Quote($value);
  1214. switch ($sOpCode)
  1215. {
  1216. case '!=':
  1217. return $this->GetSQLExpr()." != $sQValue";
  1218. break;
  1219. case '=':
  1220. default:
  1221. return $this->GetSQLExpr()." = $sQValue";
  1222. }
  1223. }
  1224. }
  1225. /**
  1226. * Base class for all kind of DB attributes, with the exception of external keys
  1227. *
  1228. * @package iTopORM
  1229. */
  1230. class AttributeDBField extends AttributeDBFieldVoid
  1231. {
  1232. static public function ListExpectedParams()
  1233. {
  1234. return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
  1235. }
  1236. public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue($this->Get("default_value"), $oHostObject);}
  1237. public function IsNullAllowed() {return $this->Get("is_null_allowed");}
  1238. }
  1239. /**
  1240. * Map an integer column to an attribute
  1241. *
  1242. * @package iTopORM
  1243. */
  1244. class AttributeInteger extends AttributeDBField
  1245. {
  1246. static public function ListExpectedParams()
  1247. {
  1248. return parent::ListExpectedParams();
  1249. //return array_merge(parent::ListExpectedParams(), array());
  1250. }
  1251. public function GetEditClass() {return "String";}
  1252. protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : '');}
  1253. public function GetValidationPattern()
  1254. {
  1255. return "^[0-9]+$";
  1256. }
  1257. public function GetBasicFilterOperators()
  1258. {
  1259. return array(
  1260. "!="=>"differs from",
  1261. "="=>"equals",
  1262. ">"=>"greater (strict) than",
  1263. ">="=>"greater than",
  1264. "<"=>"less (strict) than",
  1265. "<="=>"less than",
  1266. "in"=>"in"
  1267. );
  1268. }
  1269. public function GetBasicFilterLooseOperator()
  1270. {
  1271. // Unless we implement an "equals approximately..." or "same order of magnitude"
  1272. return "=";
  1273. }
  1274. public function GetBasicFilterSQLExpr($sOpCode, $value)
  1275. {
  1276. $sQValue = CMDBSource::Quote($value);
  1277. switch ($sOpCode)
  1278. {
  1279. case '!=':
  1280. return $this->GetSQLExpr()." != $sQValue";
  1281. break;
  1282. case '>':
  1283. return $this->GetSQLExpr()." > $sQValue";
  1284. break;
  1285. case '>=':
  1286. return $this->GetSQLExpr()." >= $sQValue";
  1287. break;
  1288. case '<':
  1289. return $this->GetSQLExpr()." < $sQValue";
  1290. break;
  1291. case '<=':
  1292. return $this->GetSQLExpr()." <= $sQValue";
  1293. break;
  1294. case 'in':
  1295. if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
  1296. return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')";
  1297. break;
  1298. case '=':
  1299. default:
  1300. return $this->GetSQLExpr()." = \"$value\"";
  1301. }
  1302. }
  1303. public function GetNullValue()
  1304. {
  1305. return null;
  1306. }
  1307. public function IsNull($proposedValue)
  1308. {
  1309. return is_null($proposedValue);
  1310. }
  1311. public function MakeRealValue($proposedValue, $oHostObj)
  1312. {
  1313. if (is_null($proposedValue)) return null;
  1314. if ($proposedValue === '') return null; // 0 is transformed into '' !
  1315. return (int)$proposedValue;
  1316. }
  1317. public function ScalarToSQL($value)
  1318. {
  1319. assert(is_numeric($value) || is_null($value));
  1320. return $value; // supposed to be an int
  1321. }
  1322. }
  1323. /**
  1324. * An external key for which the class is defined as the value of another attribute
  1325. *
  1326. * @package iTopORM
  1327. */
  1328. class AttributeObjectKey extends AttributeDBFieldVoid
  1329. {
  1330. static public function ListExpectedParams()
  1331. {
  1332. return array_merge(parent::ListExpectedParams(), array('class_attcode', 'is_null_allowed'));
  1333. }
  1334. public function GetEditClass() {return "String";}
  1335. protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");}
  1336. public function GetDefaultValue(DBObject $oHostObject = null) {return 0;}
  1337. public function IsNullAllowed()
  1338. {
  1339. return $this->Get("is_null_allowed");
  1340. }
  1341. public function GetBasicFilterOperators()
  1342. {
  1343. return parent::GetBasicFilterOperators();
  1344. }
  1345. public function GetBasicFilterLooseOperator()
  1346. {
  1347. return parent::GetBasicFilterLooseOperator();
  1348. }
  1349. public function GetBasicFilterSQLExpr($sOpCode, $value)
  1350. {
  1351. return parent::GetBasicFilterSQLExpr($sOpCode, $value);
  1352. }
  1353. public function GetNullValue()
  1354. {
  1355. return 0;
  1356. }
  1357. public function IsNull($proposedValue)
  1358. {
  1359. return ($proposedValue == 0);
  1360. }
  1361. public function MakeRealValue($proposedValue, $oHostObj)
  1362. {
  1363. if (is_null($proposedValue)) return 0;
  1364. if ($proposedValue === '') return 0;
  1365. if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey();
  1366. return (int)$proposedValue;
  1367. }
  1368. }
  1369. /**
  1370. * Display an integer between 0 and 100 as a percentage / horizontal bar graph
  1371. *
  1372. * @package iTopORM
  1373. */
  1374. class AttributePercentage extends AttributeInteger
  1375. {
  1376. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  1377. {
  1378. $iWidth = 5; // Total width of the percentage bar graph, in em...
  1379. $iValue = (int)$sValue;
  1380. if ($iValue > 100)
  1381. {
  1382. $iValue = 100;
  1383. }
  1384. else if ($iValue < 0)
  1385. {
  1386. $iValue = 0;
  1387. }
  1388. if ($iValue > 90)
  1389. {
  1390. $sColor = "#cc3300";
  1391. }
  1392. else if ($iValue > 50)
  1393. {
  1394. $sColor = "#cccc00";
  1395. }
  1396. else
  1397. {
  1398. $sColor = "#33cc00";
  1399. }
  1400. $iPercentWidth = ($iWidth * $iValue) / 100;
  1401. return "<div style=\"width:{$iWidth}em;-moz-border-radius: 3px;-webkit-border-radius: 3px;border-radius: 3px;display:inline-block;border: 1px #ccc solid;\"><div style=\"width:{$iPercentWidth}em; display:inline-block;background-color:$sColor;\">&nbsp;</div></div>&nbsp;$sValue %";
  1402. }
  1403. }
  1404. /**
  1405. * Map a decimal value column (suitable for financial computations) to an attribute
  1406. * internally in PHP such numbers are represented as string. Should you want to perform
  1407. * a calculation on them, it is recommended to use the BC Math functions in order to
  1408. * retain the precision
  1409. *
  1410. * @package iTopORM
  1411. */
  1412. class AttributeDecimal extends AttributeDBField
  1413. {
  1414. static public function ListExpectedParams()
  1415. {
  1416. return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */));
  1417. }
  1418. public function GetEditClass() {return "String";}
  1419. protected function GetSQLCol($bFullSpec = false)
  1420. {
  1421. return "DECIMAL(".$this->Get('digits').",".$this->Get('decimals').")".($bFullSpec ? $this->GetSQLColSpec() : '');
  1422. }
  1423. public function GetValidationPattern()
  1424. {
  1425. $iNbDigits = $this->Get('digits');
  1426. $iPrecision = $this->Get('decimals');
  1427. $iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below
  1428. return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
  1429. }
  1430. public function GetBasicFilterOperators()
  1431. {
  1432. return array(
  1433. "!="=>"differs from",
  1434. "="=>"equals",
  1435. ">"=>"greater (strict) than",
  1436. ">="=>"greater than",
  1437. "<"=>"less (strict) than",
  1438. "<="=>"less than",
  1439. "in"=>"in"
  1440. );
  1441. }
  1442. public function GetBasicFilterLooseOperator()
  1443. {
  1444. // Unless we implement an "equals approximately..." or "same order of magnitude"
  1445. return "=";
  1446. }
  1447. public function GetBasicFilterSQLExpr($sOpCode, $value)
  1448. {
  1449. $sQValue = CMDBSource::Quote($value);
  1450. switch ($sOpCode)
  1451. {
  1452. case '!=':
  1453. return $this->GetSQLExpr()." != $sQValue";
  1454. break;
  1455. case '>':
  1456. return $this->GetSQLExpr()." > $sQValue";
  1457. break;
  1458. case '>=':
  1459. return $this->GetSQLExpr()." >= $sQValue";
  1460. break;
  1461. case '<':
  1462. return $this->GetSQLExpr()." < $sQValue";
  1463. break;
  1464. case '<=':
  1465. return $this->GetSQLExpr()." <= $sQValue";
  1466. break;
  1467. case 'in':
  1468. if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
  1469. return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')";
  1470. break;
  1471. case '=':
  1472. default:
  1473. return $this->GetSQLExpr()." = \"$value\"";
  1474. }
  1475. }
  1476. public function GetNullValue()
  1477. {
  1478. return null;
  1479. }
  1480. public function IsNull($proposedValue)
  1481. {
  1482. return is_null($proposedValue);
  1483. }
  1484. public function MakeRealValue($proposedValue, $oHostObj)
  1485. {
  1486. if (is_null($proposedValue)) return null;
  1487. if ($proposedValue === '') return null;
  1488. return (string)$proposedValue;
  1489. }
  1490. public function ScalarToSQL($value)
  1491. {
  1492. assert(is_null($value) || preg_match('/'.$this->GetValidationPattern().'/', $value));
  1493. return $value; // null or string
  1494. }
  1495. }
  1496. /**
  1497. * Map a boolean column to an attribute
  1498. *
  1499. * @package iTopORM
  1500. */
  1501. class AttributeBoolean extends AttributeInteger
  1502. {
  1503. static public function ListExpectedParams()
  1504. {
  1505. return parent::ListExpectedParams();
  1506. //return array_merge(parent::ListExpectedParams(), array());
  1507. }
  1508. public function GetEditClass() {return "Integer";}
  1509. protected function GetSQLCol($bFullSpec = false) {return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : '');}
  1510. public function MakeRealValue($proposedValue, $oHostObj)
  1511. {
  1512. if (is_null($proposedValue)) return null;
  1513. if ($proposedValue === '') return null;
  1514. if ((int)$proposedValue) return true;
  1515. return false;
  1516. }
  1517. public function ScalarToSQL($value)
  1518. {
  1519. if ($value) return 1;
  1520. return 0;
  1521. }
  1522. public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
  1523. {
  1524. return $sValue ? '1' : '0';
  1525. }
  1526. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  1527. {
  1528. return $sValue ? '1' : '0';
  1529. }
  1530. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  1531. {
  1532. return $sValue ? '1' : '0';
  1533. }
  1534. /**
  1535. * Helper to get a value that will be JSON encoded
  1536. * The operation is the opposite to FromJSONToValue
  1537. */
  1538. public function GetForJSON($value)
  1539. {
  1540. return $value ? '1' : '0';
  1541. }
  1542. }
  1543. /**
  1544. * Map a varchar column (size < ?) to an attribute
  1545. *
  1546. * @package iTopORM
  1547. */
  1548. class AttributeString extends AttributeDBField
  1549. {
  1550. static public function ListExpectedParams()
  1551. {
  1552. return parent::ListExpectedParams();
  1553. //return array_merge(parent::ListExpectedParams(), array());
  1554. }
  1555. public function GetEditClass() {return "String";}
  1556. protected function GetSQLCol($bFullSpec = false) {return "VARCHAR(255)".($bFullSpec ? $this->GetSQLColSpec() : '');}
  1557. public function GetValidationPattern()
  1558. {
  1559. $sPattern = $this->GetOptional('validation_pattern', '');
  1560. if (empty($sPattern))
  1561. {
  1562. return parent::GetValidationPattern();
  1563. }
  1564. else
  1565. {
  1566. return $sPattern;
  1567. }
  1568. }
  1569. public function CheckFormat($value)
  1570. {
  1571. $sRegExp = $this->GetValidationPattern();
  1572. if (empty($sRegExp))
  1573. {
  1574. return true;
  1575. }
  1576. else
  1577. {
  1578. $sRegExp = str_replace('/', '\\/', $sRegExp);
  1579. return preg_match("/$sRegExp/", $value);
  1580. }
  1581. }
  1582. public function GetMaxSize()
  1583. {
  1584. return 255;
  1585. }
  1586. public function GetBasicFilterOperators()
  1587. {
  1588. return array(
  1589. "="=>"equals",
  1590. "!="=>"differs from",
  1591. "Like"=>"equals (no case)",
  1592. "NotLike"=>"differs from (no case)",
  1593. "Contains"=>"contains",
  1594. "Begins with"=>"begins with",
  1595. "Finishes with"=>"finishes with"
  1596. );
  1597. }
  1598. public function GetBasicFilterLooseOperator()
  1599. {
  1600. return "Contains";
  1601. }
  1602. public function GetBasicFilterSQLExpr($sOpCode, $value)
  1603. {
  1604. $sQValue = CMDBSource::Quote($value);
  1605. switch ($sOpCode)
  1606. {
  1607. case '=':
  1608. case '!=':
  1609. return $this->GetSQLExpr()." $sOpCode $sQValue";
  1610. case 'Begins with':
  1611. return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%");
  1612. case 'Finishes with':
  1613. return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value");
  1614. case 'Contains':
  1615. return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
  1616. case 'NotLike':
  1617. return $this->GetSQLExpr()." NOT LIKE $sQValue";
  1618. case 'Like':
  1619. default:
  1620. return $this->GetSQLExpr()." LIKE $sQValue";
  1621. }
  1622. }
  1623. public function GetNullValue()
  1624. {
  1625. return '';
  1626. }
  1627. public function IsNull($proposedValue)
  1628. {
  1629. return ($proposedValue == '');
  1630. }
  1631. public function MakeRealValue($proposedValue, $oHostObj)
  1632. {
  1633. if (is_null($proposedValue)) return '';
  1634. return (string)$proposedValue;
  1635. }
  1636. public function ScalarToSQL($value)
  1637. {
  1638. if (!is_string($value) && !is_null($value))
  1639. {
  1640. throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode()));
  1641. }
  1642. return $value;
  1643. }
  1644. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  1645. {
  1646. $sFrom = array("\r\n", $sTextQualifier);
  1647. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  1648. $sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
  1649. return $sTextQualifier.$sEscaped.$sTextQualifier;
  1650. }
  1651. public function GetDisplayStyle()
  1652. {
  1653. return $this->GetOptional('display_style', 'select');
  1654. }
  1655. }
  1656. /**
  1657. * An attibute that matches an object class
  1658. *
  1659. * @package iTopORM
  1660. */
  1661. class AttributeClass extends AttributeString
  1662. {
  1663. static public function ListExpectedParams()
  1664. {
  1665. return array_merge(parent::ListExpectedParams(), array("class_category", "more_values"));
  1666. }
  1667. public function __construct($sCode, $aParams)
  1668. {
  1669. $this->m_sCode = $sCode;
  1670. $aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']);
  1671. parent::__construct($sCode, $aParams);
  1672. }
  1673. public function GetDefaultValue(DBObject $oHostObject = null)
  1674. {
  1675. $sDefault = parent::GetDefaultValue($oHostObject);
  1676. if (!$this->IsNullAllowed() && $this->IsNull($sDefault))
  1677. {
  1678. // For this kind of attribute specifying null as default value
  1679. // is authorized even if null is not allowed
  1680. // Pick the first one...
  1681. $aClasses = $this->GetAllowedValues();
  1682. $sDefault = key($aClasses);
  1683. }
  1684. return $sDefault;
  1685. }
  1686. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  1687. {
  1688. if (empty($sValue)) return '';
  1689. return MetaModel::GetName($sValue);
  1690. }
  1691. public function RequiresIndex()
  1692. {
  1693. return true;
  1694. }
  1695. public function GetBasicFilterLooseOperator()
  1696. {
  1697. return '=';
  1698. }
  1699. }
  1700. /**
  1701. * An attibute that matches one of the language codes availables in the dictionnary
  1702. *
  1703. * @package iTopORM
  1704. */
  1705. class AttributeApplicationLanguage extends AttributeString
  1706. {
  1707. static public function ListExpectedParams()
  1708. {
  1709. return parent::ListExpectedParams();
  1710. }
  1711. public function __construct($sCode, $aParams)
  1712. {
  1713. $this->m_sCode = $sCode;
  1714. $aAvailableLanguages = Dict::GetLanguages();
  1715. $aLanguageCodes = array();
  1716. foreach($aAvailableLanguages as $sLangCode => $aInfo)
  1717. {
  1718. $aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')';
  1719. }
  1720. $aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes);
  1721. parent::__construct($sCode, $aParams);
  1722. }
  1723. public function RequiresIndex()
  1724. {
  1725. return true;
  1726. }
  1727. public function GetBasicFilterLooseOperator()
  1728. {
  1729. return '=';
  1730. }
  1731. }
  1732. /**
  1733. * The attribute dedicated to the finalclass automatic attribute
  1734. *
  1735. * @package iTopORM
  1736. */
  1737. class AttributeFinalClass extends AttributeString
  1738. {
  1739. public function __construct($sCode, $aParams)
  1740. {
  1741. $this->m_sCode = $sCode;
  1742. $aParams["allowed_values"] = null;
  1743. parent::__construct($sCode, $aParams);
  1744. $this->m_sValue = $this->Get("default_value");
  1745. }
  1746. public function IsWritable()
  1747. {
  1748. return false;
  1749. }
  1750. public function RequiresIndex()
  1751. {
  1752. return true;
  1753. }
  1754. public function SetFixedValue($sValue)
  1755. {
  1756. $this->m_sValue = $sValue;
  1757. }
  1758. public function GetDefaultValue(DBObject $oHostObject = null)
  1759. {
  1760. return $this->m_sValue;
  1761. }
  1762. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  1763. {
  1764. if (empty($sValue)) return '';
  1765. if ($bLocalize)
  1766. {
  1767. return MetaModel::GetName($sValue);
  1768. }
  1769. else
  1770. {
  1771. return $sValue;
  1772. }
  1773. }
  1774. // Because this is sometimes used to get a localized/string version of an attribute...
  1775. public function GetEditValue($sValue, $oHostObj = null)
  1776. {
  1777. if (empty($sValue)) return '';
  1778. return MetaModel::GetName($sValue);
  1779. }
  1780. /**
  1781. * Helper to get a value that will be JSON encoded
  1782. * The operation is the opposite to FromJSONToValue
  1783. */
  1784. public function GetForJSON($value)
  1785. {
  1786. // JSON values are NOT localized
  1787. return $value;
  1788. }
  1789. public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  1790. {
  1791. if ($bLocalize && $value != '')
  1792. {
  1793. $sRawValue = MetaModel::GetName($value);
  1794. }
  1795. else
  1796. {
  1797. $sRawValue = $value;
  1798. }
  1799. return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText);
  1800. }
  1801. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  1802. {
  1803. if (empty($value)) return '';
  1804. if ($bLocalize)
  1805. {
  1806. $sRawValue = MetaModel::GetName($value);
  1807. }
  1808. else
  1809. {
  1810. $sRawValue = $value;
  1811. }
  1812. return Str::pure2xml($sRawValue);
  1813. }
  1814. public function GetBasicFilterLooseOperator()
  1815. {
  1816. return '=';
  1817. }
  1818. public function GetValueLabel($sValue)
  1819. {
  1820. if (empty($sValue)) return '';
  1821. return MetaModel::GetName($sValue);
  1822. }
  1823. public function GetAllowedValues($aArgs = array(), $sContains = '')
  1824. {
  1825. $aRawValues = MetaModel::EnumChildClasses($this->GetHostClass(), ENUM_CHILD_CLASSES_ALL);
  1826. $aLocalizedValues = array();
  1827. foreach ($aRawValues as $sClass)
  1828. {
  1829. $aLocalizedValues[$sClass] = MetaModel::GetName($sClass);
  1830. }
  1831. return $aLocalizedValues;
  1832. }
  1833. }
  1834. /**
  1835. * Map a varchar column (size < ?) to an attribute that must never be shown to the user
  1836. *
  1837. * @package iTopORM
  1838. */
  1839. class AttributePassword extends AttributeString
  1840. {
  1841. static public function ListExpectedParams()
  1842. {
  1843. return parent::ListExpectedParams();
  1844. //return array_merge(parent::ListExpectedParams(), array());
  1845. }
  1846. public function GetEditClass() {return "Password";}
  1847. protected function GetSQLCol($bFullSpec = false) {return "VARCHAR(64)".($bFullSpec ? $this->GetSQLColSpec() : '');}
  1848. public function GetMaxSize()
  1849. {
  1850. return 64;
  1851. }
  1852. public function GetFilterDefinitions()
  1853. {
  1854. // Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists)
  1855. // not allowed to search on passwords!
  1856. return array();
  1857. }
  1858. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  1859. {
  1860. if (strlen($sValue) == 0)
  1861. {
  1862. return '';
  1863. }
  1864. else
  1865. {
  1866. return '******';
  1867. }
  1868. }
  1869. public function IsPartOfFingerprint() { return false; } // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt'
  1870. }
  1871. /**
  1872. * Map a text column (size < 255) to an attribute that is encrypted in the database
  1873. * The encryption is based on a key set per iTop instance. Thus if you export your
  1874. * database (in SQL) to someone else without providing the key at the same time
  1875. * the encrypted fields will remain encrypted
  1876. *
  1877. * @package iTopORM
  1878. */
  1879. class AttributeEncryptedString extends AttributeString
  1880. {
  1881. static $sKey = null; // Encryption key used for all encrypted fields
  1882. public function __construct($sCode, $aParams)
  1883. {
  1884. parent::__construct($sCode, $aParams);
  1885. if (self::$sKey == null)
  1886. {
  1887. self::$sKey = MetaModel::GetConfig()->GetEncryptionKey();
  1888. }
  1889. }
  1890. /**
  1891. * When the attribute definitions are stored in APC cache:
  1892. * 1) The static class variable $sKey is NOT serialized
  1893. * 2) The object's constructor is NOT called upon wakeup
  1894. * 3) mcrypt may crash the server if passed an empty key !!
  1895. *
  1896. * So let's restore the key (if needed) when waking up
  1897. **/
  1898. public function __wakeup()
  1899. {
  1900. if (self::$sKey == null)
  1901. {
  1902. self::$sKey = MetaModel::GetConfig()->GetEncryptionKey();
  1903. }
  1904. }
  1905. protected function GetSQLCol($bFullSpec = false) {return "TINYBLOB";}
  1906. public function GetMaxSize()
  1907. {
  1908. return 255;
  1909. }
  1910. public function GetFilterDefinitions()
  1911. {
  1912. // Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists)
  1913. // not allowed to search on encrypted fields !
  1914. return array();
  1915. }
  1916. public function MakeRealValue($proposedValue, $oHostObj)
  1917. {
  1918. if (is_null($proposedValue)) return null;
  1919. return (string)$proposedValue;
  1920. }
  1921. /**
  1922. * Decrypt the value when reading from the database
  1923. */
  1924. public function FromSQLToValue($aCols, $sPrefix = '')
  1925. {
  1926. $oSimpleCrypt = new SimpleCrypt();
  1927. $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]);
  1928. return $sValue;
  1929. }
  1930. /**
  1931. * Encrypt the value before storing it in the database
  1932. */
  1933. public function GetSQLValues($value)
  1934. {
  1935. $oSimpleCrypt = new SimpleCrypt();
  1936. $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value);
  1937. $aValues = array();
  1938. $aValues[$this->Get("sql")] = $encryptedValue;
  1939. return $aValues;
  1940. }
  1941. }
  1942. // Wiki formatting - experimental
  1943. //
  1944. // [[<objClass>:<objName>]]
  1945. // Example: [[Server:db1.tnut.com]]
  1946. define('WIKI_OBJECT_REGEXP', '/\[\[(.+):(.+)\]\]/U');
  1947. /**
  1948. * Map a text column (size > ?) to an attribute
  1949. *
  1950. * @package iTopORM
  1951. */
  1952. class AttributeText extends AttributeString
  1953. {
  1954. public function GetEditClass() {return ($this->GetFormat() == 'text') ? 'Text' : "HTML";}
  1955. protected function GetSQLCol($bFullSpec = false) {return "TEXT";}
  1956. public function GetSQLColumns($bFullSpec = false)
  1957. {
  1958. $aColumns = array();
  1959. $aColumns[$this->Get('sql')] = $this->GetSQLCol($bFullSpec);
  1960. if ($this->GetOptional('format', null) != null )
  1961. {
  1962. // Add the extra column only if the property 'format' is specified for the attribute
  1963. $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
  1964. if ($bFullSpec)
  1965. {
  1966. $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records
  1967. }
  1968. }
  1969. return $aColumns;
  1970. }
  1971. public function GetSQLExpressions($sPrefix = '')
  1972. {
  1973. if ($sPrefix == '')
  1974. {
  1975. $sPrefix = $this->Get('sql');
  1976. }
  1977. $aColumns = array();
  1978. // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
  1979. $aColumns[''] = $sPrefix;
  1980. if ($this->GetOptional('format', null) != null )
  1981. {
  1982. // Add the extra column only if the property 'format' is specified for the attribute
  1983. $aColumns['_format'] = $sPrefix.'_format';
  1984. }
  1985. return $aColumns;
  1986. }
  1987. public function GetMaxSize()
  1988. {
  1989. // Is there a way to know the current limitation for mysql?
  1990. // See mysql_field_len()
  1991. return 65535;
  1992. }
  1993. static public function RenderWikiHtml($sText)
  1994. {
  1995. $sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
  1996. if (preg_match_all($sPattern, $sText, $aAllMatches, PREG_SET_ORDER /* important !*/ |PREG_OFFSET_CAPTURE /* important ! */))
  1997. {
  1998. $aUrls = array();
  1999. $i = count($aAllMatches);
  2000. // Replace the URLs by an actual hyperlink <a href="...">...</a>
  2001. // Let's do it backwards so that the initial positions are not modified by the replacement
  2002. // This works if the matches are captured: in the order they occur in the string AND
  2003. // with their offset (i.e. position) inside the string
  2004. while($i > 0)
  2005. {
  2006. $i--;
  2007. $sUrl = $aAllMatches[$i][0][0]; // String corresponding to the main pattern
  2008. $iPos = $aAllMatches[$i][0][1]; // Position of the main pattern
  2009. $sText = substr_replace($sText, "<a href=\"$sUrl\">$sUrl</a>", $iPos, strlen($sUrl));
  2010. }
  2011. }
  2012. if (preg_match_all(WIKI_OBJECT_REGEXP, $sText, $aAllMatches, PREG_SET_ORDER))
  2013. {
  2014. foreach($aAllMatches as $iPos => $aMatches)
  2015. {
  2016. $sClass = $aMatches[1];
  2017. $sName = $aMatches[2];
  2018. if (MetaModel::IsValidClass($sClass))
  2019. {
  2020. $oObj = MetaModel::GetObjectByName($sClass, $sName, false /* MustBeFound */);
  2021. if (is_object($oObj))
  2022. {
  2023. // Propose a std link to the object
  2024. $sText = str_replace($aMatches[0], $oObj->GetHyperlink(), $sText);
  2025. }
  2026. else
  2027. {
  2028. // Propose a std link to the object
  2029. $sClassLabel = MetaModel::GetName($sClass);
  2030. $sText = str_replace($aMatches[0], "<span class=\"wiki_broken_link\">$sClassLabel:$sName</span>", $sText);
  2031. // Later: propose a link to create a new object
  2032. // Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name
  2033. //$sText = preg_replace('/\[\[(.+):(.+)\]\]/', '<a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$sClass.'&default[att1]=xxx&default[att2]=yyy">'.$sName.'</a>', $sText);
  2034. }
  2035. }
  2036. }
  2037. }
  2038. return $sText;
  2039. }
  2040. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  2041. {
  2042. $aStyles = array();
  2043. if ($this->GetWidth() != '')
  2044. {
  2045. $aStyles[] = 'width:'.$this->GetWidth();
  2046. }
  2047. if ($this->GetHeight() != '')
  2048. {
  2049. $aStyles[] = 'height:'.$this->GetHeight();
  2050. }
  2051. $sStyle = '';
  2052. if (count($aStyles) > 0)
  2053. {
  2054. $aStyles[] = 'overflow:auto';
  2055. $sStyle = 'style="'.implode(';', $aStyles).'"';
  2056. }
  2057. if ($this->GetFormat() == 'text')
  2058. {
  2059. $sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
  2060. $sValue = self::RenderWikiHtml($sValue);
  2061. return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
  2062. }
  2063. else
  2064. {
  2065. return "<div class=\"HTML\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
  2066. }
  2067. }
  2068. public function GetEditValue($sValue, $oHostObj = null)
  2069. {
  2070. if ($this->GetFormat() == 'text')
  2071. {
  2072. if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
  2073. {
  2074. foreach($aAllMatches as $iPos => $aMatches)
  2075. {
  2076. $sClass = $aMatches[1];
  2077. $sName = $aMatches[2];
  2078. if (MetaModel::IsValidClass($sClass))
  2079. {
  2080. $sClassLabel = MetaModel::GetName($sClass);
  2081. $sValue = str_replace($aMatches[0], "[[$sClassLabel:$sName]]", $sValue);
  2082. }
  2083. }
  2084. }
  2085. }
  2086. return $sValue;
  2087. }
  2088. /**
  2089. * For fields containing a potential markup, return the value without this markup
  2090. * @return string
  2091. */
  2092. public function GetAsPlainText($sValue, $oHostObj = null)
  2093. {
  2094. if ($this->GetFormat() == 'html')
  2095. {
  2096. return (string) utils::HtmlToText($this->GetEditValue($sValue, $oHostObj));
  2097. }
  2098. else
  2099. {
  2100. return parent::GetAsPlainText($sValue, $oHostObj);
  2101. }
  2102. }
  2103. public function MakeRealValue($proposedValue, $oHostObj)
  2104. {
  2105. $sValue = $proposedValue;
  2106. switch ($this->GetFormat())
  2107. {
  2108. case 'html':
  2109. $sValue = HTMLSanitizer::Sanitize($sValue);
  2110. break;
  2111. case 'text':
  2112. default:
  2113. if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
  2114. {
  2115. foreach($aAllMatches as $iPos => $aMatches)
  2116. {
  2117. $sClassLabel = $aMatches[1];
  2118. $sName = $aMatches[2];
  2119. if (!MetaModel::IsValidClass($sClassLabel))
  2120. {
  2121. $sClass = MetaModel::GetClassFromLabel($sClassLabel);
  2122. if ($sClass)
  2123. {
  2124. $sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue);
  2125. }
  2126. }
  2127. }
  2128. }
  2129. }
  2130. return $sValue;
  2131. }
  2132. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  2133. {
  2134. return Str::pure2xml($value);
  2135. }
  2136. public function GetWidth()
  2137. {
  2138. return $this->GetOptional('width', '');
  2139. }
  2140. public function GetHeight()
  2141. {
  2142. return $this->GetOptional('height', '');
  2143. }
  2144. /**
  2145. * The actual formatting of the field: either text (=plain text) or html (= text with HTML markup)
  2146. * @return string
  2147. */
  2148. public function GetFormat()
  2149. {
  2150. return $this->GetOptional('format', 'text');
  2151. }
  2152. /**
  2153. * Read the value from the row returned by the SQL query and transorms it to the appropriate
  2154. * internal format (either text or html)
  2155. * @see AttributeDBFieldVoid::FromSQLToValue()
  2156. */
  2157. public function FromSQLToValue($aCols, $sPrefix = '')
  2158. {
  2159. $value = $aCols[$sPrefix.''];
  2160. if ($this->GetOptional('format', null) != null )
  2161. {
  2162. // Read from the extra column only if the property 'format' is specified for the attribute
  2163. $sFormat = $aCols[$sPrefix.'_format'];
  2164. }
  2165. else
  2166. {
  2167. $sFormat = $this->GetFormat();
  2168. }
  2169. switch($sFormat)
  2170. {
  2171. case 'text':
  2172. if ($this->GetFormat() == 'html')
  2173. {
  2174. $value = utils::TextToHtml($value);
  2175. }
  2176. break;
  2177. case 'html':
  2178. if ($this->GetFormat() == 'text')
  2179. {
  2180. $value = utils::HtmlToText($value);
  2181. }
  2182. else
  2183. {
  2184. $value = InlineImage::FixUrls((string)$value);
  2185. }
  2186. break;
  2187. default:
  2188. // unknown format ??
  2189. }
  2190. return $value;
  2191. }
  2192. public function GetSQLValues($value)
  2193. {
  2194. $aValues = array();
  2195. $aValues[$this->Get("sql")] = $this->ScalarToSQL($value);
  2196. if ($this->GetOptional('format', null) != null )
  2197. {
  2198. // Add the extra column only if the property 'format' is specified for the attribute
  2199. $aValues[$this->Get("sql").'_format'] = $this->GetFormat();
  2200. }
  2201. return $aValues;
  2202. }
  2203. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  2204. {
  2205. switch($this->GetFormat())
  2206. {
  2207. case 'html':
  2208. if ($bConvertToPlainText)
  2209. {
  2210. $sValue = utils::HtmlToText((string)$sValue);
  2211. }
  2212. $sFrom = array("\r\n", $sTextQualifier);
  2213. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  2214. $sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
  2215. return $sTextQualifier.$sEscaped.$sTextQualifier;
  2216. break;
  2217. case 'text':
  2218. default:
  2219. return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText);
  2220. }
  2221. }
  2222. }
  2223. /**
  2224. * Map a log to an attribute
  2225. *
  2226. * @package iTopORM
  2227. */
  2228. class AttributeLongText extends AttributeText
  2229. {
  2230. protected function GetSQLCol($bFullSpec = false) {return "LONGTEXT";}
  2231. public function GetMaxSize()
  2232. {
  2233. // Is there a way to know the current limitation for mysql?
  2234. // See mysql_field_len()
  2235. return 65535*1024; // Limited... still 64 Mb!
  2236. }
  2237. }
  2238. /**
  2239. * An attibute that stores a case log (i.e journal)
  2240. *
  2241. * @package iTopORM
  2242. */
  2243. class AttributeCaseLog extends AttributeLongText
  2244. {
  2245. public function GetNullValue()
  2246. {
  2247. return '';
  2248. }
  2249. public function IsNull($proposedValue)
  2250. {
  2251. if (!($proposedValue instanceof ormCaseLog))
  2252. {
  2253. return ($proposedValue == '');
  2254. }
  2255. return ($proposedValue->GetText() == '');
  2256. }
  2257. public function ScalarToSQL($value)
  2258. {
  2259. if (!is_string($value) && !is_null($value))
  2260. {
  2261. throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass()));
  2262. }
  2263. return $value;
  2264. }
  2265. public function GetEditClass() {return "CaseLog";}
  2266. public function GetEditValue($sValue, $oHostObj = null)
  2267. {
  2268. if (!($sValue instanceOf ormCaseLog))
  2269. {
  2270. return '';
  2271. }
  2272. return $sValue->GetModifiedEntry();
  2273. }
  2274. /**
  2275. * For fields containing a potential markup, return the value without this markup
  2276. * @return string
  2277. */
  2278. public function GetAsPlainText($value, $oHostObj = null)
  2279. {
  2280. $value = $oObj->Get($sAttCode);
  2281. if ($value instanceOf ormCaseLog)
  2282. {
  2283. return $value->GetAsPlainText();
  2284. }
  2285. else
  2286. {
  2287. return (string) $value;
  2288. }
  2289. }
  2290. public function GetDefaultValue(DBObject $oHostObject = null) {return new ormCaseLog();}
  2291. public function Equals($val1, $val2) {return ($val1->GetText() == $val2->GetText());}
  2292. // Facilitate things: allow the user to Set the value from a string
  2293. public function MakeRealValue($proposedValue, $oHostObj)
  2294. {
  2295. if ($proposedValue instanceof ormCaseLog)
  2296. {
  2297. // Passthrough
  2298. $ret = $proposedValue;
  2299. }
  2300. else
  2301. {
  2302. // Append the new value if an instance of the object is supplied
  2303. //
  2304. $oPreviousLog = null;
  2305. if ($oHostObj != null)
  2306. {
  2307. $oPreviousLog = $oHostObj->Get($this->GetCode());
  2308. if (!is_object($oPreviousLog))
  2309. {
  2310. $oPreviousLog = $oHostObj->GetOriginal($this->GetCode());;
  2311. }
  2312. }
  2313. if (is_object($oPreviousLog))
  2314. {
  2315. $oCaseLog = clone($oPreviousLog);
  2316. }
  2317. else
  2318. {
  2319. $oCaseLog = new ormCaseLog();
  2320. }
  2321. if ($proposedValue instanceof stdClass)
  2322. {
  2323. $oCaseLog->AddLogEntryFromJSON($proposedValue);
  2324. }
  2325. else
  2326. {
  2327. if (strlen($proposedValue) > 0)
  2328. {
  2329. $oCaseLog->AddLogEntry($proposedValue);
  2330. }
  2331. }
  2332. $ret = $oCaseLog;
  2333. }
  2334. return $ret;
  2335. }
  2336. public function GetSQLExpressions($sPrefix = '')
  2337. {
  2338. if ($sPrefix == '')
  2339. {
  2340. $sPrefix = $this->Get('sql');
  2341. }
  2342. $aColumns = array();
  2343. // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
  2344. $aColumns[''] = $sPrefix;
  2345. $aColumns['_index'] = $sPrefix.'_index';
  2346. return $aColumns;
  2347. }
  2348. public function FromSQLToValue($aCols, $sPrefix = '')
  2349. {
  2350. if (!array_key_exists($sPrefix, $aCols))
  2351. {
  2352. $sAvailable = implode(', ', array_keys($aCols));
  2353. throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
  2354. }
  2355. $sLog = $aCols[$sPrefix];
  2356. if (isset($aCols[$sPrefix.'_index']))
  2357. {
  2358. $sIndex = $aCols[$sPrefix.'_index'];
  2359. }
  2360. else
  2361. {
  2362. // For backward compatibility, allow the current state to be: 1 log, no index
  2363. $sIndex = '';
  2364. }
  2365. if (strlen($sIndex) > 0)
  2366. {
  2367. $aIndex = unserialize($sIndex);
  2368. $value = new ormCaseLog($sLog, $aIndex);
  2369. }
  2370. else
  2371. {
  2372. $value = new ormCaseLog($sLog);
  2373. }
  2374. return $value;
  2375. }
  2376. public function GetSQLValues($value)
  2377. {
  2378. if (!($value instanceOf ormCaseLog))
  2379. {
  2380. $value = new ormCaseLog('');
  2381. }
  2382. $aValues = array();
  2383. $aValues[$this->GetCode()] = $value->GetText();
  2384. $aValues[$this->GetCode().'_index'] = serialize($value->GetIndex());
  2385. return $aValues;
  2386. }
  2387. public function GetSQLColumns($bFullSpec = false)
  2388. {
  2389. $aColumns = array();
  2390. $aColumns[$this->GetCode()] = 'LONGTEXT'; // 2^32 (4 Gb)
  2391. $aColumns[$this->GetCode().'_index'] = 'BLOB';
  2392. return $aColumns;
  2393. }
  2394. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  2395. {
  2396. if ($value instanceOf ormCaseLog)
  2397. {
  2398. $sContent = $value->GetAsHTML(null, false, array(__class__, 'RenderWikiHtml'));
  2399. }
  2400. else
  2401. {
  2402. $sContent = '';
  2403. }
  2404. $aStyles = array();
  2405. if ($this->GetWidth() != '')
  2406. {
  2407. $aStyles[] = 'width:'.$this->GetWidth();
  2408. }
  2409. if ($this->GetHeight() != '')
  2410. {
  2411. $aStyles[] = 'height:'.$this->GetHeight();
  2412. }
  2413. $sStyle = '';
  2414. if (count($aStyles) > 0)
  2415. {
  2416. $sStyle = 'style="'.implode(';', $aStyles).'"';
  2417. }
  2418. return "<div class=\"caselog\" $sStyle>".$sContent.'</div>'; }
  2419. public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  2420. {
  2421. if ($value instanceOf ormCaseLog)
  2422. {
  2423. return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText);
  2424. }
  2425. else
  2426. {
  2427. return '';
  2428. }
  2429. }
  2430. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  2431. {
  2432. if ($value instanceOf ormCaseLog)
  2433. {
  2434. return parent::GetAsXML($value->GetText(), $oHostObject, $bLocalize);
  2435. }
  2436. else
  2437. {
  2438. return '';
  2439. }
  2440. }
  2441. /**
  2442. * List the available verbs for 'GetForTemplate'
  2443. */
  2444. public function EnumTemplateVerbs()
  2445. {
  2446. return array(
  2447. '' => 'Plain text representation of all the log entries',
  2448. 'head' => 'Plain text representation of the latest entry',
  2449. 'head_html' => 'HTML representation of the latest entry',
  2450. 'html' => 'HTML representation of all the log entries',
  2451. );
  2452. }
  2453. /**
  2454. * Get various representations of the value, for insertion into a template (e.g. in Notifications)
  2455. * @param $value mixed The current value of the field
  2456. * @param $sVerb string The verb specifying the representation of the value
  2457. * @param $oHostObject DBObject The object
  2458. * @param $bLocalize bool Whether or not to localize the value
  2459. */
  2460. public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
  2461. {
  2462. switch($sVerb)
  2463. {
  2464. case '':
  2465. return $value->GetText();
  2466. case 'head':
  2467. return $value->GetLatestEntry();
  2468. case 'head_html':
  2469. return '<div class="caselog_entry">'.str_replace( array( "\r\n", "\n", "\r"), "<br/>", htmlentities($value->GetLatestEntry(), ENT_QUOTES, 'UTF-8')).'</div>';
  2470. case 'html':
  2471. return $value->GetAsEmailHtml();
  2472. default:
  2473. throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
  2474. }
  2475. }
  2476. /**
  2477. * Helper to get a value that will be JSON encoded
  2478. * The operation is the opposite to FromJSONToValue
  2479. */
  2480. public function GetForJSON($value)
  2481. {
  2482. return $value->GetForJSON();
  2483. }
  2484. /**
  2485. * Helper to form a value, given JSON decoded data
  2486. * The operation is the opposite to GetForJSON
  2487. */
  2488. public function FromJSONToValue($json)
  2489. {
  2490. if (is_string($json))
  2491. {
  2492. // Will be correctly handled in MakeRealValue
  2493. $ret = $json;
  2494. }
  2495. else
  2496. {
  2497. if (isset($json->add_item))
  2498. {
  2499. // Will be correctly handled in MakeRealValue
  2500. $ret = $json->add_item;
  2501. if (!isset($ret->message))
  2502. {
  2503. throw new Exception("Missing mandatory entry: 'message'");
  2504. }
  2505. }
  2506. else
  2507. {
  2508. $ret = ormCaseLog::FromJSON($json);
  2509. }
  2510. }
  2511. return $ret;
  2512. }
  2513. public function Fingerprint($value)
  2514. {
  2515. $sFingerprint = '';
  2516. if ($value instanceOf ormCaseLog)
  2517. {
  2518. $sFingerprint = $value->GetText();
  2519. }
  2520. return $sFingerprint;
  2521. }
  2522. /**
  2523. * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
  2524. * @return string
  2525. */
  2526. public function GetFormat()
  2527. {
  2528. return $this->GetOptional('format', 'html'); // default format for case logs is now HTML
  2529. }
  2530. }
  2531. /**
  2532. * Map a text column (size > ?), containing HTML code, to an attribute
  2533. *
  2534. * @package iTopORM
  2535. */
  2536. class AttributeHTML extends AttributeLongText
  2537. {
  2538. public function GetSQLColumns($bFullSpec = false)
  2539. {
  2540. $aColumns = array();
  2541. $aColumns[$this->GetCode()] = $this->GetSQLCol();
  2542. if ($this->GetOptional('format', null) != null )
  2543. {
  2544. // Add the extra column only if the property 'format' is specified for the attribute
  2545. $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
  2546. if ($bFullSpec)
  2547. {
  2548. $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records
  2549. }
  2550. }
  2551. return $aColumns;
  2552. }
  2553. /**
  2554. * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
  2555. * @return string
  2556. */
  2557. public function GetFormat()
  2558. {
  2559. return $this->GetOptional('format', 'html'); // Defaults to HTML
  2560. }
  2561. }
  2562. /**
  2563. * Specialization of a string: email
  2564. *
  2565. * @package iTopORM
  2566. */
  2567. class AttributeEmailAddress extends AttributeString
  2568. {
  2569. public function GetValidationPattern()
  2570. {
  2571. return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('email_validation_pattern').'$');
  2572. }
  2573. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  2574. {
  2575. if (empty($sValue)) return '';
  2576. return '<a class="mailto" href="mailto:'.$sValue.'">'.parent::GetAsHTML($sValue).'</a>';
  2577. }
  2578. }
  2579. /**
  2580. * Specialization of a string: IP address
  2581. *
  2582. * @package iTopORM
  2583. */
  2584. class AttributeIPAddress extends AttributeString
  2585. {
  2586. public function GetValidationPattern()
  2587. {
  2588. $sNum = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])';
  2589. return "^($sNum\\.$sNum\\.$sNum\\.$sNum)$";
  2590. }
  2591. public function GetOrderBySQLExpressions($sClassAlias)
  2592. {
  2593. // Note: This is the responsibility of this function to place backticks around column aliases
  2594. return array('INET_ATON(`'.$sClassAlias.$this->GetCode().'`)');
  2595. }
  2596. }
  2597. /**
  2598. * Specialization of a string: OQL expression
  2599. *
  2600. * @package iTopORM
  2601. */
  2602. class AttributeOQL extends AttributeText
  2603. {
  2604. public function GetEditClass() {return "OQLExpression";}
  2605. }
  2606. /**
  2607. * Specialization of a string: template (contains iTop placeholders like $current_contact_id$ or $this->name$)
  2608. *
  2609. * @package iTopORM
  2610. */
  2611. class AttributeTemplateString extends AttributeString
  2612. {
  2613. }
  2614. /**
  2615. * Specialization of a text: template (contains iTop placeholders like $current_contact_id$ or $this->name$)
  2616. *
  2617. * @package iTopORM
  2618. */
  2619. class AttributeTemplateText extends AttributeText
  2620. {
  2621. }
  2622. /**
  2623. * Specialization of a HTML: template (contains iTop placeholders like $current_contact_id$ or $this->name$)
  2624. *
  2625. * @package iTopORM
  2626. */
  2627. class AttributeTemplateHTML extends AttributeText
  2628. {
  2629. public function GetSQLColumns($bFullSpec = false)
  2630. {
  2631. $aColumns = array();
  2632. $aColumns[$this->GetCode()] = $this->GetSQLCol();
  2633. if ($this->GetOptional('format', null) != null )
  2634. {
  2635. // Add the extra column only if the property 'format' is specified for the attribute
  2636. $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
  2637. if ($bFullSpec)
  2638. {
  2639. $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records
  2640. }
  2641. }
  2642. return $aColumns;
  2643. }
  2644. /**
  2645. * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
  2646. * @return string
  2647. */
  2648. public function GetFormat()
  2649. {
  2650. return $this->GetOptional('format', 'html'); // Defaults to HTML
  2651. }
  2652. }
  2653. /**
  2654. * Map a enum column to an attribute
  2655. *
  2656. * @package iTopORM
  2657. */
  2658. class AttributeEnum extends AttributeString
  2659. {
  2660. static public function ListExpectedParams()
  2661. {
  2662. return parent::ListExpectedParams();
  2663. //return array_merge(parent::ListExpectedParams(), array());
  2664. }
  2665. public function GetEditClass() {return "String";}
  2666. protected function GetSQLCol($bFullSpec = false)
  2667. {
  2668. $oValDef = $this->GetValuesDef();
  2669. if ($oValDef)
  2670. {
  2671. $aValues = CMDBSource::Quote(array_keys($oValDef->GetValues(array(), "")), true);
  2672. }
  2673. else
  2674. {
  2675. $aValues = array();
  2676. }
  2677. if (count($aValues) > 0)
  2678. {
  2679. // The syntax used here do matters
  2680. // In particular, I had to remove unnecessary spaces to
  2681. // make sure that this string will match the field type returned by the DB
  2682. // (used to perform a comparison between the current DB format and the data model)
  2683. return "ENUM(".implode(",", $aValues).")".($bFullSpec ? $this->GetSQLColSpec() : '');
  2684. }
  2685. else
  2686. {
  2687. return "VARCHAR(255)".($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax!
  2688. }
  2689. }
  2690. protected function GetSQLColSpec()
  2691. {
  2692. $default = $this->ScalarToSQL($this->GetDefaultValue());
  2693. if (is_null($default))
  2694. {
  2695. $sRet = '';
  2696. }
  2697. else
  2698. {
  2699. // ENUMs values are strings so the default value must be a string as well,
  2700. // otherwise MySQL interprets the number as the zero-based index of the value in the list (i.e. the nth value in the list)
  2701. $sRet = " DEFAULT ".CMDBSource::Quote($default);
  2702. }
  2703. return $sRet;
  2704. }
  2705. public function ScalarToSQL($value)
  2706. {
  2707. // Note: for strings, the null value is an empty string and it is recorded as such in the DB
  2708. // but that wasn't working for enums, because '' is NOT one of the allowed values
  2709. // that's why a null value must be forced to a real null
  2710. $value = parent::ScalarToSQL($value);
  2711. if ($this->IsNull($value))
  2712. {
  2713. return null;
  2714. }
  2715. else
  2716. {
  2717. return $value;
  2718. }
  2719. }
  2720. public function RequiresIndex()
  2721. {
  2722. return false;
  2723. }
  2724. public function GetBasicFilterOperators()
  2725. {
  2726. return parent::GetBasicFilterOperators();
  2727. }
  2728. public function GetBasicFilterLooseOperator()
  2729. {
  2730. return '=';
  2731. }
  2732. public function GetBasicFilterSQLExpr($sOpCode, $value)
  2733. {
  2734. return parent::GetBasicFilterSQLExpr($sOpCode, $value);
  2735. }
  2736. public function GetValueLabel($sValue)
  2737. {
  2738. if (is_null($sValue))
  2739. {
  2740. // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
  2741. $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, Dict::S('Enum:Undefined'));
  2742. }
  2743. else
  2744. {
  2745. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, true /*user lang*/);
  2746. if (is_null($sLabel))
  2747. {
  2748. $sDefault = str_replace('_', ' ', $sValue);
  2749. // Browse the hierarchy again, accepting default (english) translations
  2750. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, false);
  2751. }
  2752. }
  2753. return $sLabel;
  2754. }
  2755. public function GetValueDescription($sValue)
  2756. {
  2757. if (is_null($sValue))
  2758. {
  2759. // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
  2760. $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', Dict::S('Enum:Undefined'));
  2761. }
  2762. else
  2763. {
  2764. $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', '', true /* user language only */);
  2765. if (strlen($sDescription) == 0)
  2766. {
  2767. $sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
  2768. if ($sParentClass)
  2769. {
  2770. if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
  2771. {
  2772. $oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
  2773. $sDescription = $oAttDef->GetValueDescription($sValue);
  2774. }
  2775. }
  2776. }
  2777. }
  2778. return $sDescription;
  2779. }
  2780. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  2781. {
  2782. if ($bLocalize)
  2783. {
  2784. $sLabel = $this->GetValueLabel($sValue);
  2785. $sDescription = $this->GetValueDescription($sValue);
  2786. // later, we could imagine a detailed description in the title
  2787. $sRes = "<span title=\"$sDescription\">".parent::GetAsHtml($sLabel)."</span>";
  2788. }
  2789. else
  2790. {
  2791. $sRes = parent::GetAsHtml($sValue, $oHostObject, $bLocalize);
  2792. }
  2793. return $sRes;
  2794. }
  2795. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  2796. {
  2797. if (is_null($value))
  2798. {
  2799. $sFinalValue = '';
  2800. }
  2801. elseif ($bLocalize)
  2802. {
  2803. $sFinalValue = $this->GetValueLabel($value);
  2804. }
  2805. else
  2806. {
  2807. $sFinalValue = $value;
  2808. }
  2809. $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize);
  2810. return $sRes;
  2811. }
  2812. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  2813. {
  2814. if (is_null($sValue))
  2815. {
  2816. $sFinalValue = '';
  2817. }
  2818. elseif ($bLocalize)
  2819. {
  2820. $sFinalValue = $this->GetValueLabel($sValue);
  2821. }
  2822. else
  2823. {
  2824. $sFinalValue = $sValue;
  2825. }
  2826. $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize);
  2827. return $sRes;
  2828. }
  2829. public function GetEditValue($sValue, $oHostObj = null)
  2830. {
  2831. if (is_null($sValue))
  2832. {
  2833. return '';
  2834. }
  2835. else
  2836. {
  2837. return $this->GetValueLabel($sValue);
  2838. }
  2839. }
  2840. /**
  2841. * Helper to get a value that will be JSON encoded
  2842. * The operation is the opposite to FromJSONToValue
  2843. */
  2844. public function GetForJSON($value)
  2845. {
  2846. return $value;
  2847. }
  2848. public function GetAllowedValues($aArgs = array(), $sContains = '')
  2849. {
  2850. $aRawValues = parent::GetAllowedValues($aArgs, $sContains);
  2851. if (is_null($aRawValues)) return null;
  2852. $aLocalizedValues = array();
  2853. foreach ($aRawValues as $sKey => $sValue)
  2854. {
  2855. $aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey));
  2856. }
  2857. return $aLocalizedValues;
  2858. }
  2859. /**
  2860. * An enum can be localized
  2861. */
  2862. public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
  2863. {
  2864. if ($bLocalizedValue)
  2865. {
  2866. // Lookup for the value matching the input
  2867. //
  2868. $sFoundValue = null;
  2869. $aRawValues = parent::GetAllowedValues();
  2870. if (!is_null($aRawValues))
  2871. {
  2872. foreach ($aRawValues as $sKey => $sValue)
  2873. {
  2874. $sRefValue = $this->GetValueLabel($sKey);
  2875. if ($sProposedValue == $sRefValue)
  2876. {
  2877. $sFoundValue = $sKey;
  2878. break;
  2879. }
  2880. }
  2881. }
  2882. if (is_null($sFoundValue))
  2883. {
  2884. return null;
  2885. }
  2886. return $this->MakeRealValue($sFoundValue, null);
  2887. }
  2888. else
  2889. {
  2890. return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier);
  2891. }
  2892. }
  2893. /**
  2894. * Processes the input value to align it with the values supported
  2895. * by this type of attribute. In this case: turns empty strings into nulls
  2896. * @param mixed $proposedValue The value to be set for the attribute
  2897. * @return mixed The actual value that will be set
  2898. */
  2899. public function MakeRealValue($proposedValue, $oHostObj)
  2900. {
  2901. if ($proposedValue == '') return null;
  2902. return parent::MakeRealValue($proposedValue, $oHostObj);
  2903. }
  2904. public function GetOrderByHint()
  2905. {
  2906. $aValues = $this->GetAllowedValues();
  2907. return Dict::Format('UI:OrderByHint_Values', implode(', ', $aValues));
  2908. }
  2909. }
  2910. /**
  2911. * A meta enum is an aggregation of enum from subclasses into an enum of a base class
  2912. * It has been designed is to cope with the fact that statuses must be defined in leaf classes, while it makes sense to
  2913. * have a superstatus available on the root classe(s)
  2914. *
  2915. * @package iTopORM
  2916. */
  2917. class AttributeMetaEnum extends AttributeEnum
  2918. {
  2919. static public function ListExpectedParams()
  2920. {
  2921. return array('allowed_values', 'sql', 'default_value', 'mapping');
  2922. }
  2923. public function IsWritable()
  2924. {
  2925. return false;
  2926. }
  2927. public function RequiresIndex()
  2928. {
  2929. return true;
  2930. }
  2931. public function GetPrerequisiteAttributes($sClass = null)
  2932. {
  2933. if (is_null($sClass))
  2934. {
  2935. $sClass = $this->GetHostClass();
  2936. }
  2937. $aMappingData = $this->GetMapRule($sClass);
  2938. if ($aMappingData == null)
  2939. {
  2940. $aRet = array();
  2941. }
  2942. else
  2943. {
  2944. $aRet = array($aMappingData['attcode']);
  2945. }
  2946. return $aRet;
  2947. }
  2948. /**
  2949. * Overload the standard so as to leave the data unsorted
  2950. *
  2951. * @param array $aArgs
  2952. * @param string $sContains
  2953. * @return array|null
  2954. */
  2955. public function GetAllowedValues($aArgs = array(), $sContains = '')
  2956. {
  2957. $oValSetDef = $this->GetValuesDef();
  2958. if (!$oValSetDef) return null;
  2959. $aRawValues = $oValSetDef->GetValueList();
  2960. if (is_null($aRawValues)) return null;
  2961. $aLocalizedValues = array();
  2962. foreach ($aRawValues as $sKey => $sValue)
  2963. {
  2964. $aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey));
  2965. }
  2966. return $aLocalizedValues;
  2967. }
  2968. /**
  2969. * Returns the meta value for the given object.
  2970. * See also MetaModel::RebuildMetaEnums() that must be maintained when MapValue changes
  2971. *
  2972. * @param $oObject
  2973. * @return mixed
  2974. * @throws Exception
  2975. */
  2976. public function MapValue($oObject)
  2977. {
  2978. $aMappingData = $this->GetMapRule(get_class($oObject));
  2979. if ($aMappingData == null)
  2980. {
  2981. $sRet = $this->GetDefaultValue();
  2982. }
  2983. else
  2984. {
  2985. $sAttCode = $aMappingData['attcode'];
  2986. $value = $oObject->Get($sAttCode);
  2987. if (array_key_exists($value, $aMappingData['values']))
  2988. {
  2989. $sRet = $aMappingData['values'][$value];
  2990. }
  2991. elseif ($this->GetDefaultValue() != '')
  2992. {
  2993. $sRet = $this->GetDefaultValue();
  2994. }
  2995. else
  2996. {
  2997. throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()).'::'.$this->GetCode());
  2998. }
  2999. }
  3000. return $sRet;
  3001. }
  3002. public function GetMapRule($sClass)
  3003. {
  3004. $aMappings = $this->Get('mapping');
  3005. if (array_key_exists($sClass, $aMappings))
  3006. {
  3007. $aMappingData = $aMappings[$sClass];
  3008. }
  3009. else
  3010. {
  3011. $sParent = MetaModel::GetParentClass($sClass);
  3012. $aMappingData = $this->GetMapRule($sParent);
  3013. }
  3014. return $aMappingData;
  3015. }
  3016. }
  3017. /**
  3018. * Map a date+time column to an attribute
  3019. *
  3020. * @package iTopORM
  3021. */
  3022. class AttributeDateTime extends AttributeDBField
  3023. {
  3024. static public function GetDateFormat()
  3025. {
  3026. return "Y-m-d H:i:s";
  3027. }
  3028. static public function ListExpectedParams()
  3029. {
  3030. return parent::ListExpectedParams();
  3031. //return array_merge(parent::ListExpectedParams(), array());
  3032. }
  3033. public function GetEditClass() {return "DateTime";}
  3034. protected function GetSQLCol($bFullSpec = false) {return "DATETIME";}
  3035. public static function GetAsUnixSeconds($value)
  3036. {
  3037. $oDeadlineDateTime = new DateTime($value);
  3038. $iUnixSeconds = $oDeadlineDateTime->format('U');
  3039. return $iUnixSeconds;
  3040. }
  3041. // This has been done at the time when itop was using TIMESTAMP columns,
  3042. // now that iTop is using DATETIME columns, it seems possible to have IsNullAllowed returning false... later when this is needed
  3043. public function IsNullAllowed() {return true;}
  3044. public function GetDefaultValue(DBObject $oHostObject = null)
  3045. {
  3046. $default = parent::GetDefaultValue($oHostObject);
  3047. if (!parent::IsNullAllowed())
  3048. {
  3049. if (empty($default))
  3050. {
  3051. $default = date($this->GetDateFormat());
  3052. }
  3053. }
  3054. return $default;
  3055. }
  3056. // END OF THE WORKAROUND
  3057. ///////////////////////////////////////////////////////////////
  3058. public function GetValidationPattern()
  3059. {
  3060. return "^(([0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30))))( (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])(:([0-5][0-9])){0,1}){0,1}|0000-00-00 00:00:00|0000-00-00)$";
  3061. }
  3062. public function GetBasicFilterOperators()
  3063. {
  3064. return array(
  3065. "="=>"equals",
  3066. "!="=>"differs from",
  3067. "<"=>"before",
  3068. "<="=>"before",
  3069. ">"=>"after (strictly)",
  3070. ">="=>"after",
  3071. "SameDay"=>"same day (strip time)",
  3072. "SameMonth"=>"same year/month",
  3073. "SameYear"=>"same year",
  3074. "Today"=>"today",
  3075. ">|"=>"after today + N days",
  3076. "<|"=>"before today + N days",
  3077. "=|"=>"equals today + N days",
  3078. );
  3079. }
  3080. public function GetBasicFilterLooseOperator()
  3081. {
  3082. // Unless we implement a "same xxx, depending on given precision" !
  3083. return "=";
  3084. }
  3085. public function GetBasicFilterSQLExpr($sOpCode, $value)
  3086. {
  3087. $sQValue = CMDBSource::Quote($value);
  3088. switch ($sOpCode)
  3089. {
  3090. case '=':
  3091. case '!=':
  3092. case '<':
  3093. case '<=':
  3094. case '>':
  3095. case '>=':
  3096. return $this->GetSQLExpr()." $sOpCode $sQValue";
  3097. case 'SameDay':
  3098. return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)";
  3099. case 'SameMonth':
  3100. return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')";
  3101. case 'SameYear':
  3102. return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)";
  3103. case 'Today':
  3104. return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()";
  3105. case '>|':
  3106. return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
  3107. case '<|':
  3108. return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
  3109. case '=|':
  3110. return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
  3111. default:
  3112. return $this->GetSQLExpr()." = $sQValue";
  3113. }
  3114. }
  3115. public function MakeRealValue($proposedValue, $oHostObj)
  3116. {
  3117. if (is_null($proposedValue))
  3118. {
  3119. return null;
  3120. }
  3121. if (is_string($proposedValue) && ($proposedValue == "") && $this->IsNullAllowed())
  3122. {
  3123. return null;
  3124. }
  3125. if (!is_numeric($proposedValue))
  3126. {
  3127. return $proposedValue;
  3128. }
  3129. return date(self::GetDateFormat(), $proposedValue);
  3130. }
  3131. public function ScalarToSQL($value)
  3132. {
  3133. if (is_null($value))
  3134. {
  3135. return null;
  3136. }
  3137. elseif (empty($value))
  3138. {
  3139. // Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null.
  3140. return '0000-00-00 00:00:00';
  3141. }
  3142. return $value;
  3143. }
  3144. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  3145. {
  3146. return Str::pure2html($value);
  3147. }
  3148. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  3149. {
  3150. return Str::pure2xml($value);
  3151. }
  3152. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  3153. {
  3154. $sFrom = array("\r\n", $sTextQualifier);
  3155. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  3156. $sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
  3157. return $sTextQualifier.$sEscaped.$sTextQualifier;
  3158. }
  3159. /**
  3160. * Parses a string to find some smart search patterns and build the corresponding search/OQL condition
  3161. * Each derived class is reponsible for defining and processing their own smart patterns, the base class
  3162. * does nothing special, and just calls the default (loose) operator
  3163. * @param string $sSearchText The search string to analyze for smart patterns
  3164. * @param FieldExpression The FieldExpression representing the atttribute code in this OQL query
  3165. * @param Hash $aParams Values of the query parameters
  3166. * @return Expression The search condition to be added (AND) to the current search
  3167. */
  3168. public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams)
  3169. {
  3170. // Possible smart patterns
  3171. $aPatterns = array(
  3172. 'between' => array('pattern' => '/^\[(.*),(.*)\]$/', 'operator' => 'n/a'),
  3173. 'greater than or equal' => array('pattern' => '/^>=(.*)$/', 'operator' => '>='),
  3174. 'greater than' => array('pattern' => '/^>(.*)$/', 'operator' => '>'),
  3175. 'less than or equal' => array('pattern' => '/^<=(.*)$/', 'operator' => '<='),
  3176. 'less than' => array('pattern' => '/^<(.*)$/', 'operator' => '<'),
  3177. );
  3178. $sPatternFound = '';
  3179. $aMatches = array();
  3180. foreach($aPatterns as $sPatName => $sPattern)
  3181. {
  3182. if (preg_match($sPattern['pattern'], $sSearchText, $aMatches))
  3183. {
  3184. $sPatternFound = $sPatName;
  3185. break;
  3186. }
  3187. }
  3188. switch($sPatternFound)
  3189. {
  3190. case 'between':
  3191. $sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1';
  3192. $oRightExpr = new VariableExpression($sParamName1);
  3193. $aParams[$sParamName1] = $aMatches[1];
  3194. $oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr);
  3195. $sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2';
  3196. $oRightExpr = new VariableExpression($sParamName2);
  3197. $sOperator = $this->GetBasicFilterLooseOperator();
  3198. $aParams[$sParamName2] = $aMatches[2];
  3199. $oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr);
  3200. $oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2);
  3201. break;
  3202. case 'greater than':
  3203. case 'greater than or equal':
  3204. case 'less than':
  3205. case 'less than or equal':
  3206. $sSQLOperator = $aPatterns[$sPatternFound]['operator'];
  3207. $sParamName = $oField->GetParent().'_'.$oField->GetName();
  3208. $oRightExpr = new VariableExpression($sParamName);
  3209. $aParams[$sParamName] = $aMatches[1];
  3210. $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr);
  3211. break;
  3212. default:
  3213. $oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams);
  3214. }
  3215. return $oNewCondition;
  3216. }
  3217. }
  3218. /**
  3219. * Store a duration as a number of seconds
  3220. *
  3221. * @package iTopORM
  3222. */
  3223. class AttributeDuration extends AttributeInteger
  3224. {
  3225. public function GetEditClass() {return "Duration";}
  3226. protected function GetSQLCol($bFullSpec = false) {return "INT(11) UNSIGNED";}
  3227. public function GetNullValue() {return '0';}
  3228. public function MakeRealValue($proposedValue, $oHostObj)
  3229. {
  3230. if (is_null($proposedValue)) return null;
  3231. if (!is_numeric($proposedValue)) return null;
  3232. if ( ((int)$proposedValue) < 0) return null;
  3233. return (int)$proposedValue;
  3234. }
  3235. public function ScalarToSQL($value)
  3236. {
  3237. if (is_null($value))
  3238. {
  3239. return null;
  3240. }
  3241. return $value;
  3242. }
  3243. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  3244. {
  3245. return Str::pure2html(self::FormatDuration($value));
  3246. }
  3247. public static function FormatDuration($duration)
  3248. {
  3249. $aDuration = self::SplitDuration($duration);
  3250. $sResult = '';
  3251. if ($duration < 60)
  3252. {
  3253. // Less than 1 min
  3254. $sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']);
  3255. }
  3256. else if ($duration < 3600)
  3257. {
  3258. // less than 1 hour, display it in minutes/seconds
  3259. $sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']);
  3260. }
  3261. else if ($duration < 86400)
  3262. {
  3263. // Less than 1 day, display it in hours/minutes/seconds
  3264. $sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']);
  3265. }
  3266. else
  3267. {
  3268. // more than 1 day, display it in days/hours/minutes/seconds
  3269. $sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'], $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']);
  3270. }
  3271. return $sResult;
  3272. }
  3273. static function SplitDuration($duration)
  3274. {
  3275. $duration = (int) $duration;
  3276. $days = floor($duration / 86400);
  3277. $hours = floor(($duration - (86400*$days)) / 3600);
  3278. $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60);
  3279. $seconds = ($duration % 60); // modulo
  3280. return array( 'days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds );
  3281. }
  3282. }
  3283. /**
  3284. * Map a date+time column to an attribute
  3285. *
  3286. * @package iTopORM
  3287. */
  3288. class AttributeDate extends AttributeDateTime
  3289. {
  3290. const MYDATEFORMAT = "Y-m-d";
  3291. static public function GetDateFormat()
  3292. {
  3293. return "Y-m-d";
  3294. }
  3295. static public function ListExpectedParams()
  3296. {
  3297. return parent::ListExpectedParams();
  3298. //return array_merge(parent::ListExpectedParams(), array());
  3299. }
  3300. public function GetEditClass() {return "Date";}
  3301. protected function GetSQLCol($bFullSpec = false) {return "DATE";}
  3302. public function GetValidationPattern()
  3303. {
  3304. return "^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))$";
  3305. }
  3306. }
  3307. /**
  3308. * A dead line stored as a date & time
  3309. * The only difference with the DateTime attribute is the display:
  3310. * relative to the current time
  3311. */
  3312. class AttributeDeadline extends AttributeDateTime
  3313. {
  3314. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  3315. {
  3316. $sResult = self::FormatDeadline($value);
  3317. return $sResult;
  3318. }
  3319. public static function FormatDeadline($value)
  3320. {
  3321. $sResult = '';
  3322. if ($value !== null)
  3323. {
  3324. $iValue = AttributeDateTime::GetAsUnixSeconds($value);
  3325. $sDate = $value;
  3326. $difference = $iValue - time();
  3327. if ($difference >= 0)
  3328. {
  3329. $sDifference = self::FormatDuration($difference);
  3330. }
  3331. else
  3332. {
  3333. $sDifference = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference));
  3334. }
  3335. $sFormat = MetaModel::GetConfig()->Get('deadline_format', '$difference$');
  3336. $sResult = str_replace(array('$date$', '$difference$'), array($sDate, $sDifference), $sFormat);
  3337. }
  3338. return $sResult;
  3339. }
  3340. static function FormatDuration($duration)
  3341. {
  3342. $days = floor($duration / 86400);
  3343. $hours = floor(($duration - (86400*$days)) / 3600);
  3344. $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60);
  3345. $sResult = '';
  3346. if ($duration < 60)
  3347. {
  3348. // Less than 1 min
  3349. $sResult =Dict::S('UI:Deadline_LessThan1Min');
  3350. }
  3351. else if ($duration < 3600)
  3352. {
  3353. // less than 1 hour, display it in minutes
  3354. $sResult =Dict::Format('UI:Deadline_Minutes', $minutes);
  3355. }
  3356. else if ($duration < 86400)
  3357. {
  3358. // Less that 1 day, display it in hours/minutes
  3359. $sResult =Dict::Format('UI:Deadline_Hours_Minutes', $hours, $minutes);
  3360. }
  3361. else
  3362. {
  3363. // Less that 1 day, display it in hours/minutes
  3364. $sResult =Dict::Format('UI:Deadline_Days_Hours_Minutes', $days, $hours, $minutes);
  3365. }
  3366. return $sResult;
  3367. }
  3368. }
  3369. /**
  3370. * Map a foreign key to an attribute
  3371. * AttributeExternalKey and AttributeExternalField may be an external key
  3372. * the difference is that AttributeExternalKey corresponds to a column into the defined table
  3373. * where an AttributeExternalField corresponds to a column into another table (class)
  3374. *
  3375. * @package iTopORM
  3376. */
  3377. class AttributeExternalKey extends AttributeDBFieldVoid
  3378. {
  3379. static public function ListExpectedParams()
  3380. {
  3381. return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete"));
  3382. }
  3383. public function GetEditClass() {return "ExtKey";}
  3384. protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");}
  3385. public function RequiresIndex()
  3386. {
  3387. return true;
  3388. }
  3389. public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;}
  3390. public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");}
  3391. public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;}
  3392. public function GetKeyAttCode() {return $this->GetCode();}
  3393. public function GetDisplayStyle() { return $this->GetOptional('display_style', 'select'); }
  3394. public function GetDefaultValue(DBObject $oHostObject = null) {return 0;}
  3395. public function IsNullAllowed()
  3396. {
  3397. if (MetaModel::GetConfig()->Get('disable_mandatory_ext_keys'))
  3398. {
  3399. return true;
  3400. }
  3401. return $this->Get("is_null_allowed");
  3402. }
  3403. public function GetBasicFilterOperators()
  3404. {
  3405. return parent::GetBasicFilterOperators();
  3406. }
  3407. public function GetBasicFilterLooseOperator()
  3408. {
  3409. return parent::GetBasicFilterLooseOperator();
  3410. }
  3411. public function GetBasicFilterSQLExpr($sOpCode, $value)
  3412. {
  3413. return parent::GetBasicFilterSQLExpr($sOpCode, $value);
  3414. }
  3415. // overloaded here so that an ext key always have the answer to
  3416. // "what are your possible values?"
  3417. public function GetValuesDef()
  3418. {
  3419. $oValSetDef = $this->Get("allowed_values");
  3420. if (!$oValSetDef)
  3421. {
  3422. // Let's propose every existing value
  3423. $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass());
  3424. }
  3425. return $oValSetDef;
  3426. }
  3427. public function GetAllowedValues($aArgs = array(), $sContains = '')
  3428. {
  3429. //throw new Exception("GetAllowedValues on ext key has been deprecated");
  3430. try
  3431. {
  3432. return parent::GetAllowedValues($aArgs, $sContains);
  3433. }
  3434. catch (MissingQueryArgument $e)
  3435. {
  3436. // Some required arguments could not be found, enlarge to any existing value
  3437. $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass());
  3438. return $oValSetDef->GetValues($aArgs, $sContains);
  3439. }
  3440. }
  3441. public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '')
  3442. {
  3443. $oValSetDef = $this->GetValuesDef();
  3444. $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains);
  3445. return $oSet;
  3446. }
  3447. public function GetDeletionPropagationOption()
  3448. {
  3449. return $this->Get("on_target_delete");
  3450. }
  3451. public function GetNullValue()
  3452. {
  3453. return 0;
  3454. }
  3455. public function IsNull($proposedValue)
  3456. {
  3457. return ($proposedValue == 0);
  3458. }
  3459. public function MakeRealValue($proposedValue, $oHostObj)
  3460. {
  3461. if (is_null($proposedValue)) return 0;
  3462. if ($proposedValue === '') return 0;
  3463. if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey();
  3464. return (int)$proposedValue;
  3465. }
  3466. public function GetMaximumComboLength()
  3467. {
  3468. return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
  3469. }
  3470. public function GetMinAutoCompleteChars()
  3471. {
  3472. return $this->GetOptional('min_autocomplete_chars', MetaModel::GetConfig()->Get('min_autocomplete_chars'));
  3473. }
  3474. public function AllowTargetCreation()
  3475. {
  3476. return $this->GetOptional('allow_target_creation', MetaModel::GetConfig()->Get('allow_target_creation'));
  3477. }
  3478. /**
  3479. * Find the corresponding "link" attribute on the target class
  3480. *
  3481. * @return string The attribute code on the target class, or null if none has been found
  3482. */
  3483. public function GetMirrorLinkAttribute()
  3484. {
  3485. $oRet = null;
  3486. $sRemoteClass = $this->GetTargetClass();
  3487. foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef)
  3488. {
  3489. if (!$oRemoteAttDef->IsLinkSet()) continue;
  3490. if (!is_subclass_of($this->GetHostClass(), $oRemoteAttDef->GetLinkedClass()) && $oRemoteAttDef->GetLinkedClass() != $this->GetHostClass()) continue;
  3491. if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetCode()) continue;
  3492. $oRet = $oRemoteAttDef;
  3493. break;
  3494. }
  3495. return $oRet;
  3496. }
  3497. }
  3498. /**
  3499. * Special kind of External Key to manage a hierarchy of objects
  3500. */
  3501. class AttributeHierarchicalKey extends AttributeExternalKey
  3502. {
  3503. protected $m_sTargetClass;
  3504. static public function ListExpectedParams()
  3505. {
  3506. $aParams = parent::ListExpectedParams();
  3507. $idx = array_search('targetclass', $aParams);
  3508. unset($aParams[$idx]);
  3509. $idx = array_search('jointype', $aParams);
  3510. unset($aParams[$idx]);
  3511. return $aParams; // TODO: mettre les bons parametres ici !!
  3512. }
  3513. public function GetEditClass() {return "ExtKey";}
  3514. public function RequiresIndex()
  3515. {
  3516. return true;
  3517. }
  3518. /*
  3519. * The target class is the class for which the attribute has been defined first
  3520. */
  3521. public function SetHostClass($sHostClass)
  3522. {
  3523. if (!isset($this->m_sTargetClass))
  3524. {
  3525. $this->m_sTargetClass = $sHostClass;
  3526. }
  3527. parent::SetHostClass($sHostClass);
  3528. }
  3529. public function IsHierarchicalKey() {return true;}
  3530. public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->m_sTargetClass;}
  3531. public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;}
  3532. public function GetKeyAttCode() {return $this->GetCode();}
  3533. public function GetBasicFilterOperators()
  3534. {
  3535. return parent::GetBasicFilterOperators();
  3536. }
  3537. public function GetBasicFilterLooseOperator()
  3538. {
  3539. return parent::GetBasicFilterLooseOperator();
  3540. }
  3541. public function GetSQLColumns($bFullSpec = false)
  3542. {
  3543. $aColumns = array();
  3544. $aColumns[$this->GetCode()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : '');
  3545. $aColumns[$this->GetSQLLeft()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : '');
  3546. $aColumns[$this->GetSQLRight()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : '');
  3547. return $aColumns;
  3548. }
  3549. public function GetSQLRight()
  3550. {
  3551. return $this->GetCode().'_right';
  3552. }
  3553. public function GetSQLLeft()
  3554. {
  3555. return $this->GetCode().'_left';
  3556. }
  3557. public function GetSQLValues($value)
  3558. {
  3559. if (!is_array($value))
  3560. {
  3561. $aValues[$this->GetCode()] = $value;
  3562. }
  3563. else
  3564. {
  3565. $aValues = array();
  3566. $aValues[$this->GetCode()] = $value[$this->GetCode()];
  3567. $aValues[$this->GetSQLRight()] = $value[$this->GetSQLRight()];
  3568. $aValues[$this->GetSQLLeft()] = $value[$this->GetSQLLeft()];
  3569. }
  3570. return $aValues;
  3571. }
  3572. public function GetAllowedValues($aArgs = array(), $sContains = '')
  3573. {
  3574. if (array_key_exists('this', $aArgs))
  3575. {
  3576. // Hierarchical keys have one more constraint: the "parent value" cannot be
  3577. // "under" themselves
  3578. $iRootId = $aArgs['this']->GetKey();
  3579. if ($iRootId > 0) // ignore objects that do no exist in the database...
  3580. {
  3581. $oValSetDef = $this->GetValuesDef();
  3582. $sClass = $this->m_sTargetClass;
  3583. $oFilter = DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId");
  3584. $oValSetDef->AddCondition($oFilter);
  3585. }
  3586. }
  3587. else
  3588. {
  3589. return parent::GetAllowedValues($aArgs, $sContains);
  3590. }
  3591. }
  3592. public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '')
  3593. {
  3594. $oValSetDef = $this->GetValuesDef();
  3595. if (array_key_exists('this', $aArgs))
  3596. {
  3597. // Hierarchical keys have one more constraint: the "parent value" cannot be
  3598. // "under" themselves
  3599. $iRootId = $aArgs['this']->GetKey();
  3600. if ($iRootId > 0) // ignore objects that do no exist in the database...
  3601. {
  3602. $aValuesSetDef = $this->GetValuesDef();
  3603. $sClass = $this->m_sTargetClass;
  3604. $oFilter = DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId");
  3605. $oValSetDef->AddCondition($oFilter);
  3606. }
  3607. }
  3608. $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains);
  3609. return $oSet;
  3610. }
  3611. /**
  3612. * Find the corresponding "link" attribute on the target class
  3613. *
  3614. * @return string The attribute code on the target class, or null if none has been found
  3615. */
  3616. public function GetMirrorLinkAttribute()
  3617. {
  3618. return null;
  3619. }
  3620. }
  3621. /**
  3622. * An attribute which corresponds to an external key (direct or indirect)
  3623. *
  3624. * @package iTopORM
  3625. */
  3626. class AttributeExternalField extends AttributeDefinition
  3627. {
  3628. static public function ListExpectedParams()
  3629. {
  3630. return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode"));
  3631. }
  3632. public function GetEditClass() {return "ExtField";}
  3633. public function GetFinalAttDef()
  3634. {
  3635. $oExtAttDef = $this->GetExtAttDef();
  3636. return $oExtAttDef->GetFinalAttDef();
  3637. }
  3638. protected function GetSQLCol($bFullSpec = false)
  3639. {
  3640. // throw new CoreException("external attribute: does it make any sense to request its type ?");
  3641. $oExtAttDef = $this->GetExtAttDef();
  3642. return $oExtAttDef->GetSQLCol($bFullSpec);
  3643. }
  3644. public function GetSQLExpressions($sPrefix = '')
  3645. {
  3646. if ($sPrefix == '')
  3647. {
  3648. return array('' => $this->GetCode()); // Warning: Use GetCode() since AttributeExternalField does not have any 'sql' property
  3649. }
  3650. else
  3651. {
  3652. return $sPrefix;
  3653. }
  3654. }
  3655. public function GetLabel($sDefault = null)
  3656. {
  3657. $sLabel = parent::GetLabel('');
  3658. if (strlen($sLabel) == 0)
  3659. {
  3660. $oRemoteAtt = $this->GetExtAttDef();
  3661. $sLabel = $oRemoteAtt->GetLabel($this->m_sCode);
  3662. }
  3663. return $sLabel;
  3664. }
  3665. public function GetDescription($sDefault = null)
  3666. {
  3667. $sLabel = parent::GetDescription('');
  3668. if (strlen($sLabel) == 0)
  3669. {
  3670. $oRemoteAtt = $this->GetExtAttDef();
  3671. $sLabel = $oRemoteAtt->GetDescription('');
  3672. }
  3673. return $sLabel;
  3674. }
  3675. public function GetHelpOnEdition($sDefault = null)
  3676. {
  3677. $sLabel = parent::GetHelpOnEdition('');
  3678. if (strlen($sLabel) == 0)
  3679. {
  3680. $oRemoteAtt = $this->GetExtAttDef();
  3681. $sLabel = $oRemoteAtt->GetHelpOnEdition('');
  3682. }
  3683. return $sLabel;
  3684. }
  3685. public function IsExternalKey($iType = EXTKEY_RELATIVE)
  3686. {
  3687. switch($iType)
  3688. {
  3689. case EXTKEY_ABSOLUTE:
  3690. // see further
  3691. $oRemoteAtt = $this->GetExtAttDef();
  3692. return $oRemoteAtt->IsExternalKey($iType);
  3693. case EXTKEY_RELATIVE:
  3694. return false;
  3695. default:
  3696. throw new CoreException("Unexpected value for argument iType: '$iType'");
  3697. }
  3698. }
  3699. public function GetTargetClass($iType = EXTKEY_RELATIVE)
  3700. {
  3701. return $this->GetKeyAttDef($iType)->GetTargetClass();
  3702. }
  3703. public function IsExternalField() {return true;}
  3704. public function GetKeyAttCode() {return $this->Get("extkey_attcode");}
  3705. public function GetExtAttCode() {return $this->Get("target_attcode");}
  3706. public function GetKeyAttDef($iType = EXTKEY_RELATIVE)
  3707. {
  3708. switch($iType)
  3709. {
  3710. case EXTKEY_ABSOLUTE:
  3711. // see further
  3712. $oRemoteAtt = $this->GetExtAttDef();
  3713. if ($oRemoteAtt->IsExternalField())
  3714. {
  3715. return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE);
  3716. }
  3717. else if ($oRemoteAtt->IsExternalKey())
  3718. {
  3719. return $oRemoteAtt;
  3720. }
  3721. return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter !
  3722. case EXTKEY_RELATIVE:
  3723. return MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode"));
  3724. default:
  3725. throw new CoreException("Unexpected value for argument iType: '$iType'");
  3726. }
  3727. }
  3728. public function GetPrerequisiteAttributes($sClass = null)
  3729. {
  3730. return array($this->Get("extkey_attcode"));
  3731. }
  3732. public function GetExtAttDef()
  3733. {
  3734. $oKeyAttDef = $this->GetKeyAttDef();
  3735. $oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->GetTargetClass(), $this->Get("target_attcode"));
  3736. if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode"));
  3737. return $oExtAttDef;
  3738. }
  3739. public function GetSQLExpr()
  3740. {
  3741. $oExtAttDef = $this->GetExtAttDef();
  3742. return $oExtAttDef->GetSQLExpr();
  3743. }
  3744. public function GetDefaultValue(DBObject $oHostObject = null)
  3745. {
  3746. $oExtAttDef = $this->GetExtAttDef();
  3747. return $oExtAttDef->GetDefaultValue();
  3748. }
  3749. public function IsNullAllowed()
  3750. {
  3751. $oExtAttDef = $this->GetExtAttDef();
  3752. return $oExtAttDef->IsNullAllowed();
  3753. }
  3754. public function IsScalar()
  3755. {
  3756. $oExtAttDef = $this->GetExtAttDef();
  3757. return $oExtAttDef->IsScalar();
  3758. }
  3759. public function GetFilterDefinitions()
  3760. {
  3761. return array($this->GetCode() => new FilterFromAttribute($this));
  3762. }
  3763. public function GetBasicFilterOperators()
  3764. {
  3765. $oExtAttDef = $this->GetExtAttDef();
  3766. return $oExtAttDef->GetBasicFilterOperators();
  3767. }
  3768. public function GetBasicFilterLooseOperator()
  3769. {
  3770. $oExtAttDef = $this->GetExtAttDef();
  3771. return $oExtAttDef->GetBasicFilterLooseOperator();
  3772. }
  3773. public function GetBasicFilterSQLExpr($sOpCode, $value)
  3774. {
  3775. $oExtAttDef = $this->GetExtAttDef();
  3776. return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value);
  3777. }
  3778. public function GetNullValue()
  3779. {
  3780. $oExtAttDef = $this->GetExtAttDef();
  3781. return $oExtAttDef->GetNullValue();
  3782. }
  3783. public function IsNull($proposedValue)
  3784. {
  3785. $oExtAttDef = $this->GetExtAttDef();
  3786. return $oExtAttDef->IsNull($proposedValue);
  3787. }
  3788. public function MakeRealValue($proposedValue, $oHostObj)
  3789. {
  3790. $oExtAttDef = $this->GetExtAttDef();
  3791. return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj);
  3792. }
  3793. public function ScalarToSQL($value)
  3794. {
  3795. // This one could be used in case of filtering only
  3796. $oExtAttDef = $this->GetExtAttDef();
  3797. return $oExtAttDef->ScalarToSQL($value);
  3798. }
  3799. // Do not overload GetSQLExpression here because this is handled in the joins
  3800. //public function GetSQLExpressions($sPrefix = '') {return array();}
  3801. // Here, we get the data...
  3802. public function FromSQLToValue($aCols, $sPrefix = '')
  3803. {
  3804. $oExtAttDef = $this->GetExtAttDef();
  3805. return $oExtAttDef->FromSQLToValue($aCols, $sPrefix);
  3806. }
  3807. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  3808. {
  3809. $oExtAttDef = $this->GetExtAttDef();
  3810. return $oExtAttDef->GetAsHTML($value, null, $bLocalize);
  3811. }
  3812. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  3813. {
  3814. $oExtAttDef = $this->GetExtAttDef();
  3815. return $oExtAttDef->GetAsXML($value, null, $bLocalize);
  3816. }
  3817. public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  3818. {
  3819. $oExtAttDef = $this->GetExtAttDef();
  3820. return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize, $bConvertToPlainText);
  3821. }
  3822. public function IsPartOfFingerprint() { return false; }
  3823. }
  3824. /**
  3825. * Map a varchar column to an URL (formats the ouput in HMTL)
  3826. *
  3827. * @package iTopORM
  3828. */
  3829. class AttributeURL extends AttributeString
  3830. {
  3831. static public function ListExpectedParams()
  3832. {
  3833. //return parent::ListExpectedParams();
  3834. return array_merge(parent::ListExpectedParams(), array("target"));
  3835. }
  3836. public function GetEditClass() {return "String";}
  3837. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  3838. {
  3839. $sTarget = $this->Get("target");
  3840. if (empty($sTarget)) $sTarget = "_blank";
  3841. $sLabel = Str::pure2html($sValue);
  3842. if (strlen($sLabel) > 255)
  3843. {
  3844. // Truncate the length to 128 characters, by removing the middle
  3845. $sLabel = substr($sLabel, 0, 100).'.....'.substr($sLabel, -20);
  3846. }
  3847. return "<a target=\"$sTarget\" href=\"$sValue\">$sLabel</a>";
  3848. }
  3849. public function GetValidationPattern()
  3850. {
  3851. return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('url_validation_pattern').'$');
  3852. }
  3853. }
  3854. /**
  3855. * A blob is an ormDocument, it is stored as several columns in the database
  3856. *
  3857. * @package iTopORM
  3858. */
  3859. class AttributeBlob extends AttributeDefinition
  3860. {
  3861. static public function ListExpectedParams()
  3862. {
  3863. return array_merge(parent::ListExpectedParams(), array("depends_on"));
  3864. }
  3865. public function GetEditClass() {return "Document";}
  3866. public function IsDirectField() {return true;}
  3867. public function IsScalar() {return true;}
  3868. public function IsWritable() {return true;}
  3869. public function GetDefaultValue(DBObject $oHostObject = null) {return "";}
  3870. public function IsNullAllowed(DBObject $oHostObject = null) {return $this->GetOptional("is_null_allowed", false);}
  3871. public function GetEditValue($sValue, $oHostObj = null)
  3872. {
  3873. return '';
  3874. }
  3875. // Facilitate things: allow administrators to upload a document
  3876. // from a CSV by specifying its path/URL
  3877. public function MakeRealValue($proposedValue, $oHostObj)
  3878. {
  3879. if (!is_object($proposedValue))
  3880. {
  3881. if (file_exists($proposedValue) && UserRights::IsAdministrator())
  3882. {
  3883. $sContent = file_get_contents($proposedValue);
  3884. $sExtension = strtolower(pathinfo($proposedValue, PATHINFO_EXTENSION));
  3885. $sMimeType = "application/x-octet-stream";
  3886. $aKnownExtensions = array(
  3887. 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  3888. 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
  3889. 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
  3890. 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  3891. 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  3892. 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
  3893. 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  3894. 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
  3895. 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
  3896. 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12.xlsx',
  3897. 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
  3898. 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
  3899. 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  3900. 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  3901. 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
  3902. 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  3903. 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
  3904. 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
  3905. 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
  3906. 'jpg' => 'image/jpeg',
  3907. 'jpeg' => 'image/jpeg',
  3908. 'gif' => 'image/gif',
  3909. 'png' => 'image/png',
  3910. 'pdf' => 'application/pdf',
  3911. 'doc' => 'application/msword',
  3912. 'dot' => 'application/msword',
  3913. 'xls' => 'application/vnd.ms-excel',
  3914. 'ppt' => 'application/vnd.ms-powerpoint',
  3915. 'vsd' => 'application/x-visio',
  3916. 'vdx' => 'application/visio.drawing',
  3917. 'odt' => 'application/vnd.oasis.opendocument.text',
  3918. 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
  3919. 'odp' => 'application/vnd.oasis.opendocument.presentation',
  3920. 'zip' => 'application/zip',
  3921. 'txt' => 'text/plain',
  3922. 'htm' => 'text/html',
  3923. 'html' => 'text/html',
  3924. 'exe' => 'application/octet-stream'
  3925. );
  3926. if (!array_key_exists($sExtension, $aKnownExtensions) && extension_loaded('fileinfo'))
  3927. {
  3928. $finfo = new finfo(FILEINFO_MIME);
  3929. $sMimeType = $finfo->file($proposedValue);
  3930. }
  3931. return new ormDocument($sContent, $sMimeType);
  3932. }
  3933. else
  3934. {
  3935. return new ormDocument($proposedValue, 'text/plain');
  3936. }
  3937. }
  3938. return $proposedValue;
  3939. }
  3940. public function GetSQLExpressions($sPrefix = '')
  3941. {
  3942. if ($sPrefix == '')
  3943. {
  3944. $sPrefix = $this->GetCode();
  3945. }
  3946. $aColumns = array();
  3947. // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
  3948. $aColumns[''] = $sPrefix.'_mimetype';
  3949. $aColumns['_data'] = $sPrefix.'_data';
  3950. $aColumns['_filename'] = $sPrefix.'_filename';
  3951. return $aColumns;
  3952. }
  3953. public function FromSQLToValue($aCols, $sPrefix = '')
  3954. {
  3955. if (!array_key_exists($sPrefix, $aCols))
  3956. {
  3957. $sAvailable = implode(', ', array_keys($aCols));
  3958. throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
  3959. }
  3960. $sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
  3961. if (!array_key_exists($sPrefix.'_data', $aCols))
  3962. {
  3963. $sAvailable = implode(', ', array_keys($aCols));
  3964. throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}");
  3965. }
  3966. $data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null;
  3967. if (!array_key_exists($sPrefix.'_filename', $aCols))
  3968. {
  3969. $sAvailable = implode(', ', array_keys($aCols));
  3970. throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}");
  3971. }
  3972. $sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : '';
  3973. $value = new ormDocument($data, $sMimeType, $sFileName);
  3974. return $value;
  3975. }
  3976. public function GetSQLValues($value)
  3977. {
  3978. // #@# Optimization: do not load blobs anytime
  3979. // As per mySQL doc, selecting blob columns will prevent mySQL from
  3980. // using memory in case a temporary table has to be created
  3981. // (temporary tables created on disk)
  3982. // We will have to remove the blobs from the list of attributes when doing the select
  3983. // then the use of Get() should finalize the load
  3984. if ($value instanceOf ormDocument)
  3985. {
  3986. $aValues = array();
  3987. $aValues[$this->GetCode().'_data'] = $value->GetData();
  3988. $aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType();
  3989. $aValues[$this->GetCode().'_filename'] = $value->GetFileName();
  3990. }
  3991. else
  3992. {
  3993. $aValues = array();
  3994. $aValues[$this->GetCode().'_data'] = '';
  3995. $aValues[$this->GetCode().'_mimetype'] = '';
  3996. $aValues[$this->GetCode().'_filename'] = '';
  3997. }
  3998. return $aValues;
  3999. }
  4000. public function GetSQLColumns($bFullSpec = false)
  4001. {
  4002. $aColumns = array();
  4003. $aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb)
  4004. $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)';
  4005. $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)';
  4006. return $aColumns;
  4007. }
  4008. public function GetFilterDefinitions()
  4009. {
  4010. return array();
  4011. // still not working... see later...
  4012. return array(
  4013. $this->GetCode().'->filename' => new FilterFromAttribute($this, '_filename'),
  4014. $this->GetCode().'_mimetype' => new FilterFromAttribute($this, '_mimetype'),
  4015. $this->GetCode().'_mimetype' => new FilterFromAttribute($this, '_mimetype')
  4016. );
  4017. }
  4018. public function GetBasicFilterOperators()
  4019. {
  4020. return array();
  4021. }
  4022. public function GetBasicFilterLooseOperator()
  4023. {
  4024. return '=';
  4025. }
  4026. public function GetBasicFilterSQLExpr($sOpCode, $value)
  4027. {
  4028. return 'true';
  4029. }
  4030. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  4031. {
  4032. if (is_object($value))
  4033. {
  4034. return $value->GetAsHTML();
  4035. }
  4036. }
  4037. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  4038. {
  4039. return ''; // Not exportable in CSV !
  4040. }
  4041. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  4042. {
  4043. return ''; // Not exportable in XML, or as CDATA + some subtags ??
  4044. }
  4045. /**
  4046. * Helper to get a value that will be JSON encoded
  4047. * The operation is the opposite to FromJSONToValue
  4048. */
  4049. public function GetForJSON($value)
  4050. {
  4051. if ($value instanceOf ormDocument)
  4052. {
  4053. $aValues = array();
  4054. $aValues['data'] = base64_encode($value->GetData());
  4055. $aValues['mimetype'] = $value->GetMimeType();
  4056. $aValues['filename'] = $value->GetFileName();
  4057. }
  4058. else
  4059. {
  4060. $aValues = null;
  4061. }
  4062. return $aValues;
  4063. }
  4064. /**
  4065. * Helper to form a value, given JSON decoded data
  4066. * The operation is the opposite to GetForJSON
  4067. */
  4068. public function FromJSONToValue($json)
  4069. {
  4070. if (isset($json->data))
  4071. {
  4072. $data = base64_decode($json->data);
  4073. $value = new ormDocument($data, $json->mimetype, $json->filename);
  4074. }
  4075. else
  4076. {
  4077. $value = null;
  4078. }
  4079. return $value;
  4080. }
  4081. public function Fingerprint($value)
  4082. {
  4083. $sFingerprint = '';
  4084. if ($value instanceOf ormDocument)
  4085. {
  4086. $sFingerprint = md5($value->GetData());
  4087. }
  4088. return $sFingerprint;
  4089. }
  4090. }
  4091. /**
  4092. * A stop watch is an ormStopWatch object, it is stored as several columns in the database
  4093. *
  4094. * @package iTopORM
  4095. */
  4096. class AttributeStopWatch extends AttributeDefinition
  4097. {
  4098. static public function ListExpectedParams()
  4099. {
  4100. // The list of thresholds must be an array of iPercent => array of 'option' => value
  4101. return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds"));
  4102. }
  4103. public function GetEditClass() {return "StopWatch";}
  4104. public function IsDirectField() {return true;}
  4105. public function IsScalar() {return true;}
  4106. public function IsWritable() {return false;}
  4107. public function GetDefaultValue(DBObject $oHostObject = null) {return $this->NewStopWatch();}
  4108. public function GetEditValue($value, $oHostObj = null)
  4109. {
  4110. return $value->GetTimeSpent();
  4111. }
  4112. public function GetStates()
  4113. {
  4114. return $this->Get('states');
  4115. }
  4116. public function AlwaysLoadInTables()
  4117. {
  4118. // Each and every stop watch is accessed for computing the highlight code (DBObject::GetHighlightCode())
  4119. return true;
  4120. }
  4121. /**
  4122. * Construct a brand new (but configured) stop watch
  4123. */
  4124. public function NewStopWatch()
  4125. {
  4126. $oSW = new ormStopWatch();
  4127. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4128. {
  4129. $oSW->DefineThreshold($iThreshold);
  4130. }
  4131. return $oSW;
  4132. }
  4133. // Facilitate things: allow the user to Set the value from a string
  4134. public function MakeRealValue($proposedValue, $oHostObj)
  4135. {
  4136. if (!$proposedValue instanceof ormStopWatch)
  4137. {
  4138. return $this->NewStopWatch();
  4139. }
  4140. return $proposedValue;
  4141. }
  4142. public function GetSQLExpressions($sPrefix = '')
  4143. {
  4144. if ($sPrefix == '')
  4145. {
  4146. $sPrefix = $this->GetCode(); // Warning: a stopwatch does not have any 'sql' property, so its SQL column is equal to its attribute code !!
  4147. }
  4148. $aColumns = array();
  4149. // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
  4150. $aColumns[''] = $sPrefix.'_timespent';
  4151. $aColumns['_started'] = $sPrefix.'_started';
  4152. $aColumns['_laststart'] = $sPrefix.'_laststart';
  4153. $aColumns['_stopped'] = $sPrefix.'_stopped';
  4154. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4155. {
  4156. $sThPrefix = '_'.$iThreshold;
  4157. $aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline';
  4158. $aColumns[$sThPrefix.'_passed'] = $sPrefix.$sThPrefix.'_passed';
  4159. $aColumns[$sThPrefix.'_triggered'] = $sPrefix.$sThPrefix.'_triggered';
  4160. $aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun';
  4161. }
  4162. return $aColumns;
  4163. }
  4164. public static function DateToSeconds($sDate)
  4165. {
  4166. if (is_null($sDate))
  4167. {
  4168. return null;
  4169. }
  4170. $oDateTime = new DateTime($sDate);
  4171. $iSeconds = $oDateTime->format('U');
  4172. return $iSeconds;
  4173. }
  4174. public static function SecondsToDate($iSeconds)
  4175. {
  4176. if (is_null($iSeconds))
  4177. {
  4178. return null;
  4179. }
  4180. return date("Y-m-d H:i:s", $iSeconds);
  4181. }
  4182. public function FromSQLToValue($aCols, $sPrefix = '')
  4183. {
  4184. $aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped');
  4185. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4186. {
  4187. $sThPrefix = '_'.$iThreshold;
  4188. $aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline';
  4189. $aExpectedCols[] = $sPrefix.$sThPrefix.'_passed';
  4190. $aExpectedCols[] = $sPrefix.$sThPrefix.'_triggered';
  4191. $aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun';
  4192. }
  4193. foreach ($aExpectedCols as $sExpectedCol)
  4194. {
  4195. if (!array_key_exists($sExpectedCol, $aCols))
  4196. {
  4197. $sAvailable = implode(', ', array_keys($aCols));
  4198. throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}");
  4199. }
  4200. }
  4201. $value = new ormStopWatch(
  4202. $aCols[$sPrefix],
  4203. self::DateToSeconds($aCols[$sPrefix.'_started']),
  4204. self::DateToSeconds($aCols[$sPrefix.'_laststart']),
  4205. self::DateToSeconds($aCols[$sPrefix.'_stopped'])
  4206. );
  4207. $aThresholds = array();
  4208. foreach ($this->ListThresholds() as $iThreshold => $aDefinition)
  4209. {
  4210. $sThPrefix = '_'.$iThreshold;
  4211. $value->DefineThreshold(
  4212. $iThreshold,
  4213. self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']),
  4214. (bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1),
  4215. (bool)($aCols[$sPrefix.$sThPrefix.'_triggered'] == 1),
  4216. $aCols[$sPrefix.$sThPrefix.'_overrun'],
  4217. array_key_exists('highlight', $aDefinition) ? $aDefinition['highlight'] : null
  4218. );
  4219. }
  4220. return $value;
  4221. }
  4222. public function GetSQLValues($value)
  4223. {
  4224. if ($value instanceOf ormStopWatch)
  4225. {
  4226. $aValues = array();
  4227. $aValues[$this->GetCode().'_timespent'] = $value->GetTimeSpent();
  4228. $aValues[$this->GetCode().'_started'] = self::SecondsToDate($value->GetStartDate());
  4229. $aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate());
  4230. $aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate());
  4231. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4232. {
  4233. $sPrefix = $this->GetCode().'_'.$iThreshold;
  4234. $aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold));
  4235. $aValues[$sPrefix.'_passed'] = $value->IsThresholdPassed($iThreshold) ? '1' : '0';
  4236. $aValues[$sPrefix.'_triggered'] = $value->IsThresholdTriggered($iThreshold) ? '1' : '0';
  4237. $aValues[$sPrefix.'_overrun'] = $value->GetOverrun($iThreshold);
  4238. }
  4239. }
  4240. else
  4241. {
  4242. $aValues = array();
  4243. $aValues[$this->GetCode().'_timespent'] = '';
  4244. $aValues[$this->GetCode().'_started'] = '';
  4245. $aValues[$this->GetCode().'_laststart'] = '';
  4246. $aValues[$this->GetCode().'_stopped'] = '';
  4247. }
  4248. return $aValues;
  4249. }
  4250. public function GetSQLColumns($bFullSpec = false)
  4251. {
  4252. $aColumns = array();
  4253. $aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED';
  4254. $aColumns[$this->GetCode().'_started'] = 'DATETIME';
  4255. $aColumns[$this->GetCode().'_laststart'] = 'DATETIME';
  4256. $aColumns[$this->GetCode().'_stopped'] = 'DATETIME';
  4257. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4258. {
  4259. $sPrefix = $this->GetCode().'_'.$iThreshold;
  4260. $aColumns[$sPrefix.'_deadline'] = 'DATETIME';
  4261. $aColumns[$sPrefix.'_passed'] = 'TINYINT(1) UNSIGNED';
  4262. $aColumns[$sPrefix.'_triggered'] = 'TINYINT(1)';
  4263. $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED';
  4264. }
  4265. return $aColumns;
  4266. }
  4267. public function GetFilterDefinitions()
  4268. {
  4269. $aRes = array(
  4270. $this->GetCode() => new FilterFromAttribute($this),
  4271. $this->GetCode().'_started' => new FilterFromAttribute($this, '_started'),
  4272. $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'),
  4273. $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped')
  4274. );
  4275. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4276. {
  4277. $sPrefix = $this->GetCode().'_'.$iThreshold;
  4278. $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline');
  4279. $aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed');
  4280. $aRes[$sPrefix.'_triggered'] = new FilterFromAttribute($this, '_triggered');
  4281. $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun');
  4282. }
  4283. return $aRes;
  4284. }
  4285. public function GetBasicFilterOperators()
  4286. {
  4287. return array();
  4288. }
  4289. public function GetBasicFilterLooseOperator()
  4290. {
  4291. return '=';
  4292. }
  4293. public function GetBasicFilterSQLExpr($sOpCode, $value)
  4294. {
  4295. return 'true';
  4296. }
  4297. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  4298. {
  4299. if (is_object($value))
  4300. {
  4301. return $value->GetAsHTML($this, $oHostObject);
  4302. }
  4303. }
  4304. public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  4305. {
  4306. return $value->GetTimeSpent();
  4307. }
  4308. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  4309. {
  4310. return $value->GetTimeSpent();
  4311. }
  4312. public function ListThresholds()
  4313. {
  4314. return $this->Get('thresholds');
  4315. }
  4316. public function Fingerprint($value)
  4317. {
  4318. $sFingerprint = '';
  4319. if (is_object($value))
  4320. {
  4321. $sFingerprint = $value->GetAsHTML($this);
  4322. }
  4323. return $sFingerprint;
  4324. }
  4325. /**
  4326. * To expose internal values: Declare an attribute AttributeSubItem
  4327. * and implement the GetSubItemXXXX verbs
  4328. */
  4329. public function GetSubItemSQLExpression($sItemCode)
  4330. {
  4331. $sPrefix = $this->GetCode();
  4332. switch($sItemCode)
  4333. {
  4334. case 'timespent':
  4335. return array('' => $sPrefix.'_timespent');
  4336. case 'started':
  4337. return array('' => $sPrefix.'_started');
  4338. case 'laststart':
  4339. return array('' => $sPrefix.'_laststart');
  4340. case 'stopped':
  4341. return array('' => $sPrefix.'_stopped');
  4342. }
  4343. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4344. {
  4345. $sThPrefix = $iThreshold.'_';
  4346. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4347. {
  4348. // The current threshold is concerned
  4349. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4350. switch($sThresholdCode)
  4351. {
  4352. case 'deadline':
  4353. return array('' => $sPrefix.'_'.$iThreshold.'_deadline');
  4354. case 'passed':
  4355. return array('' => $sPrefix.'_'.$iThreshold.'_passed');
  4356. case 'triggered':
  4357. return array('' => $sPrefix.'_'.$iThreshold.'_triggered');
  4358. case 'overrun':
  4359. return array('' => $sPrefix.'_'.$iThreshold.'_overrun');
  4360. }
  4361. }
  4362. }
  4363. throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode());
  4364. }
  4365. public function GetSubItemValue($sItemCode, $value, $oHostObject = null)
  4366. {
  4367. $oStopWatch = $value;
  4368. switch($sItemCode)
  4369. {
  4370. case 'timespent':
  4371. return $oStopWatch->GetTimeSpent();
  4372. case 'started':
  4373. return $oStopWatch->GetStartDate();
  4374. case 'laststart':
  4375. return $oStopWatch->GetLastStartDate();
  4376. case 'stopped':
  4377. return $oStopWatch->GetStopDate();
  4378. }
  4379. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4380. {
  4381. $sThPrefix = $iThreshold.'_';
  4382. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4383. {
  4384. // The current threshold is concerned
  4385. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4386. switch($sThresholdCode)
  4387. {
  4388. case 'deadline':
  4389. return $oStopWatch->GetThresholdDate($iThreshold);
  4390. case 'passed':
  4391. return $oStopWatch->IsThresholdPassed($iThreshold);
  4392. case 'triggered':
  4393. return $oStopWatch->IsThresholdTriggered($iThreshold);
  4394. case 'overrun':
  4395. return $oStopWatch->GetOverrun($iThreshold);
  4396. }
  4397. }
  4398. }
  4399. throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode());
  4400. }
  4401. protected function GetBooleanLabel($bValue)
  4402. {
  4403. $sDictKey = $bValue ? 'yes' : 'no';
  4404. return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey);
  4405. }
  4406. public function GetSubItemAsHTMLForHistory($sItemCode, $sValue)
  4407. {
  4408. switch($sItemCode)
  4409. {
  4410. case 'timespent':
  4411. $sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null;
  4412. break;
  4413. case 'started':
  4414. case 'laststart':
  4415. case 'stopped':
  4416. $sHtml = (int)$sValue ? date(self::GetDateFormat(), (int)$sValue) : null;
  4417. break;
  4418. default:
  4419. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4420. {
  4421. $sThPrefix = $iThreshold.'_';
  4422. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4423. {
  4424. // The current threshold is concerned
  4425. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4426. switch($sThresholdCode)
  4427. {
  4428. case 'deadline':
  4429. $sHtml = (int)$sValue ? date(self::GetDateFormat(true /*full*/), (int)$sValue) : null;
  4430. break;
  4431. case 'passed':
  4432. $sHtml = $this->GetBooleanLabel((int)$sValue);
  4433. break;
  4434. case 'triggered':
  4435. $sHtml = $this->GetBooleanLabel((int)$sValue);
  4436. break;
  4437. case 'overrun':
  4438. $sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : '';
  4439. }
  4440. }
  4441. }
  4442. }
  4443. return $sHtml;
  4444. }
  4445. static protected function GetDateFormat($bFull = false)
  4446. {
  4447. if ($bFull)
  4448. {
  4449. return "Y-m-d H:i:s";
  4450. }
  4451. else
  4452. {
  4453. return "Y-m-d H:i";
  4454. }
  4455. }
  4456. public function GetSubItemAsHTML($sItemCode, $value)
  4457. {
  4458. $sHtml = $value;
  4459. switch($sItemCode)
  4460. {
  4461. case 'timespent':
  4462. $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value));
  4463. break;
  4464. case 'started':
  4465. case 'laststart':
  4466. case 'stopped':
  4467. if (is_null($value))
  4468. {
  4469. $sHtml = ''; // Undefined
  4470. }
  4471. else
  4472. {
  4473. $sHtml = date(self::GetDateFormat(), $value);
  4474. }
  4475. break;
  4476. default:
  4477. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4478. {
  4479. $sThPrefix = $iThreshold.'_';
  4480. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4481. {
  4482. // The current threshold is concerned
  4483. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4484. switch($sThresholdCode)
  4485. {
  4486. case 'deadline':
  4487. if ($value)
  4488. {
  4489. $sDate = date(self::GetDateFormat(true /*full*/), $value);
  4490. $sHtml = Str::pure2html(AttributeDeadline::FormatDeadline($sDate));
  4491. }
  4492. else
  4493. {
  4494. $sHtml = '';
  4495. }
  4496. break;
  4497. case 'passed':
  4498. case 'triggered':
  4499. $sHtml = $this->GetBooleanLabel($value);
  4500. break;
  4501. case 'overrun':
  4502. $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value));
  4503. break;
  4504. }
  4505. }
  4506. }
  4507. }
  4508. return $sHtml;
  4509. }
  4510. public function GetSubItemAsCSV($sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false)
  4511. {
  4512. $sFrom = array("\r\n", $sTextQualifier);
  4513. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  4514. $sEscaped = str_replace($sFrom, $sTo, (string)$value);
  4515. $sRet = $sTextQualifier.$sEscaped.$sTextQualifier;
  4516. switch($sItemCode)
  4517. {
  4518. case 'timespent':
  4519. case 'started':
  4520. case 'laststart':
  4521. case 'stopped':
  4522. break;
  4523. default:
  4524. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4525. {
  4526. $sThPrefix = $iThreshold.'_';
  4527. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4528. {
  4529. // The current threshold is concerned
  4530. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4531. switch($sThresholdCode)
  4532. {
  4533. case 'deadline':
  4534. if ($value != '')
  4535. {
  4536. $sRet = $sTextQualifier.date(self::GetDateFormat(true /*full*/), $value).$sTextQualifier;
  4537. }
  4538. break;
  4539. case 'passed':
  4540. case 'triggered':
  4541. $sRet = $sTextQualifier.$this->GetBooleanLabel($value).$sTextQualifier;
  4542. break;
  4543. case 'overrun':
  4544. break;
  4545. }
  4546. }
  4547. }
  4548. }
  4549. return $sRet;
  4550. }
  4551. public function GetSubItemAsXML($sItemCode, $value)
  4552. {
  4553. $sRet = Str::pure2xml((string)$value);
  4554. switch($sItemCode)
  4555. {
  4556. case 'timespent':
  4557. case 'started':
  4558. case 'laststart':
  4559. case 'stopped':
  4560. break;
  4561. default:
  4562. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4563. {
  4564. $sThPrefix = $iThreshold.'_';
  4565. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4566. {
  4567. // The current threshold is concerned
  4568. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4569. switch($sThresholdCode)
  4570. {
  4571. case 'deadline':
  4572. break;
  4573. case 'passed':
  4574. case 'triggered':
  4575. $sRet = $this->GetBooleanLabel($value);
  4576. break;
  4577. case 'overrun':
  4578. break;
  4579. }
  4580. }
  4581. }
  4582. }
  4583. return $sRet;
  4584. }
  4585. /**
  4586. * Implemented for the HTML spreadsheet format!
  4587. */
  4588. public function GetSubItemAsEditValue($sItemCode, $value)
  4589. {
  4590. $sRet = $value;
  4591. switch($sItemCode)
  4592. {
  4593. case 'timespent':
  4594. break;
  4595. case 'started':
  4596. case 'laststart':
  4597. case 'stopped':
  4598. if (is_null($value))
  4599. {
  4600. $sRet = ''; // Undefined
  4601. }
  4602. else
  4603. {
  4604. $sRet = date(self::GetDateFormat(), $value);
  4605. }
  4606. break;
  4607. default:
  4608. foreach ($this->ListThresholds() as $iThreshold => $aFoo)
  4609. {
  4610. $sThPrefix = $iThreshold.'_';
  4611. if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix)
  4612. {
  4613. // The current threshold is concerned
  4614. $sThresholdCode = substr($sItemCode, strlen($sThPrefix));
  4615. switch($sThresholdCode)
  4616. {
  4617. case 'deadline':
  4618. if ($value)
  4619. {
  4620. $sRet = date(self::GetDateFormat(true /*full*/), $value);
  4621. }
  4622. else
  4623. {
  4624. $sRet = '';
  4625. }
  4626. break;
  4627. case 'passed':
  4628. case 'triggered':
  4629. $sRet = $this->GetBooleanLabel($value);
  4630. break;
  4631. case 'overrun':
  4632. break;
  4633. }
  4634. }
  4635. }
  4636. }
  4637. return $sRet;
  4638. }
  4639. }
  4640. /**
  4641. * View of a subvalue of another attribute
  4642. * If an attribute implements the verbs GetSubItem.... then it can expose
  4643. * internal values, each of them being an attribute and therefore they
  4644. * can be displayed at different times in the object lifecycle, and used for
  4645. * reporting (as a condition in OQL, or as an additional column in an export)
  4646. * Known usages: Stop Watches can expose threshold statuses
  4647. */
  4648. class AttributeSubItem extends AttributeDefinition
  4649. {
  4650. static public function ListExpectedParams()
  4651. {
  4652. return array_merge(parent::ListExpectedParams(), array('target_attcode', 'item_code'));
  4653. }
  4654. public function GetParentAttCode() {return $this->Get("target_attcode");}
  4655. /**
  4656. * Helper : get the attribute definition to which the execution will be forwarded
  4657. */
  4658. public function GetTargetAttDef()
  4659. {
  4660. $sClass = $this->GetHostClass();
  4661. $oParentAttDef = MetaModel::GetAttributeDef($sClass, $this->Get('target_attcode'));
  4662. return $oParentAttDef;
  4663. }
  4664. public function GetEditClass() {return "";}
  4665. public function GetValuesDef() {return null;}
  4666. public function IsDirectField() {return true;}
  4667. public function IsScalar() {return true;}
  4668. public function IsWritable() {return false;}
  4669. public function GetDefaultValue(DBObject $oHostObject = null) {return null;}
  4670. // public function IsNullAllowed() {return false;}
  4671. public function LoadInObject() {return false;} // if this verb returns false, then GetValue must be implemented
  4672. /**
  4673. * Used by DBOBject::Get()
  4674. */
  4675. public function GetValue($oHostObject, $bOriginal = false)
  4676. {
  4677. $oParent = $this->GetTargetAttDef();
  4678. $parentValue = $oHostObject->GetStrict($oParent->GetCode());
  4679. $res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject);
  4680. return $res;
  4681. }
  4682. //
  4683. // protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
  4684. public function FromSQLToValue($aCols, $sPrefix = '')
  4685. {
  4686. }
  4687. public function GetSQLColumns($bFullSpec = false)
  4688. {
  4689. return array();
  4690. }
  4691. public function GetFilterDefinitions()
  4692. {
  4693. return array($this->GetCode() => new FilterFromAttribute($this));
  4694. }
  4695. public function GetBasicFilterOperators()
  4696. {
  4697. return array();
  4698. }
  4699. public function GetBasicFilterLooseOperator()
  4700. {
  4701. return "=";
  4702. }
  4703. public function GetBasicFilterSQLExpr($sOpCode, $value)
  4704. {
  4705. $sQValue = CMDBSource::Quote($value);
  4706. switch ($sOpCode)
  4707. {
  4708. case '!=':
  4709. return $this->GetSQLExpr()." != $sQValue";
  4710. break;
  4711. case '=':
  4712. default:
  4713. return $this->GetSQLExpr()." = $sQValue";
  4714. }
  4715. }
  4716. public function GetSQLExpressions($sPrefix = '')
  4717. {
  4718. $oParent = $this->GetTargetAttDef();
  4719. $res = $oParent->GetSubItemSQLExpression($this->Get('item_code'));
  4720. return $res;
  4721. }
  4722. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  4723. {
  4724. $oParent = $this->GetTargetAttDef();
  4725. $res = $oParent->GetSubItemAsHTML($this->Get('item_code'), $value);
  4726. return $res;
  4727. }
  4728. public function GetAsHTMLForHistory($value, $oHostObject = null, $bLocalize = true)
  4729. {
  4730. $oParent = $this->GetTargetAttDef();
  4731. $res = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $value);
  4732. return $res;
  4733. }
  4734. public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  4735. {
  4736. $oParent = $this->GetTargetAttDef();
  4737. $res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier, $bConvertToPlainText);
  4738. return $res;
  4739. }
  4740. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  4741. {
  4742. $oParent = $this->GetTargetAttDef();
  4743. $res = $oParent->GetSubItemAsXML($this->Get('item_code'), $value);
  4744. return $res;
  4745. }
  4746. /**
  4747. * As of now, this function must be implemented to have the value in spreadsheet format
  4748. */
  4749. public function GetEditValue($value, $oHostObj = null)
  4750. {
  4751. $oParent = $this->GetTargetAttDef();
  4752. $res = $oParent->GetSubItemAsEditValue($this->Get('item_code'), $value);
  4753. return $res;
  4754. }
  4755. public function IsPartOfFingerprint() { return false; }
  4756. }
  4757. /**
  4758. * One way encrypted (hashed) password
  4759. */
  4760. class AttributeOneWayPassword extends AttributeDefinition
  4761. {
  4762. static public function ListExpectedParams()
  4763. {
  4764. return array_merge(parent::ListExpectedParams(), array("depends_on"));
  4765. }
  4766. public function GetEditClass() {return "One Way Password";}
  4767. public function IsDirectField() {return true;}
  4768. public function IsScalar() {return true;}
  4769. public function IsWritable() {return true;}
  4770. public function GetDefaultValue(DBObject $oHostObject = null) {return "";}
  4771. public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);}
  4772. // Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted)
  4773. public function MakeRealValue($proposedValue, $oHostObj)
  4774. {
  4775. $oPassword = $proposedValue;
  4776. if (!is_object($oPassword))
  4777. {
  4778. $oPassword = new ormPassword('', '');
  4779. $oPassword->SetPassword($proposedValue);
  4780. }
  4781. return $oPassword;
  4782. }
  4783. public function GetSQLExpressions($sPrefix = '')
  4784. {
  4785. if ($sPrefix == '')
  4786. {
  4787. $sPrefix = $this->GetCode(); // Warning: AttributeOneWayPassword does not have any sql property so code = sql !
  4788. }
  4789. $aColumns = array();
  4790. // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
  4791. $aColumns[''] = $sPrefix.'_hash';
  4792. $aColumns['_salt'] = $sPrefix.'_salt';
  4793. return $aColumns;
  4794. }
  4795. public function FromSQLToValue($aCols, $sPrefix = '')
  4796. {
  4797. if (!array_key_exists($sPrefix, $aCols))
  4798. {
  4799. $sAvailable = implode(', ', array_keys($aCols));
  4800. throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
  4801. }
  4802. $hashed = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
  4803. if (!array_key_exists($sPrefix.'_salt', $aCols))
  4804. {
  4805. $sAvailable = implode(', ', array_keys($aCols));
  4806. throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}");
  4807. }
  4808. $sSalt = isset($aCols[$sPrefix.'_salt']) ? $aCols[$sPrefix.'_salt'] : '';
  4809. $value = new ormPassword($hashed, $sSalt);
  4810. return $value;
  4811. }
  4812. public function GetSQLValues($value)
  4813. {
  4814. // #@# Optimization: do not load blobs anytime
  4815. // As per mySQL doc, selecting blob columns will prevent mySQL from
  4816. // using memory in case a temporary table has to be created
  4817. // (temporary tables created on disk)
  4818. // We will have to remove the blobs from the list of attributes when doing the select
  4819. // then the use of Get() should finalize the load
  4820. if ($value instanceOf ormPassword)
  4821. {
  4822. $aValues = array();
  4823. $aValues[$this->GetCode().'_hash'] = $value->GetHash();
  4824. $aValues[$this->GetCode().'_salt'] = $value->GetSalt();
  4825. }
  4826. else
  4827. {
  4828. $aValues = array();
  4829. $aValues[$this->GetCode().'_hash'] = '';
  4830. $aValues[$this->GetCode().'_salt'] = '';
  4831. }
  4832. return $aValues;
  4833. }
  4834. public function GetSQLColumns($bFullSpec = false)
  4835. {
  4836. $aColumns = array();
  4837. $aColumns[$this->GetCode().'_hash'] = 'TINYBLOB';
  4838. $aColumns[$this->GetCode().'_salt'] = 'TINYBLOB';
  4839. return $aColumns;
  4840. }
  4841. public function GetImportColumns()
  4842. {
  4843. $aColumns = array();
  4844. $aColumns[$this->GetCode()] = 'TINYTEXT';
  4845. return $aColumns;
  4846. }
  4847. public function FromImportToValue($aCols, $sPrefix = '')
  4848. {
  4849. if (!isset($aCols[$sPrefix]))
  4850. {
  4851. $sAvailable = implode(', ', array_keys($aCols));
  4852. throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
  4853. }
  4854. $sClearPwd = $aCols[$sPrefix];
  4855. $oPassword = new ormPassword('', '');
  4856. $oPassword->SetPassword($sClearPwd);
  4857. return $oPassword;
  4858. }
  4859. public function GetFilterDefinitions()
  4860. {
  4861. return array();
  4862. // still not working... see later...
  4863. }
  4864. public function GetBasicFilterOperators()
  4865. {
  4866. return array();
  4867. }
  4868. public function GetBasicFilterLooseOperator()
  4869. {
  4870. return '=';
  4871. }
  4872. public function GetBasicFilterSQLExpr($sOpCode, $value)
  4873. {
  4874. return 'true';
  4875. }
  4876. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  4877. {
  4878. if (is_object($value))
  4879. {
  4880. return $value->GetAsHTML();
  4881. }
  4882. }
  4883. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  4884. {
  4885. return ''; // Not exportable in CSV
  4886. }
  4887. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  4888. {
  4889. return ''; // Not exportable in XML
  4890. }
  4891. }
  4892. // Indexed array having two dimensions
  4893. class AttributeTable extends AttributeDBField
  4894. {
  4895. public function GetEditClass() {return "Table";}
  4896. protected function GetSQLCol($bFullSpec = false) {return "LONGTEXT";}
  4897. public function GetMaxSize()
  4898. {
  4899. return null;
  4900. }
  4901. public function GetNullValue()
  4902. {
  4903. return array();
  4904. }
  4905. public function IsNull($proposedValue)
  4906. {
  4907. return (count($proposedValue) == 0);
  4908. }
  4909. public function GetEditValue($sValue, $oHostObj = null)
  4910. {
  4911. return '';
  4912. }
  4913. // Facilitate things: allow the user to Set the value from a string
  4914. public function MakeRealValue($proposedValue, $oHostObj)
  4915. {
  4916. if (is_null($proposedValue))
  4917. {
  4918. return array();
  4919. }
  4920. else if (!is_array($proposedValue))
  4921. {
  4922. return array(0 => array(0 => $proposedValue));
  4923. }
  4924. return $proposedValue;
  4925. }
  4926. public function FromSQLToValue($aCols, $sPrefix = '')
  4927. {
  4928. try
  4929. {
  4930. $value = @unserialize($aCols[$sPrefix.'']);
  4931. if ($value === false)
  4932. {
  4933. $value = $this->MakeRealValue($aCols[$sPrefix.''], null);
  4934. }
  4935. }
  4936. catch(Exception $e)
  4937. {
  4938. $value = $this->MakeRealValue($aCols[$sPrefix.''], null);
  4939. }
  4940. return $value;
  4941. }
  4942. public function GetSQLValues($value)
  4943. {
  4944. $aValues = array();
  4945. $aValues[$this->Get("sql")] = serialize($value);
  4946. return $aValues;
  4947. }
  4948. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  4949. {
  4950. if (!is_array($value))
  4951. {
  4952. throw new CoreException('Expecting an array', array('found' => get_class($value)));
  4953. }
  4954. if (count($value) == 0)
  4955. {
  4956. return "";
  4957. }
  4958. $sRes = "<TABLE class=\"listResults\">";
  4959. $sRes .= "<TBODY>";
  4960. foreach($value as $iRow => $aRawData)
  4961. {
  4962. $sRes .= "<TR>";
  4963. foreach ($aRawData as $iCol => $cell)
  4964. {
  4965. $sCell = str_replace("\n", "<br>\n", Str::pure2html((string)$cell));
  4966. $sRes .= "<TD>$sCell</TD>";
  4967. }
  4968. $sRes .= "</TR>";
  4969. }
  4970. $sRes .= "</TBODY>";
  4971. $sRes .= "</TABLE>";
  4972. return $sRes;
  4973. }
  4974. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  4975. {
  4976. // Not implemented
  4977. return '';
  4978. }
  4979. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  4980. {
  4981. if (count($value) == 0)
  4982. {
  4983. return "";
  4984. }
  4985. $sRes = "";
  4986. foreach($value as $iRow => $aRawData)
  4987. {
  4988. $sRes .= "<row>";
  4989. foreach ($aRawData as $iCol => $cell)
  4990. {
  4991. $sCell = Str::pure2xml((string)$cell);
  4992. $sRes .= "<cell icol=\"$iCol\">$sCell</cell>";
  4993. }
  4994. $sRes .= "</row>";
  4995. }
  4996. return $sRes;
  4997. }
  4998. }
  4999. // The PHP value is a hash array, it is stored as a TEXT column
  5000. class AttributePropertySet extends AttributeTable
  5001. {
  5002. public function GetEditClass() {return "PropertySet";}
  5003. // Facilitate things: allow the user to Set the value from a string
  5004. public function MakeRealValue($proposedValue, $oHostObj)
  5005. {
  5006. if (!is_array($proposedValue))
  5007. {
  5008. return array('?' => (string)$proposedValue);
  5009. }
  5010. return $proposedValue;
  5011. }
  5012. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  5013. {
  5014. if (!is_array($value))
  5015. {
  5016. throw new CoreException('Expecting an array', array('found' => get_class($value)));
  5017. }
  5018. if (count($value) == 0)
  5019. {
  5020. return "";
  5021. }
  5022. $sRes = "<TABLE class=\"listResults\">";
  5023. $sRes .= "<TBODY>";
  5024. foreach($value as $sProperty => $sValue)
  5025. {
  5026. if ($sProperty == 'auth_pwd')
  5027. {
  5028. $sValue = '*****';
  5029. }
  5030. $sRes .= "<TR>";
  5031. $sCell = str_replace("\n", "<br>\n", Str::pure2html((string)$sValue));
  5032. $sRes .= "<TD class=\"label\">$sProperty</TD><TD>$sCell</TD>";
  5033. $sRes .= "</TR>";
  5034. }
  5035. $sRes .= "</TBODY>";
  5036. $sRes .= "</TABLE>";
  5037. return $sRes;
  5038. }
  5039. public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  5040. {
  5041. if (count($value) == 0)
  5042. {
  5043. return "";
  5044. }
  5045. $aRes = array();
  5046. foreach($value as $sProperty => $sValue)
  5047. {
  5048. if ($sProperty == 'auth_pwd')
  5049. {
  5050. $sValue = '*****';
  5051. }
  5052. $sFrom = array(',', '=');
  5053. $sTo = array('\,', '\=');
  5054. $aRes[] = $sProperty.'='.str_replace($sFrom, $sTo, (string)$sValue);
  5055. }
  5056. $sRaw = implode(',', $aRes);
  5057. $sFrom = array("\r\n", $sTextQualifier);
  5058. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  5059. $sEscaped = str_replace($sFrom, $sTo, $sRaw);
  5060. return $sTextQualifier.$sEscaped.$sTextQualifier;
  5061. }
  5062. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  5063. {
  5064. if (count($value) == 0)
  5065. {
  5066. return "";
  5067. }
  5068. $sRes = "";
  5069. foreach($value as $sProperty => $sValue)
  5070. {
  5071. if ($sProperty == 'auth_pwd')
  5072. {
  5073. $sValue = '*****';
  5074. }
  5075. $sRes .= "<property id=\"$sProperty\">";
  5076. $sRes .= Str::pure2xml((string)$sValue);
  5077. $sRes .= "</property>";
  5078. }
  5079. return $sRes;
  5080. }
  5081. }
  5082. /**
  5083. * The attribute dedicated to the friendly name automatic attribute (not written)
  5084. *
  5085. * @package iTopORM
  5086. */
  5087. class AttributeComputedFieldVoid extends AttributeDefinition
  5088. {
  5089. static public function ListExpectedParams()
  5090. {
  5091. return array_merge(parent::ListExpectedParams(), array());
  5092. }
  5093. public function GetEditClass() {return "";}
  5094. public function GetValuesDef() {return null;}
  5095. public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());}
  5096. public function IsDirectField() {return true;}
  5097. public function IsScalar() {return true;}
  5098. public function IsWritable() {return false;}
  5099. public function GetSQLExpr() {return null;}
  5100. public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);}
  5101. public function IsNullAllowed() {return false;}
  5102. //
  5103. // protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
  5104. public function GetSQLExpressions($sPrefix = '')
  5105. {
  5106. if ($sPrefix == '')
  5107. {
  5108. $sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property
  5109. }
  5110. return array('' => $sPrefix);
  5111. }
  5112. public function FromSQLToValue($aCols, $sPrefix = '')
  5113. {
  5114. return null;
  5115. }
  5116. public function GetSQLValues($value)
  5117. {
  5118. return array();
  5119. }
  5120. public function GetSQLColumns($bFullSpec = false)
  5121. {
  5122. return array();
  5123. }
  5124. public function GetFilterDefinitions()
  5125. {
  5126. return array($this->GetCode() => new FilterFromAttribute($this));
  5127. }
  5128. public function GetBasicFilterOperators()
  5129. {
  5130. return array("="=>"equals", "!="=>"differs from");
  5131. }
  5132. public function GetBasicFilterLooseOperator()
  5133. {
  5134. return "=";
  5135. }
  5136. public function GetBasicFilterSQLExpr($sOpCode, $value)
  5137. {
  5138. $sQValue = CMDBSource::Quote($value);
  5139. switch ($sOpCode)
  5140. {
  5141. case '!=':
  5142. return $this->GetSQLExpr()." != $sQValue";
  5143. break;
  5144. case '=':
  5145. default:
  5146. return $this->GetSQLExpr()." = $sQValue";
  5147. }
  5148. }
  5149. public function IsPartOfFingerprint() { return false; }
  5150. }
  5151. /**
  5152. * The attribute dedicated to the friendly name automatic attribute (not written)
  5153. *
  5154. * @package iTopORM
  5155. */
  5156. class AttributeFriendlyName extends AttributeComputedFieldVoid
  5157. {
  5158. public function __construct($sCode, $sExtKeyAttCode)
  5159. {
  5160. $this->m_sCode = $sCode;
  5161. $aParams = array();
  5162. // $aParams["is_null_allowed"] = false,
  5163. $aParams["default_value"] = '';
  5164. $aParams["extkey_attcode"] = $sExtKeyAttCode;
  5165. parent::__construct($sCode, $aParams);
  5166. $this->m_sValue = $this->Get("default_value");
  5167. }
  5168. public function GetKeyAttCode() {return $this->Get("extkey_attcode");}
  5169. public function GetExtAttCode() {return 'friendlyname';}
  5170. public function GetLabel($sDefault = null)
  5171. {
  5172. $sLabel = parent::GetLabel('');
  5173. if (strlen($sLabel) == 0)
  5174. {
  5175. $sKeyAttCode = $this->Get("extkey_attcode");
  5176. if ($sKeyAttCode == 'id')
  5177. {
  5178. return Dict::S('Core:FriendlyName-Label');
  5179. }
  5180. else
  5181. {
  5182. $oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
  5183. $sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode);
  5184. }
  5185. }
  5186. return $sLabel;
  5187. }
  5188. public function GetDescription($sDefault = null)
  5189. {
  5190. $sLabel = parent::GetDescription('');
  5191. if (strlen($sLabel) == 0)
  5192. {
  5193. $sKeyAttCode = $this->Get("extkey_attcode");
  5194. if ($sKeyAttCode == 'id')
  5195. {
  5196. return Dict::S('Core:FriendlyName-Description');
  5197. }
  5198. else
  5199. {
  5200. $oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
  5201. $sLabel = $oExtKeyAttDef->GetDescription('');
  5202. }
  5203. }
  5204. return $sLabel;
  5205. }
  5206. // n/a, the friendly name is made of a complex expression (see GetNameSpec)
  5207. protected function GetSQLCol($bFullSpec = false) {return "";}
  5208. public function FromSQLToValue($aCols, $sPrefix = '')
  5209. {
  5210. $sValue = $aCols[$sPrefix];
  5211. return $sValue;
  5212. }
  5213. /**
  5214. * Encrypt the value before storing it in the database
  5215. */
  5216. public function GetSQLValues($value)
  5217. {
  5218. return array();
  5219. }
  5220. public function IsWritable()
  5221. {
  5222. return false;
  5223. }
  5224. public function IsDirectField()
  5225. {
  5226. return false;
  5227. }
  5228. public function SetFixedValue($sValue)
  5229. {
  5230. $this->m_sValue = $sValue;
  5231. }
  5232. public function GetDefaultValue(DBObject $oHostObject = null)
  5233. {
  5234. return $this->m_sValue;
  5235. }
  5236. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  5237. {
  5238. return Str::pure2html((string)$sValue);
  5239. }
  5240. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  5241. {
  5242. $sFrom = array("\r\n", $sTextQualifier);
  5243. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  5244. $sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
  5245. return $sTextQualifier.$sEscaped.$sTextQualifier;
  5246. }
  5247. // Do not display friendly names in the history of change
  5248. public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
  5249. {
  5250. return '';
  5251. }
  5252. public function GetBasicFilterLooseOperator()
  5253. {
  5254. return "Contains";
  5255. }
  5256. public function GetBasicFilterSQLExpr($sOpCode, $value)
  5257. {
  5258. $sQValue = CMDBSource::Quote($value);
  5259. switch ($sOpCode)
  5260. {
  5261. case '=':
  5262. case '!=':
  5263. return $this->GetSQLExpr()." $sOpCode $sQValue";
  5264. case 'Contains':
  5265. return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
  5266. case 'NotLike':
  5267. return $this->GetSQLExpr()." NOT LIKE $sQValue";
  5268. case 'Like':
  5269. default:
  5270. return $this->GetSQLExpr()." LIKE $sQValue";
  5271. }
  5272. }
  5273. public function IsPartOfFingerprint() { return false; }
  5274. }
  5275. /**
  5276. * Holds the setting for the redundancy on a specific relation
  5277. * Its value is a string, containing either:
  5278. * - 'disabled'
  5279. * - 'n', where n is a positive integer value giving the minimum count of items upstream
  5280. * - 'n%', where n is a positive integer value, giving the minimum as a percentage of the total count of items upstream
  5281. *
  5282. * @package iTopORM
  5283. */
  5284. class AttributeRedundancySettings extends AttributeDBField
  5285. {
  5286. static public function ListExpectedParams()
  5287. {
  5288. return array('sql', 'relation_code', 'from_class', 'neighbour_id', 'enabled', 'enabled_mode', 'min_up', 'min_up_type', 'min_up_mode');
  5289. }
  5290. public function GetValuesDef() {return null;}
  5291. public function GetPrerequisiteAttributes($sClass = null) {return array();}
  5292. public function GetEditClass() {return "RedundancySetting";}
  5293. protected function GetSQLCol($bFullSpec = false)
  5294. {
  5295. return "VARCHAR(20)".($bFullSpec ? $this->GetSQLColSpec() : '');
  5296. }
  5297. public function GetValidationPattern()
  5298. {
  5299. return "^[0-9]{1,3}|[0-9]{1,2}%|disabled$";
  5300. }
  5301. public function GetMaxSize()
  5302. {
  5303. return 20;
  5304. }
  5305. public function GetDefaultValue(DBObject $oHostObject = null)
  5306. {
  5307. $sRet = 'disabled';
  5308. if ($this->Get('enabled'))
  5309. {
  5310. if ($this->Get('min_up_type') == 'count')
  5311. {
  5312. $sRet = (string) $this->Get('min_up');
  5313. }
  5314. else // percent
  5315. {
  5316. $sRet = $this->Get('min_up').'%';
  5317. }
  5318. }
  5319. return $sRet;
  5320. }
  5321. public function IsNullAllowed()
  5322. {
  5323. return false;
  5324. }
  5325. public function GetNullValue()
  5326. {
  5327. return '';
  5328. }
  5329. public function IsNull($proposedValue)
  5330. {
  5331. return ($proposedValue == '');
  5332. }
  5333. public function MakeRealValue($proposedValue, $oHostObj)
  5334. {
  5335. if (is_null($proposedValue)) return '';
  5336. return (string)$proposedValue;
  5337. }
  5338. public function ScalarToSQL($value)
  5339. {
  5340. if (!is_string($value))
  5341. {
  5342. throw new CoreException('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode()));
  5343. }
  5344. return $value;
  5345. }
  5346. public function GetRelationQueryData()
  5347. {
  5348. foreach (MetaModel::EnumRelationQueries($this->GetHostClass(), $this->Get('relation_code'), false) as $sDummy => $aQueryInfo)
  5349. {
  5350. if ($aQueryInfo['sFromClass'] == $this->Get('from_class'))
  5351. {
  5352. if ($aQueryInfo['sNeighbour'] == $this->Get('neighbour_id'))
  5353. {
  5354. return $aQueryInfo;
  5355. }
  5356. }
  5357. }
  5358. }
  5359. /**
  5360. * Find the user option label
  5361. * @param user option : disabled|cout|percent
  5362. */
  5363. public function GetUserOptionFormat($sUserOption, $sDefault = null)
  5364. {
  5365. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, null, true /*user lang*/);
  5366. if (is_null($sLabel))
  5367. {
  5368. // If no default value is specified, let's define the most relevant one for developping purposes
  5369. if (is_null($sDefault))
  5370. {
  5371. $sDefault = str_replace('_', ' ', $this->m_sCode.':'.$sUserOption.'(%1$s)');
  5372. }
  5373. // Browse the hierarchy again, accepting default (english) translations
  5374. $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, $sDefault, false);
  5375. }
  5376. return $sLabel;
  5377. }
  5378. /**
  5379. * Override to display the value in the GUI
  5380. */
  5381. public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
  5382. {
  5383. $sCurrentOption = $this->GetCurrentOption($sValue);
  5384. $sClass = $oHostObject ? get_class($oHostObject) : $this->m_sHostClass;
  5385. return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue), MetaModel::GetName($sClass));
  5386. }
  5387. public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  5388. {
  5389. $sFrom = array("\r\n", $sTextQualifier);
  5390. $sTo = array("\n", $sTextQualifier.$sTextQualifier);
  5391. $sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
  5392. return $sTextQualifier.$sEscaped.$sTextQualifier;
  5393. }
  5394. /**
  5395. * Helper to interpret the value, given the current settings and string representation of the attribute
  5396. */
  5397. public function IsEnabled($sValue)
  5398. {
  5399. if ($this->get('enabled_mode') == 'fixed')
  5400. {
  5401. $bRet = $this->get('enabled');
  5402. }
  5403. else
  5404. {
  5405. $bRet = ($sValue != 'disabled');
  5406. }
  5407. return $bRet;
  5408. }
  5409. /**
  5410. * Helper to interpret the value, given the current settings and string representation of the attribute
  5411. */
  5412. public function GetMinUpType($sValue)
  5413. {
  5414. if ($this->get('min_up_mode') == 'fixed')
  5415. {
  5416. $sRet = $this->get('min_up_type');
  5417. }
  5418. else
  5419. {
  5420. $sRet = 'count';
  5421. if (substr(trim($sValue), -1, 1) == '%')
  5422. {
  5423. $sRet = 'percent';
  5424. }
  5425. }
  5426. return $sRet;
  5427. }
  5428. /**
  5429. * Helper to interpret the value, given the current settings and string representation of the attribute
  5430. */
  5431. public function GetMinUpValue($sValue)
  5432. {
  5433. if ($this->get('min_up_mode') == 'fixed')
  5434. {
  5435. $iRet = (int) $this->Get('min_up');
  5436. }
  5437. else
  5438. {
  5439. $sRefValue = $sValue;
  5440. if (substr(trim($sValue), -1, 1) == '%')
  5441. {
  5442. $sRefValue = substr(trim($sValue), 0, -1);
  5443. }
  5444. $iRet = (int) trim($sRefValue);
  5445. }
  5446. return $iRet;
  5447. }
  5448. /**
  5449. * Helper to determine if the redundancy can be viewed/edited by the end-user
  5450. */
  5451. public function IsVisible()
  5452. {
  5453. $bRet = false;
  5454. if ($this->Get('enabled_mode') == 'fixed')
  5455. {
  5456. $bRet = $this->Get('enabled');
  5457. }
  5458. elseif ($this->Get('enabled_mode') == 'user')
  5459. {
  5460. $bRet = true;
  5461. }
  5462. return $bRet;
  5463. }
  5464. public function IsWritable()
  5465. {
  5466. if (($this->Get('enabled_mode') == 'fixed') && ($this->Get('min_up_mode') == 'fixed'))
  5467. {
  5468. return false;
  5469. }
  5470. return true;
  5471. }
  5472. /**
  5473. * Returns an HTML form that can be read by ReadValueFromPostedForm
  5474. */
  5475. public function GetDisplayForm($sCurrentValue, $oPage, $bEditMode = false, $sFormPrefix = '')
  5476. {
  5477. $sRet = '';
  5478. $aUserOptions = $this->GetUserOptions($sCurrentValue);
  5479. if (count($aUserOptions) < 2)
  5480. {
  5481. $bEditOption = false;
  5482. }
  5483. else
  5484. {
  5485. $bEditOption = $bEditMode;
  5486. }
  5487. $sCurrentOption = $this->GetCurrentOption($sCurrentValue);
  5488. foreach($aUserOptions as $sUserOption)
  5489. {
  5490. $bSelected = ($sUserOption == $sCurrentOption);
  5491. $sRet .= '<div>';
  5492. $sRet .= $this->GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditOption, $sUserOption, $bSelected);
  5493. $sRet .= '</div>';
  5494. }
  5495. return $sRet;
  5496. }
  5497. const USER_OPTION_DISABLED = 'disabled';
  5498. const USER_OPTION_ENABLED_COUNT = 'count';
  5499. const USER_OPTION_ENABLED_PERCENT = 'percent';
  5500. /**
  5501. * Depending on the xxx_mode parameters, build the list of options that are allowed to the end-user
  5502. */
  5503. protected function GetUserOptions($sValue)
  5504. {
  5505. $aRet = array();
  5506. if ($this->Get('enabled_mode') == 'user')
  5507. {
  5508. $aRet[] = self::USER_OPTION_DISABLED;
  5509. }
  5510. if ($this->Get('min_up_mode') == 'user')
  5511. {
  5512. $aRet[] = self::USER_OPTION_ENABLED_COUNT;
  5513. $aRet[] = self::USER_OPTION_ENABLED_PERCENT;
  5514. }
  5515. else
  5516. {
  5517. if ($this->GetMinUpType($sValue) == 'count')
  5518. {
  5519. $aRet[] = self::USER_OPTION_ENABLED_COUNT;
  5520. }
  5521. else
  5522. {
  5523. $aRet[] = self::USER_OPTION_ENABLED_PERCENT;
  5524. }
  5525. }
  5526. return $aRet;
  5527. }
  5528. /**
  5529. * Convert the string representation into one of the existing options
  5530. */
  5531. protected function GetCurrentOption($sValue)
  5532. {
  5533. $sRet = self::USER_OPTION_DISABLED;
  5534. if ($this->IsEnabled($sValue))
  5535. {
  5536. if ($this->GetMinUpType($sValue) == 'count')
  5537. {
  5538. $sRet = self::USER_OPTION_ENABLED_COUNT;
  5539. }
  5540. else
  5541. {
  5542. $sRet = self::USER_OPTION_ENABLED_PERCENT;
  5543. }
  5544. }
  5545. return $sRet;
  5546. }
  5547. /**
  5548. * Display an option (form, or current value)
  5549. */
  5550. protected function GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditMode, $sUserOption, $bSelected = true)
  5551. {
  5552. $sRet = '';
  5553. $iCurrentValue = $this->GetMinUpValue($sCurrentValue);
  5554. if ($bEditMode)
  5555. {
  5556. $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id');
  5557. switch ($sUserOption)
  5558. {
  5559. case self::USER_OPTION_DISABLED:
  5560. $sValue = ''; // Empty placeholder
  5561. break;
  5562. case self::USER_OPTION_ENABLED_COUNT:
  5563. if ($bEditMode)
  5564. {
  5565. $sName = $sHtmlNamesPrefix.'_min_up_count';
  5566. $sEditValue = $bSelected ? $iCurrentValue : '';
  5567. $sValue = '<input class="redundancy-min-up-count" type="string" size="3" name="'.$sName.'" value="'.$sEditValue.'">';
  5568. // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option)
  5569. $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});");
  5570. }
  5571. else
  5572. {
  5573. $sValue = $iCurrentValue;
  5574. }
  5575. break;
  5576. case self::USER_OPTION_ENABLED_PERCENT:
  5577. if ($bEditMode)
  5578. {
  5579. $sName = $sHtmlNamesPrefix.'_min_up_percent';
  5580. $sEditValue = $bSelected ? $iCurrentValue : '';
  5581. $sValue = '<input class="redundancy-min-up-percent" type="string" size="3" name="'.$sName.'" value="'.$sEditValue.'">';
  5582. // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option)
  5583. $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});");
  5584. }
  5585. else
  5586. {
  5587. $sValue = $iCurrentValue;
  5588. }
  5589. break;
  5590. }
  5591. $sLabel = sprintf($this->GetUserOptionFormat($sUserOption), $sValue, MetaModel::GetName($this->GetHostClass()));
  5592. $sOptionName = $sHtmlNamesPrefix.'_user_option';
  5593. $sOptionId = $sOptionName.'_'.$sUserOption;
  5594. $sChecked = $bSelected ? 'checked' : '';
  5595. $sRet = '<input type="radio" name="'.$sOptionName.'" id="'.$sOptionId.'" value="'.$sUserOption.'"'.$sChecked.'> <label for="'.$sOptionId.'">'.$sLabel.'</label>';
  5596. }
  5597. else
  5598. {
  5599. // Read-only: display only the currently selected option
  5600. if ($bSelected)
  5601. {
  5602. $sRet = sprintf($this->GetUserOptionFormat($sUserOption), $iCurrentValue, MetaModel::GetName($this->GetHostClass()));
  5603. }
  5604. }
  5605. return $sRet;
  5606. }
  5607. /**
  5608. * Makes the string representation out of the values given by the form defined in GetDisplayForm
  5609. */
  5610. public function ReadValueFromPostedForm($sFormPrefix)
  5611. {
  5612. $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id');
  5613. $iMinUpCount = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_count', null, 'raw_data');
  5614. $iMinUpPercent = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_percent', null, 'raw_data');
  5615. $sSelectedOption = utils::ReadPostedParam($sHtmlNamesPrefix.'_user_option', null, 'raw_data');
  5616. switch ($sSelectedOption)
  5617. {
  5618. case self::USER_OPTION_ENABLED_COUNT:
  5619. $sRet = $iMinUpCount;
  5620. break;
  5621. case self::USER_OPTION_ENABLED_PERCENT:
  5622. $sRet = $iMinUpPercent.'%';
  5623. break;
  5624. case self::USER_OPTION_DISABLED:
  5625. default:
  5626. $sRet = 'disabled';
  5627. break;
  5628. }
  5629. return $sRet;
  5630. }
  5631. }
  5632. /**
  5633. * Custom fields managed by an external implementation
  5634. *
  5635. * @package iTopORM
  5636. */
  5637. class AttributeCustomFields extends AttributeDefinition
  5638. {
  5639. static public function ListExpectedParams()
  5640. {
  5641. return array_merge(parent::ListExpectedParams(), array("handler_class"));
  5642. }
  5643. public function GetEditClass() {return "CustomFields";}
  5644. public function IsWritable() {return true;}
  5645. public function LoadFromDB() {return false;} // See ReadValue...
  5646. public function GetDefaultValue(DBObject $oHostObject = null)
  5647. {
  5648. return new ormCustomFieldsValue($oHostObject, $this->GetCode());
  5649. }
  5650. public function GetBasicFilterOperators() {return array();}
  5651. public function GetBasicFilterLooseOperator() {return '';}
  5652. public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';}
  5653. /**
  5654. * @param DBObject $oHostObject
  5655. * @param ormCustomFieldsValue|null $oValue
  5656. * @return CustomFieldsHandler
  5657. */
  5658. public function GetHandler(DBObject $oHostObject, ormCustomFieldsValue $oValue = null)
  5659. {
  5660. $sHandlerClass = $this->Get('handler_class');
  5661. $oHandler = new $sHandlerClass($oHostObject, $this->GetCode());
  5662. if (!is_null($oValue))
  5663. {
  5664. $oHandler->SetCurrentValues($oValue->GetValues());
  5665. }
  5666. return $oHandler;
  5667. }
  5668. public function GetPrerequisiteAttributes($sClass = null)
  5669. {
  5670. $sHandlerClass = $this->Get('handler_class');
  5671. return $sHandlerClass::GetPrerequisiteAttributes($sClass);
  5672. }
  5673. public function GetEditValue($sValue, $oHostObj = null)
  5674. {
  5675. return 'GetEditValueNotImplemented for '.get_class($this);
  5676. }
  5677. /**
  5678. * Makes the string representation out of the values given by the form defined in GetDisplayForm
  5679. */
  5680. public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
  5681. {
  5682. $aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
  5683. return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
  5684. }
  5685. public function MakeRealValue($proposedValue, $oHostObject)
  5686. {
  5687. if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue))
  5688. {
  5689. return $proposedValue;
  5690. }
  5691. elseif (is_string($proposedValue))
  5692. {
  5693. $aValues = json_decode($proposedValue, true);
  5694. return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
  5695. }
  5696. throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue));
  5697. }
  5698. /**
  5699. * Override to build the relevant form field
  5700. *
  5701. * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behaves more like a Prepare.
  5702. */
  5703. public function MakeFormField(DBObject $oObject, $oFormField = null)
  5704. {
  5705. if ($oFormField === null)
  5706. {
  5707. $oField = new Combodo\iTop\Form\Field\SubFormField($this->GetCode(), $sParentFormId);
  5708. $oField->SetForm($this->GetForm($oObject));
  5709. }
  5710. parent::MakeFormField($oObject, $oFormField);
  5711. return $oFormField;
  5712. }
  5713. /**
  5714. * @param DBObject $oHostObject
  5715. * @return Combodo\iTop\Form\Form
  5716. */
  5717. public function GetForm(DBObject $oHostObject, $sFormPrefix = null)
  5718. {
  5719. $oHandler = $this->GetHandler($oHostObject, $oHostObject->Get($this->GetCode()));
  5720. $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode();
  5721. $oHandler->BuildForm($sFormId);
  5722. return $oHandler->GetForm();
  5723. }
  5724. /**
  5725. * Read the data from where it has been stored. This verb must be implemented as soon as LoadFromDB returns false and LoadInObject returns true
  5726. * @param $oHostObject
  5727. * @return ormCustomFieldsValue
  5728. */
  5729. public function ReadValue($oHostObject)
  5730. {
  5731. $oHandler = $this->GetHandler($oHostObject);
  5732. $aValues = $oHandler->ReadValues();
  5733. $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
  5734. return $oRet;
  5735. }
  5736. /**
  5737. * Record the data (currently in the processing of recording the host object)
  5738. * It is assumed that the data has been checked prior to calling Write()
  5739. * @param DBObject $oHostObject
  5740. * @param ormCustomFieldsValue|null $oValue (null is the default value)
  5741. */
  5742. public function WriteValue(DBObject $oHostObject, ormCustomFieldsValue $oValue = null)
  5743. {
  5744. $oHandler = $this->GetHandler($oHostObject, $oHostObject->Get($this->GetCode()));
  5745. if (is_null($oValue))
  5746. {
  5747. $aValues = array();
  5748. }
  5749. else
  5750. {
  5751. $aValues = $oValue->GetValues();
  5752. }
  5753. return $oHandler->WriteValues($aValues);
  5754. }
  5755. /**
  5756. * Check the validity of the data
  5757. * @param DBObject $oHostObject
  5758. * @param $value
  5759. * @return bool|string true or error message
  5760. */
  5761. public function CheckValue(DBObject $oHostObject, $value)
  5762. {
  5763. try
  5764. {
  5765. $oHandler = $this->GetHandler($oHostObject, $value);
  5766. $oHandler->BuildForm();
  5767. $oForm = $oHandler->GetForm();
  5768. $oForm->Validate();
  5769. if ($oForm->GetValid())
  5770. {
  5771. $ret = true;
  5772. }
  5773. else
  5774. {
  5775. $aMessages = array();
  5776. foreach ($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages)
  5777. {
  5778. $aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages);
  5779. }
  5780. $ret = 'Invalid value: '.implode(', ', $aMessages);
  5781. }
  5782. }
  5783. catch (Exception $e)
  5784. {
  5785. $ret = $e->getMessage();
  5786. }
  5787. return $ret;
  5788. }
  5789. /**
  5790. * Cleanup data upon object deletion (object id still available here)
  5791. * @param DBObject $oHostObject
  5792. */
  5793. public function DeleteValue(DBObject $oHostObject)
  5794. {
  5795. $oHandler = $this->GetHandler($oHostObject, $oHostObject->Get($this->GetCode()));
  5796. return $oHandler->DeleteValues();
  5797. }
  5798. public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
  5799. {
  5800. return $value->GetAsHTML($bLocalize);
  5801. }
  5802. public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
  5803. {
  5804. return $value->GetAsXML($bLocalize);
  5805. }
  5806. public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
  5807. {
  5808. return $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText);
  5809. }
  5810. /**
  5811. * List the available verbs for 'GetForTemplate'
  5812. */
  5813. public function EnumTemplateVerbs()
  5814. {
  5815. $sHandlerClass = $this->Get('handler_class');
  5816. return $sHandlerClass::EnumTemplateVerbs();
  5817. }
  5818. /**
  5819. * Get various representations of the value, for insertion into a template (e.g. in Notifications)
  5820. * @param $value mixed The current value of the field
  5821. * @param $sVerb string The verb specifying the representation of the value
  5822. * @param $oHostObject DBObject The object
  5823. * @param $bLocalize bool Whether or not to localize the value
  5824. */
  5825. public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
  5826. {
  5827. return $value->GetForTemplate($sVerb, $bLocalize);
  5828. }
  5829. public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
  5830. {
  5831. return null;
  5832. }
  5833. /**
  5834. * Helper to get a value that will be JSON encoded
  5835. * The operation is the opposite to FromJSONToValue
  5836. */
  5837. public function GetForJSON($value)
  5838. {
  5839. return null;
  5840. }
  5841. /**
  5842. * Helper to form a value, given JSON decoded data
  5843. * The operation is the opposite to GetForJSON
  5844. */
  5845. public function FromJSONToValue($json)
  5846. {
  5847. return null;
  5848. }
  5849. public function Equals($val1, $val2)
  5850. {
  5851. return $val1->Equals($val2);
  5852. }
  5853. }