learnpath.class.php 532 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
  4. use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
  5. use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
  6. use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
  7. use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
  8. use Chamilo\CourseBundle\Entity\CDocument;
  9. use Chamilo\CourseBundle\Entity\CItemProperty;
  10. use Chamilo\CourseBundle\Entity\CLp;
  11. use Chamilo\CourseBundle\Entity\CLpCategory;
  12. use Chamilo\CourseBundle\Entity\CLpItem;
  13. use Chamilo\CourseBundle\Entity\CLpItemView;
  14. use Chamilo\CourseBundle\Entity\CTool;
  15. use Chamilo\UserBundle\Entity\User;
  16. use ChamiloSession as Session;
  17. use Gedmo\Sortable\Entity\Repository\SortableRepository;
  18. use Symfony\Component\Filesystem\Filesystem;
  19. use Symfony\Component\Finder\Finder;
  20. /**
  21. * Class learnpath
  22. * This class defines the parent attributes and methods for Chamilo learnpaths
  23. * and SCORM learnpaths. It is used by the scorm class.
  24. *
  25. * @todo decouple class
  26. *
  27. * @package chamilo.learnpath
  28. *
  29. * @author Yannick Warnier <ywarnier@beeznest.org>
  30. * @author Julio Montoya <gugli100@gmail.com> Several improvements and fixes
  31. */
  32. class learnpath
  33. {
  34. const MAX_LP_ITEM_TITLE_LENGTH = 32;
  35. public $attempt = 0; // The number for the current ID view.
  36. public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
  37. public $current; // Id of the current item the user is viewing.
  38. public $current_score; // The score of the current item.
  39. public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
  40. public $current_time_stop; // The time the user closed this resource.
  41. public $default_status = 'not attempted';
  42. public $encoding = 'UTF-8';
  43. public $error = '';
  44. public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
  45. public $index; // The index of the active learnpath_item in $ordered_items array.
  46. public $items = [];
  47. public $last; // item_id of last item viewed in the learning path.
  48. public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
  49. public $license; // Which license this course has been given - not used yet on 20060522.
  50. public $lp_id; // DB iid for this learnpath.
  51. public $lp_view_id; // DB ID for lp_view
  52. public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
  53. public $message = '';
  54. public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
  55. public $name; // Learnpath name (they generally have one).
  56. public $ordered_items = []; // List of the learnpath items in the order they are to be read.
  57. public $path = ''; // Path inside the scorm directory (if scorm).
  58. public $theme; // The current theme of the learning path.
  59. public $preview_image; // The current image of the learning path.
  60. public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
  61. public $accumulateWorkTime; // The min time of learnpath
  62. // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
  63. public $prevent_reinit = 1;
  64. // Describes the mode of progress bar display.
  65. public $seriousgame_mode = 0;
  66. public $progress_bar_mode = '%';
  67. // Percentage progress as saved in the db.
  68. public $progress_db = 0;
  69. public $proximity; // Wether the content is distant or local or unknown.
  70. public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
  71. // !!!This array (refs_list) is built differently depending on the nature of the LP.
  72. // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
  73. public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
  74. // TODO: Check if this type variable is useful here (instead of just in the controller script).
  75. public $user_id; //ID of the user that is viewing/using the course
  76. public $update_queue = [];
  77. public $scorm_debug = 0;
  78. public $arrMenu = []; // Array for the menu items.
  79. public $debug = 0; // Logging level.
  80. public $lp_session_id = 0;
  81. public $lp_view_session_id = 0; // The specific view might be bound to a session.
  82. public $prerequisite = 0;
  83. public $use_max_score = 1; // 1 or 0
  84. public $subscribeUsers = 0; // Subscribe users or not
  85. public $created_on = '';
  86. public $modified_on = '';
  87. public $publicated_on = '';
  88. public $expired_on = '';
  89. public $ref = null;
  90. public $course_int_id;
  91. public $course_info = [];
  92. public $categoryId;
  93. /**
  94. * Constructor.
  95. * Needs a database handler, a course code and a learnpath id from the database.
  96. * Also builds the list of items into $this->items.
  97. *
  98. * @param string $course Course code
  99. * @param int $lp_id c_lp.iid
  100. * @param int $user_id
  101. */
  102. public function __construct($course, $lp_id, $user_id)
  103. {
  104. $debug = $this->debug;
  105. $this->encoding = api_get_system_encoding();
  106. if ($debug) {
  107. error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
  108. }
  109. if (empty($course)) {
  110. $course = api_get_course_id();
  111. }
  112. $course_info = api_get_course_info($course);
  113. if (!empty($course_info)) {
  114. $this->cc = $course_info['code'];
  115. $this->course_info = $course_info;
  116. $course_id = $course_info['real_id'];
  117. } else {
  118. $this->error = 'Course code does not exist in database.';
  119. }
  120. $lp_id = (int) $lp_id;
  121. $course_id = (int) $course_id;
  122. $this->set_course_int_id($course_id);
  123. // Check learnpath ID.
  124. if (empty($lp_id) || empty($course_id)) {
  125. $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
  126. } else {
  127. // TODO: Make it flexible to use any course_code (still using env course code here).
  128. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  129. $sql = "SELECT * FROM $lp_table
  130. WHERE iid = $lp_id";
  131. if ($debug) {
  132. error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
  133. }
  134. $res = Database::query($sql);
  135. if (Database::num_rows($res) > 0) {
  136. $this->lp_id = $lp_id;
  137. $row = Database::fetch_array($res);
  138. $this->type = $row['lp_type'];
  139. $this->name = stripslashes($row['name']);
  140. $this->proximity = $row['content_local'];
  141. $this->theme = $row['theme'];
  142. $this->maker = $row['content_maker'];
  143. $this->prevent_reinit = $row['prevent_reinit'];
  144. $this->seriousgame_mode = $row['seriousgame_mode'];
  145. $this->license = $row['content_license'];
  146. $this->scorm_debug = $row['debug'];
  147. $this->js_lib = $row['js_lib'];
  148. $this->path = $row['path'];
  149. $this->preview_image = $row['preview_image'];
  150. $this->author = $row['author'];
  151. $this->hide_toc_frame = $row['hide_toc_frame'];
  152. $this->lp_session_id = $row['session_id'];
  153. $this->use_max_score = $row['use_max_score'];
  154. $this->subscribeUsers = $row['subscribe_users'];
  155. $this->created_on = $row['created_on'];
  156. $this->modified_on = $row['modified_on'];
  157. $this->ref = $row['ref'];
  158. $this->categoryId = $row['category_id'];
  159. $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
  160. $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
  161. if (!empty($row['publicated_on'])) {
  162. $this->publicated_on = $row['publicated_on'];
  163. }
  164. if (!empty($row['expired_on'])) {
  165. $this->expired_on = $row['expired_on'];
  166. }
  167. if ($this->type == 2) {
  168. if ($row['force_commit'] == 1) {
  169. $this->force_commit = true;
  170. }
  171. }
  172. $this->mode = $row['default_view_mod'];
  173. // Check user ID.
  174. if (empty($user_id)) {
  175. $this->error = 'User ID is empty';
  176. } else {
  177. $userInfo = api_get_user_info($user_id);
  178. if (!empty($userInfo)) {
  179. $this->user_id = $userInfo['user_id'];
  180. } else {
  181. $this->error = 'User ID does not exist in database #'.$user_id;
  182. }
  183. }
  184. // End of variables checking.
  185. $session_id = api_get_session_id();
  186. // Get the session condition for learning paths of the base + session.
  187. $session = api_get_session_condition($session_id);
  188. // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
  189. $lp_table = Database::get_course_table(TABLE_LP_VIEW);
  190. // Selecting by view_count descending allows to get the highest view_count first.
  191. $sql = "SELECT * FROM $lp_table
  192. WHERE
  193. c_id = $course_id AND
  194. lp_id = $lp_id AND
  195. user_id = $user_id
  196. $session
  197. ORDER BY view_count DESC";
  198. $res = Database::query($sql);
  199. if ($debug) {
  200. error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
  201. }
  202. if (Database::num_rows($res) > 0) {
  203. if ($debug) {
  204. error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
  205. }
  206. $row = Database::fetch_array($res);
  207. $this->attempt = $row['view_count'];
  208. $this->lp_view_id = $row['id'];
  209. $this->last_item_seen = $row['last_item'];
  210. $this->progress_db = $row['progress'];
  211. $this->lp_view_session_id = $row['session_id'];
  212. } elseif (!api_is_invitee()) {
  213. if ($debug) {
  214. error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
  215. }
  216. $this->attempt = 1;
  217. $params = [
  218. 'c_id' => $course_id,
  219. 'lp_id' => $lp_id,
  220. 'user_id' => $user_id,
  221. 'view_count' => 1,
  222. 'session_id' => $session_id,
  223. 'last_item' => 0,
  224. ];
  225. $this->last_item_seen = 0;
  226. $this->lp_view_session_id = $session_id;
  227. $this->lp_view_id = Database::insert($lp_table, $params);
  228. if (!empty($this->lp_view_id)) {
  229. $sql = "UPDATE $lp_table SET id = iid
  230. WHERE iid = ".$this->lp_view_id;
  231. Database::query($sql);
  232. }
  233. }
  234. // Initialise items.
  235. $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
  236. $sql = "SELECT * FROM $lp_item_table
  237. WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
  238. ORDER BY parent_item_id, display_order";
  239. $res = Database::query($sql);
  240. if ($debug) {
  241. error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
  242. error_log('-- Start while--');
  243. }
  244. $lp_item_id_list = [];
  245. while ($row = Database::fetch_array($res)) {
  246. $lp_item_id_list[] = $row['iid'];
  247. switch ($this->type) {
  248. case 3: //aicc
  249. $oItem = new aiccItem('db', $row['iid'], $course_id);
  250. if (is_object($oItem)) {
  251. $my_item_id = $oItem->get_id();
  252. $oItem->set_lp_view($this->lp_view_id, $course_id);
  253. $oItem->set_prevent_reinit($this->prevent_reinit);
  254. // Don't use reference here as the next loop will make the pointed object change.
  255. $this->items[$my_item_id] = $oItem;
  256. $this->refs_list[$oItem->ref] = $my_item_id;
  257. if ($debug) {
  258. error_log(
  259. 'learnpath::__construct() - '.
  260. 'aicc object with id '.$my_item_id.
  261. ' set in items[]',
  262. 0
  263. );
  264. }
  265. }
  266. break;
  267. case 2:
  268. $oItem = new scormItem('db', $row['iid'], $course_id);
  269. if (is_object($oItem)) {
  270. $my_item_id = $oItem->get_id();
  271. $oItem->set_lp_view($this->lp_view_id, $course_id);
  272. $oItem->set_prevent_reinit($this->prevent_reinit);
  273. // Don't use reference here as the next loop will make the pointed object change.
  274. $this->items[$my_item_id] = $oItem;
  275. $this->refs_list[$oItem->ref] = $my_item_id;
  276. if ($debug) {
  277. error_log('object with id '.$my_item_id.' set in items[]');
  278. }
  279. }
  280. break;
  281. case 1:
  282. default:
  283. if ($debug) {
  284. error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
  285. }
  286. $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
  287. if ($debug) {
  288. error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
  289. }
  290. if (is_object($oItem)) {
  291. $my_item_id = $oItem->get_id();
  292. // Moved down to when we are sure the item_view exists.
  293. //$oItem->set_lp_view($this->lp_view_id);
  294. $oItem->set_prevent_reinit($this->prevent_reinit);
  295. // Don't use reference here as the next loop will make the pointed object change.
  296. $this->items[$my_item_id] = $oItem;
  297. $this->refs_list[$my_item_id] = $my_item_id;
  298. if ($debug) {
  299. error_log(
  300. 'learnpath::__construct() '.__LINE__.
  301. ' - object with id '.$my_item_id.' set in items[]'
  302. );
  303. }
  304. }
  305. break;
  306. }
  307. // Setting the object level with variable $this->items[$i][parent]
  308. foreach ($this->items as $itemLPObject) {
  309. $level = self::get_level_for_item(
  310. $this->items,
  311. $itemLPObject->db_id
  312. );
  313. $itemLPObject->level = $level;
  314. }
  315. // Setting the view in the item object.
  316. if (is_object($this->items[$row['iid']])) {
  317. $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
  318. if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
  319. $this->items[$row['iid']]->current_start_time = 0;
  320. $this->items[$row['iid']]->current_stop_time = 0;
  321. }
  322. }
  323. }
  324. if ($debug) {
  325. error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
  326. }
  327. if (!empty($lp_item_id_list)) {
  328. $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
  329. if (!empty($lp_item_id_list_to_string)) {
  330. // Get last viewing vars.
  331. $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  332. // This query should only return one or zero result.
  333. $sql = "SELECT lp_item_id, status
  334. FROM $itemViewTable
  335. WHERE
  336. c_id = $course_id AND
  337. lp_view_id = ".$this->lp_view_id." AND
  338. lp_item_id IN ('".$lp_item_id_list_to_string."')
  339. ORDER BY view_count DESC ";
  340. if ($debug) {
  341. error_log(
  342. 'learnpath::__construct() - Selecting item_views: '.$sql,
  343. 0
  344. );
  345. }
  346. $status_list = [];
  347. $res = Database::query($sql);
  348. while ($row = Database:: fetch_array($res)) {
  349. $status_list[$row['lp_item_id']] = $row['status'];
  350. }
  351. foreach ($lp_item_id_list as $item_id) {
  352. if (isset($status_list[$item_id])) {
  353. $status = $status_list[$item_id];
  354. if (is_object($this->items[$item_id])) {
  355. $this->items[$item_id]->set_status($status);
  356. if (empty($status)) {
  357. $this->items[$item_id]->set_status(
  358. $this->default_status
  359. );
  360. }
  361. }
  362. } else {
  363. if (!api_is_invitee()) {
  364. if (is_object($this->items[$item_id])) {
  365. $this->items[$item_id]->set_status(
  366. $this->default_status
  367. );
  368. }
  369. if (!empty($this->lp_view_id)) {
  370. // Add that row to the lp_item_view table so that
  371. // we have something to show in the stats page.
  372. $params = [
  373. 'c_id' => $course_id,
  374. 'lp_item_id' => $item_id,
  375. 'lp_view_id' => $this->lp_view_id,
  376. 'view_count' => 1,
  377. 'status' => 'not attempted',
  378. 'start_time' => time(),
  379. 'total_time' => 0,
  380. 'score' => 0,
  381. ];
  382. $insertId = Database::insert($itemViewTable, $params);
  383. if ($insertId) {
  384. $sql = "UPDATE $itemViewTable SET id = iid
  385. WHERE iid = $insertId";
  386. Database::query($sql);
  387. }
  388. $this->items[$item_id]->set_lp_view(
  389. $this->lp_view_id,
  390. $course_id
  391. );
  392. }
  393. }
  394. }
  395. }
  396. }
  397. }
  398. $this->ordered_items = self::get_flat_ordered_items_list(
  399. $this->get_id(),
  400. 0,
  401. $course_id
  402. );
  403. $this->max_ordered_items = 0;
  404. foreach ($this->ordered_items as $index => $dummy) {
  405. if ($index > $this->max_ordered_items && !empty($dummy)) {
  406. $this->max_ordered_items = $index;
  407. }
  408. }
  409. // TODO: Define the current item better.
  410. $this->first();
  411. if ($debug) {
  412. error_log('lp_view_session_id '.$this->lp_view_session_id);
  413. error_log('End of learnpath constructor for learnpath '.$this->get_id());
  414. }
  415. } else {
  416. $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
  417. }
  418. }
  419. }
  420. /**
  421. * @return string
  422. */
  423. public function getCourseCode()
  424. {
  425. return $this->cc;
  426. }
  427. /**
  428. * @return int
  429. */
  430. public function get_course_int_id()
  431. {
  432. return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
  433. }
  434. /**
  435. * @param $course_id
  436. *
  437. * @return int
  438. */
  439. public function set_course_int_id($course_id)
  440. {
  441. return $this->course_int_id = (int) $course_id;
  442. }
  443. /**
  444. * Function rewritten based on old_add_item() from Yannick Warnier.
  445. * Due the fact that users can decide where the item should come, I had to overlook this function and
  446. * I found it better to rewrite it. Old function is still available.
  447. * Added also the possibility to add a description.
  448. *
  449. * @param int $parent
  450. * @param int $previous
  451. * @param string $type
  452. * @param int $id resource ID (ref)
  453. * @param string $title
  454. * @param string $description
  455. * @param int $prerequisites
  456. * @param int $max_time_allowed
  457. * @param int $userId
  458. *
  459. * @return int
  460. */
  461. public function add_item(
  462. $parent,
  463. $previous,
  464. $type = 'dir',
  465. $id,
  466. $title,
  467. $description,
  468. $prerequisites = 0,
  469. $max_time_allowed = 0,
  470. $userId = 0
  471. ) {
  472. $course_id = $this->course_info['real_id'];
  473. if ($this->debug > 0) {
  474. error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
  475. }
  476. if (empty($course_id)) {
  477. // Sometimes Oogie doesn't catch the course info but sets $this->cc
  478. $this->course_info = api_get_course_info($this->cc);
  479. $course_id = $this->course_info['real_id'];
  480. }
  481. $userId = empty($userId) ? api_get_user_id() : $userId;
  482. $sessionId = api_get_session_id();
  483. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  484. $_course = $this->course_info;
  485. $parent = (int) $parent;
  486. $previous = (int) $previous;
  487. $id = (int) $id;
  488. $max_time_allowed = htmlentities($max_time_allowed);
  489. if (empty($max_time_allowed)) {
  490. $max_time_allowed = 0;
  491. }
  492. $sql = "SELECT COUNT(iid) AS num
  493. FROM $tbl_lp_item
  494. WHERE
  495. c_id = $course_id AND
  496. lp_id = ".$this->get_id()." AND
  497. parent_item_id = ".$parent;
  498. $res_count = Database::query($sql);
  499. $row = Database::fetch_array($res_count);
  500. $num = $row['num'];
  501. $tmp_previous = 0;
  502. $display_order = 0;
  503. $next = 0;
  504. if ($num > 0) {
  505. if (empty($previous)) {
  506. $sql = "SELECT iid, next_item_id, display_order
  507. FROM $tbl_lp_item
  508. WHERE
  509. c_id = $course_id AND
  510. lp_id = ".$this->get_id()." AND
  511. parent_item_id = $parent AND
  512. previous_item_id = 0 OR
  513. previous_item_id = $parent";
  514. $result = Database::query($sql);
  515. $row = Database::fetch_array($result);
  516. if ($row) {
  517. $next = $row['iid'];
  518. }
  519. } else {
  520. $previous = (int) $previous;
  521. $sql = "SELECT iid, previous_item_id, next_item_id, display_order
  522. FROM $tbl_lp_item
  523. WHERE
  524. c_id = $course_id AND
  525. lp_id = ".$this->get_id()." AND
  526. id = $previous";
  527. $result = Database::query($sql);
  528. $row = Database::fetch_array($result);
  529. if ($row) {
  530. $tmp_previous = $row['iid'];
  531. $next = $row['next_item_id'];
  532. $display_order = $row['display_order'];
  533. }
  534. }
  535. }
  536. $id = (int) $id;
  537. $typeCleaned = Database::escape_string($type);
  538. $max_score = 100;
  539. if ($type === 'quiz') {
  540. $sql = 'SELECT SUM(ponderation)
  541. FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
  542. INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
  543. ON
  544. quiz_question.id = quiz_rel_question.question_id AND
  545. quiz_question.c_id = quiz_rel_question.c_id
  546. WHERE
  547. quiz_rel_question.exercice_id = '.$id." AND
  548. quiz_question.c_id = $course_id AND
  549. quiz_rel_question.c_id = $course_id ";
  550. $rsQuiz = Database::query($sql);
  551. $max_score = Database::result($rsQuiz, 0, 0);
  552. // Disabling the exercise if we add it inside a LP
  553. $exercise = new Exercise($course_id);
  554. $exercise->read($id);
  555. $exercise->disable();
  556. $exercise->save();
  557. }
  558. $params = [
  559. 'c_id' => $course_id,
  560. 'lp_id' => $this->get_id(),
  561. 'item_type' => $typeCleaned,
  562. 'ref' => '',
  563. 'title' => $title,
  564. 'description' => $description,
  565. 'path' => $id,
  566. 'max_score' => $max_score,
  567. 'parent_item_id' => $parent,
  568. 'previous_item_id' => $previous,
  569. 'next_item_id' => (int) $next,
  570. 'display_order' => $display_order + 1,
  571. 'prerequisite' => $prerequisites,
  572. 'max_time_allowed' => $max_time_allowed,
  573. 'min_score' => 0,
  574. 'launch_data' => '',
  575. ];
  576. if ($prerequisites != 0) {
  577. $params['prerequisite'] = $prerequisites;
  578. }
  579. $new_item_id = Database::insert($tbl_lp_item, $params);
  580. if ($new_item_id) {
  581. if ($this->debug > 2) {
  582. error_log('Inserting dir/chapter: '.$new_item_id, 0);
  583. }
  584. $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
  585. Database::query($sql);
  586. if (!empty($next)) {
  587. $sql = "UPDATE $tbl_lp_item
  588. SET previous_item_id = $new_item_id
  589. WHERE c_id = $course_id AND id = $next";
  590. Database::query($sql);
  591. }
  592. // Update the item that should be before the new item.
  593. if (!empty($tmp_previous)) {
  594. $sql = "UPDATE $tbl_lp_item
  595. SET next_item_id = $new_item_id
  596. WHERE c_id = $course_id AND id = $tmp_previous";
  597. Database::query($sql);
  598. }
  599. // Update all the items after the new item.
  600. $sql = "UPDATE $tbl_lp_item
  601. SET display_order = display_order + 1
  602. WHERE
  603. c_id = $course_id AND
  604. lp_id = ".$this->get_id()." AND
  605. iid <> $new_item_id AND
  606. parent_item_id = $parent AND
  607. display_order > $display_order";
  608. Database::query($sql);
  609. // Update the item that should come after the new item.
  610. $sql = "UPDATE $tbl_lp_item
  611. SET ref = $new_item_id
  612. WHERE c_id = $course_id AND iid = $new_item_id";
  613. Database::query($sql);
  614. // Upload audio.
  615. if (!empty($_FILES['mp3']['name'])) {
  616. // Create the audio folder if it does not exist yet.
  617. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
  618. if (!is_dir($filepath.'audio')) {
  619. mkdir(
  620. $filepath.'audio',
  621. api_get_permissions_for_new_directories()
  622. );
  623. $audio_id = add_document(
  624. $_course,
  625. '/audio',
  626. 'folder',
  627. 0,
  628. 'audio',
  629. '',
  630. 0,
  631. true,
  632. null,
  633. $sessionId,
  634. $userId
  635. );
  636. api_item_property_update(
  637. $_course,
  638. TOOL_DOCUMENT,
  639. $audio_id,
  640. 'FolderCreated',
  641. $userId,
  642. null,
  643. null,
  644. null,
  645. null,
  646. $sessionId
  647. );
  648. api_item_property_update(
  649. $_course,
  650. TOOL_DOCUMENT,
  651. $audio_id,
  652. 'invisible',
  653. $userId,
  654. null,
  655. null,
  656. null,
  657. null,
  658. $sessionId
  659. );
  660. }
  661. $file_path = handle_uploaded_document(
  662. $_course,
  663. $_FILES['mp3'],
  664. api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
  665. '/audio',
  666. $userId,
  667. '',
  668. '',
  669. '',
  670. '',
  671. false
  672. );
  673. // Getting the filename only.
  674. $file_components = explode('/', $file_path);
  675. $file = $file_components[count($file_components) - 1];
  676. // Store the mp3 file in the lp_item table.
  677. $sql = "UPDATE $tbl_lp_item SET
  678. audio = '".Database::escape_string($file)."'
  679. WHERE iid = '".intval($new_item_id)."'";
  680. Database::query($sql);
  681. }
  682. }
  683. return $new_item_id;
  684. }
  685. /**
  686. * Static admin function allowing addition of a learnpath to a course.
  687. *
  688. * @param string $courseCode
  689. * @param string $name
  690. * @param string $description
  691. * @param string $learnpath
  692. * @param string $origin
  693. * @param string $zipname Zip file containing the learnpath or directory containing the learnpath
  694. * @param string $publicated_on
  695. * @param string $expired_on
  696. * @param int $categoryId
  697. * @param int $userId
  698. *
  699. * @return int The new learnpath ID on success, 0 on failure
  700. */
  701. public static function add_lp(
  702. $courseCode,
  703. $name,
  704. $description = '',
  705. $learnpath = 'guess',
  706. $origin = 'zip',
  707. $zipname = '',
  708. $publicated_on = '',
  709. $expired_on = '',
  710. $categoryId = 0,
  711. $userId = 0
  712. ) {
  713. global $charset;
  714. if (!empty($courseCode)) {
  715. $courseInfo = api_get_course_info($courseCode);
  716. $course_id = $courseInfo['real_id'];
  717. } else {
  718. $course_id = api_get_course_int_id();
  719. $courseInfo = api_get_course_info();
  720. }
  721. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  722. // Check course code exists.
  723. // Check lp_name doesn't exist, otherwise append something.
  724. $i = 0;
  725. $name = Database::escape_string($name);
  726. $categoryId = (int) $categoryId;
  727. // Session id.
  728. $session_id = api_get_session_id();
  729. $userId = empty($userId) ? api_get_user_id() : $userId;
  730. $check_name = "SELECT * FROM $tbl_lp
  731. WHERE c_id = $course_id AND name = '$name'";
  732. $res_name = Database::query($check_name);
  733. if (empty($publicated_on)) {
  734. $publicated_on = null;
  735. } else {
  736. $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
  737. }
  738. if (empty($expired_on)) {
  739. $expired_on = null;
  740. } else {
  741. $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
  742. }
  743. while (Database::num_rows($res_name)) {
  744. // There is already one such name, update the current one a bit.
  745. $i++;
  746. $name = $name.' - '.$i;
  747. $check_name = "SELECT * FROM $tbl_lp
  748. WHERE c_id = $course_id AND name = '$name'";
  749. $res_name = Database::query($check_name);
  750. }
  751. // New name does not exist yet; keep it.
  752. // Escape description.
  753. // Kevin: added htmlentities().
  754. $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
  755. $type = 1;
  756. switch ($learnpath) {
  757. case 'guess':
  758. break;
  759. case 'dokeos':
  760. case 'chamilo':
  761. $type = 1;
  762. break;
  763. case 'aicc':
  764. break;
  765. }
  766. switch ($origin) {
  767. case 'zip':
  768. // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
  769. break;
  770. case 'manual':
  771. default:
  772. $get_max = "SELECT MAX(display_order)
  773. FROM $tbl_lp WHERE c_id = $course_id";
  774. $res_max = Database::query($get_max);
  775. if (Database::num_rows($res_max) < 1) {
  776. $dsp = 1;
  777. } else {
  778. $row = Database::fetch_array($res_max);
  779. $dsp = $row[0] + 1;
  780. }
  781. $params = [
  782. 'c_id' => $course_id,
  783. 'lp_type' => $type,
  784. 'name' => $name,
  785. 'description' => $description,
  786. 'path' => '',
  787. 'default_view_mod' => 'embedded',
  788. 'default_encoding' => 'UTF-8',
  789. 'display_order' => $dsp,
  790. 'content_maker' => 'Chamilo',
  791. 'content_local' => 'local',
  792. 'js_lib' => '',
  793. 'session_id' => $session_id,
  794. 'created_on' => api_get_utc_datetime(),
  795. 'modified_on' => api_get_utc_datetime(),
  796. 'publicated_on' => $publicated_on,
  797. 'expired_on' => $expired_on,
  798. 'category_id' => $categoryId,
  799. 'force_commit' => 0,
  800. 'content_license' => '',
  801. 'debug' => 0,
  802. 'theme' => '',
  803. 'preview_image' => '',
  804. 'author' => '',
  805. 'prerequisite' => 0,
  806. 'hide_toc_frame' => 0,
  807. 'seriousgame_mode' => 0,
  808. 'autolaunch' => 0,
  809. 'max_attempts' => 0,
  810. 'subscribe_users' => 0,
  811. 'accumulate_scorm_time' => 1,
  812. ];
  813. $id = Database::insert($tbl_lp, $params);
  814. if ($id > 0) {
  815. $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
  816. Database::query($sql);
  817. // Insert into item_property.
  818. api_item_property_update(
  819. $courseInfo,
  820. TOOL_LEARNPATH,
  821. $id,
  822. 'LearnpathAdded',
  823. $userId
  824. );
  825. api_set_default_visibility(
  826. $id,
  827. TOOL_LEARNPATH,
  828. 0,
  829. $courseInfo,
  830. $session_id,
  831. $userId
  832. );
  833. return $id;
  834. }
  835. break;
  836. }
  837. }
  838. /**
  839. * Auto completes the parents of an item in case it's been completed or passed.
  840. *
  841. * @param int $item Optional ID of the item from which to look for parents
  842. */
  843. public function autocomplete_parents($item)
  844. {
  845. $debug = $this->debug;
  846. if ($debug) {
  847. error_log('Learnpath::autocomplete_parents()');
  848. }
  849. if (empty($item)) {
  850. $item = $this->current;
  851. }
  852. $currentItem = $this->getItem($item);
  853. if ($currentItem) {
  854. $parent_id = $currentItem->get_parent();
  855. $parent = $this->getItem($parent_id);
  856. if ($parent) {
  857. // if $item points to an object and there is a parent.
  858. if ($debug) {
  859. error_log(
  860. 'Autocompleting parent of item '.$item.' '.
  861. $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
  862. 0
  863. );
  864. }
  865. // New experiment including failed and browsed in completed status.
  866. //$current_status = $currentItem->get_status();
  867. //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
  868. // Fixes chapter auto complete
  869. if (true) {
  870. // If the current item is completed or passes or succeeded.
  871. $updateParentStatus = true;
  872. if ($debug) {
  873. error_log('Status of current item is alright');
  874. }
  875. foreach ($parent->get_children() as $childItemId) {
  876. $childItem = $this->getItem($childItemId);
  877. // If children was not set try to get the info
  878. if (empty($childItem->db_item_view_id)) {
  879. $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
  880. }
  881. // Check all his brothers (parent's children) for completion status.
  882. if ($childItemId != $item) {
  883. if ($debug) {
  884. error_log(
  885. 'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
  886. 0
  887. );
  888. }
  889. // Trying completing parents of failed and browsed items as well.
  890. if ($childItem->status_is(
  891. [
  892. 'completed',
  893. 'passed',
  894. 'succeeded',
  895. 'browsed',
  896. 'failed',
  897. ]
  898. )
  899. ) {
  900. // Keep completion status to true.
  901. continue;
  902. } else {
  903. if ($debug > 2) {
  904. error_log(
  905. 'Found one incomplete child of parent #'.$parent_id.': child #'.$childItemId.' "'.$childItem->get_title().'", is '.$childItem->get_status().' db_item_view_id:#'.$childItem->db_item_view_id,
  906. 0
  907. );
  908. }
  909. $updateParentStatus = false;
  910. break;
  911. }
  912. }
  913. }
  914. if ($updateParentStatus) {
  915. // If all the children were completed:
  916. $parent->set_status('completed');
  917. $parent->save(false, $this->prerequisites_match($parent->get_id()));
  918. // Force the status to "completed"
  919. //$this->update_queue[$parent->get_id()] = $parent->get_status();
  920. $this->update_queue[$parent->get_id()] = 'completed';
  921. if ($debug) {
  922. error_log(
  923. 'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
  924. print_r($this->update_queue, 1),
  925. 0
  926. );
  927. }
  928. // Recursive call.
  929. $this->autocomplete_parents($parent->get_id());
  930. }
  931. }
  932. } else {
  933. if ($debug) {
  934. error_log("Parent #$parent_id does not exists");
  935. }
  936. }
  937. } else {
  938. if ($debug) {
  939. error_log("#$item is an item that doesn't have parents");
  940. }
  941. }
  942. }
  943. /**
  944. * Closes the current resource.
  945. *
  946. * Stops the timer
  947. * Saves into the database if required
  948. * Clears the current resource data from this object
  949. *
  950. * @return bool True on success, false on failure
  951. */
  952. public function close()
  953. {
  954. if ($this->debug > 0) {
  955. error_log('In learnpath::close()', 0);
  956. }
  957. if (empty($this->lp_id)) {
  958. $this->error = 'Trying to close this learnpath but no ID is set';
  959. return false;
  960. }
  961. $this->current_time_stop = time();
  962. $this->ordered_items = [];
  963. $this->index = 0;
  964. unset($this->lp_id);
  965. //unset other stuff
  966. return true;
  967. }
  968. /**
  969. * Static admin function allowing removal of a learnpath.
  970. *
  971. * @param array $courseInfo
  972. * @param int $id Learnpath ID
  973. * @param string $delete Whether to delete data or keep it (default: 'keep', others: 'remove')
  974. *
  975. * @return bool True on success, false on failure (might change that to return number of elements deleted)
  976. */
  977. public function delete($courseInfo = null, $id = null, $delete = 'keep')
  978. {
  979. $course_id = api_get_course_int_id();
  980. if (!empty($courseInfo)) {
  981. $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
  982. }
  983. // TODO: Implement a way of getting this to work when the current object is not set.
  984. // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
  985. // If an ID is specifically given and the current LP is not the same, prevent delete.
  986. if (!empty($id) && ($id != $this->lp_id)) {
  987. return false;
  988. }
  989. $lp = Database::get_course_table(TABLE_LP_MAIN);
  990. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  991. $lp_view = Database::get_course_table(TABLE_LP_VIEW);
  992. $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  993. // Delete lp item id.
  994. foreach ($this->items as $lpItemId => $dummy) {
  995. $sql = "DELETE FROM $lp_item_view
  996. WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
  997. Database::query($sql);
  998. }
  999. // Proposed by Christophe (nickname: clefevre)
  1000. $sql = "DELETE FROM $lp_item
  1001. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  1002. Database::query($sql);
  1003. $sql = "DELETE FROM $lp_view
  1004. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  1005. Database::query($sql);
  1006. self::toggle_publish($this->lp_id, 'i');
  1007. if ($this->type == 2 || $this->type == 3) {
  1008. // This is a scorm learning path, delete the files as well.
  1009. $sql = "SELECT path FROM $lp
  1010. WHERE iid = ".$this->lp_id;
  1011. $res = Database::query($sql);
  1012. if (Database::num_rows($res) > 0) {
  1013. $row = Database::fetch_array($res);
  1014. $path = $row['path'];
  1015. $sql = "SELECT id FROM $lp
  1016. WHERE
  1017. c_id = $course_id AND
  1018. path = '$path' AND
  1019. iid != ".$this->lp_id;
  1020. $res = Database::query($sql);
  1021. if (Database::num_rows($res) > 0) {
  1022. // Another learning path uses this directory, so don't delete it.
  1023. if ($this->debug > 2) {
  1024. error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
  1025. }
  1026. } else {
  1027. // No other LP uses that directory, delete it.
  1028. $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
  1029. // The absolute system path for this course.
  1030. $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
  1031. if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
  1032. if ($this->debug > 2) {
  1033. error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
  1034. }
  1035. // Proposed by Christophe (clefevre).
  1036. if (strcmp(substr($path, -2), "/.") == 0) {
  1037. $path = substr($path, 0, -1); // Remove "." at the end.
  1038. }
  1039. //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
  1040. rmdirr($course_scorm_dir.$path);
  1041. }
  1042. }
  1043. }
  1044. }
  1045. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  1046. $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
  1047. // Delete tools
  1048. $sql = "DELETE FROM $tbl_tool
  1049. WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
  1050. Database::query($sql);
  1051. $sql = "DELETE FROM $lp
  1052. WHERE iid = ".$this->lp_id;
  1053. Database::query($sql);
  1054. // Updates the display order of all lps.
  1055. $this->update_display_order();
  1056. api_item_property_update(
  1057. api_get_course_info(),
  1058. TOOL_LEARNPATH,
  1059. $this->lp_id,
  1060. 'delete',
  1061. api_get_user_id()
  1062. );
  1063. $link_info = GradebookUtils::isResourceInCourseGradebook(
  1064. api_get_course_id(),
  1065. 4,
  1066. $id,
  1067. api_get_session_id()
  1068. );
  1069. if ($link_info !== false) {
  1070. GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
  1071. }
  1072. if (api_get_setting('search_enabled') == 'true') {
  1073. require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
  1074. delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
  1075. }
  1076. }
  1077. /**
  1078. * Removes all the children of one item - dangerous!
  1079. *
  1080. * @param int $id Element ID of which children have to be removed
  1081. *
  1082. * @return int Total number of children removed
  1083. */
  1084. public function delete_children_items($id)
  1085. {
  1086. $course_id = $this->course_info['real_id'];
  1087. if ($this->debug > 0) {
  1088. error_log('In learnpath::delete_children_items('.$id.')', 0);
  1089. }
  1090. $num = 0;
  1091. if (empty($id) || $id != strval(intval($id))) {
  1092. return false;
  1093. }
  1094. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1095. $sql = "SELECT * FROM $lp_item
  1096. WHERE c_id = ".$course_id." AND parent_item_id = $id";
  1097. $res = Database::query($sql);
  1098. while ($row = Database::fetch_array($res)) {
  1099. $num += $this->delete_children_items($row['iid']);
  1100. $sql = "DELETE FROM $lp_item
  1101. WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
  1102. Database::query($sql);
  1103. $num++;
  1104. }
  1105. return $num;
  1106. }
  1107. /**
  1108. * Removes an item from the current learnpath.
  1109. *
  1110. * @param int $id Elem ID (0 if first)
  1111. *
  1112. * @return int Number of elements moved
  1113. *
  1114. * @todo implement resource removal
  1115. */
  1116. public function delete_item($id)
  1117. {
  1118. $course_id = api_get_course_int_id();
  1119. if ($this->debug > 0) {
  1120. error_log('In learnpath::delete_item()', 0);
  1121. }
  1122. // TODO: Implement the resource removal.
  1123. if (empty($id) || $id != strval(intval($id))) {
  1124. return false;
  1125. }
  1126. // First select item to get previous, next, and display order.
  1127. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1128. $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
  1129. $res_sel = Database::query($sql_sel);
  1130. if (Database::num_rows($res_sel) < 1) {
  1131. return false;
  1132. }
  1133. $row = Database::fetch_array($res_sel);
  1134. $previous = $row['previous_item_id'];
  1135. $next = $row['next_item_id'];
  1136. $display = $row['display_order'];
  1137. $parent = $row['parent_item_id'];
  1138. $lp = $row['lp_id'];
  1139. // Delete children items.
  1140. $num = $this->delete_children_items($id);
  1141. if ($this->debug > 2) {
  1142. error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
  1143. }
  1144. // Now delete the item.
  1145. $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
  1146. if ($this->debug > 2) {
  1147. error_log('Deleting item: '.$sql_del, 0);
  1148. }
  1149. Database::query($sql_del);
  1150. // Now update surrounding items.
  1151. $sql_upd = "UPDATE $lp_item SET next_item_id = $next
  1152. WHERE iid = $previous";
  1153. Database::query($sql_upd);
  1154. $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
  1155. WHERE iid = $next";
  1156. Database::query($sql_upd);
  1157. // Now update all following items with new display order.
  1158. $sql_all = "UPDATE $lp_item SET display_order = display_order-1
  1159. WHERE
  1160. c_id = $course_id AND
  1161. lp_id = $lp AND
  1162. parent_item_id = $parent AND
  1163. display_order > $display";
  1164. Database::query($sql_all);
  1165. //Removing prerequisites since the item will not longer exist
  1166. $sql_all = "UPDATE $lp_item SET prerequisite = ''
  1167. WHERE c_id = $course_id AND prerequisite = $id";
  1168. Database::query($sql_all);
  1169. // Remove from search engine if enabled.
  1170. if (api_get_setting('search_enabled') === 'true') {
  1171. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  1172. $sql = 'SELECT * FROM %s
  1173. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
  1174. LIMIT 1';
  1175. $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
  1176. $res = Database::query($sql);
  1177. if (Database::num_rows($res) > 0) {
  1178. $row2 = Database::fetch_array($res);
  1179. $di = new ChamiloIndexer();
  1180. $di->remove_document($row2['search_did']);
  1181. }
  1182. $sql = 'DELETE FROM %s
  1183. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
  1184. LIMIT 1';
  1185. $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
  1186. Database::query($sql);
  1187. }
  1188. }
  1189. /**
  1190. * Updates an item's content in place.
  1191. *
  1192. * @param int $id Element ID
  1193. * @param int $parent Parent item ID
  1194. * @param int $previous Previous item ID
  1195. * @param string $title Item title
  1196. * @param string $description Item description
  1197. * @param string $prerequisites Prerequisites (optional)
  1198. * @param array $audio The array resulting of the $_FILES[mp3] element
  1199. * @param int $max_time_allowed
  1200. * @param string $url
  1201. *
  1202. * @return bool True on success, false on error
  1203. */
  1204. public function edit_item(
  1205. $id,
  1206. $parent,
  1207. $previous,
  1208. $title,
  1209. $description,
  1210. $prerequisites = '0',
  1211. $audio = [],
  1212. $max_time_allowed = 0,
  1213. $url = ''
  1214. ) {
  1215. $course_id = api_get_course_int_id();
  1216. $_course = api_get_course_info();
  1217. if ($this->debug > 0) {
  1218. error_log('In learnpath::edit_item()', 0);
  1219. }
  1220. if (empty($max_time_allowed)) {
  1221. $max_time_allowed = 0;
  1222. }
  1223. if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
  1224. return false;
  1225. }
  1226. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1227. $sql = "SELECT * FROM $tbl_lp_item
  1228. WHERE iid = $id";
  1229. $res_select = Database::query($sql);
  1230. $row_select = Database::fetch_array($res_select);
  1231. $audio_update_sql = '';
  1232. if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
  1233. // Create the audio folder if it does not exist yet.
  1234. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
  1235. if (!is_dir($filepath.'audio')) {
  1236. mkdir($filepath.'audio', api_get_permissions_for_new_directories());
  1237. $audio_id = add_document(
  1238. $_course,
  1239. '/audio',
  1240. 'folder',
  1241. 0,
  1242. 'audio'
  1243. );
  1244. api_item_property_update(
  1245. $_course,
  1246. TOOL_DOCUMENT,
  1247. $audio_id,
  1248. 'FolderCreated',
  1249. api_get_user_id(),
  1250. null,
  1251. null,
  1252. null,
  1253. null,
  1254. api_get_session_id()
  1255. );
  1256. api_item_property_update(
  1257. $_course,
  1258. TOOL_DOCUMENT,
  1259. $audio_id,
  1260. 'invisible',
  1261. api_get_user_id(),
  1262. null,
  1263. null,
  1264. null,
  1265. null,
  1266. api_get_session_id()
  1267. );
  1268. }
  1269. // Upload file in documents.
  1270. $pi = pathinfo($audio['name']);
  1271. if ($pi['extension'] == 'mp3') {
  1272. $c_det = api_get_course_info($this->cc);
  1273. $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
  1274. $path = handle_uploaded_document(
  1275. $c_det,
  1276. $audio,
  1277. $bp,
  1278. '/audio',
  1279. api_get_user_id(),
  1280. 0,
  1281. null,
  1282. 0,
  1283. 'rename',
  1284. false,
  1285. 0
  1286. );
  1287. $path = substr($path, 7);
  1288. // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
  1289. $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
  1290. }
  1291. }
  1292. $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
  1293. $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
  1294. // TODO: htmlspecialchars to be checked for encoding related problems.
  1295. if ($same_parent && $same_previous) {
  1296. // Only update title and description.
  1297. $sql = "UPDATE $tbl_lp_item
  1298. SET title = '".Database::escape_string($title)."',
  1299. prerequisite = '".$prerequisites."',
  1300. description = '".Database::escape_string($description)."'
  1301. ".$audio_update_sql.",
  1302. max_time_allowed = '".Database::escape_string($max_time_allowed)."'
  1303. WHERE iid = $id";
  1304. Database::query($sql);
  1305. } else {
  1306. $old_parent = $row_select['parent_item_id'];
  1307. $old_previous = $row_select['previous_item_id'];
  1308. $old_next = $row_select['next_item_id'];
  1309. $old_order = $row_select['display_order'];
  1310. $old_prerequisite = $row_select['prerequisite'];
  1311. $old_max_time_allowed = $row_select['max_time_allowed'];
  1312. /* BEGIN -- virtually remove the current item id */
  1313. /* for the next and previous item it is like the current item doesn't exist anymore */
  1314. if ($old_previous != 0) {
  1315. // Next
  1316. $sql = "UPDATE $tbl_lp_item
  1317. SET next_item_id = $old_next
  1318. WHERE iid = $old_previous";
  1319. Database::query($sql);
  1320. }
  1321. if (!empty($old_next)) {
  1322. // Previous
  1323. $sql = "UPDATE $tbl_lp_item
  1324. SET previous_item_id = $old_previous
  1325. WHERE iid = $old_next";
  1326. Database::query($sql);
  1327. }
  1328. // display_order - 1 for every item with a display_order
  1329. // bigger then the display_order of the current item.
  1330. $sql = "UPDATE $tbl_lp_item
  1331. SET display_order = display_order - 1
  1332. WHERE
  1333. c_id = $course_id AND
  1334. display_order > $old_order AND
  1335. lp_id = ".$this->lp_id." AND
  1336. parent_item_id = $old_parent";
  1337. Database::query($sql);
  1338. /* END -- virtually remove the current item id */
  1339. /* BEGIN -- update the current item id to his new location */
  1340. if ($previous == 0) {
  1341. // Select the data of the item that should come after the current item.
  1342. $sql = "SELECT id, display_order
  1343. FROM $tbl_lp_item
  1344. WHERE
  1345. c_id = $course_id AND
  1346. lp_id = ".$this->lp_id." AND
  1347. parent_item_id = $parent AND
  1348. previous_item_id = $previous";
  1349. $res_select_old = Database::query($sql);
  1350. $row_select_old = Database::fetch_array($res_select_old);
  1351. // If the new parent didn't have children before.
  1352. if (Database::num_rows($res_select_old) == 0) {
  1353. $new_next = 0;
  1354. $new_order = 1;
  1355. } else {
  1356. $new_next = $row_select_old['id'];
  1357. $new_order = $row_select_old['display_order'];
  1358. }
  1359. } else {
  1360. // Select the data of the item that should come before the current item.
  1361. $sql = "SELECT next_item_id, display_order
  1362. FROM $tbl_lp_item
  1363. WHERE iid = $previous";
  1364. $res_select_old = Database::query($sql);
  1365. $row_select_old = Database::fetch_array($res_select_old);
  1366. $new_next = $row_select_old['next_item_id'];
  1367. $new_order = $row_select_old['display_order'] + 1;
  1368. }
  1369. // TODO: htmlspecialchars to be checked for encoding related problems.
  1370. // Update the current item with the new data.
  1371. $sql = "UPDATE $tbl_lp_item
  1372. SET
  1373. title = '".Database::escape_string($title)."',
  1374. description = '".Database::escape_string($description)."',
  1375. parent_item_id = $parent,
  1376. previous_item_id = $previous,
  1377. next_item_id = $new_next,
  1378. display_order = $new_order
  1379. $audio_update_sql
  1380. WHERE iid = $id";
  1381. Database::query($sql);
  1382. if ($previous != 0) {
  1383. // Update the previous item's next_item_id.
  1384. $sql = "UPDATE $tbl_lp_item
  1385. SET next_item_id = $id
  1386. WHERE iid = $previous";
  1387. Database::query($sql);
  1388. }
  1389. if (!empty($new_next)) {
  1390. // Update the next item's previous_item_id.
  1391. $sql = "UPDATE $tbl_lp_item
  1392. SET previous_item_id = $id
  1393. WHERE iid = $new_next";
  1394. Database::query($sql);
  1395. }
  1396. if ($old_prerequisite != $prerequisites) {
  1397. $sql = "UPDATE $tbl_lp_item
  1398. SET prerequisite = '$prerequisites'
  1399. WHERE iid = $id";
  1400. Database::query($sql);
  1401. }
  1402. if ($old_max_time_allowed != $max_time_allowed) {
  1403. // update max time allowed
  1404. $sql = "UPDATE $tbl_lp_item
  1405. SET max_time_allowed = $max_time_allowed
  1406. WHERE iid = $id";
  1407. Database::query($sql);
  1408. }
  1409. // Update all the items with the same or a bigger display_order than the current item.
  1410. $sql = "UPDATE $tbl_lp_item
  1411. SET display_order = display_order + 1
  1412. WHERE
  1413. c_id = $course_id AND
  1414. lp_id = ".$this->get_id()." AND
  1415. iid <> $id AND
  1416. parent_item_id = $parent AND
  1417. display_order >= $new_order";
  1418. Database::query($sql);
  1419. }
  1420. if ($row_select['item_type'] == 'link') {
  1421. $link = new Link();
  1422. $linkId = $row_select['path'];
  1423. $link->updateLink($linkId, $url);
  1424. }
  1425. }
  1426. /**
  1427. * Updates an item's prereq in place.
  1428. *
  1429. * @param int $id Element ID
  1430. * @param string $prerequisite_id Prerequisite Element ID
  1431. * @param int $mastery_score Prerequisite min score
  1432. * @param int $max_score Prerequisite max score
  1433. *
  1434. * @return bool True on success, false on error
  1435. */
  1436. public function edit_item_prereq(
  1437. $id,
  1438. $prerequisite_id,
  1439. $mastery_score = 0,
  1440. $max_score = 100
  1441. ) {
  1442. $course_id = api_get_course_int_id();
  1443. if ($this->debug > 0) {
  1444. error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
  1445. }
  1446. if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
  1447. return false;
  1448. }
  1449. $prerequisite_id = (int) $prerequisite_id;
  1450. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1451. if (!is_numeric($mastery_score) || $mastery_score < 0) {
  1452. $mastery_score = 0;
  1453. }
  1454. if (!is_numeric($max_score) || $max_score < 0) {
  1455. $max_score = 100;
  1456. }
  1457. /*if ($mastery_score > $max_score) {
  1458. $max_score = $mastery_score;
  1459. }*/
  1460. if (!is_numeric($prerequisite_id)) {
  1461. $prerequisite_id = 'NULL';
  1462. }
  1463. $mastery_score = floatval($mastery_score);
  1464. $max_score = floatval($max_score);
  1465. $sql = " UPDATE $tbl_lp_item
  1466. SET
  1467. prerequisite = $prerequisite_id ,
  1468. prerequisite_min_score = $mastery_score ,
  1469. prerequisite_max_score = $max_score
  1470. WHERE iid = $id";
  1471. Database::query($sql);
  1472. // TODO: Update the item object (can be ignored for now because refreshed).
  1473. return true;
  1474. }
  1475. /**
  1476. * Gets all the chapters belonging to the same parent as the item/chapter given
  1477. * Can also be called as abstract method.
  1478. *
  1479. * @param int $id Item ID
  1480. *
  1481. * @return array A list of all the "brother items" (or an empty array on failure)
  1482. */
  1483. public function getSiblingDirectories($id)
  1484. {
  1485. $course_id = api_get_course_int_id();
  1486. if ($this->debug > 0) {
  1487. error_log('In learnpath::getSiblingDirectories()', 0);
  1488. }
  1489. if (empty($id) || $id != strval(intval($id))) {
  1490. return [];
  1491. }
  1492. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1493. $sql_parent = "SELECT * FROM $lp_item
  1494. WHERE iid = $id AND item_type='dir'";
  1495. $res_parent = Database::query($sql_parent);
  1496. if (Database::num_rows($res_parent) > 0) {
  1497. $row_parent = Database::fetch_array($res_parent);
  1498. $parent = $row_parent['parent_item_id'];
  1499. $sql = "SELECT * FROM $lp_item
  1500. WHERE
  1501. parent_item_id = $parent AND
  1502. iid = $id AND
  1503. item_type='dir'
  1504. ORDER BY display_order";
  1505. $res_bros = Database::query($sql);
  1506. $list = [];
  1507. while ($row_bro = Database::fetch_array($res_bros)) {
  1508. $list[] = $row_bro;
  1509. }
  1510. return $list;
  1511. }
  1512. return [];
  1513. }
  1514. /**
  1515. * Gets all the items belonging to the same parent as the item given
  1516. * Can also be called as abstract method.
  1517. *
  1518. * @param int $id Item ID
  1519. *
  1520. * @return array A list of all the "brother items" (or an empty array on failure)
  1521. */
  1522. public function get_brother_items($id)
  1523. {
  1524. $course_id = api_get_course_int_id();
  1525. if ($this->debug > 0) {
  1526. error_log('In learnpath::get_brother_items('.$id.')', 0);
  1527. }
  1528. if (empty($id) || $id != strval(intval($id))) {
  1529. return [];
  1530. }
  1531. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1532. $sql_parent = "SELECT * FROM $lp_item
  1533. WHERE iid = $id";
  1534. $res_parent = Database::query($sql_parent);
  1535. if (Database::num_rows($res_parent) > 0) {
  1536. $row_parent = Database::fetch_array($res_parent);
  1537. $parent = $row_parent['parent_item_id'];
  1538. $sql = "SELECT * FROM $lp_item
  1539. WHERE c_id = $course_id AND parent_item_id = $parent
  1540. ORDER BY display_order";
  1541. $res_bros = Database::query($sql);
  1542. $list = [];
  1543. while ($row_bro = Database::fetch_array($res_bros)) {
  1544. $list[] = $row_bro;
  1545. }
  1546. return $list;
  1547. }
  1548. return [];
  1549. }
  1550. /**
  1551. * Get the specific prefix index terms of this learning path.
  1552. *
  1553. * @param string $prefix
  1554. *
  1555. * @return array Array of terms
  1556. */
  1557. public function get_common_index_terms_by_prefix($prefix)
  1558. {
  1559. require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
  1560. $terms = get_specific_field_values_list_by_prefix(
  1561. $prefix,
  1562. $this->cc,
  1563. TOOL_LEARNPATH,
  1564. $this->lp_id
  1565. );
  1566. $prefix_terms = [];
  1567. if (!empty($terms)) {
  1568. foreach ($terms as $term) {
  1569. $prefix_terms[] = $term['value'];
  1570. }
  1571. }
  1572. return $prefix_terms;
  1573. }
  1574. /**
  1575. * Gets the number of items currently completed.
  1576. *
  1577. * @param bool $failedStatusException flag to determine the failed status is not considered progressed
  1578. *
  1579. * @return int The number of items currently completed
  1580. */
  1581. public function get_complete_items_count($failedStatusException = false)
  1582. {
  1583. if ($this->debug > 0) {
  1584. error_log('In learnpath::get_complete_items_count()', 0);
  1585. }
  1586. $i = 0;
  1587. $completedStatusList = [
  1588. 'completed',
  1589. 'passed',
  1590. 'succeeded',
  1591. 'browsed',
  1592. ];
  1593. if (!$failedStatusException) {
  1594. $completedStatusList[] = 'failed';
  1595. }
  1596. foreach ($this->items as $id => $dummy) {
  1597. // Trying failed and browsed considered "progressed" as well.
  1598. if ($this->items[$id]->status_is($completedStatusList) &&
  1599. $this->items[$id]->get_type() != 'dir'
  1600. ) {
  1601. $i++;
  1602. }
  1603. }
  1604. return $i;
  1605. }
  1606. /**
  1607. * Gets the current item ID.
  1608. *
  1609. * @return int The current learnpath item id
  1610. */
  1611. public function get_current_item_id()
  1612. {
  1613. $current = 0;
  1614. if ($this->debug > 0) {
  1615. error_log('In learnpath::get_current_item_id()', 0);
  1616. }
  1617. if (!empty($this->current)) {
  1618. $current = $this->current;
  1619. }
  1620. if ($this->debug > 2) {
  1621. error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
  1622. }
  1623. return $current;
  1624. }
  1625. /**
  1626. * Force to get the first learnpath item id.
  1627. *
  1628. * @return int The current learnpath item id
  1629. */
  1630. public function get_first_item_id()
  1631. {
  1632. $current = 0;
  1633. if (is_array($this->ordered_items)) {
  1634. $current = $this->ordered_items[0];
  1635. }
  1636. return $current;
  1637. }
  1638. /**
  1639. * Gets the total number of items available for viewing in this SCORM.
  1640. *
  1641. * @return int The total number of items
  1642. */
  1643. public function get_total_items_count()
  1644. {
  1645. if ($this->debug > 0) {
  1646. error_log('In learnpath::get_total_items_count()', 0);
  1647. }
  1648. return count($this->items);
  1649. }
  1650. /**
  1651. * Gets the total number of items available for viewing in this SCORM but without chapters.
  1652. *
  1653. * @return int The total no-chapters number of items
  1654. */
  1655. public function getTotalItemsCountWithoutDirs()
  1656. {
  1657. if ($this->debug > 0) {
  1658. error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
  1659. }
  1660. $total = 0;
  1661. $typeListNotToCount = self::getChapterTypes();
  1662. foreach ($this->items as $temp2) {
  1663. if (!in_array($temp2->get_type(), $typeListNotToCount)) {
  1664. $total++;
  1665. }
  1666. }
  1667. return $total;
  1668. }
  1669. /**
  1670. * Sets the first element URL.
  1671. */
  1672. public function first()
  1673. {
  1674. if ($this->debug > 0) {
  1675. error_log('In learnpath::first()', 0);
  1676. error_log('$this->last_item_seen '.$this->last_item_seen);
  1677. }
  1678. // Test if the last_item_seen exists and is not a dir.
  1679. if (count($this->ordered_items) == 0) {
  1680. $this->index = 0;
  1681. }
  1682. if (!empty($this->last_item_seen) &&
  1683. !empty($this->items[$this->last_item_seen]) &&
  1684. $this->items[$this->last_item_seen]->get_type() != 'dir'
  1685. //with this change (below) the LP will NOT go to the next item, it will take lp item we left
  1686. //&& !$this->items[$this->last_item_seen]->is_done()
  1687. ) {
  1688. if ($this->debug > 2) {
  1689. error_log(
  1690. 'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
  1691. $this->items[$this->last_item_seen]->get_type()
  1692. );
  1693. }
  1694. $index = -1;
  1695. foreach ($this->ordered_items as $myindex => $item_id) {
  1696. if ($item_id == $this->last_item_seen) {
  1697. $index = $myindex;
  1698. break;
  1699. }
  1700. }
  1701. if ($index == -1) {
  1702. // Index hasn't changed, so item not found - panic (this shouldn't happen).
  1703. if ($this->debug > 2) {
  1704. error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
  1705. }
  1706. return false;
  1707. } else {
  1708. $this->last = $this->last_item_seen;
  1709. $this->current = $this->last_item_seen;
  1710. $this->index = $index;
  1711. }
  1712. } else {
  1713. if ($this->debug > 2) {
  1714. error_log('In learnpath::first() - No last item seen', 0);
  1715. }
  1716. $index = 0;
  1717. // Loop through all ordered items and stop at the first item that is
  1718. // not a directory *and* that has not been completed yet.
  1719. while (!empty($this->ordered_items[$index]) &&
  1720. is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
  1721. (
  1722. $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
  1723. $this->items[$this->ordered_items[$index]]->is_done() === true
  1724. ) && $index < $this->max_ordered_items) {
  1725. $index++;
  1726. }
  1727. $this->last = $this->current;
  1728. // current is
  1729. $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
  1730. $this->index = $index;
  1731. if ($this->debug > 2) {
  1732. error_log('$index '.$index);
  1733. error_log('In learnpath::first() - No last item seen');
  1734. error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
  1735. }
  1736. }
  1737. if ($this->debug > 2) {
  1738. error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
  1739. }
  1740. }
  1741. /**
  1742. * Gets the information about an item in a format usable as JavaScript to update
  1743. * the JS API by just printing this content into the <head> section of the message frame.
  1744. *
  1745. * @param int $item_id
  1746. *
  1747. * @return string
  1748. */
  1749. public function get_js_info($item_id = 0)
  1750. {
  1751. if ($this->debug > 0) {
  1752. error_log('In learnpath::get_js_info('.$item_id.')', 0);
  1753. }
  1754. $info = '';
  1755. $item_id = intval($item_id);
  1756. if (!empty($item_id) && is_object($this->items[$item_id])) {
  1757. //if item is defined, return values from DB
  1758. $oItem = $this->items[$item_id];
  1759. $info .= '<script language="javascript">';
  1760. $info .= "top.set_score(".$oItem->get_score().");\n";
  1761. $info .= "top.set_max(".$oItem->get_max().");\n";
  1762. $info .= "top.set_min(".$oItem->get_min().");\n";
  1763. $info .= "top.set_lesson_status('".$oItem->get_status()."');";
  1764. $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
  1765. $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
  1766. $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
  1767. $info .= "top.set_flag_synchronized();";
  1768. $info .= '</script>';
  1769. if ($this->debug > 2) {
  1770. error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
  1771. }
  1772. return $info;
  1773. } else {
  1774. // If item_id is empty, just update to default SCORM data.
  1775. $info .= '<script language="javascript">';
  1776. $info .= "top.set_score(".learnpathItem::get_score().");\n";
  1777. $info .= "top.set_max(".learnpathItem::get_max().");\n";
  1778. $info .= "top.set_min(".learnpathItem::get_min().");\n";
  1779. $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
  1780. $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
  1781. $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
  1782. $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
  1783. $info .= "top.set_flag_synchronized();";
  1784. $info .= '</script>';
  1785. if ($this->debug > 2) {
  1786. error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
  1787. }
  1788. return $info;
  1789. }
  1790. }
  1791. /**
  1792. * Gets the js library from the database.
  1793. *
  1794. * @return string The name of the javascript library to be used
  1795. */
  1796. public function get_js_lib()
  1797. {
  1798. $lib = '';
  1799. if (!empty($this->js_lib)) {
  1800. $lib = $this->js_lib;
  1801. }
  1802. return $lib;
  1803. }
  1804. /**
  1805. * Gets the learnpath database ID.
  1806. *
  1807. * @return int Learnpath ID in the lp table
  1808. */
  1809. public function get_id()
  1810. {
  1811. if (!empty($this->lp_id)) {
  1812. return $this->lp_id;
  1813. } else {
  1814. return 0;
  1815. }
  1816. }
  1817. /**
  1818. * Gets the last element URL.
  1819. *
  1820. * @return string URL to load into the viewer
  1821. */
  1822. public function get_last()
  1823. {
  1824. if ($this->debug > 0) {
  1825. error_log('In learnpath::get_last()', 0);
  1826. }
  1827. //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
  1828. if (count($this->ordered_items) > 0) {
  1829. $this->index = count($this->ordered_items) - 1;
  1830. return $this->ordered_items[$this->index];
  1831. }
  1832. return false;
  1833. }
  1834. /**
  1835. * Gets the navigation bar for the learnpath display screen.
  1836. *
  1837. * @return string The HTML string to use as a navigation bar
  1838. */
  1839. public function get_navigation_bar($idBar = null, $display = null)
  1840. {
  1841. if ($this->debug > 0) {
  1842. error_log('In learnpath::get_navigation_bar()', 0);
  1843. }
  1844. if (empty($idBar)) {
  1845. $idBar = 'control-top';
  1846. }
  1847. $lpId = $this->lp_id;
  1848. $mycurrentitemid = $this->get_current_item_id();
  1849. $reportingText = get_lang('Reporting');
  1850. $previousText = get_lang('ScormPrevious');
  1851. $nextText = get_lang('ScormNext');
  1852. $fullScreenText = get_lang('ScormExitFullScreen');
  1853. $settings = api_get_configuration_value('lp_view_settings');
  1854. $display = isset($settings['display']) ? $settings['display'] : false;
  1855. $reportingIcon = '
  1856. <a class="icon-toolbar"
  1857. id="stats_link"
  1858. href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
  1859. onclick="window.parent.API.save_asset(); return true;"
  1860. target="content_name" title="'.$reportingText.'">
  1861. <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
  1862. </a>';
  1863. if (!empty($display)) {
  1864. $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
  1865. if ($showReporting == false) {
  1866. $reportingIcon = '';
  1867. }
  1868. }
  1869. $hideArrows = false;
  1870. if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
  1871. $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
  1872. }
  1873. $previousIcon = '';
  1874. $nextIcon = '';
  1875. if ($hideArrows === false) {
  1876. $previousIcon = '
  1877. <a class="icon-toolbar" id="scorm-previous" href="#"
  1878. onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
  1879. <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
  1880. </a>';
  1881. $nextIcon = '
  1882. <a class="icon-toolbar" id="scorm-next" href="#"
  1883. onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
  1884. <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
  1885. </a>';
  1886. }
  1887. if ($this->mode === 'fullscreen') {
  1888. $navbar = '
  1889. <span id="'.$idBar.'" class="buttons">
  1890. '.$reportingIcon.'
  1891. '.$previousIcon.'
  1892. '.$nextIcon.'
  1893. <a class="icon-toolbar" id="view-embedded"
  1894. href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
  1895. <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
  1896. </a>
  1897. </span>';
  1898. } else {
  1899. $navbar = '
  1900. <span id="'.$idBar.'" class="buttons text-right">
  1901. '.$reportingIcon.'
  1902. '.$previousIcon.'
  1903. '.$nextIcon.'
  1904. </span>';
  1905. }
  1906. return $navbar;
  1907. }
  1908. /**
  1909. * Gets the next resource in queue (url).
  1910. *
  1911. * @return string URL to load into the viewer
  1912. */
  1913. public function get_next_index()
  1914. {
  1915. if ($this->debug > 0) {
  1916. error_log('In learnpath::get_next_index()', 0);
  1917. }
  1918. // TODO
  1919. $index = $this->index;
  1920. $index++;
  1921. if ($this->debug > 2) {
  1922. error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
  1923. }
  1924. while (
  1925. !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
  1926. $index < $this->max_ordered_items
  1927. ) {
  1928. $index++;
  1929. if ($index == $this->max_ordered_items) {
  1930. if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
  1931. return $this->index;
  1932. } else {
  1933. return $index;
  1934. }
  1935. }
  1936. }
  1937. if (empty($this->ordered_items[$index])) {
  1938. return $this->index;
  1939. }
  1940. if ($this->debug > 2) {
  1941. error_log('index is now '.$index, 0);
  1942. }
  1943. return $index;
  1944. }
  1945. /**
  1946. * Gets item_id for the next element.
  1947. *
  1948. * @return int Next item (DB) ID
  1949. */
  1950. public function get_next_item_id()
  1951. {
  1952. if ($this->debug > 0) {
  1953. error_log('In learnpath::get_next_item_id()', 0);
  1954. }
  1955. $new_index = $this->get_next_index();
  1956. if (!empty($new_index)) {
  1957. if (isset($this->ordered_items[$new_index])) {
  1958. if ($this->debug > 2) {
  1959. error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
  1960. }
  1961. return $this->ordered_items[$new_index];
  1962. }
  1963. }
  1964. if ($this->debug > 2) {
  1965. error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
  1966. }
  1967. return 0;
  1968. }
  1969. /**
  1970. * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
  1971. *
  1972. * Generally, the package provided is in the form of a zip file, so the function
  1973. * has been written to test a zip file. If not a zip, the function will return the
  1974. * default return value: ''
  1975. *
  1976. * @param string $file_path the path to the file
  1977. * @param string $file_name the original name of the file
  1978. *
  1979. * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
  1980. */
  1981. public static function get_package_type($file_path, $file_name)
  1982. {
  1983. // Get name of the zip file without the extension.
  1984. $file_info = pathinfo($file_name);
  1985. $extension = $file_info['extension']; // Extension only.
  1986. if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
  1987. 'dll',
  1988. 'exe',
  1989. ])) {
  1990. return 'oogie';
  1991. }
  1992. if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
  1993. 'dll',
  1994. 'exe',
  1995. ])) {
  1996. return 'woogie';
  1997. }
  1998. $zipFile = new PclZip($file_path);
  1999. // Check the zip content (real size and file extension).
  2000. $zipContentArray = $zipFile->listContent();
  2001. $package_type = '';
  2002. $manifest = '';
  2003. $aicc_match_crs = 0;
  2004. $aicc_match_au = 0;
  2005. $aicc_match_des = 0;
  2006. $aicc_match_cst = 0;
  2007. // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
  2008. if (is_array($zipContentArray) && count($zipContentArray) > 0) {
  2009. foreach ($zipContentArray as $thisContent) {
  2010. if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
  2011. // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
  2012. } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
  2013. $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
  2014. $package_type = 'scorm';
  2015. break; // Exit the foreach loop.
  2016. } elseif (
  2017. preg_match('/aicc\//i', $thisContent['filename']) ||
  2018. in_array(
  2019. strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
  2020. ['crs', 'au', 'des', 'cst']
  2021. )
  2022. ) {
  2023. $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
  2024. switch ($ext) {
  2025. case 'crs':
  2026. $aicc_match_crs = 1;
  2027. break;
  2028. case 'au':
  2029. $aicc_match_au = 1;
  2030. break;
  2031. case 'des':
  2032. $aicc_match_des = 1;
  2033. break;
  2034. case 'cst':
  2035. $aicc_match_cst = 1;
  2036. break;
  2037. default:
  2038. break;
  2039. }
  2040. //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
  2041. } else {
  2042. $package_type = '';
  2043. }
  2044. }
  2045. }
  2046. if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
  2047. // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
  2048. $package_type = 'aicc';
  2049. }
  2050. // Try with chamilo course builder
  2051. if (empty($package_type)) {
  2052. $package_type = 'chamilo';
  2053. }
  2054. return $package_type;
  2055. }
  2056. /**
  2057. * Gets the previous resource in queue (url). Also initialises time values for this viewing.
  2058. *
  2059. * @return string URL to load into the viewer
  2060. */
  2061. public function get_previous_index()
  2062. {
  2063. if ($this->debug > 0) {
  2064. error_log('In learnpath::get_previous_index()', 0);
  2065. }
  2066. $index = $this->index;
  2067. if (isset($this->ordered_items[$index - 1])) {
  2068. $index--;
  2069. while (isset($this->ordered_items[$index]) &&
  2070. ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
  2071. ) {
  2072. $index--;
  2073. if ($index < 0) {
  2074. return $this->index;
  2075. }
  2076. }
  2077. } else {
  2078. if ($this->debug > 2) {
  2079. error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
  2080. }
  2081. // There is no previous item.
  2082. }
  2083. return $index;
  2084. }
  2085. /**
  2086. * Gets item_id for the next element.
  2087. *
  2088. * @return int Previous item (DB) ID
  2089. */
  2090. public function get_previous_item_id()
  2091. {
  2092. if ($this->debug > 0) {
  2093. error_log('In learnpath::get_previous_item_id()', 0);
  2094. }
  2095. $new_index = $this->get_previous_index();
  2096. return $this->ordered_items[$new_index];
  2097. }
  2098. /**
  2099. * Returns the HTML necessary to print a mediaplayer block inside a page.
  2100. *
  2101. * @param int $lpItemId
  2102. * @param string $autostart
  2103. *
  2104. * @return string The mediaplayer HTML
  2105. */
  2106. public function get_mediaplayer($lpItemId, $autostart = 'true')
  2107. {
  2108. $course_id = api_get_course_int_id();
  2109. $_course = api_get_course_info();
  2110. if (empty($_course)) {
  2111. return '';
  2112. }
  2113. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  2114. $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  2115. $lpItemId = (int) $lpItemId;
  2116. // Getting all the information about the item.
  2117. $sql = "SELECT * FROM $tbl_lp_item as lpi
  2118. INNER JOIN $tbl_lp_item_view as lp_view
  2119. ON (lpi.iid = lp_view.lp_item_id)
  2120. WHERE
  2121. lpi.iid = $lpItemId AND
  2122. lp_view.c_id = $course_id";
  2123. $result = Database::query($sql);
  2124. $row = Database::fetch_assoc($result);
  2125. $output = '';
  2126. if (!empty($row['audio'])) {
  2127. $list = $_SESSION['oLP']->get_toc();
  2128. switch ($row['item_type']) {
  2129. case 'quiz':
  2130. $type_quiz = false;
  2131. foreach ($list as $toc) {
  2132. if ($toc['id'] == $_SESSION['oLP']->current) {
  2133. $type_quiz = true;
  2134. }
  2135. }
  2136. if ($type_quiz) {
  2137. if ($_SESSION['oLP']->prevent_reinit == 1) {
  2138. $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
  2139. } else {
  2140. $autostart_audio = $autostart;
  2141. }
  2142. }
  2143. break;
  2144. case TOOL_READOUT_TEXT:;
  2145. $autostart_audio = 'false';
  2146. break;
  2147. default:
  2148. $autostart_audio = 'true';
  2149. }
  2150. $courseInfo = api_get_course_info();
  2151. $audio = $row['audio'];
  2152. $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
  2153. $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
  2154. if (!file_exists($file)) {
  2155. $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
  2156. $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
  2157. $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
  2158. }
  2159. $player = Display::getMediaPlayer(
  2160. $file,
  2161. [
  2162. 'id' => 'lp_audio_media_player',
  2163. 'url' => $url,
  2164. 'autoplay' => $autostart_audio,
  2165. 'width' => '100%',
  2166. ]
  2167. );
  2168. // The mp3 player.
  2169. $output = '<div id="container">';
  2170. $output .= $player;
  2171. $output .= '</div>';
  2172. }
  2173. return $output;
  2174. }
  2175. /**
  2176. * @param int $studentId
  2177. * @param int $prerequisite
  2178. * @param array $courseInfo
  2179. * @param int $sessionId
  2180. *
  2181. * @return bool
  2182. */
  2183. public static function isBlockedByPrerequisite(
  2184. $studentId,
  2185. $prerequisite,
  2186. $courseInfo,
  2187. $sessionId
  2188. ) {
  2189. if (empty($courseInfo)) {
  2190. return false;
  2191. }
  2192. $courseId = $courseInfo['real_id'];
  2193. $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
  2194. if ($allow) {
  2195. if (api_is_allowed_to_edit() ||
  2196. api_is_platform_admin(true) ||
  2197. api_is_drh() ||
  2198. api_is_coach($sessionId, $courseId, false)
  2199. ) {
  2200. return false;
  2201. }
  2202. }
  2203. $isBlocked = false;
  2204. if (!empty($prerequisite)) {
  2205. $progress = self::getProgress(
  2206. $prerequisite,
  2207. $studentId,
  2208. $courseId,
  2209. $sessionId
  2210. );
  2211. if ($progress < 100) {
  2212. $isBlocked = true;
  2213. }
  2214. if (Tracking::minimunTimeAvailable($sessionId, $courseId)) {
  2215. // Block if it does not exceed minimum time
  2216. // Minimum time (in minutes) to pass the learning path
  2217. $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
  2218. if ($accumulateWorkTime > 0) {
  2219. // Total time in course (sum of times in learning paths from course)
  2220. $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
  2221. // Connect with the plugin_licences_course_session table
  2222. // which indicates what percentage of the time applies
  2223. // Minimum connection percentage
  2224. $perc = 100;
  2225. // Time from the course
  2226. $tc = $accumulateWorkTimeTotal;
  2227. // Percentage of the learning paths
  2228. $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
  2229. // Minimum time for each learning path
  2230. $accumulateWorkTime = ($pl * $tc * $perc / 100);
  2231. // Spent time (in seconds) so far in the learning path
  2232. /*$lpTime = Tracking::get_time_spent_in_lp(
  2233. $studentId,
  2234. $courseInfo['code'],
  2235. [$prerequisite],
  2236. $sessionId
  2237. );*/
  2238. $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
  2239. $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
  2240. if ($lpTime < ($accumulateWorkTime * 60)) {
  2241. $isBlocked = true;
  2242. }
  2243. }
  2244. }
  2245. }
  2246. return $isBlocked;
  2247. }
  2248. /**
  2249. * Checks if the learning path is visible for student after the progress
  2250. * of its prerequisite is completed, considering the time availability and
  2251. * the LP visibility.
  2252. *
  2253. * @param int $lp_id
  2254. * @param int $student_id
  2255. * @param null $courseCode
  2256. * @param int $sessionId
  2257. *
  2258. * @return bool
  2259. */
  2260. public static function is_lp_visible_for_student(
  2261. $lp_id,
  2262. $student_id,
  2263. $courseCode = null,
  2264. $sessionId = 0
  2265. ) {
  2266. $courseInfo = api_get_course_info($courseCode);
  2267. $lp_id = (int) $lp_id;
  2268. $sessionId = (int) $sessionId;
  2269. if (empty($courseInfo)) {
  2270. return false;
  2271. }
  2272. if (empty($sessionId)) {
  2273. $sessionId = api_get_session_id();
  2274. }
  2275. $itemInfo = api_get_item_property_info(
  2276. $courseInfo['real_id'],
  2277. TOOL_LEARNPATH,
  2278. $lp_id,
  2279. $sessionId
  2280. );
  2281. // If the item was deleted.
  2282. if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
  2283. return false;
  2284. }
  2285. // @todo remove this query and load the row info as a parameter
  2286. $table = Database::get_course_table(TABLE_LP_MAIN);
  2287. // Get current prerequisite
  2288. $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
  2289. FROM $table
  2290. WHERE iid = $lp_id";
  2291. $rs = Database::query($sql);
  2292. $now = time();
  2293. if (Database::num_rows($rs) > 0) {
  2294. $row = Database::fetch_array($rs, 'ASSOC');
  2295. $prerequisite = $row['prerequisite'];
  2296. $is_visible = true;
  2297. $isBlocked = self::isBlockedByPrerequisite(
  2298. $student_id,
  2299. $prerequisite,
  2300. $courseInfo,
  2301. $sessionId
  2302. );
  2303. if ($isBlocked) {
  2304. $is_visible = false;
  2305. }
  2306. // Also check the time availability of the LP
  2307. if ($is_visible) {
  2308. // Adding visibility restrictions
  2309. if (!empty($row['publicated_on'])) {
  2310. if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
  2311. $is_visible = false;
  2312. }
  2313. }
  2314. // Blocking empty start times see BT#2800
  2315. global $_custom;
  2316. if (isset($_custom['lps_hidden_when_no_start_date']) &&
  2317. $_custom['lps_hidden_when_no_start_date']
  2318. ) {
  2319. if (empty($row['publicated_on'])) {
  2320. $is_visible = false;
  2321. }
  2322. }
  2323. if (!empty($row['expired_on'])) {
  2324. if ($now > api_strtotime($row['expired_on'], 'UTC')) {
  2325. $is_visible = false;
  2326. }
  2327. }
  2328. }
  2329. if ($is_visible) {
  2330. $subscriptionSettings = self::getSubscriptionSettings();
  2331. // Check if the subscription users/group to a LP is ON
  2332. if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
  2333. $subscriptionSettings['allow_add_users_to_lp'] === true
  2334. ) {
  2335. // Try group
  2336. $is_visible = false;
  2337. // Checking only the user visibility
  2338. $userVisibility = api_get_item_visibility(
  2339. $courseInfo,
  2340. 'learnpath',
  2341. $row['id'],
  2342. $sessionId,
  2343. $student_id,
  2344. 'LearnpathSubscription'
  2345. );
  2346. if ($userVisibility == 1) {
  2347. $is_visible = true;
  2348. } else {
  2349. $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
  2350. if (!empty($userGroups)) {
  2351. foreach ($userGroups as $groupInfo) {
  2352. $groupId = $groupInfo['iid'];
  2353. $userVisibility = api_get_item_visibility(
  2354. $courseInfo,
  2355. 'learnpath',
  2356. $row['id'],
  2357. $sessionId,
  2358. null,
  2359. 'LearnpathSubscription',
  2360. $groupId
  2361. );
  2362. if ($userVisibility == 1) {
  2363. $is_visible = true;
  2364. break;
  2365. }
  2366. }
  2367. }
  2368. }
  2369. }
  2370. }
  2371. return $is_visible;
  2372. }
  2373. return false;
  2374. }
  2375. /**
  2376. * @param int $lpId
  2377. * @param int $userId
  2378. * @param int $courseId
  2379. * @param int $sessionId
  2380. *
  2381. * @return int
  2382. */
  2383. public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
  2384. {
  2385. $lpId = (int) $lpId;
  2386. $userId = (int) $userId;
  2387. $courseId = (int) $courseId;
  2388. $sessionId = (int) $sessionId;
  2389. $progress = 0;
  2390. $sessionCondition = api_get_session_condition($sessionId);
  2391. $table = Database::get_course_table(TABLE_LP_VIEW);
  2392. $sql = "SELECT * FROM $table
  2393. WHERE
  2394. c_id = $courseId AND
  2395. lp_id = $lpId AND
  2396. user_id = $userId $sessionCondition ";
  2397. $res = Database::query($sql);
  2398. if (Database::num_rows($res) > 0) {
  2399. $row = Database:: fetch_array($res);
  2400. $progress = $row['progress'];
  2401. }
  2402. return (int) $progress;
  2403. }
  2404. /**
  2405. * Displays a progress bar
  2406. * completed so far.
  2407. *
  2408. * @param int $percentage Progress value to display
  2409. * @param string $text_add Text to display near the progress value
  2410. *
  2411. * @return string HTML string containing the progress bar
  2412. */
  2413. public static function get_progress_bar($percentage = -1, $text_add = '')
  2414. {
  2415. $text = $percentage.$text_add;
  2416. $output = '<div class="progress">
  2417. <div id="progress_bar_value"
  2418. class="progress-bar progress-bar-success" role="progressbar"
  2419. aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
  2420. '.$text.'
  2421. </div>
  2422. </div>';
  2423. return $output;
  2424. }
  2425. /**
  2426. * @param string $mode can be '%' or 'abs'
  2427. * otherwise this value will be used $this->progress_bar_mode
  2428. *
  2429. * @return string
  2430. */
  2431. public function getProgressBar($mode = null)
  2432. {
  2433. list($percentage, $text_add) = $this->get_progress_bar_text($mode);
  2434. return self::get_progress_bar($percentage, $text_add);
  2435. }
  2436. /**
  2437. * Gets the progress bar info to display inside the progress bar.
  2438. * Also used by scorm_api.php.
  2439. *
  2440. * @param string $mode Mode of display (can be '%' or 'abs').abs means
  2441. * we display a number of completed elements per total elements
  2442. * @param int $add Additional steps to fake as completed
  2443. *
  2444. * @return array Percentage or number and symbol (% or /xx)
  2445. */
  2446. public function get_progress_bar_text($mode = '', $add = 0)
  2447. {
  2448. if ($this->debug > 0) {
  2449. error_log('In learnpath::get_progress_bar_text()', 0);
  2450. }
  2451. if (empty($mode)) {
  2452. $mode = $this->progress_bar_mode;
  2453. }
  2454. $total_items = $this->getTotalItemsCountWithoutDirs();
  2455. if ($this->debug > 2) {
  2456. error_log('Total items available in this learnpath: '.$total_items, 0);
  2457. }
  2458. $completeItems = $this->get_complete_items_count();
  2459. if ($this->debug > 2) {
  2460. error_log('Items completed so far: '.$completeItems, 0);
  2461. }
  2462. if ($add != 0) {
  2463. $completeItems += $add;
  2464. if ($this->debug > 2) {
  2465. error_log('Items completed so far (+modifier): '.$completeItems, 0);
  2466. }
  2467. }
  2468. $text = '';
  2469. if ($completeItems > $total_items) {
  2470. $completeItems = $total_items;
  2471. }
  2472. $percentage = 0;
  2473. if ($mode == '%') {
  2474. if ($total_items > 0) {
  2475. $percentage = ((float) $completeItems / (float) $total_items) * 100;
  2476. } else {
  2477. $percentage = 0;
  2478. }
  2479. $percentage = number_format($percentage, 0);
  2480. $text = '%';
  2481. } elseif ($mode == 'abs') {
  2482. $percentage = $completeItems;
  2483. $text = '/'.$total_items;
  2484. }
  2485. return [
  2486. $percentage,
  2487. $text,
  2488. ];
  2489. }
  2490. /**
  2491. * Gets the progress bar mode.
  2492. *
  2493. * @return string The progress bar mode attribute
  2494. */
  2495. public function get_progress_bar_mode()
  2496. {
  2497. if ($this->debug > 0) {
  2498. error_log('In learnpath::get_progress_bar_mode()', 0);
  2499. }
  2500. if (!empty($this->progress_bar_mode)) {
  2501. return $this->progress_bar_mode;
  2502. } else {
  2503. return '%';
  2504. }
  2505. }
  2506. /**
  2507. * Gets the learnpath theme (remote or local).
  2508. *
  2509. * @return string Learnpath theme
  2510. */
  2511. public function get_theme()
  2512. {
  2513. if ($this->debug > 0) {
  2514. error_log('In learnpath::get_theme()', 0);
  2515. }
  2516. if (!empty($this->theme)) {
  2517. return $this->theme;
  2518. } else {
  2519. return '';
  2520. }
  2521. }
  2522. /**
  2523. * Gets the learnpath session id.
  2524. *
  2525. * @return int
  2526. */
  2527. public function get_lp_session_id()
  2528. {
  2529. if ($this->debug > 0) {
  2530. error_log('In learnpath::get_lp_session_id()', 0);
  2531. }
  2532. if (!empty($this->lp_session_id)) {
  2533. return (int) $this->lp_session_id;
  2534. } else {
  2535. return 0;
  2536. }
  2537. }
  2538. /**
  2539. * Gets the learnpath image.
  2540. *
  2541. * @return string Web URL of the LP image
  2542. */
  2543. public function get_preview_image()
  2544. {
  2545. if ($this->debug > 0) {
  2546. error_log('In learnpath::get_preview_image()', 0);
  2547. }
  2548. if (!empty($this->preview_image)) {
  2549. return $this->preview_image;
  2550. } else {
  2551. return '';
  2552. }
  2553. }
  2554. /**
  2555. * @param string $size
  2556. * @param string $path_type
  2557. *
  2558. * @return bool|string
  2559. */
  2560. public function get_preview_image_path($size = null, $path_type = 'web')
  2561. {
  2562. $preview_image = $this->get_preview_image();
  2563. if (isset($preview_image) && !empty($preview_image)) {
  2564. $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
  2565. $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
  2566. if (isset($size)) {
  2567. $info = pathinfo($preview_image);
  2568. $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
  2569. if (file_exists($image_sys_path.$image_custom_size)) {
  2570. if ($path_type == 'web') {
  2571. return $image_path.$image_custom_size;
  2572. } else {
  2573. return $image_sys_path.$image_custom_size;
  2574. }
  2575. }
  2576. } else {
  2577. if ($path_type == 'web') {
  2578. return $image_path.$preview_image;
  2579. } else {
  2580. return $image_sys_path.$preview_image;
  2581. }
  2582. }
  2583. }
  2584. return false;
  2585. }
  2586. /**
  2587. * Gets the learnpath author.
  2588. *
  2589. * @return string LP's author
  2590. */
  2591. public function get_author()
  2592. {
  2593. if ($this->debug > 0) {
  2594. error_log('In learnpath::get_author()', 0);
  2595. }
  2596. if (!empty($this->author)) {
  2597. return $this->author;
  2598. } else {
  2599. return '';
  2600. }
  2601. }
  2602. /**
  2603. * Gets hide table of contents.
  2604. *
  2605. * @return int
  2606. */
  2607. public function getHideTableOfContents()
  2608. {
  2609. return (int) $this->hide_toc_frame;
  2610. }
  2611. /**
  2612. * Generate a new prerequisites string for a given item. If this item was a sco and
  2613. * its prerequisites were strings (instead of IDs), then transform those strings into
  2614. * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
  2615. * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
  2616. * same rule as the scormExport() method.
  2617. *
  2618. * @param int $item_id Item ID
  2619. *
  2620. * @return string Prerequisites string ready for the export as SCORM
  2621. */
  2622. public function get_scorm_prereq_string($item_id)
  2623. {
  2624. if ($this->debug > 0) {
  2625. error_log('In learnpath::get_scorm_prereq_string()');
  2626. }
  2627. if (!is_object($this->items[$item_id])) {
  2628. return false;
  2629. }
  2630. /** @var learnpathItem $oItem */
  2631. $oItem = $this->items[$item_id];
  2632. $prereq = $oItem->get_prereq_string();
  2633. if (empty($prereq)) {
  2634. return '';
  2635. }
  2636. if (preg_match('/^\d+$/', $prereq) &&
  2637. isset($this->items[$prereq]) &&
  2638. is_object($this->items[$prereq])
  2639. ) {
  2640. // If the prerequisite is a simple integer ID and this ID exists as an item ID,
  2641. // then simply return it (with the ITEM_ prefix).
  2642. //return 'ITEM_' . $prereq;
  2643. return $this->items[$prereq]->ref;
  2644. } else {
  2645. if (isset($this->refs_list[$prereq])) {
  2646. // It's a simple string item from which the ID can be found in the refs list,
  2647. // so we can transform it directly to an ID for export.
  2648. return $this->items[$this->refs_list[$prereq]]->ref;
  2649. } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
  2650. return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
  2651. } else {
  2652. // The last case, if it's a complex form, then find all the IDs (SCORM strings)
  2653. // and replace them, one by one, by the internal IDs (chamilo db)
  2654. // TODO: Modify the '*' replacement to replace the multiplier in front of it
  2655. // by a space as well.
  2656. $find = [
  2657. '&',
  2658. '|',
  2659. '~',
  2660. '=',
  2661. '<>',
  2662. '{',
  2663. '}',
  2664. '*',
  2665. '(',
  2666. ')',
  2667. ];
  2668. $replace = [
  2669. ' ',
  2670. ' ',
  2671. ' ',
  2672. ' ',
  2673. ' ',
  2674. ' ',
  2675. ' ',
  2676. ' ',
  2677. ' ',
  2678. ' ',
  2679. ];
  2680. $prereq_mod = str_replace($find, $replace, $prereq);
  2681. $ids = explode(' ', $prereq_mod);
  2682. foreach ($ids as $id) {
  2683. $id = trim($id);
  2684. if (isset($this->refs_list[$id])) {
  2685. $prereq = preg_replace(
  2686. '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
  2687. 'ITEM_'.$this->refs_list[$id],
  2688. $prereq
  2689. );
  2690. }
  2691. }
  2692. return $prereq;
  2693. }
  2694. }
  2695. }
  2696. /**
  2697. * Returns the XML DOM document's node.
  2698. *
  2699. * @param resource $children Reference to a list of objects to search for the given ITEM_*
  2700. * @param string $id The identifier to look for
  2701. *
  2702. * @return mixed The reference to the element found with that identifier. False if not found
  2703. */
  2704. public function get_scorm_xml_node(&$children, $id)
  2705. {
  2706. for ($i = 0; $i < $children->length; $i++) {
  2707. $item_temp = $children->item($i);
  2708. if ($item_temp->nodeName == 'item') {
  2709. if ($item_temp->getAttribute('identifier') == $id) {
  2710. return $item_temp;
  2711. }
  2712. }
  2713. $subchildren = $item_temp->childNodes;
  2714. if ($subchildren && $subchildren->length > 0) {
  2715. $val = $this->get_scorm_xml_node($subchildren, $id);
  2716. if (is_object($val)) {
  2717. return $val;
  2718. }
  2719. }
  2720. }
  2721. return false;
  2722. }
  2723. /**
  2724. * Gets the status list for all LP's items.
  2725. *
  2726. * @return array Array of [index] => [item ID => current status]
  2727. */
  2728. public function get_items_status_list()
  2729. {
  2730. if ($this->debug > 0) {
  2731. error_log('In learnpath::get_items_status_list()', 0);
  2732. }
  2733. $list = [];
  2734. foreach ($this->ordered_items as $item_id) {
  2735. $list[] = [
  2736. $item_id => $this->items[$item_id]->get_status(),
  2737. ];
  2738. }
  2739. return $list;
  2740. }
  2741. /**
  2742. * Return the number of interactions for the given learnpath Item View ID.
  2743. * This method can be used as static.
  2744. *
  2745. * @param int $lp_iv_id Item View ID
  2746. * @param int $course_id course id
  2747. *
  2748. * @return int
  2749. */
  2750. public static function get_interactions_count_from_db($lp_iv_id, $course_id)
  2751. {
  2752. $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
  2753. $lp_iv_id = (int) $lp_iv_id;
  2754. $course_id = (int) $course_id;
  2755. $sql = "SELECT count(*) FROM $table
  2756. WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
  2757. $res = Database::query($sql);
  2758. $num = 0;
  2759. if (Database::num_rows($res)) {
  2760. $row = Database::fetch_array($res);
  2761. $num = $row[0];
  2762. }
  2763. return $num;
  2764. }
  2765. /**
  2766. * Return the interactions as an array for the given lp_iv_id.
  2767. * This method can be used as static.
  2768. *
  2769. * @param int $lp_iv_id Learnpath Item View ID
  2770. *
  2771. * @return array
  2772. *
  2773. * @todo Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
  2774. */
  2775. public static function get_iv_interactions_array($lp_iv_id)
  2776. {
  2777. $course_id = api_get_course_int_id();
  2778. $list = [];
  2779. $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
  2780. if (empty($lp_iv_id)) {
  2781. return [];
  2782. }
  2783. $sql = "SELECT * FROM $table
  2784. WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
  2785. ORDER BY order_id ASC";
  2786. $res = Database::query($sql);
  2787. $num = Database::num_rows($res);
  2788. if ($num > 0) {
  2789. $list[] = [
  2790. 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
  2791. 'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
  2792. 'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
  2793. 'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
  2794. 'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
  2795. 'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
  2796. 'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
  2797. 'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
  2798. ];
  2799. while ($row = Database::fetch_array($res)) {
  2800. $list[] = [
  2801. 'order_id' => ($row['order_id'] + 1),
  2802. 'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
  2803. 'type' => $row['interaction_type'],
  2804. 'time' => $row['completion_time'],
  2805. //'correct_responses' => $row['correct_responses'],
  2806. 'correct_responses' => '', // Hide correct responses from students.
  2807. 'student_response' => $row['student_response'],
  2808. 'result' => $row['result'],
  2809. 'latency' => $row['latency'],
  2810. ];
  2811. }
  2812. }
  2813. return $list;
  2814. }
  2815. /**
  2816. * Return the number of objectives for the given learnpath Item View ID.
  2817. * This method can be used as static.
  2818. *
  2819. * @param int $lp_iv_id Item View ID
  2820. * @param int $course_id Course ID
  2821. *
  2822. * @return int Number of objectives
  2823. */
  2824. public static function get_objectives_count_from_db($lp_iv_id, $course_id)
  2825. {
  2826. $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
  2827. $course_id = (int) $course_id;
  2828. $lp_iv_id = (int) $lp_iv_id;
  2829. $sql = "SELECT count(*) FROM $table
  2830. WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
  2831. //@todo seems that this always returns 0
  2832. $res = Database::query($sql);
  2833. $num = 0;
  2834. if (Database::num_rows($res)) {
  2835. $row = Database::fetch_array($res);
  2836. $num = $row[0];
  2837. }
  2838. return $num;
  2839. }
  2840. /**
  2841. * Return the objectives as an array for the given lp_iv_id.
  2842. * This method can be used as static.
  2843. *
  2844. * @param int $lpItemViewId Learnpath Item View ID
  2845. *
  2846. * @return array
  2847. *
  2848. * @todo Translate labels
  2849. */
  2850. public static function get_iv_objectives_array($lpItemViewId = 0)
  2851. {
  2852. $course_id = api_get_course_int_id();
  2853. $lpItemViewId = (int) $lpItemViewId;
  2854. $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
  2855. $sql = "SELECT * FROM $table
  2856. WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
  2857. ORDER BY order_id ASC";
  2858. $res = Database::query($sql);
  2859. $num = Database::num_rows($res);
  2860. $list = [];
  2861. if ($num > 0) {
  2862. $list[] = [
  2863. 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
  2864. 'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
  2865. 'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
  2866. 'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
  2867. 'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
  2868. 'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
  2869. ];
  2870. while ($row = Database::fetch_array($res)) {
  2871. $list[] = [
  2872. 'order_id' => ($row['order_id'] + 1),
  2873. 'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
  2874. 'score_raw' => $row['score_raw'],
  2875. 'score_max' => $row['score_max'],
  2876. 'score_min' => $row['score_min'],
  2877. 'status' => $row['status'],
  2878. ];
  2879. }
  2880. }
  2881. return $list;
  2882. }
  2883. /**
  2884. * Generate and return the table of contents for this learnpath. The (flat) table returned can be
  2885. * used by get_html_toc() to be ready to display.
  2886. *
  2887. * @return array TOC as a table with 4 elements per row: title, link, status and level
  2888. */
  2889. public function get_toc()
  2890. {
  2891. if ($this->debug > 0) {
  2892. error_log('learnpath::get_toc()', 0);
  2893. }
  2894. $toc = [];
  2895. foreach ($this->ordered_items as $item_id) {
  2896. if ($this->debug > 2) {
  2897. error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
  2898. }
  2899. // TODO: Change this link generation and use new function instead.
  2900. $toc[] = [
  2901. 'id' => $item_id,
  2902. 'title' => $this->items[$item_id]->get_title(),
  2903. 'status' => $this->items[$item_id]->get_status(),
  2904. 'level' => $this->items[$item_id]->get_level(),
  2905. 'type' => $this->items[$item_id]->get_type(),
  2906. 'description' => $this->items[$item_id]->get_description(),
  2907. 'path' => $this->items[$item_id]->get_path(),
  2908. 'parent' => $this->items[$item_id]->get_parent(),
  2909. ];
  2910. }
  2911. if ($this->debug > 2) {
  2912. error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
  2913. }
  2914. return $toc;
  2915. }
  2916. /**
  2917. * Generate and return the table of contents for this learnpath. The JS
  2918. * table returned is used inside of scorm_api.php.
  2919. *
  2920. * @param string $varname
  2921. *
  2922. * @return string A JS array variable construction
  2923. */
  2924. public function get_items_details_as_js($varname = 'olms.lms_item_types')
  2925. {
  2926. if ($this->debug > 0) {
  2927. error_log('In learnpath::get_items_details_as_js()', 0);
  2928. }
  2929. $toc = $varname.' = new Array();';
  2930. foreach ($this->ordered_items as $item_id) {
  2931. $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
  2932. }
  2933. if ($this->debug > 2) {
  2934. error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
  2935. }
  2936. return $toc;
  2937. }
  2938. /**
  2939. * Gets the learning path type.
  2940. *
  2941. * @param bool $get_name Return the name? If false, return the ID. Default is false.
  2942. *
  2943. * @return mixed Type ID or name, depending on the parameter
  2944. */
  2945. public function get_type($get_name = false)
  2946. {
  2947. $res = false;
  2948. if ($this->debug > 0) {
  2949. error_log('In learnpath::get_type()', 0);
  2950. }
  2951. if (!empty($this->type) && (!$get_name)) {
  2952. $res = $this->type;
  2953. }
  2954. if ($this->debug > 2) {
  2955. error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
  2956. }
  2957. return $res;
  2958. }
  2959. /**
  2960. * Gets the learning path type as static method.
  2961. *
  2962. * @param int $lp_id
  2963. *
  2964. * @return mixed Type ID or name, depending on the parameter
  2965. */
  2966. public static function get_type_static($lp_id = 0)
  2967. {
  2968. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  2969. $lp_id = (int) $lp_id;
  2970. $sql = "SELECT lp_type FROM $tbl_lp
  2971. WHERE iid = $lp_id";
  2972. $res = Database::query($sql);
  2973. if ($res === false) {
  2974. return null;
  2975. }
  2976. if (Database::num_rows($res) <= 0) {
  2977. return null;
  2978. }
  2979. $row = Database::fetch_array($res);
  2980. return $row['lp_type'];
  2981. }
  2982. /**
  2983. * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
  2984. * This method can be used as abstract and is recursive.
  2985. *
  2986. * @param int $lp Learnpath ID
  2987. * @param int $parent Parent ID of the items to look for
  2988. * @param int $course_id
  2989. *
  2990. * @return array Ordered list of item IDs (empty array on error)
  2991. */
  2992. public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
  2993. {
  2994. if (empty($course_id)) {
  2995. $course_id = api_get_course_int_id();
  2996. } else {
  2997. $course_id = (int) $course_id;
  2998. }
  2999. $list = [];
  3000. if (empty($lp)) {
  3001. return $list;
  3002. }
  3003. $lp = (int) $lp;
  3004. $parent = (int) $parent;
  3005. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  3006. $sql = "SELECT iid FROM $tbl_lp_item
  3007. WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
  3008. ORDER BY display_order";
  3009. $res = Database::query($sql);
  3010. while ($row = Database::fetch_array($res)) {
  3011. $sublist = self::get_flat_ordered_items_list(
  3012. $lp,
  3013. $row['iid'],
  3014. $course_id
  3015. );
  3016. $list[] = $row['iid'];
  3017. foreach ($sublist as $item) {
  3018. $list[] = $item;
  3019. }
  3020. }
  3021. return $list;
  3022. }
  3023. /**
  3024. * @return array
  3025. */
  3026. public static function getChapterTypes()
  3027. {
  3028. return [
  3029. 'dir',
  3030. ];
  3031. }
  3032. /**
  3033. * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
  3034. *
  3035. * @param $tree
  3036. *
  3037. * @return array HTML TOC ready to display
  3038. */
  3039. public function getParentToc($tree)
  3040. {
  3041. if ($this->debug > 0) {
  3042. error_log('In learnpath::get_html_toc()', 0);
  3043. }
  3044. if (empty($tree)) {
  3045. $tree = $this->get_toc();
  3046. }
  3047. $dirTypes = self::getChapterTypes();
  3048. $myCurrentId = $this->get_current_item_id();
  3049. $listParent = [];
  3050. $listChildren = [];
  3051. $listNotParent = [];
  3052. $list = [];
  3053. foreach ($tree as $subtree) {
  3054. if (in_array($subtree['type'], $dirTypes)) {
  3055. $listChildren = $this->getChildrenToc($tree, $subtree['id']);
  3056. $subtree['children'] = $listChildren;
  3057. if (!empty($subtree['children'])) {
  3058. foreach ($subtree['children'] as $subItem) {
  3059. if ($subItem['id'] == $this->current) {
  3060. $subtree['parent_current'] = 'in';
  3061. $subtree['current'] = 'on';
  3062. }
  3063. }
  3064. }
  3065. $listParent[] = $subtree;
  3066. }
  3067. if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
  3068. $classStatus = [
  3069. 'not attempted' => 'scorm_not_attempted',
  3070. 'incomplete' => 'scorm_not_attempted',
  3071. 'failed' => 'scorm_failed',
  3072. 'completed' => 'scorm_completed',
  3073. 'passed' => 'scorm_completed',
  3074. 'succeeded' => 'scorm_completed',
  3075. 'browsed' => 'scorm_completed',
  3076. ];
  3077. if (isset($classStatus[$subtree['status']])) {
  3078. $cssStatus = $classStatus[$subtree['status']];
  3079. }
  3080. $title = Security::remove_XSS($subtree['title']);
  3081. unset($subtree['title']);
  3082. if (empty($title)) {
  3083. $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
  3084. }
  3085. $classStyle = null;
  3086. if ($subtree['id'] == $this->current) {
  3087. $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
  3088. } elseif (!in_array($subtree['type'], $dirTypes)) {
  3089. $classStyle = 'scorm_item_normal '.$classStyle.' ';
  3090. }
  3091. $subtree['title'] = $title;
  3092. $subtree['class'] = $classStyle.' '.$cssStatus;
  3093. $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
  3094. $subtree['current_id'] = $myCurrentId;
  3095. $listNotParent[] = $subtree;
  3096. }
  3097. }
  3098. $list['are_parents'] = $listParent;
  3099. $list['not_parents'] = $listNotParent;
  3100. return $list;
  3101. }
  3102. /**
  3103. * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
  3104. *
  3105. * @param array $tree
  3106. * @param int $id
  3107. * @param bool $parent
  3108. *
  3109. * @return array HTML TOC ready to display
  3110. */
  3111. public function getChildrenToc($tree, $id, $parent = true)
  3112. {
  3113. if ($this->debug > 0) {
  3114. error_log('In learnpath::get_html_toc()', 0);
  3115. }
  3116. if (empty($tree)) {
  3117. $tree = $this->get_toc();
  3118. }
  3119. $dirTypes = self::getChapterTypes();
  3120. $mycurrentitemid = $this->get_current_item_id();
  3121. $list = [];
  3122. $classStatus = [
  3123. 'not attempted' => 'scorm_not_attempted',
  3124. 'incomplete' => 'scorm_not_attempted',
  3125. 'failed' => 'scorm_failed',
  3126. 'completed' => 'scorm_completed',
  3127. 'passed' => 'scorm_completed',
  3128. 'succeeded' => 'scorm_completed',
  3129. 'browsed' => 'scorm_completed',
  3130. ];
  3131. foreach ($tree as $subtree) {
  3132. $subtree['tree'] = null;
  3133. if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
  3134. if ($subtree['id'] == $this->current) {
  3135. $subtree['current'] = 'active';
  3136. } else {
  3137. $subtree['current'] = null;
  3138. }
  3139. if (isset($classStatus[$subtree['status']])) {
  3140. $cssStatus = $classStatus[$subtree['status']];
  3141. }
  3142. $title = Security::remove_XSS($subtree['title']);
  3143. unset($subtree['title']);
  3144. if (empty($title)) {
  3145. $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
  3146. }
  3147. $classStyle = null;
  3148. if ($subtree['id'] == $this->current) {
  3149. $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
  3150. } elseif (!in_array($subtree['type'], $dirTypes)) {
  3151. $classStyle = 'scorm_item_normal '.$classStyle.' ';
  3152. }
  3153. if (in_array($subtree['type'], $dirTypes)) {
  3154. $subtree['title'] = stripslashes($title);
  3155. } else {
  3156. $subtree['title'] = $title;
  3157. $subtree['class'] = $classStyle.' '.$cssStatus;
  3158. $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
  3159. $subtree['current_id'] = $mycurrentitemid;
  3160. }
  3161. $list[] = $subtree;
  3162. }
  3163. }
  3164. return $list;
  3165. }
  3166. /**
  3167. * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
  3168. *
  3169. * @param array $toc_list
  3170. *
  3171. * @return array HTML TOC ready to display
  3172. */
  3173. public function getListArrayToc($toc_list = [])
  3174. {
  3175. if ($this->debug > 0) {
  3176. error_log('In learnpath::get_html_toc()', 0);
  3177. }
  3178. if (empty($toc_list)) {
  3179. $toc_list = $this->get_toc();
  3180. }
  3181. // Temporary variables.
  3182. $mycurrentitemid = $this->get_current_item_id();
  3183. $list = [];
  3184. $arrayList = [];
  3185. $classStatus = [
  3186. 'not attempted' => 'scorm_not_attempted',
  3187. 'incomplete' => 'scorm_not_attempted',
  3188. 'failed' => 'scorm_failed',
  3189. 'completed' => 'scorm_completed',
  3190. 'passed' => 'scorm_completed',
  3191. 'succeeded' => 'scorm_completed',
  3192. 'browsed' => 'scorm_completed',
  3193. ];
  3194. foreach ($toc_list as $item) {
  3195. $list['id'] = $item['id'];
  3196. $list['status'] = $item['status'];
  3197. $cssStatus = null;
  3198. if (isset($classStatus[$item['status']])) {
  3199. $cssStatus = $classStatus[$item['status']];
  3200. }
  3201. $classStyle = ' ';
  3202. $dirTypes = self::getChapterTypes();
  3203. if (in_array($item['type'], $dirTypes)) {
  3204. $classStyle = 'scorm_item_section ';
  3205. }
  3206. if ($item['id'] == $this->current) {
  3207. $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
  3208. } elseif (!in_array($item['type'], $dirTypes)) {
  3209. $classStyle = 'scorm_item_normal '.$classStyle.' ';
  3210. }
  3211. $title = $item['title'];
  3212. if (empty($title)) {
  3213. $title = self::rl_get_resource_name(
  3214. api_get_course_id(),
  3215. $this->get_id(),
  3216. $item['id']
  3217. );
  3218. }
  3219. $title = Security::remove_XSS($item['title']);
  3220. if (empty($item['description'])) {
  3221. $list['description'] = $title;
  3222. } else {
  3223. $list['description'] = $item['description'];
  3224. }
  3225. $list['class'] = $classStyle.' '.$cssStatus;
  3226. $list['level'] = $item['level'];
  3227. $list['type'] = $item['type'];
  3228. if (in_array($item['type'], $dirTypes)) {
  3229. $list['css_level'] = 'level_'.$item['level'];
  3230. } else {
  3231. $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
  3232. }
  3233. if (in_array($item['type'], $dirTypes)) {
  3234. $list['title'] = stripslashes($title);
  3235. } else {
  3236. $list['title'] = stripslashes($title);
  3237. $list['url'] = $this->get_link('http', $item['id'], $toc_list);
  3238. $list['current_id'] = $mycurrentitemid;
  3239. }
  3240. $arrayList[] = $list;
  3241. }
  3242. return $arrayList;
  3243. }
  3244. /**
  3245. * Returns an HTML-formatted string ready to display with teacher buttons
  3246. * in LP view menu.
  3247. *
  3248. * @return string HTML TOC ready to display
  3249. */
  3250. public function get_teacher_toc_buttons()
  3251. {
  3252. $isAllow = api_is_allowed_to_edit(null, true, false, false);
  3253. $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
  3254. $html = '';
  3255. if ($isAllow && $hideIcons == false) {
  3256. if ($this->get_lp_session_id() == api_get_session_id()) {
  3257. $html .= '<div id="actions_lp" class="actions_lp"><hr>';
  3258. $html .= '<div class="btn-group">';
  3259. $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
  3260. Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
  3261. $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
  3262. Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
  3263. $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
  3264. Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
  3265. $html .= '</div>';
  3266. $html .= '</div>';
  3267. }
  3268. }
  3269. return $html;
  3270. }
  3271. /**
  3272. * Gets the learnpath maker name - generally the editor's name.
  3273. *
  3274. * @return string Learnpath maker name
  3275. */
  3276. public function get_maker()
  3277. {
  3278. if ($this->debug > 0) {
  3279. error_log('In learnpath::get_maker()', 0);
  3280. }
  3281. if (!empty($this->maker)) {
  3282. return $this->maker;
  3283. } else {
  3284. return '';
  3285. }
  3286. }
  3287. /**
  3288. * Gets the learnpath name/title.
  3289. *
  3290. * @return string Learnpath name/title
  3291. */
  3292. public function get_name()
  3293. {
  3294. if ($this->debug > 0) {
  3295. error_log('In learnpath::get_name()', 0);
  3296. }
  3297. if (!empty($this->name)) {
  3298. return $this->name;
  3299. } else {
  3300. return 'N/A';
  3301. }
  3302. }
  3303. /**
  3304. * Gets a link to the resource from the present location, depending on item ID.
  3305. *
  3306. * @param string $type Type of link expected
  3307. * @param int $item_id Learnpath item ID
  3308. * @param bool $provided_toc
  3309. *
  3310. * @return string $provided_toc Link to the lp_item resource
  3311. */
  3312. public function get_link($type = 'http', $item_id = null, $provided_toc = false)
  3313. {
  3314. $course_id = $this->get_course_int_id();
  3315. if ($this->debug > 0) {
  3316. error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
  3317. }
  3318. if (empty($item_id)) {
  3319. if ($this->debug > 2) {
  3320. error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
  3321. error_log('using current: '.$this->get_current_item_id(), 0);
  3322. }
  3323. $item_id = $this->get_current_item_id();
  3324. if (empty($item_id)) {
  3325. if ($this->debug > 2) {
  3326. error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
  3327. }
  3328. //still empty, this means there was no item_id given and we are not in an object context or
  3329. //the object property is empty, return empty link
  3330. $this->first();
  3331. return '';
  3332. }
  3333. }
  3334. $file = '';
  3335. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  3336. $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
  3337. $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  3338. $item_id = (int) $item_id;
  3339. $sql = "SELECT
  3340. l.lp_type as ltype,
  3341. l.path as lpath,
  3342. li.item_type as litype,
  3343. li.path as lipath,
  3344. li.parameters as liparams
  3345. FROM $lp_table l
  3346. INNER JOIN $lp_item_table li
  3347. ON (li.lp_id = l.iid)
  3348. WHERE
  3349. li.iid = $item_id
  3350. ";
  3351. if ($this->debug > 2) {
  3352. error_log('In learnpath::get_link() - selecting item '.$sql, 0);
  3353. }
  3354. $res = Database::query($sql);
  3355. if (Database::num_rows($res) > 0) {
  3356. $row = Database::fetch_array($res);
  3357. $lp_type = $row['ltype'];
  3358. $lp_path = $row['lpath'];
  3359. $lp_item_type = $row['litype'];
  3360. $lp_item_path = $row['lipath'];
  3361. $lp_item_params = $row['liparams'];
  3362. if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
  3363. list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
  3364. }
  3365. $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
  3366. if ($type === 'http') {
  3367. //web path
  3368. $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
  3369. } else {
  3370. $course_path = $sys_course_path; //system path
  3371. }
  3372. // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
  3373. // then change the lp type to thread it as a normal Chamilo LP not a SCO.
  3374. if (in_array(
  3375. $lp_item_type,
  3376. ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
  3377. )
  3378. ) {
  3379. $lp_type = 1;
  3380. }
  3381. if ($this->debug > 2) {
  3382. error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
  3383. error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
  3384. }
  3385. // Now go through the specific cases to get the end of the path
  3386. // @todo Use constants instead of int values.
  3387. switch ($lp_type) {
  3388. case 1:
  3389. $file = self::rl_get_resource_link_for_learnpath(
  3390. $course_id,
  3391. $this->get_id(),
  3392. $item_id,
  3393. $this->get_view_id()
  3394. );
  3395. if ($this->debug > 0) {
  3396. error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
  3397. }
  3398. switch ($lp_item_type) {
  3399. case 'document':
  3400. // Shows a button to download the file instead of just downloading the file directly.
  3401. $documentPathInfo = pathinfo($file);
  3402. if (isset($documentPathInfo['extension'])) {
  3403. $parsed = parse_url($documentPathInfo['extension']);
  3404. if (isset($parsed['path'])) {
  3405. $extension = $parsed['path'];
  3406. $extensionsToDownload = [
  3407. 'zip',
  3408. 'ppt',
  3409. 'pptx',
  3410. 'ods',
  3411. 'xlsx',
  3412. 'xls',
  3413. 'csv',
  3414. 'doc',
  3415. 'docx',
  3416. 'dot',
  3417. ];
  3418. if (in_array($extension, $extensionsToDownload)) {
  3419. $file = api_get_path(WEB_CODE_PATH).
  3420. 'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
  3421. }
  3422. }
  3423. }
  3424. break;
  3425. case 'dir':
  3426. $file = 'lp_content.php?type=dir';
  3427. break;
  3428. case 'link':
  3429. if (Link::is_youtube_link($file)) {
  3430. $src = Link::get_youtube_video_id($file);
  3431. $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
  3432. } elseif (Link::isVimeoLink($file)) {
  3433. $src = Link::getVimeoLinkId($file);
  3434. $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
  3435. } else {
  3436. // If the current site is HTTPS and the link is
  3437. // HTTP, browsers will refuse opening the link
  3438. $urlId = api_get_current_access_url_id();
  3439. $url = api_get_access_url($urlId, false);
  3440. $protocol = substr($url['url'], 0, 5);
  3441. if ($protocol === 'https') {
  3442. $linkProtocol = substr($file, 0, 5);
  3443. if ($linkProtocol === 'http:') {
  3444. //this is the special intervention case
  3445. $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
  3446. }
  3447. }
  3448. }
  3449. break;
  3450. case 'quiz':
  3451. // Check how much attempts of a exercise exits in lp
  3452. $lp_item_id = $this->get_current_item_id();
  3453. $lp_view_id = $this->get_view_id();
  3454. $prevent_reinit = null;
  3455. if (isset($this->items[$this->current])) {
  3456. $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
  3457. }
  3458. if (empty($provided_toc)) {
  3459. if ($this->debug > 0) {
  3460. error_log('In learnpath::get_link() Loading get_toc ', 0);
  3461. }
  3462. $list = $this->get_toc();
  3463. } else {
  3464. if ($this->debug > 0) {
  3465. error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
  3466. }
  3467. $list = $provided_toc;
  3468. }
  3469. $type_quiz = false;
  3470. foreach ($list as $toc) {
  3471. if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
  3472. $type_quiz = true;
  3473. }
  3474. }
  3475. if ($type_quiz) {
  3476. $lp_item_id = (int) $lp_item_id;
  3477. $lp_view_id = (int) $lp_view_id;
  3478. $sql = "SELECT count(*) FROM $lp_item_view_table
  3479. WHERE
  3480. c_id = $course_id AND
  3481. lp_item_id='".$lp_item_id."' AND
  3482. lp_view_id ='".$lp_view_id."' AND
  3483. status='completed'";
  3484. $result = Database::query($sql);
  3485. $row_count = Database:: fetch_row($result);
  3486. $count_item_view = (int) $row_count[0];
  3487. $not_multiple_attempt = 0;
  3488. if ($prevent_reinit === 1 && $count_item_view > 0) {
  3489. $not_multiple_attempt = 1;
  3490. }
  3491. $file .= '&not_multiple_attempt='.$not_multiple_attempt;
  3492. }
  3493. break;
  3494. }
  3495. $tmp_array = explode('/', $file);
  3496. $document_name = $tmp_array[count($tmp_array) - 1];
  3497. if (strpos($document_name, '_DELETED_')) {
  3498. $file = 'blank.php?error=document_deleted';
  3499. }
  3500. break;
  3501. case 2:
  3502. if ($this->debug > 2) {
  3503. error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
  3504. }
  3505. if ($lp_item_type != 'dir') {
  3506. // Quite complex here:
  3507. // We want to make sure 'http://' (and similar) links can
  3508. // be loaded as is (withouth the Chamilo path in front) but
  3509. // some contents use this form: resource.htm?resource=http://blablabla
  3510. // which means we have to find a protocol at the path's start, otherwise
  3511. // it should not be considered as an external URL.
  3512. // if ($this->prerequisites_match($item_id)) {
  3513. if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
  3514. if ($this->debug > 2) {
  3515. error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
  3516. }
  3517. // Distant url, return as is.
  3518. $file = $lp_item_path;
  3519. } else {
  3520. if ($this->debug > 2) {
  3521. error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
  3522. }
  3523. // Prevent getting untranslatable urls.
  3524. $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
  3525. $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
  3526. // Prepare the path.
  3527. $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
  3528. // TODO: Fix this for urls with protocol header.
  3529. $file = str_replace('//', '/', $file);
  3530. $file = str_replace(':/', '://', $file);
  3531. if (substr($lp_path, -1) == '/') {
  3532. $lp_path = substr($lp_path, 0, -1);
  3533. }
  3534. if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
  3535. // if file not found.
  3536. $decoded = html_entity_decode($lp_item_path);
  3537. list($decoded) = explode('?', $decoded);
  3538. if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
  3539. $file = self::rl_get_resource_link_for_learnpath(
  3540. $course_id,
  3541. $this->get_id(),
  3542. $item_id,
  3543. $this->get_view_id()
  3544. );
  3545. if (empty($file)) {
  3546. $file = 'blank.php?error=document_not_found';
  3547. } else {
  3548. $tmp_array = explode('/', $file);
  3549. $document_name = $tmp_array[count($tmp_array) - 1];
  3550. if (strpos($document_name, '_DELETED_')) {
  3551. $file = 'blank.php?error=document_deleted';
  3552. } else {
  3553. $file = 'blank.php?error=document_not_found';
  3554. }
  3555. }
  3556. } else {
  3557. $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
  3558. }
  3559. }
  3560. }
  3561. // We want to use parameters if they were defined in the imsmanifest
  3562. if (strpos($file, 'blank.php') === false) {
  3563. $lp_item_params = ltrim($lp_item_params, '?');
  3564. $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
  3565. }
  3566. } else {
  3567. $file = 'lp_content.php?type=dir';
  3568. }
  3569. break;
  3570. case 3:
  3571. if ($this->debug > 2) {
  3572. error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
  3573. }
  3574. // Formatting AICC HACP append URL.
  3575. $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
  3576. if (!empty($lp_item_params)) {
  3577. $aicc_append .= $lp_item_params.'&';
  3578. }
  3579. if ($lp_item_type != 'dir') {
  3580. // Quite complex here:
  3581. // We want to make sure 'http://' (and similar) links can
  3582. // be loaded as is (withouth the Chamilo path in front) but
  3583. // some contents use this form: resource.htm?resource=http://blablabla
  3584. // which means we have to find a protocol at the path's start, otherwise
  3585. // it should not be considered as an external URL.
  3586. if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
  3587. if ($this->debug > 2) {
  3588. error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
  3589. }
  3590. // Distant url, return as is.
  3591. $file = $lp_item_path;
  3592. // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
  3593. /*
  3594. if (stristr($file,'<servername>') !== false) {
  3595. $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
  3596. }
  3597. */
  3598. if (stripos($file, '<servername>') !== false) {
  3599. //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
  3600. $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
  3601. $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
  3602. }
  3603. $file .= $aicc_append;
  3604. } else {
  3605. if ($this->debug > 2) {
  3606. error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
  3607. }
  3608. // Prevent getting untranslatable urls.
  3609. $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
  3610. $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
  3611. // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
  3612. $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
  3613. // TODO: Fix this for urls with protocol header.
  3614. $file = str_replace('//', '/', $file);
  3615. $file = str_replace(':/', '://', $file);
  3616. $file .= $aicc_append;
  3617. }
  3618. } else {
  3619. $file = 'lp_content.php?type=dir';
  3620. }
  3621. break;
  3622. case 4:
  3623. break;
  3624. default:
  3625. break;
  3626. }
  3627. // Replace &amp; by & because &amp; will break URL with params
  3628. $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
  3629. }
  3630. if ($this->debug > 2) {
  3631. error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
  3632. }
  3633. return $file;
  3634. }
  3635. /**
  3636. * Gets the latest usable view or generate a new one.
  3637. *
  3638. * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
  3639. *
  3640. * @return int DB lp_view id
  3641. */
  3642. public function get_view($attempt_num = 0)
  3643. {
  3644. if ($this->debug > 0) {
  3645. error_log('In learnpath::get_view()', 0);
  3646. }
  3647. $search = '';
  3648. // Use $attempt_num to enable multi-views management (disabled so far).
  3649. if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
  3650. $search = 'AND view_count = '.$attempt_num;
  3651. }
  3652. // When missing $attempt_num, search for a unique lp_view record for this lp and user.
  3653. $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
  3654. $course_id = api_get_course_int_id();
  3655. $sessionId = api_get_session_id();
  3656. $sql = "SELECT iid, view_count FROM $lp_view_table
  3657. WHERE
  3658. c_id = $course_id AND
  3659. lp_id = ".$this->get_id()." AND
  3660. user_id = ".$this->get_user_id()." AND
  3661. session_id = $sessionId
  3662. $search
  3663. ORDER BY view_count DESC";
  3664. $res = Database::query($sql);
  3665. if (Database::num_rows($res) > 0) {
  3666. $row = Database::fetch_array($res);
  3667. $this->lp_view_id = $row['iid'];
  3668. } elseif (!api_is_invitee()) {
  3669. // There is no database record, create one.
  3670. $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
  3671. ($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
  3672. Database::query($sql);
  3673. $id = Database::insert_id();
  3674. $this->lp_view_id = $id;
  3675. $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
  3676. Database::query($sql);
  3677. }
  3678. return $this->lp_view_id;
  3679. }
  3680. /**
  3681. * Gets the current view id.
  3682. *
  3683. * @return int View ID (from lp_view)
  3684. */
  3685. public function get_view_id()
  3686. {
  3687. if ($this->debug > 0) {
  3688. error_log('In learnpath::get_view_id()', 0);
  3689. }
  3690. if (!empty($this->lp_view_id)) {
  3691. return $this->lp_view_id;
  3692. } else {
  3693. return 0;
  3694. }
  3695. }
  3696. /**
  3697. * Gets the update queue.
  3698. *
  3699. * @return array Array containing IDs of items to be updated by JavaScript
  3700. */
  3701. public function get_update_queue()
  3702. {
  3703. if ($this->debug > 0) {
  3704. error_log('In learnpath::get_update_queue()', 0);
  3705. }
  3706. return $this->update_queue;
  3707. }
  3708. /**
  3709. * Gets the user ID.
  3710. *
  3711. * @return int User ID
  3712. */
  3713. public function get_user_id()
  3714. {
  3715. if ($this->debug > 0) {
  3716. error_log('In learnpath::get_user_id()', 0);
  3717. }
  3718. if (!empty($this->user_id)) {
  3719. return $this->user_id;
  3720. } else {
  3721. return false;
  3722. }
  3723. }
  3724. /**
  3725. * Checks if any of the items has an audio element attached.
  3726. *
  3727. * @return bool True or false
  3728. */
  3729. public function has_audio()
  3730. {
  3731. if ($this->debug > 1) {
  3732. error_log('In learnpath::has_audio()', 0);
  3733. }
  3734. $has = false;
  3735. foreach ($this->items as $i => $item) {
  3736. if (!empty($this->items[$i]->audio)) {
  3737. $has = true;
  3738. break;
  3739. }
  3740. }
  3741. return $has;
  3742. }
  3743. /**
  3744. * Moves an item up and down at its level.
  3745. *
  3746. * @param int $id Item to move up and down
  3747. * @param string $direction Direction 'up' or 'down'
  3748. *
  3749. * @return bool|int
  3750. */
  3751. public function move_item($id, $direction)
  3752. {
  3753. $course_id = api_get_course_int_id();
  3754. if ($this->debug > 0) {
  3755. error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
  3756. }
  3757. if (empty($id) || empty($direction)) {
  3758. return false;
  3759. }
  3760. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  3761. $sql_sel = "SELECT *
  3762. FROM $tbl_lp_item
  3763. WHERE
  3764. iid = $id
  3765. ";
  3766. $res_sel = Database::query($sql_sel);
  3767. // Check if elem exists.
  3768. if (Database::num_rows($res_sel) < 1) {
  3769. return false;
  3770. }
  3771. // Gather data.
  3772. $row = Database::fetch_array($res_sel);
  3773. $previous = $row['previous_item_id'];
  3774. $next = $row['next_item_id'];
  3775. $display = $row['display_order'];
  3776. $parent = $row['parent_item_id'];
  3777. $lp = $row['lp_id'];
  3778. // Update the item (switch with previous/next one).
  3779. switch ($direction) {
  3780. case 'up':
  3781. if ($this->debug > 2) {
  3782. error_log('Movement up detected', 0);
  3783. }
  3784. if ($display > 1) {
  3785. $sql_sel2 = "SELECT * FROM $tbl_lp_item
  3786. WHERE iid = $previous";
  3787. if ($this->debug > 2) {
  3788. error_log('Selecting previous: '.$sql_sel2, 0);
  3789. }
  3790. $res_sel2 = Database::query($sql_sel2);
  3791. if (Database::num_rows($res_sel2) < 1) {
  3792. $previous_previous = 0;
  3793. }
  3794. // Gather data.
  3795. $row2 = Database::fetch_array($res_sel2);
  3796. $previous_previous = $row2['previous_item_id'];
  3797. // Update previous_previous item (switch "next" with current).
  3798. if ($previous_previous != 0) {
  3799. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3800. next_item_id = $id
  3801. WHERE iid = $previous_previous";
  3802. if ($this->debug > 2) {
  3803. error_log($sql_upd2, 0);
  3804. }
  3805. Database::query($sql_upd2);
  3806. }
  3807. // Update previous item (switch with current).
  3808. if ($previous != 0) {
  3809. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3810. next_item_id = $next,
  3811. previous_item_id = $id,
  3812. display_order = display_order +1
  3813. WHERE iid = $previous";
  3814. if ($this->debug > 2) {
  3815. error_log($sql_upd2, 0);
  3816. }
  3817. Database::query($sql_upd2);
  3818. }
  3819. // Update current item (switch with previous).
  3820. if ($id != 0) {
  3821. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3822. next_item_id = $previous,
  3823. previous_item_id = $previous_previous,
  3824. display_order = display_order-1
  3825. WHERE c_id = ".$course_id." AND id = $id";
  3826. if ($this->debug > 2) {
  3827. error_log($sql_upd2, 0);
  3828. }
  3829. Database::query($sql_upd2);
  3830. }
  3831. // Update next item (new previous item).
  3832. if (!empty($next)) {
  3833. $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
  3834. WHERE iid = $next";
  3835. if ($this->debug > 2) {
  3836. error_log($sql_upd2, 0);
  3837. }
  3838. Database::query($sql_upd2);
  3839. }
  3840. $display = $display - 1;
  3841. }
  3842. break;
  3843. case 'down':
  3844. if ($this->debug > 2) {
  3845. error_log('Movement down detected', 0);
  3846. }
  3847. if ($next != 0) {
  3848. $sql_sel2 = "SELECT * FROM $tbl_lp_item
  3849. WHERE iid = $next";
  3850. if ($this->debug > 2) {
  3851. error_log('Selecting next: '.$sql_sel2, 0);
  3852. }
  3853. $res_sel2 = Database::query($sql_sel2);
  3854. if (Database::num_rows($res_sel2) < 1) {
  3855. $next_next = 0;
  3856. }
  3857. // Gather data.
  3858. $row2 = Database::fetch_array($res_sel2);
  3859. $next_next = $row2['next_item_id'];
  3860. // Update previous item (switch with current).
  3861. if ($previous != 0) {
  3862. $sql_upd2 = "UPDATE $tbl_lp_item
  3863. SET next_item_id = $next
  3864. WHERE iid = $previous";
  3865. Database::query($sql_upd2);
  3866. }
  3867. // Update current item (switch with previous).
  3868. if ($id != 0) {
  3869. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3870. previous_item_id = $next,
  3871. next_item_id = $next_next,
  3872. display_order = display_order + 1
  3873. WHERE iid = $id";
  3874. Database::query($sql_upd2);
  3875. }
  3876. // Update next item (new previous item).
  3877. if ($next != 0) {
  3878. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3879. previous_item_id = $previous,
  3880. next_item_id = $id,
  3881. display_order = display_order-1
  3882. WHERE iid = $next";
  3883. Database::query($sql_upd2);
  3884. }
  3885. // Update next_next item (switch "previous" with current).
  3886. if ($next_next != 0) {
  3887. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3888. previous_item_id = $id
  3889. WHERE iid = $next_next";
  3890. Database::query($sql_upd2);
  3891. }
  3892. $display = $display + 1;
  3893. }
  3894. break;
  3895. default:
  3896. return false;
  3897. }
  3898. return $display;
  3899. }
  3900. /**
  3901. * Move a LP up (display_order).
  3902. *
  3903. * @param int $lp_id Learnpath ID
  3904. * @param int $categoryId Category ID
  3905. *
  3906. * @return bool
  3907. */
  3908. public static function move_up($lp_id, $categoryId = 0)
  3909. {
  3910. $courseId = api_get_course_int_id();
  3911. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  3912. $categoryCondition = '';
  3913. if (!empty($categoryId)) {
  3914. $categoryId = (int) $categoryId;
  3915. $categoryCondition = " AND category_id = $categoryId";
  3916. }
  3917. $sql = "SELECT * FROM $lp_table
  3918. WHERE c_id = $courseId
  3919. $categoryCondition
  3920. ORDER BY display_order";
  3921. $res = Database::query($sql);
  3922. if ($res === false) {
  3923. return false;
  3924. }
  3925. $lps = [];
  3926. $lp_order = [];
  3927. $num = Database::num_rows($res);
  3928. // First check the order is correct, globally (might be wrong because
  3929. // of versions < 1.8.4)
  3930. if ($num > 0) {
  3931. $i = 1;
  3932. while ($row = Database::fetch_array($res)) {
  3933. if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
  3934. $sql = "UPDATE $lp_table SET display_order = $i
  3935. WHERE iid = ".$row['iid'];
  3936. Database::query($sql);
  3937. }
  3938. $row['display_order'] = $i;
  3939. $lps[$row['iid']] = $row;
  3940. $lp_order[$i] = $row['iid'];
  3941. $i++;
  3942. }
  3943. }
  3944. if ($num > 1) { // If there's only one element, no need to sort.
  3945. $order = $lps[$lp_id]['display_order'];
  3946. if ($order > 1) { // If it's the first element, no need to move up.
  3947. $sql = "UPDATE $lp_table SET display_order = $order
  3948. WHERE iid = ".$lp_order[$order - 1];
  3949. Database::query($sql);
  3950. $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
  3951. WHERE iid = $lp_id";
  3952. Database::query($sql);
  3953. }
  3954. }
  3955. return true;
  3956. }
  3957. /**
  3958. * Move a learnpath down (display_order).
  3959. *
  3960. * @param int $lp_id Learnpath ID
  3961. * @param int $categoryId Category ID
  3962. *
  3963. * @return bool
  3964. */
  3965. public static function move_down($lp_id, $categoryId = 0)
  3966. {
  3967. $courseId = api_get_course_int_id();
  3968. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  3969. $categoryCondition = '';
  3970. if (!empty($categoryId)) {
  3971. $categoryId = (int) $categoryId;
  3972. $categoryCondition = " AND category_id = $categoryId";
  3973. }
  3974. $sql = "SELECT * FROM $lp_table
  3975. WHERE c_id = $courseId
  3976. $categoryCondition
  3977. ORDER BY display_order";
  3978. $res = Database::query($sql);
  3979. if ($res === false) {
  3980. return false;
  3981. }
  3982. $lps = [];
  3983. $lp_order = [];
  3984. $num = Database::num_rows($res);
  3985. $max = 0;
  3986. // First check the order is correct, globally (might be wrong because
  3987. // of versions < 1.8.4).
  3988. if ($num > 0) {
  3989. $i = 1;
  3990. while ($row = Database::fetch_array($res)) {
  3991. $max = $i;
  3992. if ($row['display_order'] != $i) {
  3993. // If we find a gap in the order, we need to fix it.
  3994. $sql = "UPDATE $lp_table SET display_order = $i
  3995. WHERE iid = ".$row['iid'];
  3996. Database::query($sql);
  3997. }
  3998. $row['display_order'] = $i;
  3999. $lps[$row['iid']] = $row;
  4000. $lp_order[$i] = $row['iid'];
  4001. $i++;
  4002. }
  4003. }
  4004. if ($num > 1) { // If there's only one element, no need to sort.
  4005. $order = $lps[$lp_id]['display_order'];
  4006. if ($order < $max) { // If it's the first element, no need to move up.
  4007. $sql = "UPDATE $lp_table SET display_order = $order
  4008. WHERE iid = ".$lp_order[$order + 1];
  4009. Database::query($sql);
  4010. $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
  4011. WHERE iid = $lp_id";
  4012. Database::query($sql);
  4013. }
  4014. }
  4015. return true;
  4016. }
  4017. /**
  4018. * Updates learnpath attributes to point to the next element
  4019. * The last part is similar to set_current_item but processing the other way around.
  4020. */
  4021. public function next()
  4022. {
  4023. if ($this->debug > 0) {
  4024. error_log('In learnpath::next()', 0);
  4025. }
  4026. $this->last = $this->get_current_item_id();
  4027. $this->items[$this->last]->save(
  4028. false,
  4029. $this->prerequisites_match($this->last)
  4030. );
  4031. $this->autocomplete_parents($this->last);
  4032. $new_index = $this->get_next_index();
  4033. if ($this->debug > 2) {
  4034. error_log('New index: '.$new_index, 0);
  4035. }
  4036. $this->index = $new_index;
  4037. if ($this->debug > 2) {
  4038. error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
  4039. }
  4040. $this->current = $this->ordered_items[$new_index];
  4041. if ($this->debug > 2) {
  4042. error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
  4043. }
  4044. }
  4045. /**
  4046. * Open a resource = initialise all local variables relative to this resource. Depending on the child
  4047. * class, this might be redefined to allow several behaviours depending on the document type.
  4048. *
  4049. * @param int $id Resource ID
  4050. */
  4051. public function open($id)
  4052. {
  4053. if ($this->debug > 0) {
  4054. error_log('In learnpath::open()', 0);
  4055. }
  4056. // TODO:
  4057. // set the current resource attribute to this resource
  4058. // switch on element type (redefine in child class?)
  4059. // set status for this item to "opened"
  4060. // start timer
  4061. // initialise score
  4062. $this->index = 0; //or = the last item seen (see $this->last)
  4063. }
  4064. /**
  4065. * Check that all prerequisites are fulfilled. Returns true and an
  4066. * empty string on success, returns false
  4067. * and the prerequisite string on error.
  4068. * This function is based on the rules for aicc_script language as
  4069. * described in the SCORM 1.2 CAM documentation page 108.
  4070. *
  4071. * @param int $itemId Optional item ID. If none given, uses the current open item.
  4072. *
  4073. * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
  4074. * string otherwise
  4075. */
  4076. public function prerequisites_match($itemId = null)
  4077. {
  4078. $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
  4079. if ($allow) {
  4080. if (api_is_allowed_to_edit() ||
  4081. api_is_platform_admin(true) ||
  4082. api_is_drh() ||
  4083. api_is_coach(api_get_session_id(), api_get_course_int_id())
  4084. ) {
  4085. return true;
  4086. }
  4087. }
  4088. $debug = $this->debug;
  4089. if ($debug > 0) {
  4090. error_log('In learnpath::prerequisites_match()', 0);
  4091. }
  4092. if (empty($itemId)) {
  4093. $itemId = $this->current;
  4094. }
  4095. $currentItem = $this->getItem($itemId);
  4096. if ($currentItem) {
  4097. if ($this->type == 2) {
  4098. // Getting prereq from scorm
  4099. $prereq_string = $this->get_scorm_prereq_string($itemId);
  4100. } else {
  4101. $prereq_string = $currentItem->get_prereq_string();
  4102. }
  4103. if (empty($prereq_string)) {
  4104. if ($debug > 0) {
  4105. error_log('Found prereq_string is empty return true');
  4106. }
  4107. return true;
  4108. }
  4109. // Clean spaces.
  4110. $prereq_string = str_replace(' ', '', $prereq_string);
  4111. if ($debug > 0) {
  4112. error_log('Found prereq_string: '.$prereq_string, 0);
  4113. }
  4114. // Now send to the parse_prereq() function that will check this component's prerequisites.
  4115. $result = $currentItem->parse_prereq(
  4116. $prereq_string,
  4117. $this->items,
  4118. $this->refs_list,
  4119. $this->get_user_id()
  4120. );
  4121. if ($result === false) {
  4122. $this->set_error_msg($currentItem->prereq_alert);
  4123. }
  4124. } else {
  4125. $result = true;
  4126. if ($debug > 1) {
  4127. error_log('$this->items['.$itemId.'] was not an object', 0);
  4128. }
  4129. }
  4130. if ($debug > 1) {
  4131. error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
  4132. }
  4133. return $result;
  4134. }
  4135. /**
  4136. * Updates learnpath attributes to point to the previous element
  4137. * The last part is similar to set_current_item but processing the other way around.
  4138. */
  4139. public function previous()
  4140. {
  4141. if ($this->debug > 0) {
  4142. error_log('In learnpath::previous()', 0);
  4143. }
  4144. $this->last = $this->get_current_item_id();
  4145. $this->items[$this->last]->save(
  4146. false,
  4147. $this->prerequisites_match($this->last)
  4148. );
  4149. $this->autocomplete_parents($this->last);
  4150. $new_index = $this->get_previous_index();
  4151. $this->index = $new_index;
  4152. $this->current = $this->ordered_items[$new_index];
  4153. }
  4154. /**
  4155. * Publishes a learnpath. This basically means show or hide the learnpath
  4156. * to normal users.
  4157. * Can be used as abstract.
  4158. *
  4159. * @param int $lp_id Learnpath ID
  4160. * @param int $set_visibility New visibility
  4161. *
  4162. * @return bool
  4163. */
  4164. public static function toggle_visibility($lp_id, $set_visibility = 1)
  4165. {
  4166. $action = 'visible';
  4167. if ($set_visibility != 1) {
  4168. $action = 'invisible';
  4169. self::toggle_publish($lp_id, 'i');
  4170. }
  4171. return api_item_property_update(
  4172. api_get_course_info(),
  4173. TOOL_LEARNPATH,
  4174. $lp_id,
  4175. $action,
  4176. api_get_user_id()
  4177. );
  4178. }
  4179. /**
  4180. * Publishes a learnpath category.
  4181. * This basically means show or hide the learnpath category to normal users.
  4182. *
  4183. * @param int $id
  4184. * @param int $visibility
  4185. *
  4186. * @throws \Doctrine\ORM\NonUniqueResultException
  4187. * @throws \Doctrine\ORM\ORMException
  4188. * @throws \Doctrine\ORM\OptimisticLockException
  4189. * @throws \Doctrine\ORM\TransactionRequiredException
  4190. *
  4191. * @return bool
  4192. */
  4193. public static function toggleCategoryVisibility($id, $visibility = 1)
  4194. {
  4195. $action = 'visible';
  4196. if ($visibility != 1) {
  4197. $action = 'invisible';
  4198. $list = new LearnpathList(
  4199. api_get_user_id(),
  4200. null,
  4201. null,
  4202. null,
  4203. false,
  4204. $id
  4205. );
  4206. $lpList = $list->get_flat_list();
  4207. foreach ($lpList as $lp) {
  4208. self::toggle_visibility($lp['iid'], 0);
  4209. }
  4210. self::toggleCategoryPublish($id, 0);
  4211. }
  4212. return api_item_property_update(
  4213. api_get_course_info(),
  4214. TOOL_LEARNPATH_CATEGORY,
  4215. $id,
  4216. $action,
  4217. api_get_user_id()
  4218. );
  4219. }
  4220. /**
  4221. * Publishes a learnpath. This basically means show or hide the learnpath
  4222. * on the course homepage
  4223. * Can be used as abstract.
  4224. *
  4225. * @param int $lp_id Learnpath id
  4226. * @param string $set_visibility New visibility (v/i - visible/invisible)
  4227. *
  4228. * @return bool
  4229. */
  4230. public static function toggle_publish($lp_id, $set_visibility = 'v')
  4231. {
  4232. $course_id = api_get_course_int_id();
  4233. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  4234. $lp_id = (int) $lp_id;
  4235. $sql = "SELECT * FROM $tbl_lp
  4236. WHERE iid = $lp_id";
  4237. $result = Database::query($sql);
  4238. if (Database::num_rows($result)) {
  4239. $row = Database::fetch_array($result);
  4240. $name = Database::escape_string($row['name']);
  4241. if ($set_visibility == 'i') {
  4242. $v = 0;
  4243. }
  4244. if ($set_visibility == 'v') {
  4245. $v = 1;
  4246. }
  4247. $session_id = api_get_session_id();
  4248. $session_condition = api_get_session_condition($session_id);
  4249. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  4250. $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
  4251. $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
  4252. $sql = "SELECT * FROM $tbl_tool
  4253. WHERE
  4254. c_id = $course_id AND
  4255. (link = '$link' OR link = '$oldLink') AND
  4256. image = 'scormbuilder.gif' AND
  4257. (
  4258. link LIKE '$link%' OR
  4259. link LIKE '$oldLink%'
  4260. )
  4261. $session_condition
  4262. ";
  4263. $result = Database::query($sql);
  4264. $num = Database::num_rows($result);
  4265. if ($set_visibility == 'i' && $num > 0) {
  4266. $sql = "DELETE FROM $tbl_tool
  4267. WHERE
  4268. c_id = $course_id AND
  4269. (link = '$link' OR link = '$oldLink') AND
  4270. image='scormbuilder.gif'
  4271. $session_condition";
  4272. Database::query($sql);
  4273. } elseif ($set_visibility == 'v' && $num == 0) {
  4274. $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
  4275. ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
  4276. Database::query($sql);
  4277. $insertId = Database::insert_id();
  4278. if ($insertId) {
  4279. $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
  4280. Database::query($sql);
  4281. }
  4282. } elseif ($set_visibility == 'v' && $num > 0) {
  4283. $sql = "UPDATE $tbl_tool SET
  4284. c_id = $course_id,
  4285. name = '$name',
  4286. link = '$link',
  4287. image = 'scormbuilder.gif',
  4288. visibility = '$v',
  4289. admin = '0',
  4290. address = 'pastillegris.gif',
  4291. added_tool = 0,
  4292. session_id = $session_id
  4293. WHERE
  4294. c_id = ".$course_id." AND
  4295. (link = '$link' OR link = '$oldLink') AND
  4296. image='scormbuilder.gif'
  4297. $session_condition
  4298. ";
  4299. Database::query($sql);
  4300. } else {
  4301. // Parameter and database incompatible, do nothing, exit.
  4302. return false;
  4303. }
  4304. } else {
  4305. return false;
  4306. }
  4307. }
  4308. /**
  4309. * Publishes a learnpath.
  4310. * Show or hide the learnpath category on the course homepage.
  4311. *
  4312. * @param int $id
  4313. * @param int $setVisibility
  4314. *
  4315. * @throws \Doctrine\ORM\NonUniqueResultException
  4316. * @throws \Doctrine\ORM\ORMException
  4317. * @throws \Doctrine\ORM\OptimisticLockException
  4318. * @throws \Doctrine\ORM\TransactionRequiredException
  4319. *
  4320. * @return bool
  4321. */
  4322. public static function toggleCategoryPublish($id, $setVisibility = 1)
  4323. {
  4324. $courseId = api_get_course_int_id();
  4325. $sessionId = api_get_session_id();
  4326. $sessionCondition = api_get_session_condition(
  4327. $sessionId,
  4328. true,
  4329. false,
  4330. 't.sessionId'
  4331. );
  4332. $em = Database::getManager();
  4333. /** @var CLpCategory $category */
  4334. $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  4335. if (!$category) {
  4336. return false;
  4337. }
  4338. $link = self::getCategoryLinkForTool($id);
  4339. /** @var CTool $tool */
  4340. $tool = $em->createQuery("
  4341. SELECT t FROM ChamiloCourseBundle:CTool t
  4342. WHERE
  4343. t.cId = :course AND
  4344. t.link = :link1 AND
  4345. t.image = 'lp_category.gif' AND
  4346. t.link LIKE :link2
  4347. $sessionCondition
  4348. ")
  4349. ->setParameters([
  4350. 'course' => (int) $courseId,
  4351. 'link1' => $link,
  4352. 'link2' => "$link%",
  4353. ])
  4354. ->getOneOrNullResult();
  4355. if ($setVisibility == 0 && $tool) {
  4356. $em->remove($tool);
  4357. $em->flush();
  4358. return true;
  4359. }
  4360. if ($setVisibility == 1 && !$tool) {
  4361. $tool = new CTool();
  4362. $tool
  4363. ->setCategory('authoring')
  4364. ->setCId($courseId)
  4365. ->setName(strip_tags($category->getName()))
  4366. ->setLink($link)
  4367. ->setImage('lp_category.gif')
  4368. ->setVisibility(1)
  4369. ->setAdmin(0)
  4370. ->setAddress('pastillegris.gif')
  4371. ->setAddedTool(0)
  4372. ->setSessionId($sessionId)
  4373. ->setTarget('_self');
  4374. $em->persist($tool);
  4375. $em->flush();
  4376. $tool->setId($tool->getIid());
  4377. $em->persist($tool);
  4378. $em->flush();
  4379. return true;
  4380. }
  4381. if ($setVisibility == 1 && $tool) {
  4382. $tool
  4383. ->setName(strip_tags($category->getName()))
  4384. ->setVisibility(1);
  4385. $em->persist($tool);
  4386. $em->flush();
  4387. return true;
  4388. }
  4389. return false;
  4390. }
  4391. /**
  4392. * Check if the learnpath category is visible for a user.
  4393. *
  4394. * @param CLpCategory $category
  4395. * @param User $user
  4396. *
  4397. * @return bool
  4398. */
  4399. public static function categoryIsVisibleForStudent(
  4400. CLpCategory $category,
  4401. User $user
  4402. ) {
  4403. $subscriptionSettings = self::getSubscriptionSettings();
  4404. if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
  4405. return true;
  4406. }
  4407. $isAllowedToEdit = api_is_allowed_to_edit(null, true);
  4408. if ($isAllowedToEdit) {
  4409. return true;
  4410. }
  4411. if (empty($category)) {
  4412. return false;
  4413. }
  4414. $users = $category->getUsers();
  4415. if (empty($users) || !$users->count()) {
  4416. return true;
  4417. }
  4418. if ($category->hasUserAdded($user)) {
  4419. return true;
  4420. }
  4421. $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
  4422. if (!empty($groups)) {
  4423. $em = Database::getManager();
  4424. /** @var ItemPropertyRepository $itemRepo */
  4425. $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
  4426. /** @var CourseRepository $courseRepo */
  4427. $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
  4428. $sessionId = api_get_session_id();
  4429. $session = null;
  4430. if (!empty($sessionId)) {
  4431. $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
  4432. }
  4433. $course = $courseRepo->find(api_get_course_int_id());
  4434. // Subscribed groups to a LP
  4435. $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
  4436. TOOL_LEARNPATH_CATEGORY,
  4437. $category->getId(),
  4438. $course,
  4439. $session
  4440. );
  4441. if (!empty($subscribedGroupsInLp)) {
  4442. $groups = array_column($groups, 'iid');
  4443. /** @var CItemProperty $item */
  4444. foreach ($subscribedGroupsInLp as $item) {
  4445. if ($item->getGroup() &&
  4446. in_array($item->getGroup()->getId(), $groups)
  4447. ) {
  4448. return true;
  4449. }
  4450. }
  4451. }
  4452. }
  4453. return false;
  4454. }
  4455. /**
  4456. * Check if a learnpath category is published as course tool.
  4457. *
  4458. * @param CLpCategory $category
  4459. * @param int $courseId
  4460. *
  4461. * @return bool
  4462. */
  4463. public static function categoryIsPublished(
  4464. CLpCategory $category,
  4465. $courseId
  4466. ) {
  4467. $link = self::getCategoryLinkForTool($category->getId());
  4468. $em = Database::getManager();
  4469. $tools = $em
  4470. ->createQuery("
  4471. SELECT t FROM ChamiloCourseBundle:CTool t
  4472. WHERE t.cId = :course AND
  4473. t.name = :name AND
  4474. t.image = 'lp_category.gif' AND
  4475. t.link LIKE :link
  4476. ")
  4477. ->setParameters([
  4478. 'course' => $courseId,
  4479. 'name' => strip_tags($category->getName()),
  4480. 'link' => "$link%",
  4481. ])
  4482. ->getResult();
  4483. /** @var CTool $tool */
  4484. $tool = current($tools);
  4485. return $tool ? $tool->getVisibility() : false;
  4486. }
  4487. /**
  4488. * Restart the whole learnpath. Return the URL of the first element.
  4489. * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
  4490. * To use a similar method statically, use the create_new_attempt() method.
  4491. *
  4492. * @return bool
  4493. */
  4494. public function restart()
  4495. {
  4496. if ($this->debug > 0) {
  4497. error_log('In learnpath::restart()', 0);
  4498. }
  4499. // TODO
  4500. // Call autosave method to save the current progress.
  4501. //$this->index = 0;
  4502. if (api_is_invitee()) {
  4503. return false;
  4504. }
  4505. $session_id = api_get_session_id();
  4506. $course_id = api_get_course_int_id();
  4507. $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
  4508. $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
  4509. VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
  4510. if ($this->debug > 2) {
  4511. error_log('Inserting new lp_view for restart: '.$sql, 0);
  4512. }
  4513. Database::query($sql);
  4514. $view_id = Database::insert_id();
  4515. if ($view_id) {
  4516. $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
  4517. Database::query($sql);
  4518. $this->lp_view_id = $view_id;
  4519. $this->attempt = $this->attempt + 1;
  4520. } else {
  4521. $this->error = 'Could not insert into item_view table...';
  4522. return false;
  4523. }
  4524. $this->autocomplete_parents($this->current);
  4525. foreach ($this->items as $index => $dummy) {
  4526. $this->items[$index]->restart();
  4527. $this->items[$index]->set_lp_view($this->lp_view_id);
  4528. }
  4529. $this->first();
  4530. return true;
  4531. }
  4532. /**
  4533. * Saves the current item.
  4534. *
  4535. * @return bool
  4536. */
  4537. public function save_current()
  4538. {
  4539. $debug = $this->debug;
  4540. // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
  4541. // on $ordered_items[] but not sure it's always safe to use with $items[]).
  4542. if ($debug) {
  4543. error_log('save_current() saving item '.$this->current, 0);
  4544. error_log(''.print_r($this->items, true), 0);
  4545. }
  4546. if (isset($this->items[$this->current]) &&
  4547. is_object($this->items[$this->current])
  4548. ) {
  4549. if ($debug) {
  4550. error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
  4551. }
  4552. $res = $this->items[$this->current]->save(
  4553. false,
  4554. $this->prerequisites_match($this->current)
  4555. );
  4556. $this->autocomplete_parents($this->current);
  4557. $status = $this->items[$this->current]->get_status();
  4558. $this->update_queue[$this->current] = $status;
  4559. if ($debug) {
  4560. error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
  4561. }
  4562. return $res;
  4563. }
  4564. return false;
  4565. }
  4566. /**
  4567. * Saves the given item.
  4568. *
  4569. * @param int $item_id Optional (will take from $_REQUEST if null)
  4570. * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
  4571. *
  4572. * @return bool
  4573. */
  4574. public function save_item($item_id = null, $from_outside = true)
  4575. {
  4576. $debug = $this->debug;
  4577. if ($debug) {
  4578. error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
  4579. }
  4580. // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
  4581. // on $ordered_items[] but not sure it's always safe to use with $items[]).
  4582. if (empty($item_id)) {
  4583. $item_id = (int) $_REQUEST['id'];
  4584. }
  4585. if (empty($item_id)) {
  4586. $item_id = $this->get_current_item_id();
  4587. }
  4588. if (isset($this->items[$item_id]) &&
  4589. is_object($this->items[$item_id])
  4590. ) {
  4591. if ($debug) {
  4592. error_log('Object exists');
  4593. }
  4594. // Saving the item.
  4595. $res = $this->items[$item_id]->save(
  4596. $from_outside,
  4597. $this->prerequisites_match($item_id)
  4598. );
  4599. if ($debug) {
  4600. error_log('update_queue before:');
  4601. error_log(print_r($this->update_queue, 1));
  4602. }
  4603. $this->autocomplete_parents($item_id);
  4604. $status = $this->items[$item_id]->get_status();
  4605. $this->update_queue[$item_id] = $status;
  4606. if ($debug) {
  4607. error_log('get_status(): '.$status);
  4608. error_log('update_queue after:');
  4609. error_log(print_r($this->update_queue, 1));
  4610. }
  4611. return $res;
  4612. }
  4613. return false;
  4614. }
  4615. /**
  4616. * Saves the last item seen's ID only in case.
  4617. */
  4618. public function save_last()
  4619. {
  4620. $course_id = api_get_course_int_id();
  4621. $debug = $this->debug;
  4622. if ($debug) {
  4623. error_log('In learnpath::save_last()', 0);
  4624. }
  4625. $session_condition = api_get_session_condition(
  4626. api_get_session_id(),
  4627. true,
  4628. false
  4629. );
  4630. $table = Database::get_course_table(TABLE_LP_VIEW);
  4631. if (isset($this->current) && !api_is_invitee()) {
  4632. if ($debug) {
  4633. error_log('Saving current item ('.$this->current.') for later review', 0);
  4634. }
  4635. $sql = "UPDATE $table SET
  4636. last_item = ".intval($this->get_current_item_id())."
  4637. WHERE
  4638. c_id = $course_id AND
  4639. lp_id = ".$this->get_id()." AND
  4640. user_id = ".$this->get_user_id()." ".$session_condition;
  4641. if ($debug) {
  4642. error_log('Saving last item seen : '.$sql, 0);
  4643. }
  4644. Database::query($sql);
  4645. }
  4646. if (!api_is_invitee()) {
  4647. // Save progress.
  4648. list($progress) = $this->get_progress_bar_text('%');
  4649. if ($progress >= 0 && $progress <= 100) {
  4650. $progress = (int) $progress;
  4651. $sql = "UPDATE $table SET
  4652. progress = $progress
  4653. WHERE
  4654. c_id = $course_id AND
  4655. lp_id = ".$this->get_id()." AND
  4656. user_id = ".$this->get_user_id()." ".$session_condition;
  4657. // Ignore errors as some tables might not have the progress field just yet.
  4658. Database::query($sql);
  4659. if ($debug) {
  4660. error_log($sql);
  4661. }
  4662. $this->progress_db = $progress;
  4663. }
  4664. }
  4665. }
  4666. /**
  4667. * Sets the current item ID (checks if valid and authorized first).
  4668. *
  4669. * @param int $item_id New item ID. If not given or not authorized, defaults to current
  4670. */
  4671. public function set_current_item($item_id = null)
  4672. {
  4673. $debug = $this->debug;
  4674. if ($debug) {
  4675. error_log('In learnpath::set_current_item('.$item_id.')', 0);
  4676. }
  4677. if (empty($item_id)) {
  4678. if ($debug) {
  4679. error_log('No new current item given, ignore...', 0);
  4680. }
  4681. // Do nothing.
  4682. } else {
  4683. if ($debug) {
  4684. error_log('New current item given is '.$item_id.'...', 0);
  4685. }
  4686. if (is_numeric($item_id)) {
  4687. $item_id = (int) $item_id;
  4688. // TODO: Check in database here.
  4689. $this->last = $this->current;
  4690. $this->current = $item_id;
  4691. // TODO: Update $this->index as well.
  4692. foreach ($this->ordered_items as $index => $item) {
  4693. if ($item == $this->current) {
  4694. $this->index = $index;
  4695. break;
  4696. }
  4697. }
  4698. if ($debug) {
  4699. error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
  4700. }
  4701. } else {
  4702. if ($debug) {
  4703. error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
  4704. }
  4705. }
  4706. }
  4707. }
  4708. /**
  4709. * Sets the encoding.
  4710. *
  4711. * @param string $enc New encoding
  4712. *
  4713. * @return bool
  4714. *
  4715. * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
  4716. */
  4717. public function set_encoding($enc = 'UTF-8')
  4718. {
  4719. if ($this->debug > 0) {
  4720. error_log('In learnpath::set_encoding()', 0);
  4721. }
  4722. $enc = api_refine_encoding_id($enc);
  4723. if (empty($enc)) {
  4724. $enc = api_get_system_encoding();
  4725. }
  4726. if (api_is_encoding_supported($enc)) {
  4727. $lp = $this->get_id();
  4728. if ($lp != 0) {
  4729. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  4730. $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
  4731. WHERE iid = ".$lp;
  4732. $res = Database::query($sql);
  4733. return $res;
  4734. }
  4735. }
  4736. return false;
  4737. }
  4738. /**
  4739. * Sets the JS lib setting in the database directly.
  4740. * This is the JavaScript library file this lp needs to load on startup.
  4741. *
  4742. * @param string $lib Proximity setting
  4743. *
  4744. * @return bool True on update success. False otherwise.
  4745. */
  4746. public function set_jslib($lib = '')
  4747. {
  4748. if ($this->debug > 0) {
  4749. error_log('In learnpath::set_jslib()', 0);
  4750. }
  4751. $lp = $this->get_id();
  4752. if ($lp != 0) {
  4753. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  4754. $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
  4755. WHERE iid = $lp";
  4756. $res = Database::query($sql);
  4757. return $res;
  4758. } else {
  4759. return false;
  4760. }
  4761. }
  4762. /**
  4763. * Sets the name of the LP maker (publisher) (and save).
  4764. *
  4765. * @param string $name Optional string giving the new content_maker of this learnpath
  4766. *
  4767. * @return bool True
  4768. */
  4769. public function set_maker($name = '')
  4770. {
  4771. if ($this->debug > 0) {
  4772. error_log('In learnpath::set_maker()', 0);
  4773. }
  4774. if (empty($name)) {
  4775. return false;
  4776. }
  4777. $this->maker = $name;
  4778. $table = Database::get_course_table(TABLE_LP_MAIN);
  4779. $lp_id = $this->get_id();
  4780. $sql = "UPDATE $table SET
  4781. content_maker = '".Database::escape_string($this->maker)."'
  4782. WHERE iid = $lp_id";
  4783. if ($this->debug > 2) {
  4784. error_log('lp updated with new content_maker : '.$this->maker, 0);
  4785. }
  4786. Database::query($sql);
  4787. return true;
  4788. }
  4789. /**
  4790. * Sets the name of the current learnpath (and save).
  4791. *
  4792. * @param string $name Optional string giving the new name of this learnpath
  4793. *
  4794. * @return bool True/False
  4795. */
  4796. public function set_name($name = null)
  4797. {
  4798. if ($this->debug > 0) {
  4799. error_log('In learnpath::set_name()', 0);
  4800. }
  4801. if (empty($name)) {
  4802. return false;
  4803. }
  4804. $this->name = Database::escape_string($name);
  4805. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  4806. $lp_id = $this->get_id();
  4807. $course_id = $this->course_info['real_id'];
  4808. $sql = "UPDATE $lp_table SET
  4809. name = '".Database::escape_string($this->name)."'
  4810. WHERE iid = $lp_id";
  4811. if ($this->debug > 2) {
  4812. error_log('lp updated with new name : '.$this->name, 0);
  4813. }
  4814. $result = Database::query($sql);
  4815. // If the lp is visible on the homepage, change his name there.
  4816. if (Database::affected_rows($result)) {
  4817. $session_id = api_get_session_id();
  4818. $session_condition = api_get_session_condition($session_id);
  4819. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  4820. $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
  4821. $sql = "UPDATE $tbl_tool SET name = '$this->name'
  4822. WHERE
  4823. c_id = $course_id AND
  4824. (link='$link' AND image='scormbuilder.gif' $session_condition)";
  4825. Database::query($sql);
  4826. return true;
  4827. } else {
  4828. return false;
  4829. }
  4830. }
  4831. /**
  4832. * Set index specified prefix terms for all items in this path.
  4833. *
  4834. * @param string $terms_string Comma-separated list of terms
  4835. * @param string $prefix Xapian term prefix
  4836. *
  4837. * @return bool False on error, true otherwise
  4838. */
  4839. public function set_terms_by_prefix($terms_string, $prefix)
  4840. {
  4841. $course_id = api_get_course_int_id();
  4842. if (api_get_setting('search_enabled') !== 'true') {
  4843. return false;
  4844. }
  4845. if (!extension_loaded('xapian')) {
  4846. return false;
  4847. }
  4848. $terms_string = trim($terms_string);
  4849. $terms = explode(',', $terms_string);
  4850. array_walk($terms, 'trim_value');
  4851. $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
  4852. // Don't do anything if no change, verify only at DB, not the search engine.
  4853. if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
  4854. return false;
  4855. }
  4856. require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
  4857. require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
  4858. $items_table = Database::get_course_table(TABLE_LP_ITEM);
  4859. // TODO: Make query secure agains XSS : use member attr instead of post var.
  4860. $lp_id = (int) $_POST['lp_id'];
  4861. $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
  4862. $result = Database::query($sql);
  4863. $di = new ChamiloIndexer();
  4864. while ($lp_item = Database::fetch_array($result)) {
  4865. // Get search_did.
  4866. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  4867. $sql = 'SELECT * FROM %s
  4868. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
  4869. LIMIT 1';
  4870. $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
  4871. //echo $sql; echo '<br>';
  4872. $res = Database::query($sql);
  4873. if (Database::num_rows($res) > 0) {
  4874. $se_ref = Database::fetch_array($res);
  4875. // Compare terms.
  4876. $doc = $di->get_document($se_ref['search_did']);
  4877. $xapian_terms = xapian_get_doc_terms($doc, $prefix);
  4878. $xterms = [];
  4879. foreach ($xapian_terms as $xapian_term) {
  4880. $xterms[] = substr($xapian_term['name'], 1);
  4881. }
  4882. $dterms = $terms;
  4883. $missing_terms = array_diff($dterms, $xterms);
  4884. $deprecated_terms = array_diff($xterms, $dterms);
  4885. // Save it to search engine.
  4886. foreach ($missing_terms as $term) {
  4887. $doc->add_term($prefix.$term, 1);
  4888. }
  4889. foreach ($deprecated_terms as $term) {
  4890. $doc->remove_term($prefix.$term);
  4891. }
  4892. $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
  4893. $di->getDb()->flush();
  4894. }
  4895. }
  4896. return true;
  4897. }
  4898. /**
  4899. * Sets the theme of the LP (local/remote) (and save).
  4900. *
  4901. * @param string $name Optional string giving the new theme of this learnpath
  4902. *
  4903. * @return bool Returns true if theme name is not empty
  4904. */
  4905. public function set_theme($name = '')
  4906. {
  4907. if ($this->debug > 0) {
  4908. error_log('In learnpath::set_theme()', 0);
  4909. }
  4910. $this->theme = $name;
  4911. $table = Database::get_course_table(TABLE_LP_MAIN);
  4912. $lp_id = $this->get_id();
  4913. $sql = "UPDATE $table
  4914. SET theme = '".Database::escape_string($this->theme)."'
  4915. WHERE iid = $lp_id";
  4916. if ($this->debug > 2) {
  4917. error_log('lp updated with new theme : '.$this->theme, 0);
  4918. }
  4919. Database::query($sql);
  4920. return true;
  4921. }
  4922. /**
  4923. * Sets the image of an LP (and save).
  4924. *
  4925. * @param string $name Optional string giving the new image of this learnpath
  4926. *
  4927. * @return bool Returns true if theme name is not empty
  4928. */
  4929. public function set_preview_image($name = '')
  4930. {
  4931. if ($this->debug > 0) {
  4932. error_log('In learnpath::set_preview_image()', 0);
  4933. }
  4934. $this->preview_image = $name;
  4935. $table = Database::get_course_table(TABLE_LP_MAIN);
  4936. $lp_id = $this->get_id();
  4937. $sql = "UPDATE $table SET
  4938. preview_image = '".Database::escape_string($this->preview_image)."'
  4939. WHERE iid = $lp_id";
  4940. if ($this->debug > 2) {
  4941. error_log('lp updated with new preview image : '.$this->preview_image, 0);
  4942. }
  4943. Database::query($sql);
  4944. return true;
  4945. }
  4946. /**
  4947. * Sets the author of a LP (and save).
  4948. *
  4949. * @param string $name Optional string giving the new author of this learnpath
  4950. *
  4951. * @return bool Returns true if author's name is not empty
  4952. */
  4953. public function set_author($name = '')
  4954. {
  4955. if ($this->debug > 0) {
  4956. error_log('In learnpath::set_author()', 0);
  4957. }
  4958. $this->author = $name;
  4959. $table = Database::get_course_table(TABLE_LP_MAIN);
  4960. $lp_id = $this->get_id();
  4961. $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
  4962. WHERE iid = $lp_id";
  4963. if ($this->debug > 2) {
  4964. error_log('lp updated with new preview author : '.$this->author, 0);
  4965. }
  4966. Database::query($sql);
  4967. return true;
  4968. }
  4969. /**
  4970. * Sets the hide_toc_frame parameter of a LP (and save).
  4971. *
  4972. * @param int $hide 1 if frame is hidden 0 then else
  4973. *
  4974. * @return bool Returns true if author's name is not empty
  4975. */
  4976. public function set_hide_toc_frame($hide)
  4977. {
  4978. if ($this->debug > 0) {
  4979. error_log('In learnpath::set_hide_toc_frame()', 0);
  4980. }
  4981. if (intval($hide) == $hide) {
  4982. $this->hide_toc_frame = $hide;
  4983. $table = Database::get_course_table(TABLE_LP_MAIN);
  4984. $lp_id = $this->get_id();
  4985. $sql = "UPDATE $table SET
  4986. hide_toc_frame = '".(int) $this->hide_toc_frame."'
  4987. WHERE iid = $lp_id";
  4988. if ($this->debug > 2) {
  4989. error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
  4990. }
  4991. Database::query($sql);
  4992. return true;
  4993. } else {
  4994. return false;
  4995. }
  4996. }
  4997. /**
  4998. * Sets the prerequisite of a LP (and save).
  4999. *
  5000. * @param int $prerequisite integer giving the new prerequisite of this learnpath
  5001. *
  5002. * @return bool returns true if prerequisite is not empty
  5003. */
  5004. public function set_prerequisite($prerequisite)
  5005. {
  5006. if ($this->debug > 0) {
  5007. error_log('In learnpath::set_prerequisite()', 0);
  5008. }
  5009. $this->prerequisite = (int) $prerequisite;
  5010. $table = Database::get_course_table(TABLE_LP_MAIN);
  5011. $lp_id = $this->get_id();
  5012. $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
  5013. WHERE iid = $lp_id";
  5014. if ($this->debug > 2) {
  5015. error_log('lp updated with new preview requisite : '.$this->requisite, 0);
  5016. }
  5017. Database::query($sql);
  5018. return true;
  5019. }
  5020. /**
  5021. * Sets the location/proximity of the LP (local/remote) (and save).
  5022. *
  5023. * @param string $name Optional string giving the new location of this learnpath
  5024. *
  5025. * @return bool True on success / False on error
  5026. */
  5027. public function set_proximity($name = '')
  5028. {
  5029. if ($this->debug > 0) {
  5030. error_log('In learnpath::set_proximity()', 0);
  5031. }
  5032. if (empty($name)) {
  5033. return false;
  5034. }
  5035. $this->proximity = $name;
  5036. $table = Database::get_course_table(TABLE_LP_MAIN);
  5037. $lp_id = $this->get_id();
  5038. $sql = "UPDATE $table SET
  5039. content_local = '".Database::escape_string($name)."'
  5040. WHERE iid = $lp_id";
  5041. if ($this->debug > 2) {
  5042. error_log('lp updated with new proximity : '.$this->proximity, 0);
  5043. }
  5044. Database::query($sql);
  5045. return true;
  5046. }
  5047. /**
  5048. * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
  5049. *
  5050. * @param int $id DB ID of the item
  5051. */
  5052. public function set_previous_item($id)
  5053. {
  5054. if ($this->debug > 0) {
  5055. error_log('In learnpath::set_previous_item()', 0);
  5056. }
  5057. $this->last = $id;
  5058. }
  5059. /**
  5060. * Sets use_max_score.
  5061. *
  5062. * @param int $use_max_score Optional string giving the new location of this learnpath
  5063. *
  5064. * @return bool True on success / False on error
  5065. */
  5066. public function set_use_max_score($use_max_score = 1)
  5067. {
  5068. if ($this->debug > 0) {
  5069. error_log('In learnpath::set_use_max_score()', 0);
  5070. }
  5071. $use_max_score = (int) $use_max_score;
  5072. $this->use_max_score = $use_max_score;
  5073. $table = Database::get_course_table(TABLE_LP_MAIN);
  5074. $lp_id = $this->get_id();
  5075. $sql = "UPDATE $table SET
  5076. use_max_score = '".$this->use_max_score."'
  5077. WHERE iid = $lp_id";
  5078. if ($this->debug > 2) {
  5079. error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
  5080. }
  5081. Database::query($sql);
  5082. return true;
  5083. }
  5084. /**
  5085. * Sets and saves the expired_on date.
  5086. *
  5087. * @param string $expired_on Optional string giving the new author of this learnpath
  5088. *
  5089. * @throws \Doctrine\ORM\OptimisticLockException
  5090. *
  5091. * @return bool Returns true if author's name is not empty
  5092. */
  5093. public function set_expired_on($expired_on)
  5094. {
  5095. if ($this->debug > 0) {
  5096. error_log('In learnpath::set_expired_on()', 0);
  5097. }
  5098. $em = Database::getManager();
  5099. /** @var CLp $lp */
  5100. $lp = $em
  5101. ->getRepository('ChamiloCourseBundle:CLp')
  5102. ->findOneBy(
  5103. [
  5104. 'iid' => $this->get_id(),
  5105. ]
  5106. );
  5107. if (!$lp) {
  5108. return false;
  5109. }
  5110. $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
  5111. $lp->setExpiredOn($this->expired_on);
  5112. $em->persist($lp);
  5113. $em->flush();
  5114. if ($this->debug > 2) {
  5115. error_log('lp updated with new expired_on : '.$this->expired_on, 0);
  5116. }
  5117. return true;
  5118. }
  5119. /**
  5120. * Sets and saves the publicated_on date.
  5121. *
  5122. * @param string $publicated_on Optional string giving the new author of this learnpath
  5123. *
  5124. * @throws \Doctrine\ORM\OptimisticLockException
  5125. *
  5126. * @return bool Returns true if author's name is not empty
  5127. */
  5128. public function set_publicated_on($publicated_on)
  5129. {
  5130. if ($this->debug > 0) {
  5131. error_log('In learnpath::set_expired_on()', 0);
  5132. }
  5133. $em = Database::getManager();
  5134. /** @var CLp $lp */
  5135. $lp = $em
  5136. ->getRepository('ChamiloCourseBundle:CLp')
  5137. ->findOneBy(
  5138. [
  5139. 'iid' => $this->get_id(),
  5140. ]
  5141. );
  5142. if (!$lp) {
  5143. return false;
  5144. }
  5145. $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
  5146. $lp->setPublicatedOn($this->publicated_on);
  5147. $em->persist($lp);
  5148. $em->flush();
  5149. if ($this->debug > 2) {
  5150. error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
  5151. }
  5152. return true;
  5153. }
  5154. /**
  5155. * Sets and saves the expired_on date.
  5156. *
  5157. * @return bool Returns true if author's name is not empty
  5158. */
  5159. public function set_modified_on()
  5160. {
  5161. if ($this->debug > 0) {
  5162. error_log('In learnpath::set_expired_on()', 0);
  5163. }
  5164. $this->modified_on = api_get_utc_datetime();
  5165. $table = Database::get_course_table(TABLE_LP_MAIN);
  5166. $lp_id = $this->get_id();
  5167. $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
  5168. WHERE iid = $lp_id";
  5169. if ($this->debug > 2) {
  5170. error_log('lp updated with new expired_on : '.$this->modified_on, 0);
  5171. }
  5172. Database::query($sql);
  5173. return true;
  5174. }
  5175. /**
  5176. * Sets the object's error message.
  5177. *
  5178. * @param string $error Error message. If empty, reinits the error string
  5179. */
  5180. public function set_error_msg($error = '')
  5181. {
  5182. if ($this->debug > 0) {
  5183. error_log('In learnpath::set_error_msg()', 0);
  5184. }
  5185. if (empty($error)) {
  5186. $this->error = '';
  5187. } else {
  5188. $this->error .= $error;
  5189. }
  5190. }
  5191. /**
  5192. * Launches the current item if not 'sco'
  5193. * (starts timer and make sure there is a record ready in the DB).
  5194. *
  5195. * @param bool $allow_new_attempt Whether to allow a new attempt or not
  5196. *
  5197. * @return bool
  5198. */
  5199. public function start_current_item($allow_new_attempt = false)
  5200. {
  5201. $debug = $this->debug;
  5202. if ($debug) {
  5203. error_log('In learnpath::start_current_item()');
  5204. error_log('current: '.$this->current);
  5205. }
  5206. if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
  5207. $type = $this->get_type();
  5208. $item_type = $this->items[$this->current]->get_type();
  5209. if (($type == 2 && $item_type != 'sco') ||
  5210. ($type == 3 && $item_type != 'au') ||
  5211. ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
  5212. ) {
  5213. if ($debug) {
  5214. error_log('item type: '.$item_type);
  5215. error_log('lp type: '.$type);
  5216. }
  5217. $this->items[$this->current]->open($allow_new_attempt);
  5218. $this->autocomplete_parents($this->current);
  5219. $prereq_check = $this->prerequisites_match($this->current);
  5220. if ($debug) {
  5221. error_log('start_current_item will save item with prereq: '.$prereq_check);
  5222. }
  5223. $this->items[$this->current]->save(false, $prereq_check);
  5224. }
  5225. // If sco, then it is supposed to have been updated by some other call.
  5226. if ($item_type == 'sco') {
  5227. $this->items[$this->current]->restart();
  5228. }
  5229. }
  5230. if ($debug) {
  5231. error_log('lp_view_session_id');
  5232. error_log($this->lp_view_session_id);
  5233. error_log('api session id');
  5234. error_log(api_get_session_id());
  5235. error_log('End of learnpath::start_current_item()');
  5236. }
  5237. return true;
  5238. }
  5239. /**
  5240. * Stops the processing and counters for the old item (as held in $this->last).
  5241. *
  5242. * @return bool True/False
  5243. */
  5244. public function stop_previous_item()
  5245. {
  5246. $debug = $this->debug;
  5247. if ($debug) {
  5248. error_log('In learnpath::stop_previous_item()', 0);
  5249. }
  5250. if ($this->last != 0 && $this->last != $this->current &&
  5251. isset($this->items[$this->last]) && is_object($this->items[$this->last])
  5252. ) {
  5253. if ($debug) {
  5254. error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
  5255. }
  5256. switch ($this->get_type()) {
  5257. case '3':
  5258. if ($this->items[$this->last]->get_type() != 'au') {
  5259. if ($debug) {
  5260. error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
  5261. }
  5262. $this->items[$this->last]->close();
  5263. } else {
  5264. if ($debug) {
  5265. error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
  5266. }
  5267. }
  5268. break;
  5269. case '2':
  5270. if ($this->items[$this->last]->get_type() != 'sco') {
  5271. if ($debug) {
  5272. error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
  5273. }
  5274. $this->items[$this->last]->close();
  5275. } else {
  5276. if ($debug) {
  5277. error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
  5278. }
  5279. }
  5280. break;
  5281. case '1':
  5282. default:
  5283. if ($debug) {
  5284. error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
  5285. }
  5286. $this->items[$this->last]->close();
  5287. break;
  5288. }
  5289. } else {
  5290. if ($debug) {
  5291. error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
  5292. }
  5293. return false;
  5294. }
  5295. return true;
  5296. }
  5297. /**
  5298. * Updates the default view mode from fullscreen to embedded and inversely.
  5299. *
  5300. * @return string The current default view mode ('fullscreen' or 'embedded')
  5301. */
  5302. public function update_default_view_mode()
  5303. {
  5304. if ($this->debug > 0) {
  5305. error_log('In learnpath::update_default_view_mode()', 0);
  5306. }
  5307. $table = Database::get_course_table(TABLE_LP_MAIN);
  5308. $sql = "SELECT * FROM $table
  5309. WHERE iid = ".$this->get_id();
  5310. $res = Database::query($sql);
  5311. if (Database::num_rows($res) > 0) {
  5312. $row = Database::fetch_array($res);
  5313. $default_view_mode = $row['default_view_mod'];
  5314. $view_mode = $default_view_mode;
  5315. switch ($default_view_mode) {
  5316. case 'fullscreen': // default with popup
  5317. $view_mode = 'embedded';
  5318. break;
  5319. case 'embedded': // default view with left menu
  5320. $view_mode = 'embedframe';
  5321. break;
  5322. case 'embedframe': //folded menu
  5323. $view_mode = 'impress';
  5324. break;
  5325. case 'impress':
  5326. $view_mode = 'fullscreen';
  5327. break;
  5328. }
  5329. $sql = "UPDATE $table SET default_view_mod = '$view_mode'
  5330. WHERE iid = ".$this->get_id();
  5331. Database::query($sql);
  5332. $this->mode = $view_mode;
  5333. return $view_mode;
  5334. } else {
  5335. if ($this->debug > 2) {
  5336. error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
  5337. }
  5338. }
  5339. return -1;
  5340. }
  5341. /**
  5342. * Updates the default behaviour about auto-commiting SCORM updates.
  5343. *
  5344. * @return bool True if auto-commit has been set to 'on', false otherwise
  5345. */
  5346. public function update_default_scorm_commit()
  5347. {
  5348. if ($this->debug > 0) {
  5349. error_log('In learnpath::update_default_scorm_commit()', 0);
  5350. }
  5351. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5352. $sql = "SELECT * FROM $lp_table
  5353. WHERE iid = ".$this->get_id();
  5354. $res = Database::query($sql);
  5355. if (Database::num_rows($res) > 0) {
  5356. $row = Database::fetch_array($res);
  5357. $force = $row['force_commit'];
  5358. if ($force == 1) {
  5359. $force = 0;
  5360. $force_return = false;
  5361. } elseif ($force == 0) {
  5362. $force = 1;
  5363. $force_return = true;
  5364. }
  5365. $sql = "UPDATE $lp_table SET force_commit = $force
  5366. WHERE iid = ".$this->get_id();
  5367. Database::query($sql);
  5368. $this->force_commit = $force_return;
  5369. return $force_return;
  5370. } else {
  5371. if ($this->debug > 2) {
  5372. error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
  5373. }
  5374. }
  5375. return -1;
  5376. }
  5377. /**
  5378. * Updates the order of learning paths (goes through all of them by order and fills the gaps).
  5379. *
  5380. * @return bool True on success, false on failure
  5381. */
  5382. public function update_display_order()
  5383. {
  5384. $course_id = api_get_course_int_id();
  5385. $table = Database::get_course_table(TABLE_LP_MAIN);
  5386. $sql = "SELECT * FROM $table
  5387. WHERE c_id = $course_id
  5388. ORDER BY display_order";
  5389. $res = Database::query($sql);
  5390. if ($res === false) {
  5391. return false;
  5392. }
  5393. $num = Database::num_rows($res);
  5394. // First check the order is correct, globally (might be wrong because
  5395. // of versions < 1.8.4).
  5396. if ($num > 0) {
  5397. $i = 1;
  5398. while ($row = Database::fetch_array($res)) {
  5399. if ($row['display_order'] != $i) {
  5400. // If we find a gap in the order, we need to fix it.
  5401. $sql = "UPDATE $table SET display_order = $i
  5402. WHERE iid = ".$row['iid'];
  5403. Database::query($sql);
  5404. }
  5405. $i++;
  5406. }
  5407. }
  5408. return true;
  5409. }
  5410. /**
  5411. * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
  5412. *
  5413. * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
  5414. */
  5415. public function update_reinit()
  5416. {
  5417. if ($this->debug > 0) {
  5418. error_log('In learnpath::update_reinit()', 0);
  5419. }
  5420. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5421. $sql = "SELECT * FROM $lp_table
  5422. WHERE iid = ".$this->get_id();
  5423. $res = Database::query($sql);
  5424. if (Database::num_rows($res) > 0) {
  5425. $row = Database::fetch_array($res);
  5426. $force = $row['prevent_reinit'];
  5427. if ($force == 1) {
  5428. $force = 0;
  5429. } elseif ($force == 0) {
  5430. $force = 1;
  5431. }
  5432. $sql = "UPDATE $lp_table SET prevent_reinit = $force
  5433. WHERE iid = ".$this->get_id();
  5434. Database::query($sql);
  5435. $this->prevent_reinit = $force;
  5436. return $force;
  5437. } else {
  5438. if ($this->debug > 2) {
  5439. error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
  5440. }
  5441. }
  5442. return -1;
  5443. }
  5444. /**
  5445. * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
  5446. *
  5447. * @return string 'single', 'multi' or 'seriousgame'
  5448. *
  5449. * @author ndiechburg <noel@cblue.be>
  5450. */
  5451. public function get_attempt_mode()
  5452. {
  5453. //Set default value for seriousgame_mode
  5454. if (!isset($this->seriousgame_mode)) {
  5455. $this->seriousgame_mode = 0;
  5456. }
  5457. // Set default value for prevent_reinit
  5458. if (!isset($this->prevent_reinit)) {
  5459. $this->prevent_reinit = 1;
  5460. }
  5461. if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
  5462. return 'seriousgame';
  5463. }
  5464. if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
  5465. return 'single';
  5466. }
  5467. if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
  5468. return 'multiple';
  5469. }
  5470. return 'single';
  5471. }
  5472. /**
  5473. * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
  5474. *
  5475. * @param string 'seriousgame', 'single' or 'multiple'
  5476. *
  5477. * @return bool
  5478. *
  5479. * @author ndiechburg <noel@cblue.be>
  5480. */
  5481. public function set_attempt_mode($mode)
  5482. {
  5483. switch ($mode) {
  5484. case 'seriousgame':
  5485. $sg_mode = 1;
  5486. $prevent_reinit = 1;
  5487. break;
  5488. case 'single':
  5489. $sg_mode = 0;
  5490. $prevent_reinit = 1;
  5491. break;
  5492. case 'multiple':
  5493. $sg_mode = 0;
  5494. $prevent_reinit = 0;
  5495. break;
  5496. default:
  5497. $sg_mode = 0;
  5498. $prevent_reinit = 0;
  5499. break;
  5500. }
  5501. $this->prevent_reinit = $prevent_reinit;
  5502. $this->seriousgame_mode = $sg_mode;
  5503. $table = Database::get_course_table(TABLE_LP_MAIN);
  5504. $sql = "UPDATE $table SET
  5505. prevent_reinit = $prevent_reinit ,
  5506. seriousgame_mode = $sg_mode
  5507. WHERE iid = ".$this->get_id();
  5508. $res = Database::query($sql);
  5509. if ($res) {
  5510. return true;
  5511. } else {
  5512. return false;
  5513. }
  5514. }
  5515. /**
  5516. * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
  5517. *
  5518. * @author ndiechburg <noel@cblue.be>
  5519. */
  5520. public function switch_attempt_mode()
  5521. {
  5522. if ($this->debug > 0) {
  5523. error_log('In learnpath::switch_attempt_mode()', 0);
  5524. }
  5525. $mode = $this->get_attempt_mode();
  5526. switch ($mode) {
  5527. case 'single':
  5528. $next_mode = 'multiple';
  5529. break;
  5530. case 'multiple':
  5531. $next_mode = 'seriousgame';
  5532. break;
  5533. case 'seriousgame':
  5534. $next_mode = 'single';
  5535. break;
  5536. default:
  5537. $next_mode = 'single';
  5538. break;
  5539. }
  5540. $this->set_attempt_mode($next_mode);
  5541. }
  5542. /**
  5543. * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
  5544. * but possibility to do again a completed item.
  5545. *
  5546. * @return bool true if seriousgame_mode has been set to 1, false otherwise
  5547. *
  5548. * @author ndiechburg <noel@cblue.be>
  5549. */
  5550. public function set_seriousgame_mode()
  5551. {
  5552. if ($this->debug > 0) {
  5553. error_log('In learnpath::set_seriousgame_mode()', 0);
  5554. }
  5555. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5556. $sql = "SELECT * FROM $lp_table
  5557. WHERE iid = ".$this->get_id();
  5558. $res = Database::query($sql);
  5559. if (Database::num_rows($res) > 0) {
  5560. $row = Database::fetch_array($res);
  5561. $force = $row['seriousgame_mode'];
  5562. if ($force == 1) {
  5563. $force = 0;
  5564. } elseif ($force == 0) {
  5565. $force = 1;
  5566. }
  5567. $sql = "UPDATE $lp_table SET seriousgame_mode = $force
  5568. WHERE iid = ".$this->get_id();
  5569. Database::query($sql);
  5570. $this->seriousgame_mode = $force;
  5571. return $force;
  5572. } else {
  5573. if ($this->debug > 2) {
  5574. error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
  5575. }
  5576. }
  5577. return -1;
  5578. }
  5579. /**
  5580. * Updates the "scorm_debug" value that shows or hide the debug window.
  5581. *
  5582. * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
  5583. */
  5584. public function update_scorm_debug()
  5585. {
  5586. if ($this->debug > 0) {
  5587. error_log('In learnpath::update_scorm_debug()', 0);
  5588. }
  5589. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5590. $sql = "SELECT * FROM $lp_table
  5591. WHERE iid = ".$this->get_id();
  5592. $res = Database::query($sql);
  5593. if (Database::num_rows($res) > 0) {
  5594. $row = Database::fetch_array($res);
  5595. $force = $row['debug'];
  5596. if ($force == 1) {
  5597. $force = 0;
  5598. } elseif ($force == 0) {
  5599. $force = 1;
  5600. }
  5601. $sql = "UPDATE $lp_table SET debug = $force
  5602. WHERE iid = ".$this->get_id();
  5603. Database::query($sql);
  5604. $this->scorm_debug = $force;
  5605. return $force;
  5606. } else {
  5607. if ($this->debug > 2) {
  5608. error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
  5609. }
  5610. }
  5611. return -1;
  5612. }
  5613. /**
  5614. * Function that makes a call to the function sort_tree_array and create_tree_array.
  5615. *
  5616. * @author Kevin Van Den Haute
  5617. *
  5618. * @param array
  5619. */
  5620. public function tree_array($array)
  5621. {
  5622. if ($this->debug > 1) {
  5623. error_log('In learnpath::tree_array()', 0);
  5624. }
  5625. $array = $this->sort_tree_array($array);
  5626. $this->create_tree_array($array);
  5627. }
  5628. /**
  5629. * Creates an array with the elements of the learning path tree in it.
  5630. *
  5631. * @author Kevin Van Den Haute
  5632. *
  5633. * @param array $array
  5634. * @param int $parent
  5635. * @param int $depth
  5636. * @param array $tmp
  5637. */
  5638. public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
  5639. {
  5640. if ($this->debug > 1) {
  5641. error_log('In learnpath::create_tree_array())', 0);
  5642. }
  5643. if (is_array($array)) {
  5644. for ($i = 0; $i < count($array); $i++) {
  5645. if ($array[$i]['parent_item_id'] == $parent) {
  5646. if (!in_array($array[$i]['parent_item_id'], $tmp)) {
  5647. $tmp[] = $array[$i]['parent_item_id'];
  5648. $depth++;
  5649. }
  5650. $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
  5651. $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
  5652. $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
  5653. $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
  5654. $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
  5655. $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
  5656. $this->arrMenu[] = [
  5657. 'id' => $array[$i]['id'],
  5658. 'ref' => $ref,
  5659. 'item_type' => $array[$i]['item_type'],
  5660. 'title' => $array[$i]['title'],
  5661. 'path' => $path,
  5662. 'description' => $array[$i]['description'],
  5663. 'parent_item_id' => $array[$i]['parent_item_id'],
  5664. 'previous_item_id' => $array[$i]['previous_item_id'],
  5665. 'next_item_id' => $array[$i]['next_item_id'],
  5666. 'min_score' => $array[$i]['min_score'],
  5667. 'max_score' => $array[$i]['max_score'],
  5668. 'mastery_score' => $array[$i]['mastery_score'],
  5669. 'display_order' => $array[$i]['display_order'],
  5670. 'prerequisite' => $preq,
  5671. 'depth' => $depth,
  5672. 'audio' => $audio,
  5673. 'prerequisite_min_score' => $prerequisiteMinScore,
  5674. 'prerequisite_max_score' => $prerequisiteMaxScore,
  5675. ];
  5676. $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
  5677. }
  5678. }
  5679. }
  5680. }
  5681. /**
  5682. * Sorts a multi dimensional array by parent id and display order.
  5683. *
  5684. * @author Kevin Van Den Haute
  5685. *
  5686. * @param array $array (array with al the learning path items in it)
  5687. *
  5688. * @return array
  5689. */
  5690. public function sort_tree_array($array)
  5691. {
  5692. foreach ($array as $key => $row) {
  5693. $parent[$key] = $row['parent_item_id'];
  5694. $position[$key] = $row['display_order'];
  5695. }
  5696. if (count($array) > 0) {
  5697. array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
  5698. }
  5699. return $array;
  5700. }
  5701. /**
  5702. * Function that creates a html list of learning path items so that we can add audio files to them.
  5703. *
  5704. * @author Kevin Van Den Haute
  5705. *
  5706. * @return string
  5707. */
  5708. public function overview()
  5709. {
  5710. if ($this->debug > 0) {
  5711. error_log('In learnpath::overview()', 0);
  5712. }
  5713. $return = '';
  5714. $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
  5715. // we need to start a form when we want to update all the mp3 files
  5716. if ($update_audio == 'true') {
  5717. $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS($_GET['updateaudio']).'&action='.Security::remove_XSS($_GET['action']).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
  5718. }
  5719. $return .= '<div id="message"></div>';
  5720. if (count($this->items) == 0) {
  5721. $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
  5722. } else {
  5723. $return_audio = '<table class="data_table">';
  5724. $return_audio .= '<tr>';
  5725. $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
  5726. $return_audio .= '<th>'.get_lang('Audio').'</th>';
  5727. $return_audio .= '</tr>';
  5728. if ($update_audio != 'true') {
  5729. $return .= '<div class="col-md-12">';
  5730. $return .= self::return_new_tree($update_audio);
  5731. $return .= '</div>';
  5732. $return .= Display::div(
  5733. Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
  5734. ['style' => 'float:left; margin-top:15px;width:100%']
  5735. );
  5736. } else {
  5737. $return_audio .= self::return_new_tree($update_audio);
  5738. $return .= $return_audio.'</table>';
  5739. }
  5740. // We need to close the form when we are updating the mp3 files.
  5741. if ($update_audio == 'true') {
  5742. $return .= '<div class="footer-audio">';
  5743. $return .= Display::button(
  5744. 'save_audio',
  5745. '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
  5746. ['class' => 'btn btn-primary', 'type' => 'submit']
  5747. );
  5748. $return .= '</div>';
  5749. }
  5750. }
  5751. // We need to close the form when we are updating the mp3 files.
  5752. if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
  5753. $return .= '</form>';
  5754. }
  5755. return $return;
  5756. }
  5757. /**
  5758. * @param string $update_audio
  5759. *
  5760. * @return array
  5761. */
  5762. public function processBuildMenuElements($update_audio = 'false')
  5763. {
  5764. $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
  5765. $course_id = api_get_course_int_id();
  5766. $table = Database::get_course_table(TABLE_LP_ITEM);
  5767. $sql = "SELECT * FROM $table
  5768. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  5769. $result = Database::query($sql);
  5770. $arrLP = [];
  5771. while ($row = Database::fetch_array($result)) {
  5772. $arrLP[] = [
  5773. 'id' => $row['iid'],
  5774. 'item_type' => $row['item_type'],
  5775. 'title' => Security::remove_XSS($row['title']),
  5776. 'path' => $row['path'],
  5777. 'description' => Security::remove_XSS($row['description']),
  5778. 'parent_item_id' => $row['parent_item_id'],
  5779. 'previous_item_id' => $row['previous_item_id'],
  5780. 'next_item_id' => $row['next_item_id'],
  5781. 'max_score' => $row['max_score'],
  5782. 'min_score' => $row['min_score'],
  5783. 'mastery_score' => $row['mastery_score'],
  5784. 'prerequisite' => $row['prerequisite'],
  5785. 'display_order' => $row['display_order'],
  5786. 'audio' => $row['audio'],
  5787. 'prerequisite_max_score' => $row['prerequisite_max_score'],
  5788. 'prerequisite_min_score' => $row['prerequisite_min_score'],
  5789. ];
  5790. }
  5791. $this->tree_array($arrLP);
  5792. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  5793. unset($this->arrMenu);
  5794. $default_data = null;
  5795. $default_content = null;
  5796. $elements = [];
  5797. $return_audio = null;
  5798. $iconPath = api_get_path(SYS_CODE_PATH).'img/';
  5799. $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
  5800. for ($i = 0; $i < count($arrLP); $i++) {
  5801. $title = $arrLP[$i]['title'];
  5802. $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
  5803. // Link for the documents
  5804. if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
  5805. $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5806. $title_cut = Display::url(
  5807. $title_cut,
  5808. $url,
  5809. [
  5810. 'class' => 'ajax moved',
  5811. 'data-title' => $title,
  5812. 'title' => $title,
  5813. ]
  5814. );
  5815. }
  5816. // Detect if type is FINAL_ITEM to set path_id to SESSION
  5817. if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
  5818. Session::write('pathItem', $arrLP[$i]['path']);
  5819. }
  5820. if (($i % 2) == 0) {
  5821. $oddClass = 'row_odd';
  5822. } else {
  5823. $oddClass = 'row_even';
  5824. }
  5825. $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
  5826. $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
  5827. if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
  5828. $icon = Display::return_icon('lp_'.$icon_name.'.png');
  5829. } else {
  5830. if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
  5831. $icon = Display::return_icon('lp_'.$icon_name.'.gif');
  5832. } else {
  5833. if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
  5834. $icon = Display::return_icon('certificate.png');
  5835. } else {
  5836. $icon = Display::return_icon('folder_document.gif');
  5837. }
  5838. }
  5839. }
  5840. // The audio column.
  5841. $return_audio .= '<td align="left" style="padding-left:10px;">';
  5842. $audio = '';
  5843. if (!$update_audio || $update_audio != 'true') {
  5844. if (empty($arrLP[$i]['audio'])) {
  5845. $audio .= '';
  5846. }
  5847. } else {
  5848. $types = self::getChapterTypes();
  5849. if (!in_array($arrLP[$i]['item_type'], $types)) {
  5850. $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
  5851. if (!empty($arrLP[$i]['audio'])) {
  5852. $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
  5853. <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
  5854. }
  5855. }
  5856. }
  5857. $return_audio .= Display::span($icon.' '.$title).
  5858. Display::tag(
  5859. 'td',
  5860. $audio,
  5861. ['style' => '']
  5862. );
  5863. $return_audio .= '</td>';
  5864. $move_icon = '';
  5865. $move_item_icon = '';
  5866. $edit_icon = '';
  5867. $delete_icon = '';
  5868. $audio_icon = '';
  5869. $prerequisities_icon = '';
  5870. $forumIcon = '';
  5871. $previewIcon = '';
  5872. $pluginCalendarIcon = '';
  5873. $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
  5874. $plugin = null;
  5875. if ($pluginCalendar) {
  5876. $plugin = LearningCalendarPlugin::create();
  5877. }
  5878. $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
  5879. if ($is_allowed_to_edit) {
  5880. if (!$update_audio || $update_audio != 'true') {
  5881. if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
  5882. $move_icon .= '<a class="moved" href="#">';
  5883. $move_icon .= Display::return_icon(
  5884. 'move_everywhere.png',
  5885. get_lang('Move'),
  5886. [],
  5887. ICON_SIZE_TINY
  5888. );
  5889. $move_icon .= '</a>';
  5890. }
  5891. }
  5892. // No edit for this item types
  5893. if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
  5894. if ($arrLP[$i]['item_type'] != 'dir') {
  5895. $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
  5896. $edit_icon .= Display::return_icon(
  5897. 'edit.png',
  5898. get_lang('LearnpathEditModule'),
  5899. [],
  5900. ICON_SIZE_TINY
  5901. );
  5902. $edit_icon .= '</a>';
  5903. if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
  5904. $forumThread = null;
  5905. if (isset($this->items[$arrLP[$i]['id']])) {
  5906. $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
  5907. $this->course_int_id,
  5908. $this->lp_session_id
  5909. );
  5910. }
  5911. if ($forumThread) {
  5912. $forumIconUrl = $mainUrl.'&'.http_build_query([
  5913. 'action' => 'dissociate_forum',
  5914. 'id' => $arrLP[$i]['id'],
  5915. 'lp_id' => $this->lp_id,
  5916. ]);
  5917. $forumIcon = Display::url(
  5918. Display::return_icon(
  5919. 'forum.png',
  5920. get_lang('DissociateForumToLPItem'),
  5921. [],
  5922. ICON_SIZE_TINY
  5923. ),
  5924. $forumIconUrl,
  5925. ['class' => 'btn btn-default lp-btn-dissociate-forum']
  5926. );
  5927. } else {
  5928. $forumIconUrl = $mainUrl.'&'.http_build_query([
  5929. 'action' => 'create_forum',
  5930. 'id' => $arrLP[$i]['id'],
  5931. 'lp_id' => $this->lp_id,
  5932. ]);
  5933. $forumIcon = Display::url(
  5934. Display::return_icon(
  5935. 'forum.png',
  5936. get_lang('AssociateForumToLPItem'),
  5937. [],
  5938. ICON_SIZE_TINY
  5939. ),
  5940. $forumIconUrl,
  5941. ['class' => 'btn btn-default lp-btn-associate-forum']
  5942. );
  5943. }
  5944. }
  5945. } else {
  5946. $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
  5947. $edit_icon .= Display::return_icon(
  5948. 'edit.png',
  5949. get_lang('LearnpathEditModule'),
  5950. [],
  5951. ICON_SIZE_TINY
  5952. );
  5953. $edit_icon .= '</a>';
  5954. }
  5955. } else {
  5956. if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
  5957. $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
  5958. $edit_icon .= Display::return_icon(
  5959. 'edit.png',
  5960. get_lang('Edit'),
  5961. [],
  5962. ICON_SIZE_TINY
  5963. );
  5964. $edit_icon .= '</a>';
  5965. }
  5966. }
  5967. if ($pluginCalendar) {
  5968. $pluginLink = $pluginUrl.
  5969. '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5970. $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
  5971. $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
  5972. if ($itemInfo && $itemInfo['value'] == 1) {
  5973. $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
  5974. }
  5975. $pluginCalendarIcon = Display::url(
  5976. $iconCalendar,
  5977. $pluginLink,
  5978. ['class' => 'btn btn-default']
  5979. );
  5980. }
  5981. $delete_icon .= ' <a
  5982. href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
  5983. onclick="return confirmation(\''.addslashes($title).'\');"
  5984. class="btn btn-default">';
  5985. $delete_icon .= Display::return_icon(
  5986. 'delete.png',
  5987. get_lang('LearnpathDeleteModule'),
  5988. [],
  5989. ICON_SIZE_TINY
  5990. );
  5991. $delete_icon .= '</a>';
  5992. $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5993. $previewImage = Display::return_icon(
  5994. 'preview_view.png',
  5995. get_lang('Preview'),
  5996. [],
  5997. ICON_SIZE_TINY
  5998. );
  5999. switch ($arrLP[$i]['item_type']) {
  6000. case TOOL_DOCUMENT:
  6001. case TOOL_LP_FINAL_ITEM:
  6002. case TOOL_READOUT_TEXT:
  6003. $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  6004. $previewIcon = Display::url(
  6005. $previewImage,
  6006. $urlPreviewLink,
  6007. [
  6008. 'target' => '_blank',
  6009. 'class' => 'btn btn-default',
  6010. 'data-title' => $arrLP[$i]['title'],
  6011. 'title' => $arrLP[$i]['title'],
  6012. ]
  6013. );
  6014. break;
  6015. case TOOL_THREAD:
  6016. case TOOL_FORUM:
  6017. case TOOL_QUIZ:
  6018. case TOOL_STUDENTPUBLICATION:
  6019. case TOOL_LP_FINAL_ITEM:
  6020. case TOOL_LINK:
  6021. //$target = '';
  6022. //$class = 'btn btn-default ajax';
  6023. //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
  6024. $class = 'btn btn-default';
  6025. $target = '_blank';
  6026. //}
  6027. $link = self::rl_get_resource_link_for_learnpath(
  6028. $this->course_int_id,
  6029. $this->lp_id,
  6030. $arrLP[$i]['id'],
  6031. 0
  6032. );
  6033. $previewIcon = Display::url(
  6034. $previewImage,
  6035. $link,
  6036. [
  6037. 'class' => $class,
  6038. 'data-title' => $arrLP[$i]['title'],
  6039. 'title' => $arrLP[$i]['title'],
  6040. 'target' => $target,
  6041. ]
  6042. );
  6043. break;
  6044. default:
  6045. $previewIcon = Display::url(
  6046. $previewImage,
  6047. $url.'&action=view_item',
  6048. ['class' => 'btn btn-default', 'target' => '_blank']
  6049. );
  6050. break;
  6051. }
  6052. if ($arrLP[$i]['item_type'] != 'dir') {
  6053. $prerequisities_icon = Display::url(
  6054. Display::return_icon(
  6055. 'accept.png',
  6056. get_lang('LearnpathPrerequisites'),
  6057. [],
  6058. ICON_SIZE_TINY
  6059. ),
  6060. $url.'&action=edit_item_prereq',
  6061. ['class' => 'btn btn-default']
  6062. );
  6063. if ($arrLP[$i]['item_type'] != 'final_item') {
  6064. $move_item_icon = Display::url(
  6065. Display::return_icon(
  6066. 'move.png',
  6067. get_lang('Move'),
  6068. [],
  6069. ICON_SIZE_TINY
  6070. ),
  6071. $url.'&action=move_item',
  6072. ['class' => 'btn btn-default']
  6073. );
  6074. }
  6075. $audio_icon = Display::url(
  6076. Display::return_icon(
  6077. 'audio.png',
  6078. get_lang('UplUpload'),
  6079. [],
  6080. ICON_SIZE_TINY
  6081. ),
  6082. $url.'&action=add_audio',
  6083. ['class' => 'btn btn-default']
  6084. );
  6085. }
  6086. }
  6087. if ($update_audio != 'true') {
  6088. $row = $move_icon.' '.$icon.
  6089. Display::span($title_cut).
  6090. Display::tag(
  6091. 'div',
  6092. "<div class=\"btn-group btn-group-xs\">
  6093. $previewIcon
  6094. $audio
  6095. $edit_icon
  6096. $pluginCalendarIcon
  6097. $forumIcon
  6098. $prerequisities_icon
  6099. $move_item_icon
  6100. $audio_icon
  6101. $delete_icon
  6102. </div>",
  6103. ['class' => 'btn-toolbar button_actions']
  6104. );
  6105. } else {
  6106. $row =
  6107. Display::span($title.$icon).
  6108. Display::span($audio, ['class' => 'button_actions']);
  6109. }
  6110. $parent_id = $arrLP[$i]['parent_item_id'];
  6111. $default_data[$arrLP[$i]['id']] = $row;
  6112. $default_content[$arrLP[$i]['id']] = $arrLP[$i];
  6113. if (empty($parent_id)) {
  6114. $elements[$arrLP[$i]['id']]['data'] = $row;
  6115. $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
  6116. } else {
  6117. $parent_arrays = [];
  6118. if ($arrLP[$i]['depth'] > 1) {
  6119. //Getting list of parents
  6120. for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
  6121. foreach ($arrLP as $item) {
  6122. if ($item['id'] == $parent_id) {
  6123. if ($item['parent_item_id'] == 0) {
  6124. $parent_id = $item['id'];
  6125. break;
  6126. } else {
  6127. $parent_id = $item['parent_item_id'];
  6128. if (empty($parent_arrays)) {
  6129. $parent_arrays[] = intval($item['id']);
  6130. }
  6131. $parent_arrays[] = $parent_id;
  6132. break;
  6133. }
  6134. }
  6135. }
  6136. }
  6137. }
  6138. if (!empty($parent_arrays)) {
  6139. $parent_arrays = array_reverse($parent_arrays);
  6140. $val = '$elements';
  6141. $x = 0;
  6142. foreach ($parent_arrays as $item) {
  6143. if ($x != count($parent_arrays) - 1) {
  6144. $val .= '["'.$item.'"]["children"]';
  6145. } else {
  6146. $val .= '["'.$item.'"]["children"]';
  6147. }
  6148. $x++;
  6149. }
  6150. $val .= "";
  6151. $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
  6152. eval($code_str);
  6153. } else {
  6154. $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
  6155. $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
  6156. }
  6157. }
  6158. }
  6159. return [
  6160. 'elements' => $elements,
  6161. 'default_data' => $default_data,
  6162. 'default_content' => $default_content,
  6163. 'return_audio' => $return_audio,
  6164. ];
  6165. }
  6166. /**
  6167. * @param string $updateAudio true/false strings
  6168. *
  6169. * @return string
  6170. */
  6171. public function returnLpItemList($updateAudio)
  6172. {
  6173. $result = $this->processBuildMenuElements($updateAudio);
  6174. $html = self::print_recursive(
  6175. $result['elements'],
  6176. $result['default_data'],
  6177. $result['default_content']
  6178. );
  6179. if (!empty($html)) {
  6180. $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
  6181. }
  6182. return $html;
  6183. }
  6184. /**
  6185. * @param string $update_audio
  6186. * @param bool $drop_element_here
  6187. *
  6188. * @return string
  6189. */
  6190. public function return_new_tree($update_audio = 'false', $drop_element_here = false)
  6191. {
  6192. $return = '';
  6193. $result = $this->processBuildMenuElements($update_audio);
  6194. $list = '<ul id="lp_item_list">';
  6195. $tree = $this->print_recursive(
  6196. $result['elements'],
  6197. $result['default_data'],
  6198. $result['default_content']
  6199. );
  6200. if (!empty($tree)) {
  6201. $list .= $tree;
  6202. } else {
  6203. if ($drop_element_here) {
  6204. $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
  6205. }
  6206. }
  6207. $list .= '</ul>';
  6208. $return .= Display::panelCollapse(
  6209. $this->name,
  6210. $list,
  6211. 'scorm-list',
  6212. null,
  6213. 'scorm-list-accordion',
  6214. 'scorm-list-collapse'
  6215. );
  6216. if ($update_audio === 'true') {
  6217. $return = $result['return_audio'];
  6218. }
  6219. return $return;
  6220. }
  6221. /**
  6222. * @param array $elements
  6223. * @param array $default_data
  6224. * @param array $default_content
  6225. *
  6226. * @return string
  6227. */
  6228. public function print_recursive($elements, $default_data, $default_content)
  6229. {
  6230. $return = '';
  6231. foreach ($elements as $key => $item) {
  6232. if (isset($item['load_data']) || empty($item['data'])) {
  6233. $item['data'] = $default_data[$item['load_data']];
  6234. $item['type'] = $default_content[$item['load_data']]['item_type'];
  6235. }
  6236. $sub_list = '';
  6237. if (isset($item['type']) && $item['type'] == 'dir') {
  6238. // empty value
  6239. $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
  6240. }
  6241. if (empty($item['children'])) {
  6242. $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
  6243. $active = null;
  6244. if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
  6245. $active = 'active';
  6246. }
  6247. $return .= Display::tag(
  6248. 'li',
  6249. Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
  6250. ['id' => $key, 'class' => 'record li_container']
  6251. );
  6252. } else {
  6253. // Sections
  6254. $data = '';
  6255. if (isset($item['children'])) {
  6256. $data = self::print_recursive($item['children'], $default_data, $default_content);
  6257. }
  6258. $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
  6259. $return .= Display::tag(
  6260. 'li',
  6261. Display::div($item['data'], ['class' => 'item_data']).$sub_list,
  6262. ['id' => $key, 'class' => 'record li_container']
  6263. );
  6264. }
  6265. }
  6266. return $return;
  6267. }
  6268. /**
  6269. * This function builds the action menu.
  6270. *
  6271. * @param bool $returnContent Optional
  6272. * @param bool $showRequirementButtons Optional. Allow show the requirements button
  6273. * @param bool $isConfigPage Optional. If is the config page, show the edit button
  6274. * @param bool $allowExpand Optional. Allow show the expand/contract button
  6275. *
  6276. * @return string
  6277. */
  6278. public function build_action_menu(
  6279. $returnContent = false,
  6280. $showRequirementButtons = true,
  6281. $isConfigPage = false,
  6282. $allowExpand = true
  6283. ) {
  6284. $actionsLeft = '';
  6285. $actionsRight = '';
  6286. $actionsLeft .= Display::url(
  6287. Display::return_icon(
  6288. 'back.png',
  6289. get_lang('ReturnToLearningPaths'),
  6290. '',
  6291. ICON_SIZE_MEDIUM
  6292. ),
  6293. 'lp_controller.php?'.api_get_cidreq()
  6294. );
  6295. $actionsLeft .= Display::url(
  6296. Display::return_icon(
  6297. 'preview_view.png',
  6298. get_lang('Preview'),
  6299. '',
  6300. ICON_SIZE_MEDIUM
  6301. ),
  6302. 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6303. 'action' => 'view',
  6304. 'lp_id' => $this->lp_id,
  6305. 'isStudentView' => 'true',
  6306. ])
  6307. );
  6308. $actionsLeft .= Display::url(
  6309. Display::return_icon(
  6310. 'upload_audio.png',
  6311. get_lang('UpdateAllAudioFragments'),
  6312. '',
  6313. ICON_SIZE_MEDIUM
  6314. ),
  6315. 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6316. 'action' => 'admin_view',
  6317. 'lp_id' => $this->lp_id,
  6318. 'updateaudio' => 'true',
  6319. ])
  6320. );
  6321. if (!$isConfigPage) {
  6322. $actionsLeft .= Display::url(
  6323. Display::return_icon(
  6324. 'settings.png',
  6325. get_lang('CourseSettings'),
  6326. '',
  6327. ICON_SIZE_MEDIUM
  6328. ),
  6329. 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6330. 'action' => 'edit',
  6331. 'lp_id' => $this->lp_id,
  6332. ])
  6333. );
  6334. } else {
  6335. $actionsLeft .= Display::url(
  6336. Display::return_icon(
  6337. 'edit.png',
  6338. get_lang('Edit'),
  6339. '',
  6340. ICON_SIZE_MEDIUM
  6341. ),
  6342. 'lp_controller.php?'.http_build_query([
  6343. 'action' => 'build',
  6344. 'lp_id' => $this->lp_id,
  6345. ]).'&'.api_get_cidreq()
  6346. );
  6347. }
  6348. if ($allowExpand) {
  6349. $actionsLeft .= Display::url(
  6350. Display::return_icon(
  6351. 'expand.png',
  6352. get_lang('Expand'),
  6353. ['id' => 'expand'],
  6354. ICON_SIZE_MEDIUM
  6355. ).
  6356. Display::return_icon(
  6357. 'contract.png',
  6358. get_lang('Collapse'),
  6359. ['id' => 'contract', 'class' => 'hide'],
  6360. ICON_SIZE_MEDIUM
  6361. ),
  6362. '#',
  6363. ['role' => 'button', 'id' => 'hide_bar_template']
  6364. );
  6365. }
  6366. if ($showRequirementButtons) {
  6367. $buttons = [
  6368. [
  6369. 'title' => get_lang('SetPrerequisiteForEachItem'),
  6370. 'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6371. 'action' => 'set_previous_step_as_prerequisite',
  6372. 'lp_id' => $this->lp_id,
  6373. ]),
  6374. ],
  6375. [
  6376. 'title' => get_lang('ClearAllPrerequisites'),
  6377. 'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6378. 'action' => 'clear_prerequisites',
  6379. 'lp_id' => $this->lp_id,
  6380. ]),
  6381. ],
  6382. ];
  6383. $actionsRight = Display::groupButtonWithDropDown(
  6384. get_lang('PrerequisitesOptions'),
  6385. $buttons,
  6386. true
  6387. );
  6388. }
  6389. $toolbar = Display::toolbarAction(
  6390. 'actions-lp-controller',
  6391. [$actionsLeft, $actionsRight]
  6392. );
  6393. if ($returnContent) {
  6394. return $toolbar;
  6395. }
  6396. echo $toolbar;
  6397. }
  6398. /**
  6399. * Creates the default learning path folder.
  6400. *
  6401. * @param array $course
  6402. * @param int $creatorId
  6403. *
  6404. * @return bool
  6405. */
  6406. public static function generate_learning_path_folder($course, $creatorId = 0)
  6407. {
  6408. // Creating learning_path folder
  6409. $dir = '/learning_path';
  6410. $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
  6411. $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
  6412. $folder = false;
  6413. if (!is_dir($filepath.'/'.$dir)) {
  6414. $folderData = create_unexisting_directory(
  6415. $course,
  6416. $creatorId,
  6417. 0,
  6418. null,
  6419. 0,
  6420. $filepath,
  6421. $dir,
  6422. get_lang('LearningPaths'),
  6423. 0
  6424. );
  6425. if (!empty($folderData)) {
  6426. $folder = true;
  6427. }
  6428. } else {
  6429. $folder = true;
  6430. }
  6431. return $folder;
  6432. }
  6433. /**
  6434. * @param array $course
  6435. * @param string $lp_name
  6436. * @param int $creatorId
  6437. *
  6438. * @return array
  6439. */
  6440. public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
  6441. {
  6442. $filepath = '';
  6443. $dir = '/learning_path/';
  6444. if (empty($lp_name)) {
  6445. $lp_name = $this->name;
  6446. }
  6447. $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
  6448. $folder = self::generate_learning_path_folder($course, $creatorId);
  6449. // Limits title size
  6450. $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
  6451. $dir = $dir.$title;
  6452. // Creating LP folder
  6453. $documentId = null;
  6454. if ($folder) {
  6455. $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
  6456. if (!is_dir($filepath.'/'.$dir)) {
  6457. $folderData = create_unexisting_directory(
  6458. $course,
  6459. $creatorId,
  6460. 0,
  6461. 0,
  6462. 0,
  6463. $filepath,
  6464. $dir,
  6465. $lp_name
  6466. );
  6467. if (!empty($folderData)) {
  6468. $folder = true;
  6469. }
  6470. $documentId = $folderData['id'];
  6471. } else {
  6472. $folder = true;
  6473. }
  6474. $dir = $dir.'/';
  6475. if ($folder) {
  6476. $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
  6477. }
  6478. }
  6479. if (empty($documentId)) {
  6480. $dir = api_remove_trailing_slash($dir);
  6481. $documentId = DocumentManager::get_document_id($course, $dir, 0);
  6482. }
  6483. $array = [
  6484. 'dir' => $dir,
  6485. 'filepath' => $filepath,
  6486. 'folder' => $folder,
  6487. 'id' => $documentId,
  6488. ];
  6489. return $array;
  6490. }
  6491. /**
  6492. * Create a new document //still needs some finetuning.
  6493. *
  6494. * @param array $courseInfo
  6495. * @param string $content
  6496. * @param string $title
  6497. * @param string $extension
  6498. * @param int $parentId
  6499. * @param int $creatorId creator id
  6500. *
  6501. * @return int
  6502. */
  6503. public function create_document(
  6504. $courseInfo,
  6505. $content = '',
  6506. $title = '',
  6507. $extension = 'html',
  6508. $parentId = 0,
  6509. $creatorId = 0
  6510. ) {
  6511. if (!empty($courseInfo)) {
  6512. $course_id = $courseInfo['real_id'];
  6513. } else {
  6514. $course_id = api_get_course_int_id();
  6515. }
  6516. $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
  6517. $sessionId = api_get_session_id();
  6518. // Generates folder
  6519. $result = $this->generate_lp_folder($courseInfo);
  6520. $dir = $result['dir'];
  6521. if (empty($parentId) || $parentId == '/') {
  6522. $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
  6523. $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
  6524. if ($parentId === '/') {
  6525. $dir = '/';
  6526. }
  6527. // Please, do not modify this dirname formatting.
  6528. if (strstr($dir, '..')) {
  6529. $dir = '/';
  6530. }
  6531. if (!empty($dir[0]) && $dir[0] == '.') {
  6532. $dir = substr($dir, 1);
  6533. }
  6534. if (!empty($dir[0]) && $dir[0] != '/') {
  6535. $dir = '/'.$dir;
  6536. }
  6537. if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
  6538. $dir .= '/';
  6539. }
  6540. } else {
  6541. $parentInfo = DocumentManager::get_document_data_by_id(
  6542. $parentId,
  6543. $courseInfo['code']
  6544. );
  6545. if (!empty($parentInfo)) {
  6546. $dir = $parentInfo['path'].'/';
  6547. }
  6548. }
  6549. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  6550. if (!is_dir($filepath)) {
  6551. $dir = '/';
  6552. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  6553. }
  6554. // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
  6555. // is already escaped twice when it gets here.
  6556. $originalTitle = !empty($title) ? $title : $_POST['title'];
  6557. if (!empty($title)) {
  6558. $title = api_replace_dangerous_char(stripslashes($title));
  6559. } else {
  6560. $title = api_replace_dangerous_char(stripslashes($_POST['title']));
  6561. }
  6562. $title = disable_dangerous_file($title);
  6563. $filename = $title;
  6564. $content = !empty($content) ? $content : $_POST['content_lp'];
  6565. $tmp_filename = $filename;
  6566. $i = 0;
  6567. while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
  6568. $tmp_filename = $filename.'_'.++$i;
  6569. }
  6570. $filename = $tmp_filename.'.'.$extension;
  6571. if ($extension == 'html') {
  6572. $content = stripslashes($content);
  6573. $content = str_replace(
  6574. api_get_path(WEB_COURSE_PATH),
  6575. api_get_path(REL_PATH).'courses/',
  6576. $content
  6577. );
  6578. // Change the path of mp3 to absolute.
  6579. // The first regexp deals with :// urls.
  6580. $content = preg_replace(
  6581. "|(flashvars=\"file=)([^:/]+)/|",
  6582. "$1".api_get_path(
  6583. REL_COURSE_PATH
  6584. ).$courseInfo['path'].'/document/',
  6585. $content
  6586. );
  6587. // The second regexp deals with audio/ urls.
  6588. $content = preg_replace(
  6589. "|(flashvars=\"file=)([^/]+)/|",
  6590. "$1".api_get_path(
  6591. REL_COURSE_PATH
  6592. ).$courseInfo['path'].'/document/$2/',
  6593. $content
  6594. );
  6595. // For flv player: To prevent edition problem with firefox,
  6596. // we have to use a strange tip (don't blame me please).
  6597. $content = str_replace(
  6598. '</body>',
  6599. '<style type="text/css">body{}</style></body>',
  6600. $content
  6601. );
  6602. }
  6603. if (!file_exists($filepath.$filename)) {
  6604. if ($fp = @fopen($filepath.$filename, 'w')) {
  6605. fputs($fp, $content);
  6606. fclose($fp);
  6607. $file_size = filesize($filepath.$filename);
  6608. $save_file_path = $dir.$filename;
  6609. $document_id = add_document(
  6610. $courseInfo,
  6611. $save_file_path,
  6612. 'file',
  6613. $file_size,
  6614. $tmp_filename,
  6615. '',
  6616. 0, //readonly
  6617. true,
  6618. null,
  6619. $sessionId,
  6620. $creatorId
  6621. );
  6622. if ($document_id) {
  6623. api_item_property_update(
  6624. $courseInfo,
  6625. TOOL_DOCUMENT,
  6626. $document_id,
  6627. 'DocumentAdded',
  6628. $creatorId,
  6629. null,
  6630. null,
  6631. null,
  6632. null,
  6633. $sessionId
  6634. );
  6635. $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
  6636. $new_title = $originalTitle;
  6637. if ($new_comment || $new_title) {
  6638. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6639. $ct = '';
  6640. if ($new_comment) {
  6641. $ct .= ", comment='".Database::escape_string($new_comment)."'";
  6642. }
  6643. if ($new_title) {
  6644. $ct .= ", title='".Database::escape_string($new_title)."' ";
  6645. }
  6646. $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
  6647. WHERE c_id = ".$course_id." AND id = ".$document_id;
  6648. Database::query($sql);
  6649. }
  6650. }
  6651. return $document_id;
  6652. }
  6653. }
  6654. }
  6655. /**
  6656. * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
  6657. *
  6658. * @param array $_course array
  6659. */
  6660. public function edit_document($_course)
  6661. {
  6662. $course_id = api_get_course_int_id();
  6663. $urlAppend = api_get_configuration_value('url_append');
  6664. // Please, do not modify this dirname formatting.
  6665. $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
  6666. $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
  6667. if (strstr($dir, '..')) {
  6668. $dir = '/';
  6669. }
  6670. if (isset($dir[0]) && $dir[0] == '.') {
  6671. $dir = substr($dir, 1);
  6672. }
  6673. if (isset($dir[0]) && $dir[0] != '/') {
  6674. $dir = '/'.$dir;
  6675. }
  6676. if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
  6677. $dir .= '/';
  6678. }
  6679. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
  6680. if (!is_dir($filepath)) {
  6681. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
  6682. }
  6683. $table_doc = Database::get_course_table(TABLE_DOCUMENT);
  6684. if (isset($_POST['path']) && !empty($_POST['path'])) {
  6685. $document_id = (int) $_POST['path'];
  6686. $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
  6687. if (empty($documentInfo)) {
  6688. // Try with iid
  6689. $table = Database::get_course_table(TABLE_DOCUMENT);
  6690. $sql = "SELECT id, path FROM $table
  6691. WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
  6692. $res_doc = Database::query($sql);
  6693. $row = Database::fetch_array($res_doc);
  6694. if ($row) {
  6695. $document_id = $row['id'];
  6696. $documentPath = $row['path'];
  6697. }
  6698. } else {
  6699. $documentPath = $documentInfo['path'];
  6700. }
  6701. $content = stripslashes($_POST['content_lp']);
  6702. $file = $filepath.$documentPath;
  6703. if (!file_exists($file)) {
  6704. return false;
  6705. }
  6706. if ($fp = @fopen($file, 'w')) {
  6707. $content = str_replace(
  6708. api_get_path(WEB_COURSE_PATH),
  6709. $urlAppend.api_get_path(REL_COURSE_PATH),
  6710. $content
  6711. );
  6712. // Change the path of mp3 to absolute.
  6713. // The first regexp deals with :// urls.
  6714. $content = preg_replace(
  6715. "|(flashvars=\"file=)([^:/]+)/|",
  6716. "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
  6717. $content
  6718. );
  6719. // The second regexp deals with audio/ urls.
  6720. $content = preg_replace(
  6721. "|(flashvars=\"file=)([^:/]+)/|",
  6722. "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
  6723. $content
  6724. );
  6725. fputs($fp, $content);
  6726. fclose($fp);
  6727. $sql = "UPDATE $table_doc SET
  6728. title='".Database::escape_string($_POST['title'])."'
  6729. WHERE c_id = $course_id AND id = ".$document_id;
  6730. Database::query($sql);
  6731. }
  6732. }
  6733. }
  6734. /**
  6735. * Displays the selected item, with a panel for manipulating the item.
  6736. *
  6737. * @param int $item_id
  6738. * @param string $msg
  6739. * @param bool $show_actions
  6740. *
  6741. * @return string
  6742. */
  6743. public function display_item($item_id, $msg = null, $show_actions = true)
  6744. {
  6745. $course_id = api_get_course_int_id();
  6746. $return = '';
  6747. if (is_numeric($item_id)) {
  6748. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  6749. $sql = "SELECT lp.* FROM $tbl_lp_item as lp
  6750. WHERE lp.iid = ".intval($item_id);
  6751. $result = Database::query($sql);
  6752. while ($row = Database::fetch_array($result, 'ASSOC')) {
  6753. $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
  6754. // Prevents wrong parent selection for document, see Bug#1251.
  6755. if ($row['item_type'] != 'dir') {
  6756. $_SESSION['parent_item_id'] = $row['parent_item_id'];
  6757. }
  6758. if ($show_actions) {
  6759. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6760. }
  6761. $return .= '<div style="padding:10px;">';
  6762. if ($msg != '') {
  6763. $return .= $msg;
  6764. }
  6765. $return .= '<h3>'.$row['title'].'</h3>';
  6766. switch ($row['item_type']) {
  6767. case TOOL_THREAD:
  6768. $link = $this->rl_get_resource_link_for_learnpath(
  6769. $course_id,
  6770. $row['lp_id'],
  6771. $item_id,
  6772. 0
  6773. );
  6774. $return .= Display::url(
  6775. get_lang('GoToThread'),
  6776. $link,
  6777. ['class' => 'btn btn-primary']
  6778. );
  6779. break;
  6780. case TOOL_FORUM:
  6781. $return .= Display::url(
  6782. get_lang('GoToForum'),
  6783. api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
  6784. ['class' => 'btn btn-primary']
  6785. );
  6786. break;
  6787. case TOOL_QUIZ:
  6788. if (!empty($row['path'])) {
  6789. $exercise = new Exercise();
  6790. $exercise->read($row['path']);
  6791. $return .= $exercise->description.'<br />';
  6792. $return .= Display::url(
  6793. get_lang('GoToExercise'),
  6794. api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
  6795. ['class' => 'btn btn-primary']
  6796. );
  6797. }
  6798. break;
  6799. case TOOL_LP_FINAL_ITEM:
  6800. $return .= $this->getSavedFinalItem();
  6801. break;
  6802. case TOOL_DOCUMENT:
  6803. case TOOL_READOUT_TEXT:
  6804. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6805. $sql_doc = "SELECT path FROM ".$tbl_doc."
  6806. WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
  6807. $result = Database::query($sql_doc);
  6808. $path_file = Database::result($result, 0, 0);
  6809. $path_parts = pathinfo($path_file);
  6810. // TODO: Correct the following naive comparisons.
  6811. if (in_array($path_parts['extension'], [
  6812. 'html',
  6813. 'txt',
  6814. 'png',
  6815. 'jpg',
  6816. 'JPG',
  6817. 'jpeg',
  6818. 'JPEG',
  6819. 'gif',
  6820. 'swf',
  6821. 'pdf',
  6822. 'htm',
  6823. ])) {
  6824. $return .= $this->display_document($row['path'], true, true);
  6825. }
  6826. break;
  6827. case TOOL_HOTPOTATOES:
  6828. $return .= $this->display_document($row['path'], false, true);
  6829. break;
  6830. }
  6831. $return .= '</div>';
  6832. }
  6833. }
  6834. return $return;
  6835. }
  6836. /**
  6837. * Shows the needed forms for editing a specific item.
  6838. *
  6839. * @param int $item_id
  6840. *
  6841. * @throws Exception
  6842. * @throws HTML_QuickForm_Error
  6843. *
  6844. * @return string
  6845. */
  6846. public function display_edit_item($item_id)
  6847. {
  6848. $course_id = api_get_course_int_id();
  6849. $return = '';
  6850. $item_id = (int) $item_id;
  6851. if (empty($item_id)) {
  6852. return '';
  6853. }
  6854. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  6855. $sql = "SELECT * FROM $tbl_lp_item
  6856. WHERE iid = ".$item_id;
  6857. $res = Database::query($sql);
  6858. $row = Database::fetch_array($res);
  6859. switch ($row['item_type']) {
  6860. case 'dir':
  6861. case 'asset':
  6862. case 'sco':
  6863. if (isset($_GET['view']) && $_GET['view'] == 'build') {
  6864. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6865. $return .= $this->display_item_form(
  6866. $row['item_type'],
  6867. get_lang('EditCurrentChapter').' :',
  6868. 'edit',
  6869. $item_id,
  6870. $row
  6871. );
  6872. } else {
  6873. $return .= $this->display_item_form(
  6874. $row['item_type'],
  6875. get_lang('EditCurrentChapter').' :',
  6876. 'edit_item',
  6877. $item_id,
  6878. $row
  6879. );
  6880. }
  6881. break;
  6882. case TOOL_DOCUMENT:
  6883. case TOOL_READOUT_TEXT:
  6884. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6885. $sql = "SELECT lp.*, doc.path as dir
  6886. FROM $tbl_lp_item as lp
  6887. LEFT JOIN $tbl_doc as doc
  6888. ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
  6889. WHERE
  6890. doc.c_id = $course_id AND
  6891. lp.iid = ".$item_id;
  6892. $res_step = Database::query($sql);
  6893. $row_step = Database::fetch_array($res_step, 'ASSOC');
  6894. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6895. if ($row['item_type'] === TOOL_DOCUMENT) {
  6896. $return .= $this->display_document_form('edit', $item_id, $row_step);
  6897. }
  6898. if ($row['item_type'] === TOOL_READOUT_TEXT) {
  6899. $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
  6900. }
  6901. break;
  6902. case TOOL_LINK:
  6903. $linkId = (int) $row['path'];
  6904. if (!empty($linkId)) {
  6905. $table = Database::get_course_table(TABLE_LINK);
  6906. $sql = 'SELECT url FROM '.$table.'
  6907. WHERE c_id = '.$course_id.' AND iid = '.$linkId;
  6908. $res_link = Database::query($sql);
  6909. $row_link = Database::fetch_array($res_link);
  6910. if (empty($row_link)) {
  6911. // Try with id
  6912. $sql = 'SELECT url FROM '.$table.'
  6913. WHERE c_id = '.$course_id.' AND id = '.$linkId;
  6914. $res_link = Database::query($sql);
  6915. $row_link = Database::fetch_array($res_link);
  6916. }
  6917. if (is_array($row_link)) {
  6918. $row['url'] = $row_link['url'];
  6919. }
  6920. }
  6921. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6922. $return .= $this->display_link_form('edit', $item_id, $row);
  6923. break;
  6924. case TOOL_LP_FINAL_ITEM:
  6925. Session::write('finalItem', true);
  6926. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6927. $sql = "SELECT lp.*, doc.path as dir
  6928. FROM $tbl_lp_item as lp
  6929. LEFT JOIN $tbl_doc as doc
  6930. ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
  6931. WHERE
  6932. doc.c_id = $course_id AND
  6933. lp.iid = ".$item_id;
  6934. $res_step = Database::query($sql);
  6935. $row_step = Database::fetch_array($res_step, 'ASSOC');
  6936. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6937. $return .= $this->display_document_form('edit', $item_id, $row_step);
  6938. break;
  6939. case TOOL_QUIZ:
  6940. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6941. $return .= $this->display_quiz_form('edit', $item_id, $row);
  6942. break;
  6943. case TOOL_HOTPOTATOES:
  6944. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6945. $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
  6946. break;
  6947. case TOOL_STUDENTPUBLICATION:
  6948. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6949. $return .= $this->display_student_publication_form('edit', $item_id, $row);
  6950. break;
  6951. case TOOL_FORUM:
  6952. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6953. $return .= $this->display_forum_form('edit', $item_id, $row);
  6954. break;
  6955. case TOOL_THREAD:
  6956. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6957. $return .= $this->display_thread_form('edit', $item_id, $row);
  6958. break;
  6959. }
  6960. return $return;
  6961. }
  6962. /**
  6963. * Function that displays a list with al the resources that
  6964. * could be added to the learning path.
  6965. *
  6966. * @throws Exception
  6967. * @throws HTML_QuickForm_Error
  6968. *
  6969. * @return bool
  6970. */
  6971. public function display_resources()
  6972. {
  6973. $course_code = api_get_course_id();
  6974. // Get all the docs.
  6975. $documents = $this->get_documents(true);
  6976. // Get all the exercises.
  6977. $exercises = $this->get_exercises();
  6978. // Get all the links.
  6979. $links = $this->get_links();
  6980. // Get all the student publications.
  6981. $works = $this->get_student_publications();
  6982. // Get all the forums.
  6983. $forums = $this->get_forums(null, $course_code);
  6984. // Get the final item form (see BT#11048) .
  6985. $finish = $this->getFinalItemForm();
  6986. $headers = [
  6987. Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
  6988. Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
  6989. Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
  6990. Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
  6991. Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
  6992. Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
  6993. Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
  6994. ];
  6995. echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
  6996. $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
  6997. echo Display::tabs(
  6998. $headers,
  6999. [
  7000. $documents,
  7001. $exercises,
  7002. $links,
  7003. $works,
  7004. $forums,
  7005. $dir,
  7006. $finish,
  7007. ],
  7008. 'resource_tab'
  7009. );
  7010. return true;
  7011. }
  7012. /**
  7013. * Returns the extension of a document.
  7014. *
  7015. * @param string $filename
  7016. *
  7017. * @return string Extension (part after the last dot)
  7018. */
  7019. public function get_extension($filename)
  7020. {
  7021. $explode = explode('.', $filename);
  7022. return $explode[count($explode) - 1];
  7023. }
  7024. /**
  7025. * Displays a document by id.
  7026. *
  7027. * @param int $id
  7028. * @param bool $show_title
  7029. * @param bool $iframe
  7030. * @param bool $edit_link
  7031. *
  7032. * @return string
  7033. */
  7034. public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
  7035. {
  7036. $_course = api_get_course_info();
  7037. $course_id = api_get_course_int_id();
  7038. $id = (int) $id;
  7039. $return = '';
  7040. $table = Database::get_course_table(TABLE_DOCUMENT);
  7041. $sql_doc = "SELECT * FROM $table
  7042. WHERE c_id = $course_id AND iid = $id";
  7043. $res_doc = Database::query($sql_doc);
  7044. $row_doc = Database::fetch_array($res_doc);
  7045. // TODO: Add a path filter.
  7046. if ($iframe) {
  7047. $return .= '<iframe id="learnpath_preview_frame" frameborder="0" height="400" width="100%" scrolling="auto" src="'.api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq().'"></iframe>';
  7048. } else {
  7049. $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
  7050. }
  7051. return $return;
  7052. }
  7053. /**
  7054. * Return HTML form to add/edit a quiz.
  7055. *
  7056. * @param string $action Action (add/edit)
  7057. * @param int $id Item ID if already exists
  7058. * @param mixed $extra_info Extra information (quiz ID if integer)
  7059. *
  7060. * @throws Exception
  7061. *
  7062. * @return string HTML form
  7063. */
  7064. public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
  7065. {
  7066. $course_id = api_get_course_int_id();
  7067. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  7068. $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
  7069. if ($id != 0 && is_array($extra_info)) {
  7070. $item_title = $extra_info['title'];
  7071. $item_description = $extra_info['description'];
  7072. } elseif (is_numeric($extra_info)) {
  7073. $sql = "SELECT title, description
  7074. FROM $tbl_quiz
  7075. WHERE c_id = $course_id AND iid = ".$extra_info;
  7076. $result = Database::query($sql);
  7077. $row = Database::fetch_array($result);
  7078. $item_title = $row['title'];
  7079. $item_description = $row['description'];
  7080. } else {
  7081. $item_title = '';
  7082. $item_description = '';
  7083. }
  7084. $item_title = Security::remove_XSS($item_title);
  7085. $item_description = Security::remove_XSS($item_description);
  7086. if ($id != 0 && is_array($extra_info)) {
  7087. $parent = $extra_info['parent_item_id'];
  7088. } else {
  7089. $parent = 0;
  7090. }
  7091. $sql = "SELECT * FROM $tbl_lp_item
  7092. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  7093. $result = Database::query($sql);
  7094. $arrLP = [];
  7095. while ($row = Database::fetch_array($result)) {
  7096. $arrLP[] = [
  7097. 'id' => $row['iid'],
  7098. 'item_type' => $row['item_type'],
  7099. 'title' => $row['title'],
  7100. 'path' => $row['path'],
  7101. 'description' => $row['description'],
  7102. 'parent_item_id' => $row['parent_item_id'],
  7103. 'previous_item_id' => $row['previous_item_id'],
  7104. 'next_item_id' => $row['next_item_id'],
  7105. 'display_order' => $row['display_order'],
  7106. 'max_score' => $row['max_score'],
  7107. 'min_score' => $row['min_score'],
  7108. 'mastery_score' => $row['mastery_score'],
  7109. 'prerequisite' => $row['prerequisite'],
  7110. 'max_time_allowed' => $row['max_time_allowed'],
  7111. ];
  7112. }
  7113. $this->tree_array($arrLP);
  7114. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7115. unset($this->arrMenu);
  7116. $form = new FormValidator(
  7117. 'quiz_form',
  7118. 'POST',
  7119. $this->getCurrentBuildingModeURL()
  7120. );
  7121. $defaults = [];
  7122. if ($action == 'add') {
  7123. $legend = get_lang('CreateTheExercise');
  7124. } elseif ($action == 'move') {
  7125. $legend = get_lang('MoveTheCurrentExercise');
  7126. } else {
  7127. $legend = get_lang('EditCurrentExecice');
  7128. }
  7129. if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
  7130. $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
  7131. }
  7132. $form->addHeader($legend);
  7133. if ($action != 'move') {
  7134. $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
  7135. $defaults['title'] = $item_title;
  7136. }
  7137. // Select for Parent item, root or chapter
  7138. $selectParent = $form->addSelect(
  7139. 'parent',
  7140. get_lang('Parent'),
  7141. [],
  7142. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
  7143. );
  7144. $selectParent->addOption($this->name, 0);
  7145. $arrHide = [
  7146. $id,
  7147. ];
  7148. for ($i = 0; $i < count($arrLP); $i++) {
  7149. if ($action != 'add') {
  7150. if (
  7151. ($arrLP[$i]['item_type'] == 'dir') &&
  7152. !in_array($arrLP[$i]['id'], $arrHide) &&
  7153. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7154. ) {
  7155. $selectParent->addOption(
  7156. $arrLP[$i]['title'],
  7157. $arrLP[$i]['id'],
  7158. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7159. );
  7160. if ($parent == $arrLP[$i]['id']) {
  7161. $selectParent->setSelected($arrLP[$i]['id']);
  7162. }
  7163. } else {
  7164. $arrHide[] = $arrLP[$i]['id'];
  7165. }
  7166. } else {
  7167. if ($arrLP[$i]['item_type'] == 'dir') {
  7168. $selectParent->addOption(
  7169. $arrLP[$i]['title'],
  7170. $arrLP[$i]['id'],
  7171. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7172. );
  7173. if ($parent == $arrLP[$i]['id']) {
  7174. $selectParent->setSelected($arrLP[$i]['id']);
  7175. }
  7176. }
  7177. }
  7178. }
  7179. if (is_array($arrLP)) {
  7180. reset($arrLP);
  7181. }
  7182. $selectPrevious = $form->addSelect(
  7183. 'previous',
  7184. get_lang('Position'),
  7185. [],
  7186. ['id' => 'previous']
  7187. );
  7188. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  7189. for ($i = 0; $i < count($arrLP); $i++) {
  7190. if ($arrLP[$i]['parent_item_id'] == $parent &&
  7191. $arrLP[$i]['id'] != $id
  7192. ) {
  7193. $selectPrevious->addOption(
  7194. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  7195. $arrLP[$i]['id']
  7196. );
  7197. if (is_array($extra_info)) {
  7198. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  7199. $selectPrevious->setSelected($arrLP[$i]['id']);
  7200. }
  7201. } elseif ($action == 'add') {
  7202. $selectPrevious->setSelected($arrLP[$i]['id']);
  7203. }
  7204. }
  7205. }
  7206. if ($action != 'move') {
  7207. $arrHide = [];
  7208. for ($i = 0; $i < count($arrLP); $i++) {
  7209. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  7210. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7211. }
  7212. }
  7213. }
  7214. if ($action == 'add') {
  7215. $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
  7216. } else {
  7217. $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
  7218. }
  7219. if ($action == 'move') {
  7220. $form->addHidden('title', $item_title);
  7221. $form->addHidden('description', $item_description);
  7222. }
  7223. if (is_numeric($extra_info)) {
  7224. $form->addHidden('path', $extra_info);
  7225. } elseif (is_array($extra_info)) {
  7226. $form->addHidden('path', $extra_info['path']);
  7227. }
  7228. $form->addHidden('type', TOOL_QUIZ);
  7229. $form->addHidden('post_time', time());
  7230. $form->setDefaults($defaults);
  7231. return '<div class="sectioncomment">'.$form->returnForm().'</div>';
  7232. }
  7233. /**
  7234. * Addition of Hotpotatoes tests.
  7235. *
  7236. * @param string $action
  7237. * @param int $id Internal ID of the item
  7238. * @param string $extra_info
  7239. *
  7240. * @return string HTML structure to display the hotpotatoes addition formular
  7241. */
  7242. public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
  7243. {
  7244. $course_id = api_get_course_int_id();
  7245. $uploadPath = DIR_HOTPOTATOES; //defined in main_api
  7246. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  7247. if ($id != 0 && is_array($extra_info)) {
  7248. $item_title = stripslashes($extra_info['title']);
  7249. $item_description = stripslashes($extra_info['description']);
  7250. } elseif (is_numeric($extra_info)) {
  7251. $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
  7252. $sql = "SELECT * FROM ".$TBL_DOCUMENT."
  7253. WHERE
  7254. c_id = ".$course_id." AND
  7255. path LIKE '".$uploadPath."/%/%htm%' AND
  7256. iid = ".(int) $extra_info."
  7257. ORDER BY iid ASC";
  7258. $res_hot = Database::query($sql);
  7259. $row = Database::fetch_array($res_hot);
  7260. $item_title = $row['title'];
  7261. $item_description = $row['description'];
  7262. if (!empty($row['comment'])) {
  7263. $item_title = $row['comment'];
  7264. }
  7265. } else {
  7266. $item_title = '';
  7267. $item_description = '';
  7268. }
  7269. if ($id != 0 && is_array($extra_info)) {
  7270. $parent = $extra_info['parent_item_id'];
  7271. } else {
  7272. $parent = 0;
  7273. }
  7274. $sql = "SELECT * FROM $tbl_lp_item
  7275. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  7276. $result = Database::query($sql);
  7277. $arrLP = [];
  7278. while ($row = Database::fetch_array($result)) {
  7279. $arrLP[] = [
  7280. 'id' => $row['id'],
  7281. 'item_type' => $row['item_type'],
  7282. 'title' => $row['title'],
  7283. 'path' => $row['path'],
  7284. 'description' => $row['description'],
  7285. 'parent_item_id' => $row['parent_item_id'],
  7286. 'previous_item_id' => $row['previous_item_id'],
  7287. 'next_item_id' => $row['next_item_id'],
  7288. 'display_order' => $row['display_order'],
  7289. 'max_score' => $row['max_score'],
  7290. 'min_score' => $row['min_score'],
  7291. 'mastery_score' => $row['mastery_score'],
  7292. 'prerequisite' => $row['prerequisite'],
  7293. 'max_time_allowed' => $row['max_time_allowed'],
  7294. ];
  7295. }
  7296. $legend = '<legend>';
  7297. if ($action == 'add') {
  7298. $legend .= get_lang('CreateTheExercise');
  7299. } elseif ($action == 'move') {
  7300. $legend .= get_lang('MoveTheCurrentExercise');
  7301. } else {
  7302. $legend .= get_lang('EditCurrentExecice');
  7303. }
  7304. if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
  7305. $legend .= Display:: return_message(
  7306. get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
  7307. );
  7308. }
  7309. $legend .= '</legend>';
  7310. $return = '<form method="POST">';
  7311. $return .= $legend;
  7312. $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
  7313. $return .= '<tr>';
  7314. $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
  7315. $return .= '<td class="input">';
  7316. $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
  7317. $return .= '<option class="top" value="0">'.$this->name.'</option>';
  7318. $arrHide = [
  7319. $id,
  7320. ];
  7321. if (count($arrLP) > 0) {
  7322. for ($i = 0; $i < count($arrLP); $i++) {
  7323. if ($action != 'add') {
  7324. if ($arrLP[$i]['item_type'] == 'dir' &&
  7325. !in_array($arrLP[$i]['id'], $arrHide) &&
  7326. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7327. ) {
  7328. $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
  7329. } else {
  7330. $arrHide[] = $arrLP[$i]['id'];
  7331. }
  7332. } else {
  7333. if ($arrLP[$i]['item_type'] == 'dir') {
  7334. $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
  7335. }
  7336. }
  7337. }
  7338. reset($arrLP);
  7339. }
  7340. $return .= '</select>';
  7341. $return .= '</td>';
  7342. $return .= '</tr>';
  7343. $return .= '<tr>';
  7344. $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
  7345. $return .= '<td class="input">';
  7346. $return .= '<select id="previous" name="previous" size="1">';
  7347. $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
  7348. for ($i = 0; $i < count($arrLP); $i++) {
  7349. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  7350. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  7351. $selected = 'selected="selected" ';
  7352. } elseif ($action == 'add') {
  7353. $selected = 'selected="selected" ';
  7354. } else {
  7355. $selected = '';
  7356. }
  7357. $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
  7358. }
  7359. }
  7360. $return .= '</select>';
  7361. $return .= '</td>';
  7362. $return .= '</tr>';
  7363. if ($action != 'move') {
  7364. $return .= '<tr>';
  7365. $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
  7366. $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
  7367. $return .= '</tr>';
  7368. $id_prerequisite = 0;
  7369. if (is_array($arrLP) && count($arrLP) > 0) {
  7370. foreach ($arrLP as $key => $value) {
  7371. if ($value['id'] == $id) {
  7372. $id_prerequisite = $value['prerequisite'];
  7373. break;
  7374. }
  7375. }
  7376. $arrHide = [];
  7377. for ($i = 0; $i < count($arrLP); $i++) {
  7378. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  7379. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7380. }
  7381. }
  7382. }
  7383. }
  7384. $return .= '<tr>';
  7385. $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
  7386. get_lang('SaveHotpotatoes').'</button></td>';
  7387. $return .= '</tr>';
  7388. $return .= '</table>';
  7389. if ($action == 'move') {
  7390. $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
  7391. $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
  7392. }
  7393. if (is_numeric($extra_info)) {
  7394. $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
  7395. } elseif (is_array($extra_info)) {
  7396. $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
  7397. }
  7398. $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
  7399. $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
  7400. $return .= '</form>';
  7401. return $return;
  7402. }
  7403. /**
  7404. * Return the form to display the forum edit/add option.
  7405. *
  7406. * @param string $action
  7407. * @param int $id ID of the lp_item if already exists
  7408. * @param string $extra_info
  7409. *
  7410. * @throws Exception
  7411. *
  7412. * @return string HTML form
  7413. */
  7414. public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
  7415. {
  7416. $course_id = api_get_course_int_id();
  7417. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  7418. $tbl_forum = Database::get_course_table(TABLE_FORUM);
  7419. if ($id != 0 && is_array($extra_info)) {
  7420. $item_title = stripslashes($extra_info['title']);
  7421. } elseif (is_numeric($extra_info)) {
  7422. $sql = "SELECT forum_title as title, forum_comment as comment
  7423. FROM ".$tbl_forum."
  7424. WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
  7425. $result = Database::query($sql);
  7426. $row = Database::fetch_array($result);
  7427. $item_title = $row['title'];
  7428. $item_description = $row['comment'];
  7429. } else {
  7430. $item_title = '';
  7431. $item_description = '';
  7432. }
  7433. if ($id != 0 && is_array($extra_info)) {
  7434. $parent = $extra_info['parent_item_id'];
  7435. } else {
  7436. $parent = 0;
  7437. }
  7438. $sql = "SELECT * FROM $tbl_lp_item
  7439. WHERE
  7440. c_id = $course_id AND
  7441. lp_id = ".$this->lp_id;
  7442. $result = Database::query($sql);
  7443. $arrLP = [];
  7444. while ($row = Database::fetch_array($result)) {
  7445. $arrLP[] = [
  7446. 'id' => $row['iid'],
  7447. 'item_type' => $row['item_type'],
  7448. 'title' => $row['title'],
  7449. 'path' => $row['path'],
  7450. 'description' => $row['description'],
  7451. 'parent_item_id' => $row['parent_item_id'],
  7452. 'previous_item_id' => $row['previous_item_id'],
  7453. 'next_item_id' => $row['next_item_id'],
  7454. 'display_order' => $row['display_order'],
  7455. 'max_score' => $row['max_score'],
  7456. 'min_score' => $row['min_score'],
  7457. 'mastery_score' => $row['mastery_score'],
  7458. 'prerequisite' => $row['prerequisite'],
  7459. ];
  7460. }
  7461. $this->tree_array($arrLP);
  7462. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7463. unset($this->arrMenu);
  7464. if ($action == 'add') {
  7465. $legend = get_lang('CreateTheForum');
  7466. } elseif ($action == 'move') {
  7467. $legend = get_lang('MoveTheCurrentForum');
  7468. } else {
  7469. $legend = get_lang('EditCurrentForum');
  7470. }
  7471. $form = new FormValidator(
  7472. 'forum_form',
  7473. 'POST',
  7474. $this->getCurrentBuildingModeURL()
  7475. );
  7476. $defaults = [];
  7477. $form->addHeader($legend);
  7478. if ($action != 'move') {
  7479. $form->addText(
  7480. 'title',
  7481. get_lang('Title'),
  7482. true,
  7483. ['id' => 'idTitle', 'class' => 'learnpath_item_form']
  7484. );
  7485. $defaults['title'] = $item_title;
  7486. }
  7487. $selectParent = $form->addSelect(
  7488. 'parent',
  7489. get_lang('Parent'),
  7490. [],
  7491. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
  7492. );
  7493. $selectParent->addOption($this->name, 0);
  7494. $arrHide = [
  7495. $id,
  7496. ];
  7497. for ($i = 0; $i < count($arrLP); $i++) {
  7498. if ($action != 'add') {
  7499. if ($arrLP[$i]['item_type'] == 'dir' &&
  7500. !in_array($arrLP[$i]['id'], $arrHide) &&
  7501. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7502. ) {
  7503. $selectParent->addOption(
  7504. $arrLP[$i]['title'],
  7505. $arrLP[$i]['id'],
  7506. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7507. );
  7508. if ($parent == $arrLP[$i]['id']) {
  7509. $selectParent->setSelected($arrLP[$i]['id']);
  7510. }
  7511. } else {
  7512. $arrHide[] = $arrLP[$i]['id'];
  7513. }
  7514. } else {
  7515. if ($arrLP[$i]['item_type'] == 'dir') {
  7516. $selectParent->addOption(
  7517. $arrLP[$i]['title'],
  7518. $arrLP[$i]['id'],
  7519. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7520. );
  7521. if ($parent == $arrLP[$i]['id']) {
  7522. $selectParent->setSelected($arrLP[$i]['id']);
  7523. }
  7524. }
  7525. }
  7526. }
  7527. if (is_array($arrLP)) {
  7528. reset($arrLP);
  7529. }
  7530. $selectPrevious = $form->addSelect(
  7531. 'previous',
  7532. get_lang('Position'),
  7533. [],
  7534. ['id' => 'previous', 'class' => 'learnpath_item_form']
  7535. );
  7536. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  7537. for ($i = 0; $i < count($arrLP); $i++) {
  7538. if ($arrLP[$i]['parent_item_id'] == $parent &&
  7539. $arrLP[$i]['id'] != $id
  7540. ) {
  7541. $selectPrevious->addOption(
  7542. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  7543. $arrLP[$i]['id']
  7544. );
  7545. if (isset($extra_info['previous_item_id']) &&
  7546. $extra_info['previous_item_id'] == $arrLP[$i]['id']
  7547. ) {
  7548. $selectPrevious->setSelected($arrLP[$i]['id']);
  7549. } elseif ($action == 'add') {
  7550. $selectPrevious->setSelected($arrLP[$i]['id']);
  7551. }
  7552. }
  7553. }
  7554. if ($action != 'move') {
  7555. $id_prerequisite = 0;
  7556. if (is_array($arrLP)) {
  7557. foreach ($arrLP as $key => $value) {
  7558. if ($value['id'] == $id) {
  7559. $id_prerequisite = $value['prerequisite'];
  7560. break;
  7561. }
  7562. }
  7563. }
  7564. $arrHide = [];
  7565. for ($i = 0; $i < count($arrLP); $i++) {
  7566. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  7567. if (isset($extra_info['previous_item_id']) &&
  7568. $extra_info['previous_item_id'] == $arrLP[$i]['id']
  7569. ) {
  7570. $s_selected_position = $arrLP[$i]['id'];
  7571. } elseif ($action == 'add') {
  7572. $s_selected_position = 0;
  7573. }
  7574. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7575. }
  7576. }
  7577. }
  7578. if ($action == 'add') {
  7579. $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
  7580. } else {
  7581. $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
  7582. }
  7583. if ($action == 'move') {
  7584. $form->addHidden('title', $item_title);
  7585. $form->addHidden('description', $item_description);
  7586. }
  7587. if (is_numeric($extra_info)) {
  7588. $form->addHidden('path', $extra_info);
  7589. } elseif (is_array($extra_info)) {
  7590. $form->addHidden('path', $extra_info['path']);
  7591. }
  7592. $form->addHidden('type', TOOL_FORUM);
  7593. $form->addHidden('post_time', time());
  7594. $form->setDefaults($defaults);
  7595. return '<div class="sectioncomment">'.$form->returnForm().'</div>';
  7596. }
  7597. /**
  7598. * Return HTML form to add/edit forum threads.
  7599. *
  7600. * @param string $action
  7601. * @param int $id Item ID if already exists in learning path
  7602. * @param string $extra_info
  7603. *
  7604. * @throws Exception
  7605. *
  7606. * @return string HTML form
  7607. */
  7608. public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
  7609. {
  7610. $course_id = api_get_course_int_id();
  7611. if (empty($course_id)) {
  7612. return null;
  7613. }
  7614. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  7615. $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
  7616. if ($id != 0 && is_array($extra_info)) {
  7617. $item_title = stripslashes($extra_info['title']);
  7618. } elseif (is_numeric($extra_info)) {
  7619. $sql = "SELECT thread_title as title FROM $tbl_forum
  7620. WHERE c_id = $course_id AND thread_id = ".$extra_info;
  7621. $result = Database::query($sql);
  7622. $row = Database::fetch_array($result);
  7623. $item_title = $row['title'];
  7624. $item_description = '';
  7625. } else {
  7626. $item_title = '';
  7627. $item_description = '';
  7628. }
  7629. if ($id != 0 && is_array($extra_info)) {
  7630. $parent = $extra_info['parent_item_id'];
  7631. } else {
  7632. $parent = 0;
  7633. }
  7634. $sql = "SELECT * FROM $tbl_lp_item
  7635. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  7636. $result = Database::query($sql);
  7637. $arrLP = [];
  7638. while ($row = Database::fetch_array($result)) {
  7639. $arrLP[] = [
  7640. 'id' => $row['iid'],
  7641. 'item_type' => $row['item_type'],
  7642. 'title' => $row['title'],
  7643. 'path' => $row['path'],
  7644. 'description' => $row['description'],
  7645. 'parent_item_id' => $row['parent_item_id'],
  7646. 'previous_item_id' => $row['previous_item_id'],
  7647. 'next_item_id' => $row['next_item_id'],
  7648. 'display_order' => $row['display_order'],
  7649. 'max_score' => $row['max_score'],
  7650. 'min_score' => $row['min_score'],
  7651. 'mastery_score' => $row['mastery_score'],
  7652. 'prerequisite' => $row['prerequisite'],
  7653. ];
  7654. }
  7655. $this->tree_array($arrLP);
  7656. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7657. unset($this->arrMenu);
  7658. $form = new FormValidator(
  7659. 'thread_form',
  7660. 'POST',
  7661. $this->getCurrentBuildingModeURL()
  7662. );
  7663. $defaults = [];
  7664. if ($action == 'add') {
  7665. $legend = get_lang('CreateTheForum');
  7666. } elseif ($action == 'move') {
  7667. $legend = get_lang('MoveTheCurrentForum');
  7668. } else {
  7669. $legend = get_lang('EditCurrentForum');
  7670. }
  7671. $form->addHeader($legend);
  7672. $selectParent = $form->addSelect(
  7673. 'parent',
  7674. get_lang('Parent'),
  7675. [],
  7676. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
  7677. );
  7678. $selectParent->addOption($this->name, 0);
  7679. $arrHide = [
  7680. $id,
  7681. ];
  7682. for ($i = 0; $i < count($arrLP); $i++) {
  7683. if ($action != 'add') {
  7684. if (
  7685. ($arrLP[$i]['item_type'] == 'dir') &&
  7686. !in_array($arrLP[$i]['id'], $arrHide) &&
  7687. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7688. ) {
  7689. $selectParent->addOption(
  7690. $arrLP[$i]['title'],
  7691. $arrLP[$i]['id'],
  7692. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7693. );
  7694. if ($parent == $arrLP[$i]['id']) {
  7695. $selectParent->setSelected($arrLP[$i]['id']);
  7696. }
  7697. } else {
  7698. $arrHide[] = $arrLP[$i]['id'];
  7699. }
  7700. } else {
  7701. if ($arrLP[$i]['item_type'] == 'dir') {
  7702. $selectParent->addOption(
  7703. $arrLP[$i]['title'],
  7704. $arrLP[$i]['id'],
  7705. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7706. );
  7707. if ($parent == $arrLP[$i]['id']) {
  7708. $selectParent->setSelected($arrLP[$i]['id']);
  7709. }
  7710. }
  7711. }
  7712. }
  7713. if ($arrLP != null) {
  7714. reset($arrLP);
  7715. }
  7716. $selectPrevious = $form->addSelect(
  7717. 'previous',
  7718. get_lang('Position'),
  7719. [],
  7720. ['id' => 'previous']
  7721. );
  7722. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  7723. for ($i = 0; $i < count($arrLP); $i++) {
  7724. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  7725. $selectPrevious->addOption(
  7726. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  7727. $arrLP[$i]['id']
  7728. );
  7729. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  7730. $selectPrevious->setSelected($arrLP[$i]['id']);
  7731. } elseif ($action == 'add') {
  7732. $selectPrevious->setSelected($arrLP[$i]['id']);
  7733. }
  7734. }
  7735. }
  7736. if ($action != 'move') {
  7737. $form->addText(
  7738. 'title',
  7739. get_lang('Title'),
  7740. true,
  7741. ['id' => 'idTitle']
  7742. );
  7743. $defaults['title'] = $item_title;
  7744. $id_prerequisite = 0;
  7745. if ($arrLP != null) {
  7746. foreach ($arrLP as $key => $value) {
  7747. if ($value['id'] == $id) {
  7748. $id_prerequisite = $value['prerequisite'];
  7749. break;
  7750. }
  7751. }
  7752. }
  7753. $arrHide = [];
  7754. $s_selected_position = 0;
  7755. for ($i = 0; $i < count($arrLP); $i++) {
  7756. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  7757. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  7758. $s_selected_position = $arrLP[$i]['id'];
  7759. } elseif ($action == 'add') {
  7760. $s_selected_position = 0;
  7761. }
  7762. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7763. }
  7764. }
  7765. $selectPrerequisites = $form->addSelect(
  7766. 'prerequisites',
  7767. get_lang('LearnpathPrerequisites'),
  7768. [],
  7769. ['id' => 'prerequisites']
  7770. );
  7771. $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
  7772. foreach ($arrHide as $key => $value) {
  7773. $selectPrerequisites->addOption($value['value'], $key);
  7774. if ($key == $s_selected_position && $action == 'add') {
  7775. $selectPrerequisites->setSelected($key);
  7776. } elseif ($key == $id_prerequisite && $action == 'edit') {
  7777. $selectPrerequisites->setSelected($key);
  7778. }
  7779. }
  7780. }
  7781. $form->addButtonSave(get_lang('Ok'), 'submit_button');
  7782. if ($action == 'move') {
  7783. $form->addHidden('title', $item_title);
  7784. $form->addHidden('description', $item_description);
  7785. }
  7786. if (is_numeric($extra_info)) {
  7787. $form->addHidden('path', $extra_info);
  7788. } elseif (is_array($extra_info)) {
  7789. $form->addHidden('path', $extra_info['path']);
  7790. }
  7791. $form->addHidden('type', TOOL_THREAD);
  7792. $form->addHidden('post_time', time());
  7793. $form->setDefaults($defaults);
  7794. return $form->returnForm();
  7795. }
  7796. /**
  7797. * Return the HTML form to display an item (generally a dir item).
  7798. *
  7799. * @param string $item_type
  7800. * @param string $title
  7801. * @param string $action
  7802. * @param int $id
  7803. * @param string $extra_info
  7804. *
  7805. * @throws Exception
  7806. * @throws HTML_QuickForm_Error
  7807. *
  7808. * @return string HTML form
  7809. */
  7810. public function display_item_form(
  7811. $item_type,
  7812. $title = '',
  7813. $action = 'add_item',
  7814. $id = 0,
  7815. $extra_info = 'new'
  7816. ) {
  7817. $_course = api_get_course_info();
  7818. global $charset;
  7819. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  7820. $item_title = '';
  7821. $item_description = '';
  7822. $item_path_fck = '';
  7823. if ($id != 0 && is_array($extra_info)) {
  7824. $item_title = $extra_info['title'];
  7825. $item_description = $extra_info['description'];
  7826. $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
  7827. $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
  7828. }
  7829. $parent = 0;
  7830. if ($id != 0 && is_array($extra_info)) {
  7831. $parent = $extra_info['parent_item_id'];
  7832. }
  7833. $id = (int) $id;
  7834. $sql = "SELECT * FROM $tbl_lp_item
  7835. WHERE
  7836. lp_id = ".$this->lp_id." AND
  7837. iid != $id";
  7838. if ($item_type == 'dir') {
  7839. $sql .= " AND parent_item_id = 0";
  7840. }
  7841. $result = Database::query($sql);
  7842. $arrLP = [];
  7843. while ($row = Database::fetch_array($result)) {
  7844. $arrLP[] = [
  7845. 'id' => $row['iid'],
  7846. 'item_type' => $row['item_type'],
  7847. 'title' => $row['title'],
  7848. 'path' => $row['path'],
  7849. 'description' => $row['description'],
  7850. 'parent_item_id' => $row['parent_item_id'],
  7851. 'previous_item_id' => $row['previous_item_id'],
  7852. 'next_item_id' => $row['next_item_id'],
  7853. 'max_score' => $row['max_score'],
  7854. 'min_score' => $row['min_score'],
  7855. 'mastery_score' => $row['mastery_score'],
  7856. 'prerequisite' => $row['prerequisite'],
  7857. 'display_order' => $row['display_order'],
  7858. ];
  7859. }
  7860. $this->tree_array($arrLP);
  7861. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7862. unset($this->arrMenu);
  7863. $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
  7864. $form = new FormValidator('form', 'POST', $url);
  7865. $defaults['title'] = api_html_entity_decode(
  7866. $item_title,
  7867. ENT_QUOTES,
  7868. $charset
  7869. );
  7870. $defaults['description'] = $item_description;
  7871. $form->addHeader($title);
  7872. $arrHide[0]['value'] = Security::remove_XSS($this->name);
  7873. $arrHide[0]['padding'] = 20;
  7874. $charset = api_get_system_encoding();
  7875. for ($i = 0; $i < count($arrLP); $i++) {
  7876. if ($action != 'add') {
  7877. if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
  7878. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7879. ) {
  7880. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7881. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  7882. if ($parent == $arrLP[$i]['id']) {
  7883. $s_selected_parent = $arrHide[$arrLP[$i]['id']];
  7884. }
  7885. }
  7886. } else {
  7887. if ($arrLP[$i]['item_type'] == 'dir') {
  7888. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7889. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  7890. if ($parent == $arrLP[$i]['id']) {
  7891. $s_selected_parent = $arrHide[$arrLP[$i]['id']];
  7892. }
  7893. }
  7894. }
  7895. }
  7896. if ($action != 'move') {
  7897. $form->addElement('text', 'title', get_lang('Title'));
  7898. $form->applyFilter('title', 'html_filter');
  7899. $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
  7900. } else {
  7901. $form->addElement('hidden', 'title');
  7902. }
  7903. $parentSelect = $form->addElement(
  7904. 'select',
  7905. 'parent',
  7906. get_lang('Parent'),
  7907. '',
  7908. [
  7909. 'id' => 'idParent',
  7910. 'onchange' => "javascript: load_cbo(this.value);",
  7911. ]
  7912. );
  7913. foreach ($arrHide as $key => $value) {
  7914. $parentSelect->addOption(
  7915. $value['value'],
  7916. $key,
  7917. 'style="padding-left:'.$value['padding'].'px;"'
  7918. );
  7919. $lastPosition = $key;
  7920. }
  7921. if (!empty($s_selected_parent)) {
  7922. $parentSelect->setSelected($s_selected_parent);
  7923. }
  7924. if (is_array($arrLP)) {
  7925. reset($arrLP);
  7926. }
  7927. $arrHide = [];
  7928. // POSITION
  7929. for ($i = 0; $i < count($arrLP); $i++) {
  7930. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  7931. //this is the same!
  7932. if (isset($extra_info['previous_item_id']) &&
  7933. $extra_info['previous_item_id'] == $arrLP[$i]['id']
  7934. ) {
  7935. $s_selected_position = $arrLP[$i]['id'];
  7936. } elseif ($action == 'add') {
  7937. $s_selected_position = $arrLP[$i]['id'];
  7938. }
  7939. $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
  7940. }
  7941. }
  7942. $position = $form->addElement(
  7943. 'select',
  7944. 'previous',
  7945. get_lang('Position'),
  7946. '',
  7947. ['id' => 'previous']
  7948. );
  7949. $padding = isset($value['padding']) ? $value['padding'] : 0;
  7950. $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
  7951. $lastPosition = null;
  7952. foreach ($arrHide as $key => $value) {
  7953. $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
  7954. $lastPosition = $key;
  7955. }
  7956. if (!empty($s_selected_position)) {
  7957. $position->setSelected($s_selected_position);
  7958. }
  7959. // When new chapter add at the end
  7960. if ($action == 'add_item') {
  7961. $position->setSelected($lastPosition);
  7962. }
  7963. if (is_array($arrLP)) {
  7964. reset($arrLP);
  7965. }
  7966. $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
  7967. //fix in order to use the tab
  7968. if ($item_type == 'dir') {
  7969. $form->addElement('hidden', 'type', 'dir');
  7970. }
  7971. $extension = null;
  7972. if (!empty($item_path)) {
  7973. $extension = pathinfo($item_path, PATHINFO_EXTENSION);
  7974. }
  7975. //assets can't be modified
  7976. //$item_type == 'asset' ||
  7977. if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
  7978. if ($item_type == 'sco') {
  7979. $form->addElement(
  7980. 'html',
  7981. '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
  7982. );
  7983. }
  7984. $renderer = $form->defaultRenderer();
  7985. $renderer->setElementTemplate(
  7986. '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
  7987. 'content_lp'
  7988. );
  7989. $relative_prefix = '';
  7990. $editor_config = [
  7991. 'ToolbarSet' => 'LearningPathDocuments',
  7992. 'Width' => '100%',
  7993. 'Height' => '500',
  7994. 'FullPage' => true,
  7995. 'CreateDocumentDir' => $relative_prefix,
  7996. 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
  7997. 'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
  7998. ];
  7999. $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
  8000. $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
  8001. $defaults['content_lp'] = file_get_contents($content_path);
  8002. }
  8003. if (!empty($id)) {
  8004. $form->addHidden('id', $id);
  8005. }
  8006. $form->addElement('hidden', 'type', $item_type);
  8007. $form->addElement('hidden', 'post_time', time());
  8008. $form->setDefaults($defaults);
  8009. return $form->returnForm();
  8010. }
  8011. /**
  8012. * @return string
  8013. */
  8014. public function getCurrentBuildingModeURL()
  8015. {
  8016. $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
  8017. $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
  8018. $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
  8019. $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
  8020. $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
  8021. return $currentUrl;
  8022. }
  8023. /**
  8024. * Returns the form to update or create a document.
  8025. *
  8026. * @param string $action (add/edit)
  8027. * @param int $id ID of the lp_item (if already exists)
  8028. * @param mixed $extra_info Integer if document ID, string if info ('new')
  8029. *
  8030. * @throws Exception
  8031. * @throws HTML_QuickForm_Error
  8032. *
  8033. * @return string HTML form
  8034. */
  8035. public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
  8036. {
  8037. $course_id = api_get_course_int_id();
  8038. $_course = api_get_course_info();
  8039. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8040. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  8041. $no_display_edit_textarea = false;
  8042. $item_description = '';
  8043. //If action==edit document
  8044. //We don't display the document form if it's not an editable document (html or txt file)
  8045. if ($action === 'edit') {
  8046. if (is_array($extra_info)) {
  8047. $path_parts = pathinfo($extra_info['dir']);
  8048. if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
  8049. $no_display_edit_textarea = true;
  8050. }
  8051. }
  8052. }
  8053. $no_display_add = false;
  8054. // If action==add an existing document
  8055. // We don't display the document form if it's not an editable document (html or txt file).
  8056. if ($action === 'add') {
  8057. if (is_numeric($extra_info)) {
  8058. $extra_info = (int) $extra_info;
  8059. $sql_doc = "SELECT path FROM $tbl_doc
  8060. WHERE c_id = $course_id AND iid = ".$extra_info;
  8061. $result = Database::query($sql_doc);
  8062. $path_file = Database::result($result, 0, 0);
  8063. $path_parts = pathinfo($path_file);
  8064. if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
  8065. $no_display_add = true;
  8066. }
  8067. }
  8068. }
  8069. if ($id != 0 && is_array($extra_info)) {
  8070. $item_title = stripslashes($extra_info['title']);
  8071. $item_description = stripslashes($extra_info['description']);
  8072. if (empty($item_title)) {
  8073. $path_parts = pathinfo($extra_info['path']);
  8074. $item_title = stripslashes($path_parts['filename']);
  8075. }
  8076. } elseif (is_numeric($extra_info)) {
  8077. $sql = "SELECT path, title FROM $tbl_doc
  8078. WHERE
  8079. c_id = ".$course_id." AND
  8080. iid = ".intval($extra_info);
  8081. $result = Database::query($sql);
  8082. $row = Database::fetch_array($result);
  8083. $item_title = $row['title'];
  8084. $item_title = str_replace('_', ' ', $item_title);
  8085. if (empty($item_title)) {
  8086. $path_parts = pathinfo($row['path']);
  8087. $item_title = stripslashes($path_parts['filename']);
  8088. }
  8089. } else {
  8090. $item_title = '';
  8091. $item_description = '';
  8092. }
  8093. $return = '<legend>';
  8094. $parent = 0;
  8095. if ($id != 0 && is_array($extra_info)) {
  8096. $parent = $extra_info['parent_item_id'];
  8097. }
  8098. $sql = "SELECT * FROM $tbl_lp_item
  8099. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  8100. $result = Database::query($sql);
  8101. $arrLP = [];
  8102. while ($row = Database::fetch_array($result)) {
  8103. $arrLP[] = [
  8104. 'id' => $row['iid'],
  8105. 'item_type' => $row['item_type'],
  8106. 'title' => $row['title'],
  8107. 'path' => $row['path'],
  8108. 'description' => $row['description'],
  8109. 'parent_item_id' => $row['parent_item_id'],
  8110. 'previous_item_id' => $row['previous_item_id'],
  8111. 'next_item_id' => $row['next_item_id'],
  8112. 'display_order' => $row['display_order'],
  8113. 'max_score' => $row['max_score'],
  8114. 'min_score' => $row['min_score'],
  8115. 'mastery_score' => $row['mastery_score'],
  8116. 'prerequisite' => $row['prerequisite'],
  8117. ];
  8118. }
  8119. $this->tree_array($arrLP);
  8120. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  8121. unset($this->arrMenu);
  8122. if ($action == 'add') {
  8123. $return .= get_lang('CreateTheDocument');
  8124. } elseif ($action == 'move') {
  8125. $return .= get_lang('MoveTheCurrentDocument');
  8126. } else {
  8127. $return .= get_lang('EditTheCurrentDocument');
  8128. }
  8129. $return .= '</legend>';
  8130. if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
  8131. $return .= Display::return_message(
  8132. '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
  8133. false
  8134. );
  8135. }
  8136. $form = new FormValidator(
  8137. 'form',
  8138. 'POST',
  8139. $this->getCurrentBuildingModeURL(),
  8140. '',
  8141. ['enctype' => 'multipart/form-data']
  8142. );
  8143. $defaults['title'] = Security::remove_XSS($item_title);
  8144. if (empty($item_title)) {
  8145. $defaults['title'] = Security::remove_XSS($item_title);
  8146. }
  8147. $defaults['description'] = $item_description;
  8148. $form->addElement('html', $return);
  8149. if ($action != 'move') {
  8150. $data = $this->generate_lp_folder($_course);
  8151. if ($action != 'edit') {
  8152. $folders = DocumentManager::get_all_document_folders(
  8153. $_course,
  8154. 0,
  8155. true
  8156. );
  8157. DocumentManager::build_directory_selector(
  8158. $folders,
  8159. '',
  8160. [],
  8161. true,
  8162. $form,
  8163. 'directory_parent_id'
  8164. );
  8165. }
  8166. if (isset($data['id'])) {
  8167. $defaults['directory_parent_id'] = $data['id'];
  8168. }
  8169. $form->addElement(
  8170. 'text',
  8171. 'title',
  8172. get_lang('Title'),
  8173. ['id' => 'idTitle', 'class' => 'col-md-4']
  8174. );
  8175. $form->applyFilter('title', 'html_filter');
  8176. }
  8177. $arrHide[0]['value'] = $this->name;
  8178. $arrHide[0]['padding'] = 20;
  8179. for ($i = 0; $i < count($arrLP); $i++) {
  8180. if ($action != 'add') {
  8181. if ($arrLP[$i]['item_type'] == 'dir' &&
  8182. !in_array($arrLP[$i]['id'], $arrHide) &&
  8183. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  8184. ) {
  8185. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8186. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  8187. }
  8188. } else {
  8189. if ($arrLP[$i]['item_type'] == 'dir') {
  8190. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8191. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  8192. }
  8193. }
  8194. }
  8195. $parentSelect = $form->addSelect(
  8196. 'parent',
  8197. get_lang('Parent'),
  8198. [],
  8199. [
  8200. 'id' => 'idParent',
  8201. 'onchange' => 'javascript: load_cbo(this.value);',
  8202. ]
  8203. );
  8204. $my_count = 0;
  8205. foreach ($arrHide as $key => $value) {
  8206. if ($my_count != 0) {
  8207. // The LP name is also the first section and is not in the same charset like the other sections.
  8208. $value['value'] = Security::remove_XSS($value['value']);
  8209. $parentSelect->addOption(
  8210. $value['value'],
  8211. $key,
  8212. 'style="padding-left:'.$value['padding'].'px;"'
  8213. );
  8214. } else {
  8215. $value['value'] = Security::remove_XSS($value['value']);
  8216. $parentSelect->addOption(
  8217. $value['value'],
  8218. $key,
  8219. 'style="padding-left:'.$value['padding'].'px;"'
  8220. );
  8221. }
  8222. $my_count++;
  8223. }
  8224. if (!empty($id)) {
  8225. $parentSelect->setSelected($parent);
  8226. } else {
  8227. $parent_item_id = Session::read('parent_item_id', 0);
  8228. $parentSelect->setSelected($parent_item_id);
  8229. }
  8230. if (is_array($arrLP)) {
  8231. reset($arrLP);
  8232. }
  8233. $arrHide = [];
  8234. // POSITION
  8235. for ($i = 0; $i < count($arrLP); $i++) {
  8236. if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
  8237. $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
  8238. ) {
  8239. $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
  8240. }
  8241. }
  8242. $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
  8243. $position = $form->addSelect(
  8244. 'previous',
  8245. get_lang('Position'),
  8246. [],
  8247. ['id' => 'previous']
  8248. );
  8249. $position->addOption(get_lang('FirstPosition'), 0);
  8250. foreach ($arrHide as $key => $value) {
  8251. $padding = isset($value['padding']) ? $value['padding'] : 20;
  8252. $position->addOption(
  8253. $value['value'],
  8254. $key,
  8255. 'style="padding-left:'.$padding.'px;"'
  8256. );
  8257. }
  8258. $position->setSelected($selectedPosition);
  8259. if (is_array($arrLP)) {
  8260. reset($arrLP);
  8261. }
  8262. if ($action != 'move') {
  8263. $arrHide = [];
  8264. for ($i = 0; $i < count($arrLP); $i++) {
  8265. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
  8266. $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
  8267. ) {
  8268. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8269. }
  8270. }
  8271. if (!$no_display_add) {
  8272. $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
  8273. $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
  8274. if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
  8275. $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
  8276. ) {
  8277. if (isset($_POST['content'])) {
  8278. $content = stripslashes($_POST['content']);
  8279. } elseif (is_array($extra_info)) {
  8280. //If it's an html document or a text file
  8281. if (!$no_display_edit_textarea) {
  8282. $content = $this->display_document(
  8283. $extra_info['path'],
  8284. false,
  8285. false
  8286. );
  8287. }
  8288. } elseif (is_numeric($extra_info)) {
  8289. $content = $this->display_document(
  8290. $extra_info,
  8291. false,
  8292. false
  8293. );
  8294. } else {
  8295. $content = '';
  8296. }
  8297. if (!$no_display_edit_textarea) {
  8298. // We need to calculate here some specific settings for the online editor.
  8299. // The calculated settings work for documents in the Documents tool
  8300. // (on the root or in subfolders).
  8301. // For documents in native scorm packages it is unclear whether the
  8302. // online editor should be activated or not.
  8303. // A new document, it is in the root of the repository.
  8304. $relative_path = '';
  8305. $relative_prefix = '';
  8306. if (is_array($extra_info) && $extra_info != 'new') {
  8307. // The document already exists. Whe have to determine its relative path towards the repository root.
  8308. $relative_path = explode('/', $extra_info['dir']);
  8309. $cnt = count($relative_path) - 2;
  8310. if ($cnt < 0) {
  8311. $cnt = 0;
  8312. }
  8313. $relative_prefix = str_repeat('../', $cnt);
  8314. $relative_path = array_slice($relative_path, 1, $cnt);
  8315. $relative_path = implode('/', $relative_path);
  8316. if (strlen($relative_path) > 0) {
  8317. $relative_path = $relative_path.'/';
  8318. }
  8319. } else {
  8320. $result = $this->generate_lp_folder($_course);
  8321. $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
  8322. $relative_prefix = '../../';
  8323. }
  8324. $editor_config = [
  8325. 'ToolbarSet' => 'LearningPathDocuments',
  8326. 'Width' => '100%',
  8327. 'Height' => '500',
  8328. 'FullPage' => true,
  8329. 'CreateDocumentDir' => $relative_prefix,
  8330. 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
  8331. 'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
  8332. ];
  8333. if ($_GET['action'] == 'add_item') {
  8334. $class = 'add';
  8335. $text = get_lang('LPCreateDocument');
  8336. } else {
  8337. if ($_GET['action'] == 'edit_item') {
  8338. $class = 'save';
  8339. $text = get_lang('SaveDocument');
  8340. }
  8341. }
  8342. $form->addButtonSave($text, 'submit_button');
  8343. $renderer = $form->defaultRenderer();
  8344. $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
  8345. $form->addElement('html', '<div class="editor-lp">');
  8346. $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
  8347. $form->addElement('html', '</div>');
  8348. $defaults['content_lp'] = $content;
  8349. }
  8350. } elseif (is_numeric($extra_info)) {
  8351. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  8352. $return = $this->display_document($extra_info, true, true, true);
  8353. $form->addElement('html', $return);
  8354. }
  8355. }
  8356. }
  8357. if (isset($extra_info['item_type']) &&
  8358. $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
  8359. ) {
  8360. $parentSelect->freeze();
  8361. $position->freeze();
  8362. }
  8363. if ($action == 'move') {
  8364. $form->addElement('hidden', 'title', $item_title);
  8365. $form->addElement('hidden', 'description', $item_description);
  8366. }
  8367. if (is_numeric($extra_info)) {
  8368. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  8369. $form->addElement('hidden', 'path', $extra_info);
  8370. } elseif (is_array($extra_info)) {
  8371. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  8372. $form->addElement('hidden', 'path', $extra_info['path']);
  8373. }
  8374. $form->addElement('hidden', 'type', TOOL_DOCUMENT);
  8375. $form->addElement('hidden', 'post_time', time());
  8376. $form->setDefaults($defaults);
  8377. return $form->returnForm();
  8378. }
  8379. /**
  8380. * Returns the form to update or create a read-out text.
  8381. *
  8382. * @param string $action "add" or "edit"
  8383. * @param int $id ID of the lp_item (if already exists)
  8384. * @param mixed $extra_info Integer if document ID, string if info ('new')
  8385. *
  8386. * @throws Exception
  8387. * @throws HTML_QuickForm_Error
  8388. *
  8389. * @return string HTML form
  8390. */
  8391. public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
  8392. {
  8393. $course_id = api_get_course_int_id();
  8394. $_course = api_get_course_info();
  8395. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8396. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  8397. $no_display_edit_textarea = false;
  8398. $item_description = '';
  8399. //If action==edit document
  8400. //We don't display the document form if it's not an editable document (html or txt file)
  8401. if ($action == 'edit') {
  8402. if (is_array($extra_info)) {
  8403. $path_parts = pathinfo($extra_info['dir']);
  8404. if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
  8405. $no_display_edit_textarea = true;
  8406. }
  8407. }
  8408. }
  8409. $no_display_add = false;
  8410. if ($id != 0 && is_array($extra_info)) {
  8411. $item_title = stripslashes($extra_info['title']);
  8412. $item_description = stripslashes($extra_info['description']);
  8413. $item_terms = stripslashes($extra_info['terms']);
  8414. if (empty($item_title)) {
  8415. $path_parts = pathinfo($extra_info['path']);
  8416. $item_title = stripslashes($path_parts['filename']);
  8417. }
  8418. } elseif (is_numeric($extra_info)) {
  8419. $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
  8420. $result = Database::query($sql);
  8421. $row = Database::fetch_array($result);
  8422. $item_title = $row['title'];
  8423. $item_title = str_replace('_', ' ', $item_title);
  8424. if (empty($item_title)) {
  8425. $path_parts = pathinfo($row['path']);
  8426. $item_title = stripslashes($path_parts['filename']);
  8427. }
  8428. } else {
  8429. $item_title = '';
  8430. $item_description = '';
  8431. }
  8432. if ($id != 0 && is_array($extra_info)) {
  8433. $parent = $extra_info['parent_item_id'];
  8434. } else {
  8435. $parent = 0;
  8436. }
  8437. $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  8438. $result = Database::query($sql);
  8439. $arrLP = [];
  8440. while ($row = Database::fetch_array($result)) {
  8441. $arrLP[] = [
  8442. 'id' => $row['iid'],
  8443. 'item_type' => $row['item_type'],
  8444. 'title' => $row['title'],
  8445. 'path' => $row['path'],
  8446. 'description' => $row['description'],
  8447. 'parent_item_id' => $row['parent_item_id'],
  8448. 'previous_item_id' => $row['previous_item_id'],
  8449. 'next_item_id' => $row['next_item_id'],
  8450. 'display_order' => $row['display_order'],
  8451. 'max_score' => $row['max_score'],
  8452. 'min_score' => $row['min_score'],
  8453. 'mastery_score' => $row['mastery_score'],
  8454. 'prerequisite' => $row['prerequisite'],
  8455. ];
  8456. }
  8457. $this->tree_array($arrLP);
  8458. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  8459. unset($this->arrMenu);
  8460. if ($action == 'add') {
  8461. $formHeader = get_lang('CreateTheDocument');
  8462. } else {
  8463. $formHeader = get_lang('EditTheCurrentDocument');
  8464. }
  8465. if ('edit' === $action) {
  8466. $urlAudioIcon = Display::url(
  8467. Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
  8468. api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
  8469. .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
  8470. );
  8471. } else {
  8472. $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
  8473. }
  8474. $form = new FormValidator(
  8475. 'frm_add_reading',
  8476. 'POST',
  8477. $this->getCurrentBuildingModeURL(),
  8478. '',
  8479. ['enctype' => 'multipart/form-data']
  8480. );
  8481. $form->addHeader($formHeader);
  8482. $form->addHtml(
  8483. Display::return_message(
  8484. sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
  8485. 'normal',
  8486. false
  8487. )
  8488. );
  8489. $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
  8490. $defaults['description'] = $item_description;
  8491. $data = $this->generate_lp_folder($_course);
  8492. if ($action != 'edit') {
  8493. $folders = DocumentManager::get_all_document_folders($_course, 0, true);
  8494. DocumentManager::build_directory_selector(
  8495. $folders,
  8496. '',
  8497. [],
  8498. true,
  8499. $form,
  8500. 'directory_parent_id'
  8501. );
  8502. }
  8503. if (isset($data['id'])) {
  8504. $defaults['directory_parent_id'] = $data['id'];
  8505. }
  8506. $form->addElement(
  8507. 'text',
  8508. 'title',
  8509. get_lang('Title')
  8510. );
  8511. $form->applyFilter('title', 'trim');
  8512. $form->applyFilter('title', 'html_filter');
  8513. $arrHide[0]['value'] = $this->name;
  8514. $arrHide[0]['padding'] = 20;
  8515. for ($i = 0; $i < count($arrLP); $i++) {
  8516. if ($action != 'add') {
  8517. if ($arrLP[$i]['item_type'] == 'dir' &&
  8518. !in_array($arrLP[$i]['id'], $arrHide) &&
  8519. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  8520. ) {
  8521. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8522. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  8523. }
  8524. } else {
  8525. if ($arrLP[$i]['item_type'] == 'dir') {
  8526. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8527. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  8528. }
  8529. }
  8530. }
  8531. $parent_select = $form->addSelect(
  8532. 'parent',
  8533. get_lang('Parent'),
  8534. [],
  8535. ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
  8536. );
  8537. $my_count = 0;
  8538. foreach ($arrHide as $key => $value) {
  8539. if ($my_count != 0) {
  8540. // The LP name is also the first section and is not in the same charset like the other sections.
  8541. $value['value'] = Security::remove_XSS($value['value']);
  8542. $parent_select->addOption(
  8543. $value['value'],
  8544. $key,
  8545. 'style="padding-left:'.$value['padding'].'px;"'
  8546. );
  8547. } else {
  8548. $value['value'] = Security::remove_XSS($value['value']);
  8549. $parent_select->addOption(
  8550. $value['value'],
  8551. $key,
  8552. 'style="padding-left:'.$value['padding'].'px;"'
  8553. );
  8554. }
  8555. $my_count++;
  8556. }
  8557. if (!empty($id)) {
  8558. $parent_select->setSelected($parent);
  8559. } else {
  8560. $parent_item_id = Session::read('parent_item_id', 0);
  8561. $parent_select->setSelected($parent_item_id);
  8562. }
  8563. if (is_array($arrLP)) {
  8564. reset($arrLP);
  8565. }
  8566. $arrHide = [];
  8567. $s_selected_position = null;
  8568. // POSITION
  8569. $lastPosition = null;
  8570. for ($i = 0; $i < count($arrLP); $i++) {
  8571. if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
  8572. $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
  8573. ) {
  8574. if ((isset($extra_info['previous_item_id']) &&
  8575. $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
  8576. ) {
  8577. $s_selected_position = $arrLP[$i]['id'];
  8578. }
  8579. $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
  8580. }
  8581. $lastPosition = $arrLP[$i]['id'];
  8582. }
  8583. if (empty($s_selected_position)) {
  8584. $s_selected_position = $lastPosition;
  8585. }
  8586. $position = $form->addSelect(
  8587. 'previous',
  8588. get_lang('Position'),
  8589. []
  8590. );
  8591. $position->addOption(get_lang('FirstPosition'), 0);
  8592. foreach ($arrHide as $key => $value) {
  8593. $padding = isset($value['padding']) ? $value['padding'] : 20;
  8594. $position->addOption(
  8595. $value['value'],
  8596. $key,
  8597. 'style="padding-left:'.$padding.'px;"'
  8598. );
  8599. }
  8600. $position->setSelected($s_selected_position);
  8601. if (is_array($arrLP)) {
  8602. reset($arrLP);
  8603. }
  8604. $arrHide = [];
  8605. for ($i = 0; $i < count($arrLP); $i++) {
  8606. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
  8607. $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
  8608. ) {
  8609. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8610. }
  8611. }
  8612. if (!$no_display_add) {
  8613. $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
  8614. $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
  8615. if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
  8616. if (!$no_display_edit_textarea) {
  8617. $content = '';
  8618. if (isset($_POST['content'])) {
  8619. $content = stripslashes($_POST['content']);
  8620. } elseif (is_array($extra_info)) {
  8621. $content = $this->display_document($extra_info['path'], false, false);
  8622. } elseif (is_numeric($extra_info)) {
  8623. $content = $this->display_document($extra_info, false, false);
  8624. }
  8625. // A new document, it is in the root of the repository.
  8626. if (is_array($extra_info) && $extra_info != 'new') {
  8627. } else {
  8628. $this->generate_lp_folder($_course);
  8629. }
  8630. if ($_GET['action'] == 'add_item') {
  8631. $text = get_lang('LPCreateDocument');
  8632. } else {
  8633. $text = get_lang('SaveDocument');
  8634. }
  8635. $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
  8636. $form
  8637. ->defaultRenderer()
  8638. ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
  8639. $form->addButtonSave($text, 'submit_button');
  8640. $defaults['content_lp'] = $content;
  8641. }
  8642. } elseif (is_numeric($extra_info)) {
  8643. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  8644. $return = $this->display_document($extra_info, true, true, true);
  8645. $form->addElement('html', $return);
  8646. }
  8647. }
  8648. if (is_numeric($extra_info)) {
  8649. $form->addElement('hidden', 'path', $extra_info);
  8650. } elseif (is_array($extra_info)) {
  8651. $form->addElement('hidden', 'path', $extra_info['path']);
  8652. }
  8653. $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
  8654. $form->addElement('hidden', 'post_time', time());
  8655. $form->setDefaults($defaults);
  8656. return $form->returnForm();
  8657. }
  8658. /**
  8659. * @param array $courseInfo
  8660. * @param string $content
  8661. * @param string $title
  8662. * @param int $parentId
  8663. *
  8664. * @throws \Doctrine\ORM\ORMException
  8665. * @throws \Doctrine\ORM\OptimisticLockException
  8666. * @throws \Doctrine\ORM\TransactionRequiredException
  8667. *
  8668. * @return int
  8669. */
  8670. public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
  8671. {
  8672. $creatorId = api_get_user_id();
  8673. $sessionId = api_get_session_id();
  8674. // Generates folder
  8675. $result = $this->generate_lp_folder($courseInfo);
  8676. $dir = $result['dir'];
  8677. if (empty($parentId) || $parentId == '/') {
  8678. $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
  8679. $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
  8680. if ($parentId === '/') {
  8681. $dir = '/';
  8682. }
  8683. // Please, do not modify this dirname formatting.
  8684. if (strstr($dir, '..')) {
  8685. $dir = '/';
  8686. }
  8687. if (!empty($dir[0]) && $dir[0] == '.') {
  8688. $dir = substr($dir, 1);
  8689. }
  8690. if (!empty($dir[0]) && $dir[0] != '/') {
  8691. $dir = '/'.$dir;
  8692. }
  8693. if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
  8694. $dir .= '/';
  8695. }
  8696. } else {
  8697. $parentInfo = DocumentManager::get_document_data_by_id(
  8698. $parentId,
  8699. $courseInfo['code']
  8700. );
  8701. if (!empty($parentInfo)) {
  8702. $dir = $parentInfo['path'].'/';
  8703. }
  8704. }
  8705. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  8706. if (!is_dir($filepath)) {
  8707. $dir = '/';
  8708. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  8709. }
  8710. $originalTitle = !empty($title) ? $title : $_POST['title'];
  8711. if (!empty($title)) {
  8712. $title = api_replace_dangerous_char(stripslashes($title));
  8713. } else {
  8714. $title = api_replace_dangerous_char(stripslashes($_POST['title']));
  8715. }
  8716. $title = disable_dangerous_file($title);
  8717. $filename = $title;
  8718. $content = !empty($content) ? $content : $_POST['content_lp'];
  8719. $tmpFileName = $filename;
  8720. $i = 0;
  8721. while (file_exists($filepath.$tmpFileName.'.html')) {
  8722. $tmpFileName = $filename.'_'.++$i;
  8723. }
  8724. $filename = $tmpFileName.'.html';
  8725. $content = stripslashes($content);
  8726. if (file_exists($filepath.$filename)) {
  8727. return 0;
  8728. }
  8729. $putContent = file_put_contents($filepath.$filename, $content);
  8730. if ($putContent === false) {
  8731. return 0;
  8732. }
  8733. $fileSize = filesize($filepath.$filename);
  8734. $saveFilePath = $dir.$filename;
  8735. $documentId = add_document(
  8736. $courseInfo,
  8737. $saveFilePath,
  8738. 'file',
  8739. $fileSize,
  8740. $tmpFileName,
  8741. '',
  8742. 0, //readonly
  8743. true,
  8744. null,
  8745. $sessionId,
  8746. $creatorId
  8747. );
  8748. if (!$documentId) {
  8749. return 0;
  8750. }
  8751. api_item_property_update(
  8752. $courseInfo,
  8753. TOOL_DOCUMENT,
  8754. $documentId,
  8755. 'DocumentAdded',
  8756. $creatorId,
  8757. null,
  8758. null,
  8759. null,
  8760. null,
  8761. $sessionId
  8762. );
  8763. $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
  8764. $newTitle = $originalTitle;
  8765. if ($newComment || $newTitle) {
  8766. $em = Database::getManager();
  8767. /** @var CDocument $doc */
  8768. $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
  8769. if ($newComment) {
  8770. $doc->setComment($newComment);
  8771. }
  8772. if ($newTitle) {
  8773. $doc->setTitle($newTitle);
  8774. }
  8775. $em->persist($doc);
  8776. $em->flush();
  8777. }
  8778. return $documentId;
  8779. }
  8780. /**
  8781. * Return HTML form to add/edit a link item.
  8782. *
  8783. * @param string $action (add/edit)
  8784. * @param int $id Item ID if exists
  8785. * @param mixed $extra_info
  8786. *
  8787. * @throws Exception
  8788. * @throws HTML_QuickForm_Error
  8789. *
  8790. * @return string HTML form
  8791. */
  8792. public function display_link_form($action = 'add', $id = 0, $extra_info = '')
  8793. {
  8794. $course_id = api_get_course_int_id();
  8795. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8796. $tbl_link = Database::get_course_table(TABLE_LINK);
  8797. $item_title = '';
  8798. $item_description = '';
  8799. $item_url = '';
  8800. if ($id != 0 && is_array($extra_info)) {
  8801. $item_title = stripslashes($extra_info['title']);
  8802. $item_description = stripslashes($extra_info['description']);
  8803. $item_url = stripslashes($extra_info['url']);
  8804. } elseif (is_numeric($extra_info)) {
  8805. $extra_info = (int) $extra_info;
  8806. $sql = "SELECT title, description, url
  8807. FROM $tbl_link
  8808. WHERE c_id = $course_id AND iid = $extra_info";
  8809. $result = Database::query($sql);
  8810. $row = Database::fetch_array($result);
  8811. $item_title = $row['title'];
  8812. $item_description = $row['description'];
  8813. $item_url = $row['url'];
  8814. }
  8815. $form = new FormValidator(
  8816. 'edit_link',
  8817. 'POST',
  8818. $this->getCurrentBuildingModeURL()
  8819. );
  8820. $defaults = [];
  8821. $parent = 0;
  8822. if ($id != 0 && is_array($extra_info)) {
  8823. $parent = $extra_info['parent_item_id'];
  8824. }
  8825. $sql = "SELECT * FROM $tbl_lp_item
  8826. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  8827. $result = Database::query($sql);
  8828. $arrLP = [];
  8829. while ($row = Database::fetch_array($result)) {
  8830. $arrLP[] = [
  8831. 'id' => $row['id'],
  8832. 'item_type' => $row['item_type'],
  8833. 'title' => $row['title'],
  8834. 'path' => $row['path'],
  8835. 'description' => $row['description'],
  8836. 'parent_item_id' => $row['parent_item_id'],
  8837. 'previous_item_id' => $row['previous_item_id'],
  8838. 'next_item_id' => $row['next_item_id'],
  8839. 'display_order' => $row['display_order'],
  8840. 'max_score' => $row['max_score'],
  8841. 'min_score' => $row['min_score'],
  8842. 'mastery_score' => $row['mastery_score'],
  8843. 'prerequisite' => $row['prerequisite'],
  8844. ];
  8845. }
  8846. $this->tree_array($arrLP);
  8847. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  8848. unset($this->arrMenu);
  8849. if ($action == 'add') {
  8850. $legend = get_lang('CreateTheLink');
  8851. } elseif ($action == 'move') {
  8852. $legend = get_lang('MoveCurrentLink');
  8853. } else {
  8854. $legend = get_lang('EditCurrentLink');
  8855. }
  8856. $form->addHeader($legend);
  8857. if ($action != 'move') {
  8858. $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
  8859. $defaults['title'] = $item_title;
  8860. }
  8861. $selectParent = $form->addSelect(
  8862. 'parent',
  8863. get_lang('Parent'),
  8864. [],
  8865. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
  8866. );
  8867. $selectParent->addOption($this->name, 0);
  8868. $arrHide = [
  8869. $id,
  8870. ];
  8871. $parent_item_id = Session::read('parent_item_id', 0);
  8872. for ($i = 0; $i < count($arrLP); $i++) {
  8873. if ($action != 'add') {
  8874. if (
  8875. ($arrLP[$i]['item_type'] == 'dir') &&
  8876. !in_array($arrLP[$i]['id'], $arrHide) &&
  8877. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  8878. ) {
  8879. $selectParent->addOption(
  8880. $arrLP[$i]['title'],
  8881. $arrLP[$i]['id'],
  8882. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
  8883. );
  8884. if ($parent == $arrLP[$i]['id']) {
  8885. $selectParent->setSelected($arrLP[$i]['id']);
  8886. }
  8887. } else {
  8888. $arrHide[] = $arrLP[$i]['id'];
  8889. }
  8890. } else {
  8891. if ($arrLP[$i]['item_type'] == 'dir') {
  8892. $selectParent->addOption(
  8893. $arrLP[$i]['title'],
  8894. $arrLP[$i]['id'],
  8895. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  8896. );
  8897. if ($parent_item_id == $arrLP[$i]['id']) {
  8898. $selectParent->setSelected($arrLP[$i]['id']);
  8899. }
  8900. }
  8901. }
  8902. }
  8903. if (is_array($arrLP)) {
  8904. reset($arrLP);
  8905. }
  8906. $selectPrevious = $form->addSelect(
  8907. 'previous',
  8908. get_lang('Position'),
  8909. [],
  8910. ['id' => 'previous', 'class' => 'learnpath_item_form']
  8911. );
  8912. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  8913. for ($i = 0; $i < count($arrLP); $i++) {
  8914. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  8915. $selectPrevious->addOption(
  8916. $arrLP[$i]['title'],
  8917. $arrLP[$i]['id']
  8918. );
  8919. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  8920. $selectPrevious->setSelected($arrLP[$i]['id']);
  8921. } elseif ($action == 'add') {
  8922. $selectPrevious->setSelected($arrLP[$i]['id']);
  8923. }
  8924. }
  8925. }
  8926. if ($action != 'move') {
  8927. $urlAttributes = ['class' => 'learnpath_item_form'];
  8928. if (is_numeric($extra_info)) {
  8929. $urlAttributes['disabled'] = 'disabled';
  8930. }
  8931. $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
  8932. $defaults['url'] = $item_url;
  8933. $arrHide = [];
  8934. for ($i = 0; $i < count($arrLP); $i++) {
  8935. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  8936. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8937. }
  8938. }
  8939. }
  8940. if ($action == 'add') {
  8941. $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
  8942. } else {
  8943. $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
  8944. }
  8945. if ($action == 'move') {
  8946. $form->addHidden('title', $item_title);
  8947. $form->addHidden('description', $item_description);
  8948. }
  8949. if (is_numeric($extra_info)) {
  8950. $form->addHidden('path', $extra_info);
  8951. } elseif (is_array($extra_info)) {
  8952. $form->addHidden('path', $extra_info['path']);
  8953. }
  8954. $form->addHidden('type', TOOL_LINK);
  8955. $form->addHidden('post_time', time());
  8956. $form->setDefaults($defaults);
  8957. return '<div class="sectioncomment">'.$form->returnForm().'</div>';
  8958. }
  8959. /**
  8960. * Return HTML form to add/edit a student publication (work).
  8961. *
  8962. * @param string $action
  8963. * @param int $id Item ID if already exists
  8964. * @param string $extra_info
  8965. *
  8966. * @throws Exception
  8967. *
  8968. * @return string HTML form
  8969. */
  8970. public function display_student_publication_form(
  8971. $action = 'add',
  8972. $id = 0,
  8973. $extra_info = ''
  8974. ) {
  8975. $course_id = api_get_course_int_id();
  8976. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8977. $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
  8978. $item_title = get_lang('Student_publication');
  8979. if ($id != 0 && is_array($extra_info)) {
  8980. $item_title = stripslashes($extra_info['title']);
  8981. $item_description = stripslashes($extra_info['description']);
  8982. } elseif (is_numeric($extra_info)) {
  8983. $extra_info = (int) $extra_info;
  8984. $sql = "SELECT title, description
  8985. FROM $tbl_publication
  8986. WHERE c_id = $course_id AND id = ".$extra_info;
  8987. $result = Database::query($sql);
  8988. $row = Database::fetch_array($result);
  8989. if ($row) {
  8990. $item_title = $row['title'];
  8991. }
  8992. }
  8993. $parent = 0;
  8994. if ($id != 0 && is_array($extra_info)) {
  8995. $parent = $extra_info['parent_item_id'];
  8996. }
  8997. $sql = "SELECT * FROM $tbl_lp_item
  8998. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  8999. $result = Database::query($sql);
  9000. $arrLP = [];
  9001. while ($row = Database::fetch_array($result)) {
  9002. $arrLP[] = [
  9003. 'id' => $row['iid'],
  9004. 'item_type' => $row['item_type'],
  9005. 'title' => $row['title'],
  9006. 'path' => $row['path'],
  9007. 'description' => $row['description'],
  9008. 'parent_item_id' => $row['parent_item_id'],
  9009. 'previous_item_id' => $row['previous_item_id'],
  9010. 'next_item_id' => $row['next_item_id'],
  9011. 'display_order' => $row['display_order'],
  9012. 'max_score' => $row['max_score'],
  9013. 'min_score' => $row['min_score'],
  9014. 'mastery_score' => $row['mastery_score'],
  9015. 'prerequisite' => $row['prerequisite'],
  9016. ];
  9017. }
  9018. $this->tree_array($arrLP);
  9019. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  9020. unset($this->arrMenu);
  9021. $form = new FormValidator('frm_student_publication', 'post', '#');
  9022. if ($action == 'add') {
  9023. $form->addHeader(get_lang('Student_publication'));
  9024. } elseif ($action == 'move') {
  9025. $form->addHeader(get_lang('MoveCurrentStudentPublication'));
  9026. } else {
  9027. $form->addHeader(get_lang('EditCurrentStudentPublication'));
  9028. }
  9029. if ($action != 'move') {
  9030. $form->addText(
  9031. 'title',
  9032. get_lang('Title'),
  9033. true,
  9034. ['class' => 'learnpath_item_form', 'id' => 'idTitle']
  9035. );
  9036. }
  9037. $parentSelect = $form->addSelect(
  9038. 'parent',
  9039. get_lang('Parent'),
  9040. ['0' => $this->name],
  9041. [
  9042. 'onchange' => 'javascript: load_cbo(this.value);',
  9043. 'class' => 'learnpath_item_form',
  9044. 'id' => 'idParent',
  9045. ]
  9046. );
  9047. $arrHide = [$id];
  9048. for ($i = 0; $i < count($arrLP); $i++) {
  9049. if ($action != 'add') {
  9050. if (
  9051. ($arrLP[$i]['item_type'] == 'dir') &&
  9052. !in_array($arrLP[$i]['id'], $arrHide) &&
  9053. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  9054. ) {
  9055. $parentSelect->addOption(
  9056. $arrLP[$i]['title'],
  9057. $arrLP[$i]['id'],
  9058. ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
  9059. );
  9060. if ($parent == $arrLP[$i]['id']) {
  9061. $parentSelect->setSelected($arrLP[$i]['id']);
  9062. }
  9063. } else {
  9064. $arrHide[] = $arrLP[$i]['id'];
  9065. }
  9066. } else {
  9067. if ($arrLP[$i]['item_type'] == 'dir') {
  9068. $parentSelect->addOption(
  9069. $arrLP[$i]['title'],
  9070. $arrLP[$i]['id'],
  9071. ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
  9072. );
  9073. if ($parent == $arrLP[$i]['id']) {
  9074. $parentSelect->setSelected($arrLP[$i]['id']);
  9075. }
  9076. }
  9077. }
  9078. }
  9079. if (is_array($arrLP)) {
  9080. reset($arrLP);
  9081. }
  9082. $previousSelect = $form->addSelect(
  9083. 'previous',
  9084. get_lang('Position'),
  9085. ['0' => get_lang('FirstPosition')],
  9086. ['id' => 'previous', 'class' => 'learnpath_item_form']
  9087. );
  9088. for ($i = 0; $i < count($arrLP); $i++) {
  9089. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  9090. $previousSelect->addOption(
  9091. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  9092. $arrLP[$i]['id']
  9093. );
  9094. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  9095. $previousSelect->setSelected($arrLP[$i]['id']);
  9096. } elseif ($action == 'add') {
  9097. $previousSelect->setSelected($arrLP[$i]['id']);
  9098. }
  9099. }
  9100. }
  9101. if ($action == 'add') {
  9102. $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
  9103. } else {
  9104. $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
  9105. }
  9106. if ($action == 'move') {
  9107. $form->addHidden('title', $item_title);
  9108. $form->addHidden('description', $item_description);
  9109. }
  9110. if (is_numeric($extra_info)) {
  9111. $form->addHidden('path', $extra_info);
  9112. } elseif (is_array($extra_info)) {
  9113. $form->addHidden('path', $extra_info['path']);
  9114. }
  9115. $form->addHidden('type', TOOL_STUDENTPUBLICATION);
  9116. $form->addHidden('post_time', time());
  9117. $form->setDefaults(['title' => $item_title]);
  9118. $return = '<div class="sectioncomment">';
  9119. $return .= $form->returnForm();
  9120. $return .= '</div>';
  9121. return $return;
  9122. }
  9123. /**
  9124. * Displays the menu for manipulating a step.
  9125. *
  9126. * @param id $item_id
  9127. * @param string $item_type
  9128. *
  9129. * @return string
  9130. */
  9131. public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
  9132. {
  9133. $_course = api_get_course_info();
  9134. $course_code = api_get_course_id();
  9135. $return = '<div class="actions">';
  9136. switch ($item_type) {
  9137. case 'dir':
  9138. // Commented the message cause should not show it.
  9139. //$lang = get_lang('TitleManipulateChapter');
  9140. break;
  9141. case TOOL_LP_FINAL_ITEM:
  9142. case TOOL_DOCUMENT:
  9143. // Commented the message cause should not show it.
  9144. //$lang = get_lang('TitleManipulateDocument');
  9145. break;
  9146. case TOOL_LINK:
  9147. case 'link':
  9148. // Commented the message cause should not show it.
  9149. //$lang = get_lang('TitleManipulateLink');
  9150. break;
  9151. case TOOL_QUIZ:
  9152. // Commented the message cause should not show it.
  9153. //$lang = get_lang('TitleManipulateQuiz');
  9154. break;
  9155. case TOOL_STUDENTPUBLICATION:
  9156. // Commented the message cause should not show it.
  9157. //$lang = get_lang('TitleManipulateStudentPublication');
  9158. break;
  9159. }
  9160. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  9161. $item_id = (int) $item_id;
  9162. $sql = "SELECT * FROM $tbl_lp_item
  9163. WHERE iid = ".$item_id;
  9164. $result = Database::query($sql);
  9165. $row = Database::fetch_assoc($result);
  9166. $audio_player = null;
  9167. // We display an audio player if needed.
  9168. if (!empty($row['audio'])) {
  9169. $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
  9170. $audio_player .= '<div class="lp_mediaplayer" id="container">'
  9171. .'<audio src="'.$webAudioPath.'" controls>'
  9172. .'</div><br>';
  9173. }
  9174. $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
  9175. if ($item_type != TOOL_LP_FINAL_ITEM) {
  9176. $return .= Display::url(
  9177. Display::return_icon(
  9178. 'edit.png',
  9179. get_lang('Edit'),
  9180. [],
  9181. ICON_SIZE_SMALL
  9182. ),
  9183. $url.'&action=edit_item&path_item='.$row['path']
  9184. );
  9185. $return .= Display::url(
  9186. Display::return_icon(
  9187. 'move.png',
  9188. get_lang('Move'),
  9189. [],
  9190. ICON_SIZE_SMALL
  9191. ),
  9192. $url.'&action=move_item'
  9193. );
  9194. }
  9195. // Commented for now as prerequisites cannot be added to chapters.
  9196. if ($item_type != 'dir') {
  9197. $return .= Display::url(
  9198. Display::return_icon(
  9199. 'accept.png',
  9200. get_lang('LearnpathPrerequisites'),
  9201. [],
  9202. ICON_SIZE_SMALL
  9203. ),
  9204. $url.'&action=edit_item_prereq'
  9205. );
  9206. }
  9207. $return .= Display::url(
  9208. Display::return_icon(
  9209. 'delete.png',
  9210. get_lang('Delete'),
  9211. [],
  9212. ICON_SIZE_SMALL
  9213. ),
  9214. $url.'&action=delete_item'
  9215. );
  9216. if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
  9217. $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
  9218. if (empty($documentData)) {
  9219. // Try with iid
  9220. $table = Database::get_course_table(TABLE_DOCUMENT);
  9221. $sql = "SELECT path FROM $table
  9222. WHERE
  9223. c_id = ".api_get_course_int_id()." AND
  9224. iid = ".$row['path']." AND
  9225. path NOT LIKE '%_DELETED_%'";
  9226. $result = Database::query($sql);
  9227. $documentData = Database::fetch_array($result);
  9228. if ($documentData) {
  9229. $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
  9230. }
  9231. }
  9232. if (isset($documentData['absolute_path_from_document'])) {
  9233. $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
  9234. }
  9235. }
  9236. $return .= '</div>';
  9237. if (!empty($audio_player)) {
  9238. $return .= $audio_player;
  9239. }
  9240. return $return;
  9241. }
  9242. /**
  9243. * Creates the javascript needed for filling up the checkboxes without page reload.
  9244. *
  9245. * @return string
  9246. */
  9247. public function get_js_dropdown_array()
  9248. {
  9249. $course_id = api_get_course_int_id();
  9250. $return = 'var child_name = new Array();'."\n";
  9251. $return .= 'var child_value = new Array();'."\n\n";
  9252. $return .= 'child_name[0] = new Array();'."\n";
  9253. $return .= 'child_value[0] = new Array();'."\n\n";
  9254. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  9255. $sql = "SELECT * FROM ".$tbl_lp_item."
  9256. WHERE
  9257. c_id = $course_id AND
  9258. lp_id = ".$this->lp_id." AND
  9259. parent_item_id = 0
  9260. ORDER BY display_order ASC";
  9261. $res_zero = Database::query($sql);
  9262. $i = 0;
  9263. while ($row_zero = Database::fetch_array($res_zero)) {
  9264. if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
  9265. if ($row_zero['item_type'] == TOOL_QUIZ) {
  9266. $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
  9267. }
  9268. $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
  9269. $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
  9270. $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
  9271. }
  9272. }
  9273. $return .= "\n";
  9274. $sql = "SELECT * FROM $tbl_lp_item
  9275. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  9276. $res = Database::query($sql);
  9277. while ($row = Database::fetch_array($res)) {
  9278. $sql_parent = "SELECT * FROM ".$tbl_lp_item."
  9279. WHERE
  9280. c_id = ".$course_id." AND
  9281. parent_item_id = ".$row['iid']."
  9282. ORDER BY display_order ASC";
  9283. $res_parent = Database::query($sql_parent);
  9284. $i = 0;
  9285. $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
  9286. $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
  9287. while ($row_parent = Database::fetch_array($res_parent)) {
  9288. $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
  9289. $return .= 'child_name['.$row['iid'].']['.$i.'] = '.$js_var.' ;'."\n";
  9290. $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
  9291. }
  9292. $return .= "\n";
  9293. }
  9294. $return .= "
  9295. function load_cbo(id) {
  9296. if (!id) {
  9297. return false;
  9298. }
  9299. var cbo = document.getElementById('previous');
  9300. for(var i = cbo.length - 1; i > 0; i--) {
  9301. cbo.options[i] = null;
  9302. }
  9303. var k=0;
  9304. for(var i = 1; i <= child_name[id].length; i++){
  9305. var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
  9306. option.style.paddingLeft = '40px';
  9307. cbo.options[i] = option;
  9308. k = i;
  9309. }
  9310. cbo.options[k].selected = true;
  9311. $('#previous').selectpicker('refresh');
  9312. }";
  9313. return $return;
  9314. }
  9315. /**
  9316. * Display the form to allow moving an item.
  9317. *
  9318. * @param int $item_id Item ID
  9319. *
  9320. * @throws Exception
  9321. * @throws HTML_QuickForm_Error
  9322. *
  9323. * @return string HTML form
  9324. */
  9325. public function display_move_item($item_id)
  9326. {
  9327. $return = '';
  9328. if (is_numeric($item_id)) {
  9329. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  9330. $sql = "SELECT * FROM $tbl_lp_item
  9331. WHERE iid = $item_id";
  9332. $res = Database::query($sql);
  9333. $row = Database::fetch_array($res);
  9334. switch ($row['item_type']) {
  9335. case 'dir':
  9336. case 'asset':
  9337. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9338. $return .= $this->display_item_form(
  9339. $row['item_type'],
  9340. get_lang('MoveCurrentChapter'),
  9341. 'move',
  9342. $item_id,
  9343. $row
  9344. );
  9345. break;
  9346. case TOOL_DOCUMENT:
  9347. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9348. $return .= $this->display_document_form('move', $item_id, $row);
  9349. break;
  9350. case TOOL_LINK:
  9351. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9352. $return .= $this->display_link_form('move', $item_id, $row);
  9353. break;
  9354. case TOOL_HOTPOTATOES:
  9355. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9356. $return .= $this->display_link_form('move', $item_id, $row);
  9357. break;
  9358. case TOOL_QUIZ:
  9359. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9360. $return .= $this->display_quiz_form('move', $item_id, $row);
  9361. break;
  9362. case TOOL_STUDENTPUBLICATION:
  9363. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9364. $return .= $this->display_student_publication_form('move', $item_id, $row);
  9365. break;
  9366. case TOOL_FORUM:
  9367. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9368. $return .= $this->display_forum_form('move', $item_id, $row);
  9369. break;
  9370. case TOOL_THREAD:
  9371. $return .= $this->display_manipulate($item_id, $row['item_type']);
  9372. $return .= $this->display_forum_form('move', $item_id, $row);
  9373. break;
  9374. }
  9375. }
  9376. return $return;
  9377. }
  9378. /**
  9379. * Return HTML form to allow prerequisites selection.
  9380. *
  9381. * @todo use FormValidator
  9382. *
  9383. * @param int Item ID
  9384. *
  9385. * @return string HTML form
  9386. */
  9387. public function display_item_prerequisites_form($item_id = 0)
  9388. {
  9389. $course_id = api_get_course_int_id();
  9390. $item_id = (int) $item_id;
  9391. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  9392. /* Current prerequisite */
  9393. $sql = "SELECT * FROM $tbl_lp_item
  9394. WHERE iid = $item_id";
  9395. $result = Database::query($sql);
  9396. $row = Database::fetch_array($result);
  9397. $prerequisiteId = $row['prerequisite'];
  9398. $return = '<legend>';
  9399. $return .= get_lang('AddEditPrerequisites');
  9400. $return .= '</legend>';
  9401. $return .= '<form method="POST">';
  9402. $return .= '<div class="table-responsive">';
  9403. $return .= '<table class="table table-hover">';
  9404. $return .= '<thead>';
  9405. $return .= '<tr>';
  9406. $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
  9407. $return .= '<th width="140">'.get_lang('Minimum').'</th>';
  9408. $return .= '<th width="140">'.get_lang('Maximum').'</th>';
  9409. $return .= '</tr>';
  9410. $return .= '</thead>';
  9411. // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
  9412. $return .= '<tbody>';
  9413. $return .= '<tr>';
  9414. $return .= '<td colspan="3">';
  9415. $return .= '<div class="radio learnpath"><label for="idNone">';
  9416. $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
  9417. $return .= get_lang('None').'</label>';
  9418. $return .= '</div>';
  9419. $return .= '</tr>';
  9420. $sql = "SELECT * FROM $tbl_lp_item
  9421. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  9422. $result = Database::query($sql);
  9423. $arrLP = [];
  9424. $selectedMinScore = [];
  9425. $selectedMaxScore = [];
  9426. $masteryScore = [];
  9427. while ($row = Database::fetch_array($result)) {
  9428. if ($row['iid'] == $item_id) {
  9429. $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
  9430. $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
  9431. }
  9432. $masteryScore[$row['iid']] = $row['mastery_score'];
  9433. $arrLP[] = [
  9434. 'id' => $row['iid'],
  9435. 'item_type' => $row['item_type'],
  9436. 'title' => $row['title'],
  9437. 'ref' => $row['ref'],
  9438. 'description' => $row['description'],
  9439. 'parent_item_id' => $row['parent_item_id'],
  9440. 'previous_item_id' => $row['previous_item_id'],
  9441. 'next_item_id' => $row['next_item_id'],
  9442. 'max_score' => $row['max_score'],
  9443. 'min_score' => $row['min_score'],
  9444. 'mastery_score' => $row['mastery_score'],
  9445. 'prerequisite' => $row['prerequisite'],
  9446. 'display_order' => $row['display_order'],
  9447. 'prerequisite_min_score' => $row['prerequisite_min_score'],
  9448. 'prerequisite_max_score' => $row['prerequisite_max_score'],
  9449. ];
  9450. }
  9451. $this->tree_array($arrLP);
  9452. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  9453. unset($this->arrMenu);
  9454. for ($i = 0; $i < count($arrLP); $i++) {
  9455. $item = $arrLP[$i];
  9456. if ($item['id'] == $item_id) {
  9457. break;
  9458. }
  9459. $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
  9460. $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
  9461. $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
  9462. $return .= '<tr>';
  9463. $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
  9464. $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
  9465. $return .= '<label for="id'.$item['id'].'">';
  9466. $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').($item['item_type'] == 'dir' ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
  9467. $icon_name = str_replace(' ', '', $item['item_type']);
  9468. if (file_exists('../img/lp_'.$icon_name.'.png')) {
  9469. $return .= Display::return_icon('lp_'.$icon_name.'.png');
  9470. } else {
  9471. if (file_exists('../img/lp_'.$icon_name.'.png')) {
  9472. $return .= Display::return_icon('lp_'.$icon_name.'.png');
  9473. } else {
  9474. $return .= Display::return_icon('folder_document.png');
  9475. }
  9476. }
  9477. $return .= $item['title'].'</label>';
  9478. $return .= '</div>';
  9479. $return .= '</td>';
  9480. if ($item['item_type'] == TOOL_QUIZ) {
  9481. // lets update max_score Quiz information depending of the Quiz Advanced properties
  9482. $lpItemObj = new LpItem($course_id, $item['id']);
  9483. $exercise = new Exercise($course_id);
  9484. $exercise->read($lpItemObj->path);
  9485. $lpItemObj->max_score = $exercise->get_max_score();
  9486. $lpItemObj->update();
  9487. $item['max_score'] = $lpItemObj->max_score;
  9488. if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
  9489. // Backwards compatibility with 1.9.x use mastery_score as min value
  9490. $selectedMinScoreValue = $masteryScoreAsMinValue;
  9491. }
  9492. $return .= '<td>';
  9493. $return .= '<input
  9494. class="form-control"
  9495. size="4" maxlength="3"
  9496. name="min_'.$item['id'].'"
  9497. type="number"
  9498. min="0"
  9499. step="1"
  9500. max="'.$item['max_score'].'"
  9501. value="'.$selectedMinScoreValue.'"
  9502. />';
  9503. $return .= '</td>';
  9504. $return .= '<td>';
  9505. $return .= '<input
  9506. class="form-control"
  9507. size="4"
  9508. maxlength="3"
  9509. name="max_'.$item['id'].'"
  9510. type="number"
  9511. min="0"
  9512. step="1"
  9513. max="'.$item['max_score'].'"
  9514. value="'.$selectedMaxScoreValue.'"
  9515. />';
  9516. $return .= '</td>';
  9517. }
  9518. if ($item['item_type'] == TOOL_HOTPOTATOES) {
  9519. $return .= '<td>';
  9520. $return .= '<input
  9521. size="4"
  9522. maxlength="3"
  9523. name="min_'.$item['id'].'"
  9524. type="number"
  9525. min="0"
  9526. step="1"
  9527. max="'.$item['max_score'].'"
  9528. value="'.$selectedMinScoreValue.'"
  9529. />';
  9530. $return .= '</td>';
  9531. $return .= '<td>';
  9532. $return .= '<input
  9533. size="4"
  9534. maxlength="3"
  9535. name="max_'.$item['id'].'"
  9536. type="number"
  9537. min="0"
  9538. step="1"
  9539. max="'.$item['max_score'].'"
  9540. value="'.$selectedMaxScoreValue.'"
  9541. />';
  9542. $return .= '</td>';
  9543. }
  9544. $return .= '</tr>';
  9545. }
  9546. $return .= '<tr>';
  9547. $return .= '</tr>';
  9548. $return .= '</tbody>';
  9549. $return .= '</table>';
  9550. $return .= '</div>';
  9551. $return .= '<div class="form-group">';
  9552. $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
  9553. get_lang('ModifyPrerequisites').'</button>';
  9554. $return .= '</form>';
  9555. return $return;
  9556. }
  9557. /**
  9558. * Return HTML list to allow prerequisites selection for lp.
  9559. *
  9560. * @return string HTML form
  9561. */
  9562. public function display_lp_prerequisites_list()
  9563. {
  9564. $course_id = api_get_course_int_id();
  9565. $lp_id = $this->lp_id;
  9566. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  9567. // get current prerequisite
  9568. $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
  9569. $result = Database::query($sql);
  9570. $row = Database::fetch_array($result);
  9571. $prerequisiteId = $row['prerequisite'];
  9572. $session_id = api_get_session_id();
  9573. $session_condition = api_get_session_condition($session_id, true, true);
  9574. $sql = "SELECT * FROM $tbl_lp
  9575. WHERE c_id = $course_id $session_condition
  9576. ORDER BY display_order ";
  9577. $rs = Database::query($sql);
  9578. $return = '';
  9579. $return .= '<select name="prerequisites" class="form-control">';
  9580. $return .= '<option value="0">'.get_lang('None').'</option>';
  9581. if (Database::num_rows($rs) > 0) {
  9582. while ($row = Database::fetch_array($rs)) {
  9583. if ($row['id'] == $lp_id) {
  9584. continue;
  9585. }
  9586. $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
  9587. }
  9588. }
  9589. $return .= '</select>';
  9590. return $return;
  9591. }
  9592. /**
  9593. * Creates a list with all the documents in it.
  9594. *
  9595. * @param bool $showInvisibleFiles
  9596. *
  9597. * @throws Exception
  9598. * @throws HTML_QuickForm_Error
  9599. *
  9600. * @return string
  9601. */
  9602. public function get_documents($showInvisibleFiles = false)
  9603. {
  9604. $course_info = api_get_course_info();
  9605. $sessionId = api_get_session_id();
  9606. $documentTree = DocumentManager::get_document_preview(
  9607. $course_info,
  9608. $this->lp_id,
  9609. null,
  9610. $sessionId,
  9611. true,
  9612. null,
  9613. null,
  9614. $showInvisibleFiles,
  9615. true
  9616. );
  9617. $headers = [
  9618. get_lang('Files'),
  9619. get_lang('CreateTheDocument'),
  9620. get_lang('CreateReadOutText'),
  9621. get_lang('Upload'),
  9622. ];
  9623. $form = new FormValidator(
  9624. 'form_upload',
  9625. 'POST',
  9626. $this->getCurrentBuildingModeURL(),
  9627. '',
  9628. ['enctype' => 'multipart/form-data']
  9629. );
  9630. $folders = DocumentManager::get_all_document_folders(
  9631. api_get_course_info(),
  9632. 0,
  9633. true
  9634. );
  9635. $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
  9636. DocumentManager::build_directory_selector(
  9637. $folders,
  9638. $lpPathInfo['id'],
  9639. [],
  9640. true,
  9641. $form,
  9642. 'directory_parent_id'
  9643. );
  9644. $group = [
  9645. $form->createElement(
  9646. 'radio',
  9647. 'if_exists',
  9648. get_lang("UplWhatIfFileExists"),
  9649. get_lang('UplDoNothing'),
  9650. 'nothing'
  9651. ),
  9652. $form->createElement(
  9653. 'radio',
  9654. 'if_exists',
  9655. null,
  9656. get_lang('UplOverwriteLong'),
  9657. 'overwrite'
  9658. ),
  9659. $form->createElement(
  9660. 'radio',
  9661. 'if_exists',
  9662. null,
  9663. get_lang('UplRenameLong'),
  9664. 'rename'
  9665. ),
  9666. ];
  9667. $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
  9668. $fileExistsOption = api_get_setting('document_if_file_exists_option');
  9669. $defaultFileExistsOption = 'rename';
  9670. if (!empty($fileExistsOption)) {
  9671. $defaultFileExistsOption = $fileExistsOption;
  9672. }
  9673. $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
  9674. // Check box options
  9675. $form->addElement(
  9676. 'checkbox',
  9677. 'unzip',
  9678. get_lang('Options'),
  9679. get_lang('Uncompress')
  9680. );
  9681. $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
  9682. $form->addMultipleUpload($url);
  9683. $new = $this->display_document_form('add', 0);
  9684. $frmReadOutText = $this->displayFrmReadOutText('add');
  9685. $tabs = Display::tabs(
  9686. $headers,
  9687. [$documentTree, $new, $frmReadOutText, $form->returnForm()],
  9688. 'subtab'
  9689. );
  9690. return $tabs;
  9691. }
  9692. /**
  9693. * Creates a list with all the exercises (quiz) in it.
  9694. *
  9695. * @return string
  9696. */
  9697. public function get_exercises()
  9698. {
  9699. $course_id = api_get_course_int_id();
  9700. $session_id = api_get_session_id();
  9701. $userInfo = api_get_user_info();
  9702. // New for hotpotatoes.
  9703. $uploadPath = DIR_HOTPOTATOES; //defined in main_api
  9704. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  9705. $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
  9706. $condition_session = api_get_session_condition($session_id, true, true);
  9707. $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
  9708. $activeCondition = ' active <> -1 ';
  9709. if ($setting) {
  9710. $activeCondition = ' active = 1 ';
  9711. }
  9712. $sql_quiz = "SELECT * FROM $tbl_quiz
  9713. WHERE c_id = $course_id AND $activeCondition $condition_session
  9714. ORDER BY title ASC";
  9715. $sql_hot = "SELECT * FROM $tbl_doc
  9716. WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%' $condition_session
  9717. ORDER BY id ASC";
  9718. $res_quiz = Database::query($sql_quiz);
  9719. $res_hot = Database::query($sql_hot);
  9720. $return = '<ul class="lp_resource">';
  9721. $return .= '<li class="lp_resource_element">';
  9722. $return .= Display::return_icon('new_exercice.png');
  9723. $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
  9724. get_lang('NewExercise').'</a>';
  9725. $return .= '</li>';
  9726. $previewIcon = Display::return_icon(
  9727. 'preview_view.png',
  9728. get_lang('Preview')
  9729. );
  9730. $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
  9731. $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
  9732. $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
  9733. // Display hotpotatoes
  9734. while ($row_hot = Database::fetch_array($res_hot)) {
  9735. $link = Display::url(
  9736. $previewIcon,
  9737. $exerciseUrl.'&file='.$row_hot['path'],
  9738. ['target' => '_blank']
  9739. );
  9740. $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
  9741. $return .= '<a class="moved" href="#">';
  9742. $return .= Display::return_icon(
  9743. 'move_everywhere.png',
  9744. get_lang('Move'),
  9745. [],
  9746. ICON_SIZE_TINY
  9747. );
  9748. $return .= '</a> ';
  9749. $return .= Display::return_icon('hotpotatoes_s.png');
  9750. $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
  9751. ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
  9752. $return .= '</li>';
  9753. }
  9754. $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
  9755. while ($row_quiz = Database::fetch_array($res_quiz)) {
  9756. $title = strip_tags(
  9757. api_html_entity_decode($row_quiz['title'])
  9758. );
  9759. $visibility = api_get_item_visibility(
  9760. ['real_id' => $course_id],
  9761. TOOL_QUIZ,
  9762. $row_quiz['iid'],
  9763. $session_id
  9764. );
  9765. $link = Display::url(
  9766. $previewIcon,
  9767. $exerciseUrl.'&exerciseId='.$row_quiz['id'],
  9768. ['target' => '_blank']
  9769. );
  9770. $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
  9771. $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
  9772. $return .= $quizIcon;
  9773. $sessionStar = api_get_session_image(
  9774. $row_quiz['session_id'],
  9775. $userInfo['status']
  9776. );
  9777. $return .= Display::url(
  9778. Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
  9779. api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
  9780. [
  9781. 'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
  9782. ]
  9783. );
  9784. $return .= '</li>';
  9785. }
  9786. $return .= '</ul>';
  9787. return $return;
  9788. }
  9789. /**
  9790. * Creates a list with all the links in it.
  9791. *
  9792. * @return string
  9793. */
  9794. public function get_links()
  9795. {
  9796. $selfUrl = api_get_self();
  9797. $courseIdReq = api_get_cidreq();
  9798. $course = api_get_course_info();
  9799. $userInfo = api_get_user_info();
  9800. $course_id = $course['real_id'];
  9801. $tbl_link = Database::get_course_table(TABLE_LINK);
  9802. $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
  9803. $moveEverywhereIcon = Display::return_icon(
  9804. 'move_everywhere.png',
  9805. get_lang('Move'),
  9806. [],
  9807. ICON_SIZE_TINY
  9808. );
  9809. $session_id = api_get_session_id();
  9810. $condition_session = api_get_session_condition(
  9811. $session_id,
  9812. true,
  9813. true,
  9814. "link.session_id"
  9815. );
  9816. $sql = "SELECT
  9817. link.id as link_id,
  9818. link.title as link_title,
  9819. link.session_id as link_session_id,
  9820. link.category_id as category_id,
  9821. link_category.category_title as category_title
  9822. FROM $tbl_link as link
  9823. LEFT JOIN $linkCategoryTable as link_category
  9824. ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
  9825. WHERE link.c_id = ".$course_id." $condition_session
  9826. ORDER BY link_category.category_title ASC, link.title ASC";
  9827. $result = Database::query($sql);
  9828. $categorizedLinks = [];
  9829. $categories = [];
  9830. while ($link = Database::fetch_array($result)) {
  9831. if (!$link['category_id']) {
  9832. $link['category_title'] = get_lang('Uncategorized');
  9833. }
  9834. $categories[$link['category_id']] = $link['category_title'];
  9835. $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
  9836. }
  9837. $linksHtmlCode =
  9838. '<script>
  9839. function toggle_tool(tool, id) {
  9840. if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
  9841. document.getElementById(tool+"_"+id+"_content").style.display = "block";
  9842. document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
  9843. } else {
  9844. document.getElementById(tool+"_"+id+"_content").style.display = "none";
  9845. document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
  9846. }
  9847. }
  9848. </script>
  9849. <ul class="lp_resource">
  9850. <li class="lp_resource_element">
  9851. '.Display::return_icon('linksnew.gif').'
  9852. <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
  9853. get_lang('LinkAdd').'
  9854. </a>
  9855. </li>';
  9856. foreach ($categorizedLinks as $categoryId => $links) {
  9857. $linkNodes = null;
  9858. foreach ($links as $key => $linkInfo) {
  9859. $title = $linkInfo['link_title'];
  9860. $linkSessionId = $linkInfo['link_session_id'];
  9861. $link = Display::url(
  9862. Display::return_icon('preview_view.png', get_lang('Preview')),
  9863. api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
  9864. ['target' => '_blank']
  9865. );
  9866. if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
  9867. $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
  9868. $linkNodes .=
  9869. '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
  9870. <a class="moved" href="#">'.
  9871. $moveEverywhereIcon.
  9872. '</a>
  9873. '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
  9874. <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
  9875. TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
  9876. Security::remove_XSS($title).$sessionStar.$link.
  9877. '</a>
  9878. </li>';
  9879. }
  9880. }
  9881. $linksHtmlCode .=
  9882. '<li>
  9883. <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
  9884. <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
  9885. align="absbottom" />
  9886. </a>
  9887. <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
  9888. </li>
  9889. <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
  9890. }
  9891. $linksHtmlCode .= '</ul>';
  9892. return $linksHtmlCode;
  9893. }
  9894. /**
  9895. * Creates a list with all the student publications in it.
  9896. *
  9897. * @return string
  9898. */
  9899. public function get_student_publications()
  9900. {
  9901. $return = '<ul class="lp_resource">';
  9902. $return .= '<li class="lp_resource_element">';
  9903. $return .= Display::return_icon('works_new.gif');
  9904. $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
  9905. get_lang('AddAssignmentPage').'</a>';
  9906. $return .= '</li>';
  9907. require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
  9908. $works = getWorkListTeacher(0, 100, null, null, null);
  9909. if (!empty($works)) {
  9910. foreach ($works as $work) {
  9911. $link = Display::url(
  9912. Display::return_icon('preview_view.png', get_lang('Preview')),
  9913. api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
  9914. ['target' => '_blank']
  9915. );
  9916. $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
  9917. $return .= '<a class="moved" href="#">';
  9918. $return .= Display::return_icon(
  9919. 'move_everywhere.png',
  9920. get_lang('Move'),
  9921. [],
  9922. ICON_SIZE_TINY
  9923. );
  9924. $return .= '</a> ';
  9925. $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
  9926. $return .= ' <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id.'">'.
  9927. Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
  9928. </a>';
  9929. $return .= '</li>';
  9930. }
  9931. }
  9932. $return .= '</ul>';
  9933. return $return;
  9934. }
  9935. /**
  9936. * Creates a list with all the forums in it.
  9937. *
  9938. * @return string
  9939. */
  9940. public function get_forums()
  9941. {
  9942. require_once '../forum/forumfunction.inc.php';
  9943. require_once '../forum/forumconfig.inc.php';
  9944. $forumCategories = get_forum_categories();
  9945. $forumsInNoCategory = get_forums_in_category(0);
  9946. if (!empty($forumsInNoCategory)) {
  9947. $forumCategories = array_merge(
  9948. $forumCategories,
  9949. [
  9950. [
  9951. 'cat_id' => 0,
  9952. 'session_id' => 0,
  9953. 'visibility' => 1,
  9954. 'cat_comment' => null,
  9955. ],
  9956. ]
  9957. );
  9958. }
  9959. $forumList = get_forums();
  9960. $a_forums = [];
  9961. foreach ($forumCategories as $forumCategory) {
  9962. // The forums in this category.
  9963. $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
  9964. if (!empty($forumsInCategory)) {
  9965. foreach ($forumList as $forum) {
  9966. if (isset($forum['forum_category']) &&
  9967. $forum['forum_category'] == $forumCategory['cat_id']
  9968. ) {
  9969. $a_forums[] = $forum;
  9970. }
  9971. }
  9972. }
  9973. }
  9974. $return = '<ul class="lp_resource">';
  9975. // First add link
  9976. $return .= '<li class="lp_resource_element">';
  9977. $return .= Display::return_icon('new_forum.png');
  9978. $return .= Display::url(
  9979. get_lang('CreateANewForum'),
  9980. api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
  9981. 'action' => 'add',
  9982. 'content' => 'forum',
  9983. 'lp_id' => $this->lp_id,
  9984. ]),
  9985. ['title' => get_lang('CreateANewForum')]
  9986. );
  9987. $return .= '</li>';
  9988. $return .= '<script>
  9989. function toggle_forum(forum_id) {
  9990. if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
  9991. document.getElementById("forum_"+forum_id+"_content").style.display = "block";
  9992. document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
  9993. } else {
  9994. document.getElementById("forum_"+forum_id+"_content").style.display = "none";
  9995. document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
  9996. }
  9997. }
  9998. </script>';
  9999. foreach ($a_forums as $forum) {
  10000. if (!empty($forum['forum_id'])) {
  10001. $link = Display::url(
  10002. Display::return_icon('preview_view.png', get_lang('Preview')),
  10003. api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
  10004. ['target' => '_blank']
  10005. );
  10006. $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
  10007. $return .= '<a class="moved" href="#">';
  10008. $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
  10009. $return .= ' </a>';
  10010. $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
  10011. $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
  10012. <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
  10013. </a>
  10014. <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forum['forum_id'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
  10015. Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
  10016. $return .= '</li>';
  10017. $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
  10018. $a_threads = get_threads($forum['forum_id']);
  10019. if (is_array($a_threads)) {
  10020. foreach ($a_threads as $thread) {
  10021. $link = Display::url(
  10022. Display::return_icon('preview_view.png', get_lang('Preview')),
  10023. api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
  10024. ['target' => '_blank']
  10025. );
  10026. $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
  10027. $return .= '&nbsp;<a class="moved" href="#">';
  10028. $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
  10029. $return .= ' </a>';
  10030. $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
  10031. $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$thread['thread_id'].'&lp_id='.$this->lp_id.'">'.
  10032. Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
  10033. $return .= '</li>';
  10034. }
  10035. }
  10036. $return .= '</div>';
  10037. }
  10038. }
  10039. $return .= '</ul>';
  10040. return $return;
  10041. }
  10042. /**
  10043. * // TODO: The output encoding should be equal to the system encoding.
  10044. *
  10045. * Exports the learning path as a SCORM package. This is the main function that
  10046. * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
  10047. * whole thing and returns the zip.
  10048. *
  10049. * This method needs to be called in PHP5, as it will fail with non-adequate
  10050. * XML package (like the ones for PHP4), and it is *not* a static method, so
  10051. * you need to call it on a learnpath object.
  10052. *
  10053. * @TODO The method might be redefined later on in the scorm class itself to avoid
  10054. * creating a SCORM structure if there is one already. However, if the initial SCORM
  10055. * path has been modified, it should use the generic method here below.
  10056. *
  10057. * @return string Returns the zip package string, or null if error
  10058. */
  10059. public function scormExport()
  10060. {
  10061. api_set_more_memory_and_time_limits();
  10062. $_course = api_get_course_info();
  10063. $course_id = $_course['real_id'];
  10064. // Create the zip handler (this will remain available throughout the method).
  10065. $archivePath = api_get_path(SYS_ARCHIVE_PATH);
  10066. $sys_course_path = api_get_path(SYS_COURSE_PATH);
  10067. $temp_dir_short = uniqid('scorm_export', true);
  10068. $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
  10069. $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
  10070. $zip_folder = new PclZip($temp_zip_file);
  10071. $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
  10072. $root_path = $main_path = api_get_path(SYS_PATH);
  10073. $files_cleanup = [];
  10074. // Place to temporarily stash the zip file.
  10075. // create the temp dir if it doesn't exist
  10076. // or do a cleanup before creating the zip file.
  10077. if (!is_dir($temp_zip_dir)) {
  10078. mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
  10079. } else {
  10080. // Cleanup: Check the temp dir for old files and delete them.
  10081. $handle = opendir($temp_zip_dir);
  10082. while (false !== ($file = readdir($handle))) {
  10083. if ($file != '.' && $file != '..') {
  10084. unlink("$temp_zip_dir/$file");
  10085. }
  10086. }
  10087. closedir($handle);
  10088. }
  10089. $zip_files = $zip_files_abs = $zip_files_dist = [];
  10090. if (is_dir($current_course_path.'/scorm/'.$this->path) &&
  10091. is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
  10092. ) {
  10093. // Remove the possible . at the end of the path.
  10094. $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
  10095. $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
  10096. mkdir(
  10097. $dest_path_to_scorm_folder,
  10098. api_get_permissions_for_new_directories(),
  10099. true
  10100. );
  10101. copyr(
  10102. $current_course_path.'/scorm/'.$this->path,
  10103. $dest_path_to_scorm_folder,
  10104. ['imsmanifest'],
  10105. $zip_files
  10106. );
  10107. }
  10108. // Build a dummy imsmanifest structure.
  10109. // Do not add to the zip yet (we still need it).
  10110. // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
  10111. // Aggregation Model official document, section "2.3 Content Packaging".
  10112. // We are going to build a UTF-8 encoded manifest.
  10113. // Later we will recode it to the desired (and supported) encoding.
  10114. $xmldoc = new DOMDocument('1.0');
  10115. $root = $xmldoc->createElement('manifest');
  10116. $root->setAttribute('identifier', 'SingleCourseManifest');
  10117. $root->setAttribute('version', '1.1');
  10118. $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
  10119. $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
  10120. $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  10121. $root->setAttribute(
  10122. 'xsi:schemaLocation',
  10123. 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'
  10124. );
  10125. // Build mandatory sub-root container elements.
  10126. $metadata = $xmldoc->createElement('metadata');
  10127. $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
  10128. $metadata->appendChild($md_schema);
  10129. $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
  10130. $metadata->appendChild($md_schemaversion);
  10131. $root->appendChild($metadata);
  10132. $organizations = $xmldoc->createElement('organizations');
  10133. $resources = $xmldoc->createElement('resources');
  10134. // Build the only organization we will use in building our learnpaths.
  10135. $organizations->setAttribute('default', 'chamilo_scorm_export');
  10136. $organization = $xmldoc->createElement('organization');
  10137. $organization->setAttribute('identifier', 'chamilo_scorm_export');
  10138. // To set the title of the SCORM entity (=organization), we take the name given
  10139. // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
  10140. // learning path charset) as it is the encoding that defines how it is stored
  10141. // in the database. Then we convert it to HTML entities again as the "&" character
  10142. // alone is not authorized in XML (must be &amp;).
  10143. // The title is then decoded twice when extracting (see scorm::parse_manifest).
  10144. $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
  10145. $organization->appendChild($org_title);
  10146. $folder_name = 'document';
  10147. // Removes the learning_path/scorm_folder path when exporting see #4841
  10148. $path_to_remove = '';
  10149. $path_to_replace = '';
  10150. $result = $this->generate_lp_folder($_course);
  10151. if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
  10152. $path_to_remove = 'document'.$result['dir'];
  10153. $path_to_replace = $folder_name.'/';
  10154. }
  10155. // Fixes chamilo scorm exports
  10156. if ($this->ref === 'chamilo_scorm_export') {
  10157. $path_to_remove = 'scorm/'.$this->path.'/document/';
  10158. }
  10159. // For each element, add it to the imsmanifest structure, then add it to the zip.
  10160. $link_updates = [];
  10161. $links_to_create = [];
  10162. foreach ($this->ordered_items as $index => $itemId) {
  10163. /** @var learnpathItem $item */
  10164. $item = $this->items[$itemId];
  10165. if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
  10166. // Get included documents from this item.
  10167. if ($item->type === 'sco') {
  10168. $inc_docs = $item->get_resources_from_source(
  10169. null,
  10170. $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
  10171. );
  10172. } else {
  10173. $inc_docs = $item->get_resources_from_source();
  10174. }
  10175. // Give a child element <item> to the <organization> element.
  10176. $my_item_id = $item->get_id();
  10177. $my_item = $xmldoc->createElement('item');
  10178. $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
  10179. $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
  10180. $my_item->setAttribute('isvisible', 'true');
  10181. // Give a child element <title> to the <item> element.
  10182. $my_title = $xmldoc->createElement(
  10183. 'title',
  10184. htmlspecialchars(
  10185. api_utf8_encode($item->get_title()),
  10186. ENT_QUOTES,
  10187. 'UTF-8'
  10188. )
  10189. );
  10190. $my_item->appendChild($my_title);
  10191. // Give a child element <adlcp:prerequisites> to the <item> element.
  10192. $my_prereqs = $xmldoc->createElement(
  10193. 'adlcp:prerequisites',
  10194. $this->get_scorm_prereq_string($my_item_id)
  10195. );
  10196. $my_prereqs->setAttribute('type', 'aicc_script');
  10197. $my_item->appendChild($my_prereqs);
  10198. // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
  10199. //$xmldoc->createElement('adlcp:maxtimeallowed','');
  10200. // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
  10201. //$xmldoc->createElement('adlcp:timelimitaction','');
  10202. // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
  10203. //$xmldoc->createElement('adlcp:datafromlms','');
  10204. // Give a child element <adlcp:masteryscore> to the <item> element.
  10205. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
  10206. $my_item->appendChild($my_masteryscore);
  10207. // Attach this item to the organization element or hits parent if there is one.
  10208. if (!empty($item->parent) && $item->parent != 0) {
  10209. $children = $organization->childNodes;
  10210. $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
  10211. if (is_object($possible_parent)) {
  10212. $possible_parent->appendChild($my_item);
  10213. } else {
  10214. if ($this->debug > 0) {
  10215. error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
  10216. }
  10217. }
  10218. } else {
  10219. if ($this->debug > 0) {
  10220. error_log('No parent');
  10221. }
  10222. $organization->appendChild($my_item);
  10223. }
  10224. // Get the path of the file(s) from the course directory root.
  10225. $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
  10226. $my_xml_file_path = $my_file_path;
  10227. if (!empty($path_to_remove)) {
  10228. // From docs
  10229. $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
  10230. // From quiz
  10231. if ($this->ref === 'chamilo_scorm_export') {
  10232. $path_to_remove = 'scorm/'.$this->path.'/';
  10233. $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
  10234. }
  10235. }
  10236. $my_sub_dir = dirname($my_file_path);
  10237. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  10238. $my_xml_sub_dir = $my_sub_dir;
  10239. // Give a <resource> child to the <resources> element
  10240. $my_resource = $xmldoc->createElement('resource');
  10241. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  10242. $my_resource->setAttribute('type', 'webcontent');
  10243. $my_resource->setAttribute('href', $my_xml_file_path);
  10244. // adlcp:scormtype can be either 'sco' or 'asset'.
  10245. if ($item->type === 'sco') {
  10246. $my_resource->setAttribute('adlcp:scormtype', 'sco');
  10247. } else {
  10248. $my_resource->setAttribute('adlcp:scormtype', 'asset');
  10249. }
  10250. // xml:base is the base directory to find the files declared in this resource.
  10251. $my_resource->setAttribute('xml:base', '');
  10252. // Give a <file> child to the <resource> element.
  10253. $my_file = $xmldoc->createElement('file');
  10254. $my_file->setAttribute('href', $my_xml_file_path);
  10255. $my_resource->appendChild($my_file);
  10256. // Dependency to other files - not yet supported.
  10257. $i = 1;
  10258. if ($inc_docs) {
  10259. foreach ($inc_docs as $doc_info) {
  10260. if (count($doc_info) < 1 || empty($doc_info[0])) {
  10261. continue;
  10262. }
  10263. $my_dep = $xmldoc->createElement('resource');
  10264. $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
  10265. $my_dep->setAttribute('identifier', $res_id);
  10266. $my_dep->setAttribute('type', 'webcontent');
  10267. $my_dep->setAttribute('adlcp:scormtype', 'asset');
  10268. $my_dep_file = $xmldoc->createElement('file');
  10269. // Check type of URL.
  10270. if ($doc_info[1] == 'remote') {
  10271. // Remote file. Save url as is.
  10272. $my_dep_file->setAttribute('href', $doc_info[0]);
  10273. $my_dep->setAttribute('xml:base', '');
  10274. } elseif ($doc_info[1] === 'local') {
  10275. switch ($doc_info[2]) {
  10276. case 'url':
  10277. // Local URL - save path as url for now, don't zip file.
  10278. $abs_path = api_get_path(SYS_PATH).
  10279. str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
  10280. $current_dir = dirname($abs_path);
  10281. $current_dir = str_replace('\\', '/', $current_dir);
  10282. $file_path = realpath($abs_path);
  10283. $file_path = str_replace('\\', '/', $file_path);
  10284. $my_dep_file->setAttribute('href', $file_path);
  10285. $my_dep->setAttribute('xml:base', '');
  10286. if (strstr($file_path, $main_path) !== false) {
  10287. // The calculated real path is really inside Chamilo's root path.
  10288. // Reduce file path to what's under the DocumentRoot.
  10289. $replace = $file_path;
  10290. $file_path = substr($file_path, strlen($root_path) - 1);
  10291. $destinationFile = $file_path;
  10292. if (strstr($file_path, 'upload/users') !== false) {
  10293. $pos = strpos($file_path, 'my_files/');
  10294. if ($pos !== false) {
  10295. $onlyDirectory = str_replace(
  10296. 'upload/users/',
  10297. '',
  10298. substr($file_path, $pos, strlen($file_path))
  10299. );
  10300. }
  10301. $replace = $onlyDirectory;
  10302. $destinationFile = $replace;
  10303. }
  10304. $zip_files_abs[] = $file_path;
  10305. $link_updates[$my_file_path][] = [
  10306. 'orig' => $doc_info[0],
  10307. 'dest' => $destinationFile,
  10308. 'replace' => $replace,
  10309. ];
  10310. $my_dep_file->setAttribute('href', $file_path);
  10311. $my_dep->setAttribute('xml:base', '');
  10312. } elseif (empty($file_path)) {
  10313. $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
  10314. $file_path = str_replace('//', '/', $file_path);
  10315. if (file_exists($file_path)) {
  10316. // We get the relative path.
  10317. $file_path = substr($file_path, strlen($current_dir));
  10318. $zip_files[] = $my_sub_dir.'/'.$file_path;
  10319. $link_updates[$my_file_path][] = [
  10320. 'orig' => $doc_info[0],
  10321. 'dest' => $file_path,
  10322. ];
  10323. $my_dep_file->setAttribute('href', $file_path);
  10324. $my_dep->setAttribute('xml:base', '');
  10325. }
  10326. }
  10327. break;
  10328. case 'abs':
  10329. // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
  10330. $my_dep_file->setAttribute('href', $doc_info[0]);
  10331. $my_dep->setAttribute('xml:base', '');
  10332. // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
  10333. // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
  10334. $abs_img_path_without_subdir = $doc_info[0];
  10335. $relp = api_get_path(REL_PATH); // The url-append config param.
  10336. $pos = strpos($abs_img_path_without_subdir, $relp);
  10337. if ($pos === 0) {
  10338. $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
  10339. }
  10340. $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
  10341. $file_path = str_replace(['\\', '//'], '/', $file_path);
  10342. // Prepare the current directory path (until just under 'document') with a trailing slash.
  10343. $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
  10344. // Check if the current document is in that path.
  10345. if (strstr($file_path, $cur_path) !== false) {
  10346. $destinationFile = substr($file_path, strlen($cur_path));
  10347. $filePathNoCoursePart = substr($file_path, strlen($cur_path));
  10348. $fileToTest = $cur_path.$my_file_path;
  10349. if (!empty($path_to_remove)) {
  10350. $fileToTest = str_replace(
  10351. $path_to_remove.'/',
  10352. $path_to_replace,
  10353. $cur_path.$my_file_path
  10354. );
  10355. }
  10356. $relative_path = api_get_relative_path($fileToTest, $file_path);
  10357. // Put the current document in the zip (this array is the array
  10358. // that will manage documents already in the course folder - relative).
  10359. $zip_files[] = $filePathNoCoursePart;
  10360. // Update the links to the current document in the
  10361. // containing document (make them relative).
  10362. $link_updates[$my_file_path][] = [
  10363. 'orig' => $doc_info[0],
  10364. 'dest' => $destinationFile,
  10365. 'replace' => $relative_path,
  10366. ];
  10367. $my_dep_file->setAttribute('href', $file_path);
  10368. $my_dep->setAttribute('xml:base', '');
  10369. } elseif (strstr($file_path, $main_path) !== false) {
  10370. // The calculated real path is really inside Chamilo's root path.
  10371. // Reduce file path to what's under the DocumentRoot.
  10372. $file_path = substr($file_path, strlen($root_path));
  10373. $zip_files_abs[] = $file_path;
  10374. $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
  10375. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10376. $my_dep->setAttribute('xml:base', '');
  10377. } elseif (empty($file_path)) {
  10378. // Probably this is an image inside "/main" directory
  10379. $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
  10380. $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
  10381. if (file_exists($file_path)) {
  10382. if (strstr($file_path, 'main/default_course_document') !== false) {
  10383. // We get the relative path.
  10384. $pos = strpos($file_path, 'main/default_course_document/');
  10385. if ($pos !== false) {
  10386. $onlyDirectory = str_replace(
  10387. 'main/default_course_document/',
  10388. '',
  10389. substr($file_path, $pos, strlen($file_path))
  10390. );
  10391. }
  10392. $destinationFile = 'default_course_document/'.$onlyDirectory;
  10393. $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
  10394. $zip_files_abs[] = $fileAbs;
  10395. $link_updates[$my_file_path][] = [
  10396. 'orig' => $doc_info[0],
  10397. 'dest' => $destinationFile,
  10398. ];
  10399. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10400. $my_dep->setAttribute('xml:base', '');
  10401. }
  10402. }
  10403. }
  10404. break;
  10405. case 'rel':
  10406. // Path relative to the current document.
  10407. // Save xml:base as current document's directory and save file in zip as subdir.file_path
  10408. if (substr($doc_info[0], 0, 2) === '..') {
  10409. // Relative path going up.
  10410. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
  10411. $current_dir = str_replace('\\', '/', $current_dir);
  10412. $file_path = realpath($current_dir.$doc_info[0]);
  10413. $file_path = str_replace('\\', '/', $file_path);
  10414. if (strstr($file_path, $main_path) !== false) {
  10415. // The calculated real path is really inside Chamilo's root path.
  10416. // Reduce file path to what's under the DocumentRoot.
  10417. $file_path = substr($file_path, strlen($root_path));
  10418. $zip_files_abs[] = $file_path;
  10419. $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
  10420. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10421. $my_dep->setAttribute('xml:base', '');
  10422. }
  10423. } else {
  10424. $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
  10425. $my_dep_file->setAttribute('href', $doc_info[0]);
  10426. $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
  10427. }
  10428. break;
  10429. default:
  10430. $my_dep_file->setAttribute('href', $doc_info[0]);
  10431. $my_dep->setAttribute('xml:base', '');
  10432. break;
  10433. }
  10434. }
  10435. $my_dep->appendChild($my_dep_file);
  10436. $resources->appendChild($my_dep);
  10437. $dependency = $xmldoc->createElement('dependency');
  10438. $dependency->setAttribute('identifierref', $res_id);
  10439. $my_resource->appendChild($dependency);
  10440. $i++;
  10441. }
  10442. }
  10443. $resources->appendChild($my_resource);
  10444. $zip_files[] = $my_file_path;
  10445. } else {
  10446. // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
  10447. switch ($item->type) {
  10448. case TOOL_LINK:
  10449. $my_item = $xmldoc->createElement('item');
  10450. $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
  10451. $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
  10452. $my_item->setAttribute('isvisible', 'true');
  10453. // Give a child element <title> to the <item> element.
  10454. $my_title = $xmldoc->createElement(
  10455. 'title',
  10456. htmlspecialchars(
  10457. api_utf8_encode($item->get_title()),
  10458. ENT_QUOTES,
  10459. 'UTF-8'
  10460. )
  10461. );
  10462. $my_item->appendChild($my_title);
  10463. // Give a child element <adlcp:prerequisites> to the <item> element.
  10464. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
  10465. $my_prereqs->setAttribute('type', 'aicc_script');
  10466. $my_item->appendChild($my_prereqs);
  10467. // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
  10468. //$xmldoc->createElement('adlcp:maxtimeallowed', '');
  10469. // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
  10470. //$xmldoc->createElement('adlcp:timelimitaction', '');
  10471. // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
  10472. //$xmldoc->createElement('adlcp:datafromlms', '');
  10473. // Give a child element <adlcp:masteryscore> to the <item> element.
  10474. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
  10475. $my_item->appendChild($my_masteryscore);
  10476. // Attach this item to the organization element or its parent if there is one.
  10477. if (!empty($item->parent) && $item->parent != 0) {
  10478. $children = $organization->childNodes;
  10479. for ($i = 0; $i < $children->length; $i++) {
  10480. $item_temp = $children->item($i);
  10481. if ($item_temp->nodeName == 'item') {
  10482. if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
  10483. $item_temp->appendChild($my_item);
  10484. }
  10485. }
  10486. }
  10487. } else {
  10488. $organization->appendChild($my_item);
  10489. }
  10490. $my_file_path = 'link_'.$item->get_id().'.html';
  10491. $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
  10492. WHERE c_id = '.$course_id.' AND id = '.$item->path;
  10493. $rs = Database::query($sql);
  10494. if ($link = Database::fetch_array($rs)) {
  10495. $url = $link['url'];
  10496. $title = stripslashes($link['title']);
  10497. $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
  10498. $my_xml_file_path = $my_file_path;
  10499. $my_sub_dir = dirname($my_file_path);
  10500. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  10501. $my_xml_sub_dir = $my_sub_dir;
  10502. // Give a <resource> child to the <resources> element.
  10503. $my_resource = $xmldoc->createElement('resource');
  10504. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  10505. $my_resource->setAttribute('type', 'webcontent');
  10506. $my_resource->setAttribute('href', $my_xml_file_path);
  10507. // adlcp:scormtype can be either 'sco' or 'asset'.
  10508. $my_resource->setAttribute('adlcp:scormtype', 'asset');
  10509. // xml:base is the base directory to find the files declared in this resource.
  10510. $my_resource->setAttribute('xml:base', '');
  10511. // give a <file> child to the <resource> element.
  10512. $my_file = $xmldoc->createElement('file');
  10513. $my_file->setAttribute('href', $my_xml_file_path);
  10514. $my_resource->appendChild($my_file);
  10515. $resources->appendChild($my_resource);
  10516. }
  10517. break;
  10518. case TOOL_QUIZ:
  10519. $exe_id = $item->path;
  10520. // Should be using ref when everything will be cleaned up in this regard.
  10521. $exe = new Exercise();
  10522. $exe->read($exe_id);
  10523. $my_item = $xmldoc->createElement('item');
  10524. $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
  10525. $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
  10526. $my_item->setAttribute('isvisible', 'true');
  10527. // Give a child element <title> to the <item> element.
  10528. $my_title = $xmldoc->createElement(
  10529. 'title',
  10530. htmlspecialchars(
  10531. api_utf8_encode($item->get_title()),
  10532. ENT_QUOTES,
  10533. 'UTF-8'
  10534. )
  10535. );
  10536. $my_item->appendChild($my_title);
  10537. $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
  10538. $my_item->appendChild($my_max_score);
  10539. // Give a child element <adlcp:prerequisites> to the <item> element.
  10540. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
  10541. $my_prereqs->setAttribute('type', 'aicc_script');
  10542. $my_item->appendChild($my_prereqs);
  10543. // Give a child element <adlcp:masteryscore> to the <item> element.
  10544. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
  10545. $my_item->appendChild($my_masteryscore);
  10546. // Attach this item to the organization element or hits parent if there is one.
  10547. if (!empty($item->parent) && $item->parent != 0) {
  10548. $children = $organization->childNodes;
  10549. $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
  10550. if ($possible_parent) {
  10551. if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
  10552. $possible_parent->appendChild($my_item);
  10553. }
  10554. }
  10555. } else {
  10556. $organization->appendChild($my_item);
  10557. }
  10558. // Get the path of the file(s) from the course directory root
  10559. //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
  10560. $my_file_path = 'quiz_'.$item->get_id().'.html';
  10561. // Write the contents of the exported exercise into a (big) html file
  10562. // to later pack it into the exported SCORM. The file will be removed afterwards.
  10563. $scormExercise = new ScormExercise($exe, true);
  10564. $contents = $scormExercise->export();
  10565. $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
  10566. $res = file_put_contents($tmp_file_path, $contents);
  10567. if ($res === false) {
  10568. error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
  10569. }
  10570. $files_cleanup[] = $tmp_file_path;
  10571. $my_xml_file_path = $my_file_path;
  10572. $my_sub_dir = dirname($my_file_path);
  10573. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  10574. $my_xml_sub_dir = $my_sub_dir;
  10575. // Give a <resource> child to the <resources> element.
  10576. $my_resource = $xmldoc->createElement('resource');
  10577. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  10578. $my_resource->setAttribute('type', 'webcontent');
  10579. $my_resource->setAttribute('href', $my_xml_file_path);
  10580. // adlcp:scormtype can be either 'sco' or 'asset'.
  10581. $my_resource->setAttribute('adlcp:scormtype', 'sco');
  10582. // xml:base is the base directory to find the files declared in this resource.
  10583. $my_resource->setAttribute('xml:base', '');
  10584. // Give a <file> child to the <resource> element.
  10585. $my_file = $xmldoc->createElement('file');
  10586. $my_file->setAttribute('href', $my_xml_file_path);
  10587. $my_resource->appendChild($my_file);
  10588. // Get included docs.
  10589. $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
  10590. // Dependency to other files - not yet supported.
  10591. $i = 1;
  10592. foreach ($inc_docs as $doc_info) {
  10593. if (count($doc_info) < 1 || empty($doc_info[0])) {
  10594. continue;
  10595. }
  10596. $my_dep = $xmldoc->createElement('resource');
  10597. $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
  10598. $my_dep->setAttribute('identifier', $res_id);
  10599. $my_dep->setAttribute('type', 'webcontent');
  10600. $my_dep->setAttribute('adlcp:scormtype', 'asset');
  10601. $my_dep_file = $xmldoc->createElement('file');
  10602. // Check type of URL.
  10603. if ($doc_info[1] == 'remote') {
  10604. // Remote file. Save url as is.
  10605. $my_dep_file->setAttribute('href', $doc_info[0]);
  10606. $my_dep->setAttribute('xml:base', '');
  10607. } elseif ($doc_info[1] == 'local') {
  10608. switch ($doc_info[2]) {
  10609. case 'url': // Local URL - save path as url for now, don't zip file.
  10610. // Save file but as local file (retrieve from URL).
  10611. $abs_path = api_get_path(SYS_PATH).
  10612. str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
  10613. $current_dir = dirname($abs_path);
  10614. $current_dir = str_replace('\\', '/', $current_dir);
  10615. $file_path = realpath($abs_path);
  10616. $file_path = str_replace('\\', '/', $file_path);
  10617. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10618. $my_dep->setAttribute('xml:base', '');
  10619. if (strstr($file_path, $main_path) !== false) {
  10620. // The calculated real path is really inside the chamilo root path.
  10621. // Reduce file path to what's under the DocumentRoot.
  10622. $file_path = substr($file_path, strlen($root_path));
  10623. $zip_files_abs[] = $file_path;
  10624. $link_updates[$my_file_path][] = [
  10625. 'orig' => $doc_info[0],
  10626. 'dest' => 'document/'.$file_path,
  10627. ];
  10628. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10629. $my_dep->setAttribute('xml:base', '');
  10630. } elseif (empty($file_path)) {
  10631. $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
  10632. $file_path = str_replace('//', '/', $file_path);
  10633. if (file_exists($file_path)) {
  10634. $file_path = substr($file_path, strlen($current_dir));
  10635. // We get the relative path.
  10636. $zip_files[] = $my_sub_dir.'/'.$file_path;
  10637. $link_updates[$my_file_path][] = [
  10638. 'orig' => $doc_info[0],
  10639. 'dest' => 'document/'.$file_path,
  10640. ];
  10641. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10642. $my_dep->setAttribute('xml:base', '');
  10643. }
  10644. }
  10645. break;
  10646. case 'abs':
  10647. // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
  10648. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
  10649. $current_dir = str_replace('\\', '/', $current_dir);
  10650. $file_path = realpath($doc_info[0]);
  10651. $file_path = str_replace('\\', '/', $file_path);
  10652. $my_dep_file->setAttribute('href', $file_path);
  10653. $my_dep->setAttribute('xml:base', '');
  10654. if (strstr($file_path, $main_path) !== false) {
  10655. // The calculated real path is really inside the chamilo root path.
  10656. // Reduce file path to what's under the DocumentRoot.
  10657. $file_path = substr($file_path, strlen($root_path));
  10658. $zip_files_abs[] = $file_path;
  10659. $link_updates[$my_file_path][] = [
  10660. 'orig' => $doc_info[0],
  10661. 'dest' => $file_path,
  10662. ];
  10663. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10664. $my_dep->setAttribute('xml:base', '');
  10665. } elseif (empty($file_path)) {
  10666. $docSysPartPath = str_replace(
  10667. api_get_path(REL_COURSE_PATH),
  10668. '',
  10669. $doc_info[0]
  10670. );
  10671. $docSysPartPathNoCourseCode = str_replace(
  10672. $_course['directory'].'/',
  10673. '',
  10674. $docSysPartPath
  10675. );
  10676. $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
  10677. if (file_exists($docSysPath)) {
  10678. $file_path = $docSysPartPathNoCourseCode;
  10679. $zip_files[] = $my_sub_dir.'/'.$file_path;
  10680. $link_updates[$my_file_path][] = [
  10681. 'orig' => $doc_info[0],
  10682. 'dest' => $file_path,
  10683. ];
  10684. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10685. $my_dep->setAttribute('xml:base', '');
  10686. }
  10687. }
  10688. break;
  10689. case 'rel':
  10690. // Path relative to the current document. Save xml:base as current document's
  10691. // directory and save file in zip as subdir.file_path
  10692. if (substr($doc_info[0], 0, 2) === '..') {
  10693. // Relative path going up.
  10694. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
  10695. $current_dir = str_replace('\\', '/', $current_dir);
  10696. $file_path = realpath($current_dir.$doc_info[0]);
  10697. $file_path = str_replace('\\', '/', $file_path);
  10698. //error_log($file_path.' <-> '.$main_path, 0);
  10699. if (strstr($file_path, $main_path) !== false) {
  10700. // The calculated real path is really inside Chamilo's root path.
  10701. // Reduce file path to what's under the DocumentRoot.
  10702. $file_path = substr($file_path, strlen($root_path));
  10703. $file_path_dest = $file_path;
  10704. // File path is courses/CHAMILO/document/....
  10705. $info_file_path = explode('/', $file_path);
  10706. if ($info_file_path[0] == 'courses') {
  10707. // Add character "/" in file path.
  10708. $file_path_dest = 'document/'.$file_path;
  10709. }
  10710. $zip_files_abs[] = $file_path;
  10711. $link_updates[$my_file_path][] = [
  10712. 'orig' => $doc_info[0],
  10713. 'dest' => $file_path_dest,
  10714. ];
  10715. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10716. $my_dep->setAttribute('xml:base', '');
  10717. }
  10718. } else {
  10719. $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
  10720. $my_dep_file->setAttribute('href', $doc_info[0]);
  10721. $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
  10722. }
  10723. break;
  10724. default:
  10725. $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
  10726. $my_dep->setAttribute('xml:base', '');
  10727. break;
  10728. }
  10729. }
  10730. $my_dep->appendChild($my_dep_file);
  10731. $resources->appendChild($my_dep);
  10732. $dependency = $xmldoc->createElement('dependency');
  10733. $dependency->setAttribute('identifierref', $res_id);
  10734. $my_resource->appendChild($dependency);
  10735. $i++;
  10736. }
  10737. $resources->appendChild($my_resource);
  10738. $zip_files[] = $my_file_path;
  10739. break;
  10740. default:
  10741. // Get the path of the file(s) from the course directory root
  10742. $my_file_path = 'non_exportable.html';
  10743. //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
  10744. $my_xml_file_path = $my_file_path;
  10745. $my_sub_dir = dirname($my_file_path);
  10746. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  10747. //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
  10748. $my_xml_sub_dir = $my_sub_dir;
  10749. // Give a <resource> child to the <resources> element.
  10750. $my_resource = $xmldoc->createElement('resource');
  10751. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  10752. $my_resource->setAttribute('type', 'webcontent');
  10753. $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
  10754. // adlcp:scormtype can be either 'sco' or 'asset'.
  10755. $my_resource->setAttribute('adlcp:scormtype', 'asset');
  10756. // xml:base is the base directory to find the files declared in this resource.
  10757. $my_resource->setAttribute('xml:base', '');
  10758. // Give a <file> child to the <resource> element.
  10759. $my_file = $xmldoc->createElement('file');
  10760. $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
  10761. $my_resource->appendChild($my_file);
  10762. $resources->appendChild($my_resource);
  10763. break;
  10764. }
  10765. }
  10766. }
  10767. $organizations->appendChild($organization);
  10768. $root->appendChild($organizations);
  10769. $root->appendChild($resources);
  10770. $xmldoc->appendChild($root);
  10771. $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
  10772. // then add the file to the zip, then destroy the file (this is done automatically).
  10773. // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
  10774. foreach ($zip_files as $file_path) {
  10775. if (empty($file_path)) {
  10776. continue;
  10777. }
  10778. $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
  10779. $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
  10780. if (!empty($path_to_remove) && !empty($path_to_replace)) {
  10781. $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
  10782. }
  10783. $this->create_path($dest_file);
  10784. @copy($filePath, $dest_file);
  10785. // Check if the file needs a link update.
  10786. if (in_array($file_path, array_keys($link_updates))) {
  10787. $string = file_get_contents($dest_file);
  10788. unlink($dest_file);
  10789. foreach ($link_updates[$file_path] as $old_new) {
  10790. // This is an ugly hack that allows .flv files to be found by the flv player that
  10791. // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
  10792. // to find the flv to play in document/main/, so we replace main/ in the flv path by
  10793. // ../../.. to return from inc/lib/flv_player to the document/main path.
  10794. if (substr($old_new['dest'], -3) === 'flv' &&
  10795. substr($old_new['dest'], 0, 5) === 'main/'
  10796. ) {
  10797. $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
  10798. } elseif (substr($old_new['dest'], -3) === 'flv' &&
  10799. substr($old_new['dest'], 0, 6) === 'video/'
  10800. ) {
  10801. $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
  10802. }
  10803. // Fix to avoid problems with default_course_document
  10804. if (strpos('main/default_course_document', $old_new['dest']) === false) {
  10805. $newDestination = $old_new['dest'];
  10806. if (isset($old_new['replace']) && !empty($old_new['replace'])) {
  10807. $newDestination = $old_new['replace'];
  10808. }
  10809. } else {
  10810. $newDestination = str_replace('document/', '', $old_new['dest']);
  10811. }
  10812. $string = str_replace($old_new['orig'], $newDestination, $string);
  10813. // Add files inside the HTMLs
  10814. $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
  10815. $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
  10816. if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
  10817. copy(
  10818. $sys_course_path.$new_path,
  10819. $destinationFile
  10820. );
  10821. }
  10822. }
  10823. file_put_contents($dest_file, $string);
  10824. }
  10825. if (file_exists($filePath) && $copyAll) {
  10826. $extension = $this->get_extension($filePath);
  10827. if (in_array($extension, ['html', 'html'])) {
  10828. $containerOrigin = dirname($filePath);
  10829. $containerDestination = dirname($dest_file);
  10830. $finder = new Finder();
  10831. $finder->files()->in($containerOrigin)
  10832. ->notName('*_DELETED_*')
  10833. ->exclude('share_folder')
  10834. ->exclude('chat_files')
  10835. ->exclude('certificates')
  10836. ;
  10837. if (is_dir($containerOrigin) &&
  10838. is_dir($containerDestination)
  10839. ) {
  10840. $fs = new Filesystem();
  10841. $fs->mirror(
  10842. $containerOrigin,
  10843. $containerDestination,
  10844. $finder
  10845. );
  10846. }
  10847. }
  10848. }
  10849. }
  10850. foreach ($zip_files_abs as $file_path) {
  10851. if (empty($file_path)) {
  10852. continue;
  10853. }
  10854. if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
  10855. continue;
  10856. }
  10857. $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
  10858. if (strstr($file_path, 'upload/users') !== false) {
  10859. $pos = strpos($file_path, 'my_files/');
  10860. if ($pos !== false) {
  10861. $onlyDirectory = str_replace(
  10862. 'upload/users/',
  10863. '',
  10864. substr($file_path, $pos, strlen($file_path))
  10865. );
  10866. $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
  10867. }
  10868. }
  10869. if (strstr($file_path, 'default_course_document/') !== false) {
  10870. $replace = str_replace('/main', '', $file_path);
  10871. $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
  10872. }
  10873. if (empty($dest_file)) {
  10874. continue;
  10875. }
  10876. $this->create_path($dest_file);
  10877. copy($main_path.$file_path, $dest_file);
  10878. // Check if the file needs a link update.
  10879. if (in_array($file_path, array_keys($link_updates))) {
  10880. $string = file_get_contents($dest_file);
  10881. unlink($dest_file);
  10882. foreach ($link_updates[$file_path] as $old_new) {
  10883. // This is an ugly hack that allows .flv files to be found by the flv player that
  10884. // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
  10885. // to find the flv to play in document/main/, so we replace main/ in the flv path by
  10886. // ../../.. to return from inc/lib/flv_player to the document/main path.
  10887. if (substr($old_new['dest'], -3) == 'flv' &&
  10888. substr($old_new['dest'], 0, 5) == 'main/'
  10889. ) {
  10890. $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
  10891. }
  10892. $string = str_replace($old_new['orig'], $old_new['dest'], $string);
  10893. }
  10894. file_put_contents($dest_file, $string);
  10895. }
  10896. }
  10897. if (is_array($links_to_create)) {
  10898. foreach ($links_to_create as $file => $link) {
  10899. $content = '<!DOCTYPE html><head>
  10900. <meta charset="'.api_get_language_isocode().'" />
  10901. <title>'.$link['title'].'</title>
  10902. </head>
  10903. <body dir="'.api_get_text_direction().'">
  10904. <div style="text-align:center">
  10905. <a href="'.$link['url'].'">'.$link['title'].'</a></div>
  10906. </body>
  10907. </html>';
  10908. file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
  10909. }
  10910. }
  10911. // Add non exportable message explanation.
  10912. $lang_not_exportable = get_lang('ThisItemIsNotExportable');
  10913. $file_content = '<!DOCTYPE html><head>
  10914. <meta charset="'.api_get_language_isocode().'" />
  10915. <title>'.$lang_not_exportable.'</title>
  10916. <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
  10917. </head>
  10918. <body dir="'.api_get_text_direction().'">';
  10919. $file_content .=
  10920. <<<EOD
  10921. <style>
  10922. .error-message {
  10923. font-family: arial, verdana, helvetica, sans-serif;
  10924. border-width: 1px;
  10925. border-style: solid;
  10926. left: 50%;
  10927. margin: 10px auto;
  10928. min-height: 30px;
  10929. padding: 5px;
  10930. right: 50%;
  10931. width: 500px;
  10932. background-color: #FFD1D1;
  10933. border-color: #FF0000;
  10934. color: #000;
  10935. }
  10936. </style>
  10937. <body>
  10938. <div class="error-message">
  10939. $lang_not_exportable
  10940. </div>
  10941. </body>
  10942. </html>
  10943. EOD;
  10944. if (!is_dir($archivePath.$temp_dir_short.'/document')) {
  10945. @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
  10946. }
  10947. file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
  10948. // Add the extra files that go along with a SCORM package.
  10949. $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
  10950. $fs = new Filesystem();
  10951. $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
  10952. // Finalize the imsmanifest structure, add to the zip, then return the zip.
  10953. $manifest = @$xmldoc->saveXML();
  10954. $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
  10955. file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
  10956. $zip_folder->add(
  10957. $archivePath.'/'.$temp_dir_short,
  10958. PCLZIP_OPT_REMOVE_PATH,
  10959. $archivePath.'/'.$temp_dir_short.'/'
  10960. );
  10961. // Clean possible temporary files.
  10962. foreach ($files_cleanup as $file) {
  10963. $res = unlink($file);
  10964. if ($res === false) {
  10965. error_log(
  10966. 'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
  10967. 0
  10968. );
  10969. }
  10970. }
  10971. $name = api_replace_dangerous_char($this->get_name()).'.zip';
  10972. DocumentManager::file_send_for_download($temp_zip_file, true, $name);
  10973. }
  10974. /**
  10975. * @param int $lp_id
  10976. *
  10977. * @return bool
  10978. */
  10979. public function scorm_export_to_pdf($lp_id)
  10980. {
  10981. $lp_id = (int) $lp_id;
  10982. $files_to_export = [];
  10983. $course_data = api_get_course_info($this->cc);
  10984. if (!empty($course_data)) {
  10985. $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
  10986. $list = self::get_flat_ordered_items_list($lp_id);
  10987. if (!empty($list)) {
  10988. foreach ($list as $item_id) {
  10989. $item = $this->items[$item_id];
  10990. switch ($item->type) {
  10991. case 'document':
  10992. //Getting documents from a LP with chamilo documents
  10993. $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
  10994. // Try loading document from the base course.
  10995. if (empty($file_data) && !empty($sessionId)) {
  10996. $file_data = DocumentManager::get_document_data_by_id(
  10997. $item->path,
  10998. $this->cc,
  10999. false,
  11000. 0
  11001. );
  11002. }
  11003. $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
  11004. if (file_exists($file_path)) {
  11005. $files_to_export[] = [
  11006. 'title' => $item->get_title(),
  11007. 'path' => $file_path,
  11008. ];
  11009. }
  11010. break;
  11011. case 'asset': //commes from a scorm package generated by chamilo
  11012. case 'sco':
  11013. $file_path = $scorm_path.'/'.$item->path;
  11014. if (file_exists($file_path)) {
  11015. $files_to_export[] = [
  11016. 'title' => $item->get_title(),
  11017. 'path' => $file_path,
  11018. ];
  11019. }
  11020. break;
  11021. case 'dir':
  11022. $files_to_export[] = [
  11023. 'title' => $item->get_title(),
  11024. 'path' => null,
  11025. ];
  11026. break;
  11027. }
  11028. }
  11029. }
  11030. $pdf = new PDF();
  11031. $result = $pdf->html_to_pdf(
  11032. $files_to_export,
  11033. $this->name,
  11034. $this->cc,
  11035. true
  11036. );
  11037. return $result;
  11038. }
  11039. return false;
  11040. }
  11041. /**
  11042. * Temp function to be moved in main_api or the best place around for this.
  11043. * Creates a file path if it doesn't exist.
  11044. *
  11045. * @param string $path
  11046. */
  11047. public function create_path($path)
  11048. {
  11049. $path_bits = explode('/', dirname($path));
  11050. // IS_WINDOWS_OS has been defined in main_api.lib.php
  11051. $path_built = IS_WINDOWS_OS ? '' : '/';
  11052. foreach ($path_bits as $bit) {
  11053. if (!empty($bit)) {
  11054. $new_path = $path_built.$bit;
  11055. if (is_dir($new_path)) {
  11056. $path_built = $new_path.'/';
  11057. } else {
  11058. mkdir($new_path, api_get_permissions_for_new_directories());
  11059. $path_built = $new_path.'/';
  11060. }
  11061. }
  11062. }
  11063. }
  11064. /**
  11065. * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
  11066. *
  11067. * @return bool The results of the unlink function, or false if there was no image to start with
  11068. */
  11069. public function delete_lp_image()
  11070. {
  11071. $img = $this->get_preview_image();
  11072. if ($img != '') {
  11073. $del_file = $this->get_preview_image_path(null, 'sys');
  11074. if (isset($del_file) && file_exists($del_file)) {
  11075. $del_file_2 = $this->get_preview_image_path(64, 'sys');
  11076. if (file_exists($del_file_2)) {
  11077. unlink($del_file_2);
  11078. }
  11079. $this->set_preview_image('');
  11080. return @unlink($del_file);
  11081. }
  11082. }
  11083. return false;
  11084. }
  11085. /**
  11086. * Uploads an author image to the upload/learning_path/images directory.
  11087. *
  11088. * @param array The image array, coming from the $_FILES superglobal
  11089. *
  11090. * @return bool True on success, false on error
  11091. */
  11092. public function upload_image($image_array)
  11093. {
  11094. if (!empty($image_array['name'])) {
  11095. $upload_ok = process_uploaded_file($image_array);
  11096. $has_attachment = true;
  11097. }
  11098. if ($upload_ok && $has_attachment) {
  11099. $courseDir = api_get_course_path().'/upload/learning_path/images';
  11100. $sys_course_path = api_get_path(SYS_COURSE_PATH);
  11101. $updir = $sys_course_path.$courseDir;
  11102. // Try to add an extension to the file if it hasn't one.
  11103. $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
  11104. if (filter_extension($new_file_name)) {
  11105. $file_extension = explode('.', $image_array['name']);
  11106. $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
  11107. $filename = uniqid('');
  11108. $new_file_name = $filename.'.'.$file_extension;
  11109. $new_path = $updir.'/'.$new_file_name;
  11110. // Resize the image.
  11111. $temp = new Image($image_array['tmp_name']);
  11112. $temp->resize(104);
  11113. $result = $temp->send_image($new_path);
  11114. // Storing the image filename.
  11115. if ($result) {
  11116. $this->set_preview_image($new_file_name);
  11117. //Resize to 64px to use on course homepage
  11118. $temp->resize(64);
  11119. $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
  11120. return true;
  11121. }
  11122. }
  11123. }
  11124. return false;
  11125. }
  11126. /**
  11127. * @param int $lp_id
  11128. * @param string $status
  11129. */
  11130. public function set_autolaunch($lp_id, $status)
  11131. {
  11132. $course_id = api_get_course_int_id();
  11133. $lp_id = intval($lp_id);
  11134. $status = intval($status);
  11135. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  11136. // Setting everything to autolaunch = 0
  11137. $attributes['autolaunch'] = 0;
  11138. $where = [
  11139. 'session_id = ? AND c_id = ? ' => [
  11140. api_get_session_id(),
  11141. $course_id,
  11142. ],
  11143. ];
  11144. Database::update($lp_table, $attributes, $where);
  11145. if ($status == 1) {
  11146. //Setting my lp_id to autolaunch = 1
  11147. $attributes['autolaunch'] = 1;
  11148. $where = [
  11149. 'iid = ? AND session_id = ? AND c_id = ?' => [
  11150. $lp_id,
  11151. api_get_session_id(),
  11152. $course_id,
  11153. ],
  11154. ];
  11155. Database::update($lp_table, $attributes, $where);
  11156. }
  11157. }
  11158. /**
  11159. * Gets previous_item_id for the next element of the lp_item table.
  11160. *
  11161. * @author Isaac flores paz
  11162. *
  11163. * @return int Previous item ID
  11164. */
  11165. public function select_previous_item_id()
  11166. {
  11167. $course_id = api_get_course_int_id();
  11168. if ($this->debug > 0) {
  11169. error_log('In learnpath::select_previous_item_id()', 0);
  11170. }
  11171. $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  11172. // Get the max order of the items
  11173. $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
  11174. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  11175. $rs_max_order = Database::query($sql);
  11176. $row_max_order = Database::fetch_object($rs_max_order);
  11177. $max_order = $row_max_order->display_order;
  11178. // Get the previous item ID
  11179. $sql = "SELECT iid as previous FROM $table_lp_item
  11180. WHERE
  11181. c_id = $course_id AND
  11182. lp_id = ".$this->lp_id." AND
  11183. display_order = '$max_order' ";
  11184. $rs_max = Database::query($sql);
  11185. $row_max = Database::fetch_object($rs_max);
  11186. // Return the previous item ID
  11187. return $row_max->previous;
  11188. }
  11189. /**
  11190. * Copies an LP.
  11191. */
  11192. public function copy()
  11193. {
  11194. // Course builder
  11195. $cb = new CourseBuilder();
  11196. //Setting tools that will be copied
  11197. $cb->set_tools_to_build(['learnpaths']);
  11198. //Setting elements that will be copied
  11199. $cb->set_tools_specific_id_list(
  11200. ['learnpaths' => [$this->lp_id]]
  11201. );
  11202. $course = $cb->build();
  11203. //Course restorer
  11204. $course_restorer = new CourseRestorer($course);
  11205. $course_restorer->set_add_text_in_items(true);
  11206. $course_restorer->set_tool_copy_settings(
  11207. ['learnpaths' => ['reset_dates' => true]]
  11208. );
  11209. $course_restorer->restore(
  11210. api_get_course_id(),
  11211. api_get_session_id(),
  11212. false,
  11213. false
  11214. );
  11215. }
  11216. /**
  11217. * Verify document size.
  11218. *
  11219. * @param string $s
  11220. *
  11221. * @return bool
  11222. */
  11223. public static function verify_document_size($s)
  11224. {
  11225. $post_max = ini_get('post_max_size');
  11226. if (substr($post_max, -1, 1) == 'M') {
  11227. $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
  11228. } elseif (substr($post_max, -1, 1) == 'G') {
  11229. $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
  11230. }
  11231. $upl_max = ini_get('upload_max_filesize');
  11232. if (substr($upl_max, -1, 1) == 'M') {
  11233. $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
  11234. } elseif (substr($upl_max, -1, 1) == 'G') {
  11235. $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
  11236. }
  11237. $documents_total_space = DocumentManager::documents_total_space();
  11238. $course_max_space = DocumentManager::get_course_quota();
  11239. $total_size = filesize($s) + $documents_total_space;
  11240. if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
  11241. return true;
  11242. } else {
  11243. return false;
  11244. }
  11245. }
  11246. /**
  11247. * Clear LP prerequisites.
  11248. */
  11249. public function clear_prerequisites()
  11250. {
  11251. $course_id = $this->get_course_int_id();
  11252. if ($this->debug > 0) {
  11253. error_log('In learnpath::clear_prerequisites()', 0);
  11254. }
  11255. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  11256. $lp_id = $this->get_id();
  11257. //Cleaning prerequisites
  11258. $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
  11259. WHERE c_id = $course_id AND lp_id = $lp_id";
  11260. Database::query($sql);
  11261. //Cleaning mastery score for exercises
  11262. $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
  11263. WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
  11264. Database::query($sql);
  11265. }
  11266. public function set_previous_step_as_prerequisite_for_all_items()
  11267. {
  11268. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  11269. $course_id = $this->get_course_int_id();
  11270. $lp_id = $this->get_id();
  11271. if (!empty($this->items)) {
  11272. $previous_item_id = null;
  11273. $previous_item_max = 0;
  11274. $previous_item_type = null;
  11275. $last_item_not_dir = null;
  11276. $last_item_not_dir_type = null;
  11277. $last_item_not_dir_max = null;
  11278. foreach ($this->ordered_items as $itemId) {
  11279. $item = $this->getItem($itemId);
  11280. // if there was a previous item... (otherwise jump to set it)
  11281. if (!empty($previous_item_id)) {
  11282. $current_item_id = $item->get_id(); //save current id
  11283. if ($item->get_type() != 'dir') {
  11284. // Current item is not a folder, so it qualifies to get a prerequisites
  11285. if ($last_item_not_dir_type == 'quiz') {
  11286. // if previous is quiz, mark its max score as default score to be achieved
  11287. $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
  11288. WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
  11289. Database::query($sql);
  11290. }
  11291. // now simply update the prerequisite to set it to the last non-chapter item
  11292. $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
  11293. WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
  11294. Database::query($sql);
  11295. // record item as 'non-chapter' reference
  11296. $last_item_not_dir = $item->get_id();
  11297. $last_item_not_dir_type = $item->get_type();
  11298. $last_item_not_dir_max = $item->get_max();
  11299. }
  11300. } else {
  11301. if ($item->get_type() != 'dir') {
  11302. // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
  11303. $last_item_not_dir = $item->get_id();
  11304. $last_item_not_dir_type = $item->get_type();
  11305. $last_item_not_dir_max = $item->get_max();
  11306. }
  11307. }
  11308. // Saving the item as "previous item" for the next loop
  11309. $previous_item_id = $item->get_id();
  11310. $previous_item_max = $item->get_max();
  11311. $previous_item_type = $item->get_type();
  11312. }
  11313. }
  11314. }
  11315. /**
  11316. * @param array $params
  11317. *
  11318. * @throws \Doctrine\ORM\OptimisticLockException
  11319. *
  11320. * @return int
  11321. */
  11322. public static function createCategory($params)
  11323. {
  11324. $em = Database::getManager();
  11325. $item = new CLpCategory();
  11326. $item->setName($params['name']);
  11327. $item->setCId($params['c_id']);
  11328. $em->persist($item);
  11329. $em->flush();
  11330. api_item_property_update(
  11331. api_get_course_info(),
  11332. TOOL_LEARNPATH_CATEGORY,
  11333. $item->getId(),
  11334. 'visible',
  11335. api_get_user_id()
  11336. );
  11337. return $item->getId();
  11338. }
  11339. /**
  11340. * @param array $params
  11341. *
  11342. * @throws \Doctrine\ORM\ORMException
  11343. * @throws \Doctrine\ORM\OptimisticLockException
  11344. * @throws \Doctrine\ORM\TransactionRequiredException
  11345. */
  11346. public static function updateCategory($params)
  11347. {
  11348. $em = Database::getManager();
  11349. /** @var CLpCategory $item */
  11350. $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
  11351. if ($item) {
  11352. $item->setName($params['name']);
  11353. $em->merge($item);
  11354. $em->flush();
  11355. }
  11356. }
  11357. /**
  11358. * @param int $id
  11359. *
  11360. * @throws \Doctrine\ORM\ORMException
  11361. * @throws \Doctrine\ORM\OptimisticLockException
  11362. * @throws \Doctrine\ORM\TransactionRequiredException
  11363. */
  11364. public static function moveUpCategory($id)
  11365. {
  11366. $id = (int) $id;
  11367. $em = Database::getManager();
  11368. /** @var CLpCategory $item */
  11369. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  11370. if ($item) {
  11371. $position = $item->getPosition() - 1;
  11372. $item->setPosition($position);
  11373. $em->persist($item);
  11374. $em->flush();
  11375. }
  11376. }
  11377. /**
  11378. * @param int $id
  11379. *
  11380. * @throws \Doctrine\ORM\ORMException
  11381. * @throws \Doctrine\ORM\OptimisticLockException
  11382. * @throws \Doctrine\ORM\TransactionRequiredException
  11383. */
  11384. public static function moveDownCategory($id)
  11385. {
  11386. $id = (int) $id;
  11387. $em = Database::getManager();
  11388. /** @var CLpCategory $item */
  11389. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  11390. if ($item) {
  11391. $position = $item->getPosition() + 1;
  11392. $item->setPosition($position);
  11393. $em->persist($item);
  11394. $em->flush();
  11395. }
  11396. }
  11397. /**
  11398. * @param int $courseId
  11399. *
  11400. * @throws \Doctrine\ORM\Query\QueryException
  11401. *
  11402. * @return int|mixed
  11403. */
  11404. public static function getCountCategories($courseId)
  11405. {
  11406. if (empty($courseId)) {
  11407. return 0;
  11408. }
  11409. $em = Database::getManager();
  11410. $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
  11411. $query->setParameter('id', $courseId);
  11412. return $query->getSingleScalarResult();
  11413. }
  11414. /**
  11415. * @param int $courseId
  11416. *
  11417. * @return mixed
  11418. */
  11419. public static function getCategories($courseId)
  11420. {
  11421. $em = Database::getManager();
  11422. //Default behaviour
  11423. /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
  11424. array('cId' => $course_id),
  11425. array('name' => 'ASC')
  11426. );*/
  11427. // Using doctrine extensions
  11428. /** @var SortableRepository $repo */
  11429. $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
  11430. $items = $repo
  11431. ->getBySortableGroupsQuery(['cId' => $courseId])
  11432. ->getResult();
  11433. return $items;
  11434. }
  11435. /**
  11436. * @param int $id
  11437. *
  11438. * @throws \Doctrine\ORM\ORMException
  11439. * @throws \Doctrine\ORM\OptimisticLockException
  11440. * @throws \Doctrine\ORM\TransactionRequiredException
  11441. *
  11442. * @return CLpCategory
  11443. */
  11444. public static function getCategory($id)
  11445. {
  11446. $id = (int) $id;
  11447. $em = Database::getManager();
  11448. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  11449. return $item;
  11450. }
  11451. /**
  11452. * @param int $courseId
  11453. *
  11454. * @return array
  11455. */
  11456. public static function getCategoryByCourse($courseId)
  11457. {
  11458. $em = Database::getManager();
  11459. $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
  11460. ['cId' => $courseId]
  11461. );
  11462. return $items;
  11463. }
  11464. /**
  11465. * @param int $id
  11466. *
  11467. * @throws \Doctrine\ORM\ORMException
  11468. * @throws \Doctrine\ORM\OptimisticLockException
  11469. * @throws \Doctrine\ORM\TransactionRequiredException
  11470. *
  11471. * @return mixed
  11472. */
  11473. public static function deleteCategory($id)
  11474. {
  11475. $em = Database::getManager();
  11476. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  11477. if ($item) {
  11478. $courseId = $item->getCId();
  11479. $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
  11480. $query->setParameter('id', $courseId);
  11481. $query->setParameter('catId', $item->getId());
  11482. $lps = $query->getResult();
  11483. // Setting category = 0.
  11484. if ($lps) {
  11485. foreach ($lps as $lpItem) {
  11486. $lpItem->setCategoryId(0);
  11487. }
  11488. }
  11489. // Removing category.
  11490. $em->remove($item);
  11491. $em->flush();
  11492. $courseInfo = api_get_course_info_by_id($courseId);
  11493. $sessionId = api_get_session_id();
  11494. // Delete link tool
  11495. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  11496. $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
  11497. // Delete tools
  11498. $sql = "DELETE FROM $tbl_tool
  11499. WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
  11500. Database::query($sql);
  11501. }
  11502. }
  11503. /**
  11504. * @param int $courseId
  11505. * @param bool $addSelectOption
  11506. *
  11507. * @return mixed
  11508. */
  11509. public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
  11510. {
  11511. $items = self::getCategoryByCourse($courseId);
  11512. $cats = [];
  11513. if ($addSelectOption) {
  11514. $cats = [get_lang('SelectACategory')];
  11515. }
  11516. if (!empty($items)) {
  11517. foreach ($items as $cat) {
  11518. $cats[$cat->getId()] = $cat->getName();
  11519. }
  11520. }
  11521. return $cats;
  11522. }
  11523. /**
  11524. * @param string $courseCode
  11525. * @param int $lpId
  11526. * @param int $user_id
  11527. *
  11528. * @return learnpath
  11529. */
  11530. public static function getLpFromSession($courseCode, $lpId, $user_id)
  11531. {
  11532. $debug = 0;
  11533. $learnPath = null;
  11534. $lpObject = Session::read('lpobject');
  11535. if ($lpObject !== null) {
  11536. $learnPath = unserialize($lpObject);
  11537. if ($debug) {
  11538. error_log('getLpFromSession: unserialize');
  11539. error_log('------getLpFromSession------');
  11540. error_log('------unserialize------');
  11541. error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
  11542. error_log("api_get_sessionid: ".api_get_session_id());
  11543. }
  11544. }
  11545. if (!is_object($learnPath)) {
  11546. $learnPath = new learnpath($courseCode, $lpId, $user_id);
  11547. if ($debug) {
  11548. error_log('------getLpFromSession------');
  11549. error_log('getLpFromSession: create new learnpath');
  11550. error_log("create new LP with $courseCode - $lpId - $user_id");
  11551. error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
  11552. error_log("api_get_sessionid: ".api_get_session_id());
  11553. }
  11554. }
  11555. return $learnPath;
  11556. }
  11557. /**
  11558. * @param int $itemId
  11559. *
  11560. * @return learnpathItem|false
  11561. */
  11562. public function getItem($itemId)
  11563. {
  11564. if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
  11565. return $this->items[$itemId];
  11566. }
  11567. return false;
  11568. }
  11569. /**
  11570. * @return int
  11571. */
  11572. public function getCategoryId()
  11573. {
  11574. return (int) $this->categoryId;
  11575. }
  11576. /**
  11577. * @param int $categoryId
  11578. *
  11579. * @return bool
  11580. */
  11581. public function setCategoryId($categoryId)
  11582. {
  11583. $this->categoryId = (int) $categoryId;
  11584. $table = Database::get_course_table(TABLE_LP_MAIN);
  11585. $lp_id = $this->get_id();
  11586. $sql = "UPDATE $table SET category_id = ".$this->categoryId."
  11587. WHERE iid = $lp_id";
  11588. Database::query($sql);
  11589. return true;
  11590. }
  11591. /**
  11592. * Get whether this is a learning path with the possibility to subscribe
  11593. * users or not.
  11594. *
  11595. * @return int
  11596. */
  11597. public function getSubscribeUsers()
  11598. {
  11599. return $this->subscribeUsers;
  11600. }
  11601. /**
  11602. * Set whether this is a learning path with the possibility to subscribe
  11603. * users or not.
  11604. *
  11605. * @param int $value (0 = false, 1 = true)
  11606. *
  11607. * @return bool
  11608. */
  11609. public function setSubscribeUsers($value)
  11610. {
  11611. if ($this->debug > 0) {
  11612. error_log('In learnpath::set_subscribe_users()', 0);
  11613. }
  11614. $this->subscribeUsers = (int) $value;
  11615. $table = Database::get_course_table(TABLE_LP_MAIN);
  11616. $lp_id = $this->get_id();
  11617. $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
  11618. WHERE iid = $lp_id";
  11619. Database::query($sql);
  11620. return true;
  11621. }
  11622. /**
  11623. * Calculate the count of stars for a user in this LP
  11624. * This calculation is based on the following rules:
  11625. * - the student gets one star when he gets to 50% of the learning path
  11626. * - the student gets a second star when the average score of all tests inside the learning path >= 50%
  11627. * - the student gets a third star when the average score of all tests inside the learning path >= 80%
  11628. * - the student gets the final star when the score for the *last* test is >= 80%.
  11629. *
  11630. * @param int $sessionId Optional. The session ID
  11631. *
  11632. * @return int The count of stars
  11633. */
  11634. public function getCalculateStars($sessionId = 0)
  11635. {
  11636. $stars = 0;
  11637. $progress = self::getProgress(
  11638. $this->lp_id,
  11639. $this->user_id,
  11640. $this->course_int_id,
  11641. $sessionId
  11642. );
  11643. if ($progress >= 50) {
  11644. $stars++;
  11645. }
  11646. // Calculate stars chapters evaluation
  11647. $exercisesItems = $this->getExercisesItems();
  11648. if (!empty($exercisesItems)) {
  11649. $totalResult = 0;
  11650. foreach ($exercisesItems as $exerciseItem) {
  11651. $exerciseResultInfo = Event::getExerciseResultsByUser(
  11652. $this->user_id,
  11653. $exerciseItem->path,
  11654. $this->course_int_id,
  11655. $sessionId,
  11656. $this->lp_id,
  11657. $exerciseItem->db_id
  11658. );
  11659. $exerciseResultInfo = end($exerciseResultInfo);
  11660. if (!$exerciseResultInfo) {
  11661. continue;
  11662. }
  11663. if (!empty($exerciseResultInfo['exe_weighting'])) {
  11664. $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
  11665. } else {
  11666. $exerciseResult = 0;
  11667. }
  11668. $totalResult += $exerciseResult;
  11669. }
  11670. $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
  11671. if ($totalExerciseAverage >= 50) {
  11672. $stars++;
  11673. }
  11674. if ($totalExerciseAverage >= 80) {
  11675. $stars++;
  11676. }
  11677. }
  11678. // Calculate star for final evaluation
  11679. $finalEvaluationItem = $this->getFinalEvaluationItem();
  11680. if (!empty($finalEvaluationItem)) {
  11681. $evaluationResultInfo = Event::getExerciseResultsByUser(
  11682. $this->user_id,
  11683. $finalEvaluationItem->path,
  11684. $this->course_int_id,
  11685. $sessionId,
  11686. $this->lp_id,
  11687. $finalEvaluationItem->db_id
  11688. );
  11689. $evaluationResultInfo = end($evaluationResultInfo);
  11690. if ($evaluationResultInfo) {
  11691. $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
  11692. if ($evaluationResult >= 80) {
  11693. $stars++;
  11694. }
  11695. }
  11696. }
  11697. return $stars;
  11698. }
  11699. /**
  11700. * Get the items of exercise type.
  11701. *
  11702. * @return array The items. Otherwise return false
  11703. */
  11704. public function getExercisesItems()
  11705. {
  11706. $exercises = [];
  11707. foreach ($this->items as $item) {
  11708. if ($item->type != 'quiz') {
  11709. continue;
  11710. }
  11711. $exercises[] = $item;
  11712. }
  11713. array_pop($exercises);
  11714. return $exercises;
  11715. }
  11716. /**
  11717. * Get the item of exercise type (evaluation type).
  11718. *
  11719. * @return array The final evaluation. Otherwise return false
  11720. */
  11721. public function getFinalEvaluationItem()
  11722. {
  11723. $exercises = [];
  11724. foreach ($this->items as $item) {
  11725. if ($item->type != 'quiz') {
  11726. continue;
  11727. }
  11728. $exercises[] = $item;
  11729. }
  11730. return array_pop($exercises);
  11731. }
  11732. /**
  11733. * Calculate the total points achieved for the current user in this learning path.
  11734. *
  11735. * @param int $sessionId Optional. The session Id
  11736. *
  11737. * @return int
  11738. */
  11739. public function getCalculateScore($sessionId = 0)
  11740. {
  11741. // Calculate stars chapters evaluation
  11742. $exercisesItems = $this->getExercisesItems();
  11743. $finalEvaluationItem = $this->getFinalEvaluationItem();
  11744. $totalExercisesResult = 0;
  11745. $totalEvaluationResult = 0;
  11746. if ($exercisesItems !== false) {
  11747. foreach ($exercisesItems as $exerciseItem) {
  11748. $exerciseResultInfo = Event::getExerciseResultsByUser(
  11749. $this->user_id,
  11750. $exerciseItem->path,
  11751. $this->course_int_id,
  11752. $sessionId,
  11753. $this->lp_id,
  11754. $exerciseItem->db_id
  11755. );
  11756. $exerciseResultInfo = end($exerciseResultInfo);
  11757. if (!$exerciseResultInfo) {
  11758. continue;
  11759. }
  11760. $totalExercisesResult += $exerciseResultInfo['exe_result'];
  11761. }
  11762. }
  11763. if (!empty($finalEvaluationItem)) {
  11764. $evaluationResultInfo = Event::getExerciseResultsByUser(
  11765. $this->user_id,
  11766. $finalEvaluationItem->path,
  11767. $this->course_int_id,
  11768. $sessionId,
  11769. $this->lp_id,
  11770. $finalEvaluationItem->db_id
  11771. );
  11772. $evaluationResultInfo = end($evaluationResultInfo);
  11773. if ($evaluationResultInfo) {
  11774. $totalEvaluationResult += $evaluationResultInfo['exe_result'];
  11775. }
  11776. }
  11777. return $totalExercisesResult + $totalEvaluationResult;
  11778. }
  11779. /**
  11780. * Check if URL is not allowed to be show in a iframe.
  11781. *
  11782. * @param string $src
  11783. *
  11784. * @return string
  11785. */
  11786. public function fixBlockedLinks($src)
  11787. {
  11788. $urlInfo = parse_url($src);
  11789. $platformProtocol = 'https';
  11790. if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
  11791. $platformProtocol = 'http';
  11792. }
  11793. $protocolFixApplied = false;
  11794. //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
  11795. $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
  11796. $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
  11797. if ($platformProtocol != $scheme) {
  11798. Session::write('x_frame_source', $src);
  11799. $src = 'blank.php?error=x_frames_options';
  11800. $protocolFixApplied = true;
  11801. }
  11802. if ($protocolFixApplied == false) {
  11803. if (strpos(api_get_path(WEB_PATH), $host) === false) {
  11804. // Check X-Frame-Options
  11805. $ch = curl_init();
  11806. $options = [
  11807. CURLOPT_URL => $src,
  11808. CURLOPT_RETURNTRANSFER => true,
  11809. CURLOPT_HEADER => true,
  11810. CURLOPT_FOLLOWLOCATION => true,
  11811. CURLOPT_ENCODING => "",
  11812. CURLOPT_AUTOREFERER => true,
  11813. CURLOPT_CONNECTTIMEOUT => 120,
  11814. CURLOPT_TIMEOUT => 120,
  11815. CURLOPT_MAXREDIRS => 10,
  11816. ];
  11817. $proxySettings = api_get_configuration_value('proxy_settings');
  11818. if (!empty($proxySettings) &&
  11819. isset($proxySettings['curl_setopt_array'])
  11820. ) {
  11821. $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
  11822. $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
  11823. }
  11824. curl_setopt_array($ch, $options);
  11825. $response = curl_exec($ch);
  11826. $httpCode = curl_getinfo($ch);
  11827. $headers = substr($response, 0, $httpCode['header_size']);
  11828. $error = false;
  11829. if (stripos($headers, 'X-Frame-Options: DENY') > -1
  11830. //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
  11831. ) {
  11832. $error = true;
  11833. }
  11834. if ($error) {
  11835. Session::write('x_frame_source', $src);
  11836. $src = 'blank.php?error=x_frames_options';
  11837. }
  11838. }
  11839. }
  11840. return $src;
  11841. }
  11842. /**
  11843. * Check if this LP has a created forum in the basis course.
  11844. *
  11845. * @return bool
  11846. */
  11847. public function lpHasForum()
  11848. {
  11849. $forumTable = Database::get_course_table(TABLE_FORUM);
  11850. $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
  11851. $fakeFrom = "
  11852. $forumTable f
  11853. INNER JOIN $itemProperty ip
  11854. ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
  11855. ";
  11856. $resultData = Database::select(
  11857. 'COUNT(f.iid) AS qty',
  11858. $fakeFrom,
  11859. [
  11860. 'where' => [
  11861. 'ip.visibility != ? AND ' => 2,
  11862. 'ip.tool = ? AND ' => TOOL_FORUM,
  11863. 'f.c_id = ? AND ' => intval($this->course_int_id),
  11864. 'f.lp_id = ?' => intval($this->lp_id),
  11865. ],
  11866. ],
  11867. 'first'
  11868. );
  11869. return $resultData['qty'] > 0;
  11870. }
  11871. /**
  11872. * Get the forum for this learning path.
  11873. *
  11874. * @param int $sessionId
  11875. *
  11876. * @return bool
  11877. */
  11878. public function getForum($sessionId = 0)
  11879. {
  11880. $forumTable = Database::get_course_table(TABLE_FORUM);
  11881. $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
  11882. $fakeFrom = "$forumTable f
  11883. INNER JOIN $itemProperty ip ";
  11884. if ($this->lp_session_id == 0) {
  11885. $fakeFrom .= "
  11886. ON (
  11887. f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
  11888. f.session_id = ip.session_id OR ip.session_id IS NULL
  11889. )
  11890. )
  11891. ";
  11892. } else {
  11893. $fakeFrom .= "
  11894. ON (
  11895. f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
  11896. )
  11897. ";
  11898. }
  11899. $resultData = Database::select(
  11900. 'f.*',
  11901. $fakeFrom,
  11902. [
  11903. 'where' => [
  11904. 'ip.visibility != ? AND ' => 2,
  11905. 'ip.tool = ? AND ' => TOOL_FORUM,
  11906. 'f.session_id = ? AND ' => $sessionId,
  11907. 'f.c_id = ? AND ' => intval($this->course_int_id),
  11908. 'f.lp_id = ?' => intval($this->lp_id),
  11909. ],
  11910. ],
  11911. 'first'
  11912. );
  11913. if (empty($resultData)) {
  11914. return false;
  11915. }
  11916. return $resultData;
  11917. }
  11918. /**
  11919. * Create a forum for this learning path.
  11920. *
  11921. * @param int $forumCategoryId
  11922. *
  11923. * @return int The forum ID if was created. Otherwise return false
  11924. */
  11925. public function createForum($forumCategoryId)
  11926. {
  11927. require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
  11928. $forumId = store_forum(
  11929. [
  11930. 'lp_id' => $this->lp_id,
  11931. 'forum_title' => $this->name,
  11932. 'forum_comment' => null,
  11933. 'forum_category' => intval($forumCategoryId),
  11934. 'students_can_edit_group' => ['students_can_edit' => 0],
  11935. 'allow_new_threads_group' => ['allow_new_threads' => 0],
  11936. 'default_view_type_group' => ['default_view_type' => 'flat'],
  11937. 'group_forum' => 0,
  11938. 'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
  11939. ],
  11940. [],
  11941. true
  11942. );
  11943. return $forumId;
  11944. }
  11945. /**
  11946. * Get the LP Final Item form.
  11947. *
  11948. * @throws Exception
  11949. * @throws HTML_QuickForm_Error
  11950. *
  11951. * @return string
  11952. */
  11953. public function getFinalItemForm()
  11954. {
  11955. $finalItem = $this->getFinalItem();
  11956. $title = '';
  11957. if ($finalItem) {
  11958. $title = $finalItem->get_title();
  11959. $buttonText = get_lang('Save');
  11960. $content = $this->getSavedFinalItem();
  11961. } else {
  11962. $buttonText = get_lang('LPCreateDocument');
  11963. $content = $this->getFinalItemTemplate();
  11964. }
  11965. $courseInfo = api_get_course_info();
  11966. $result = $this->generate_lp_folder($courseInfo);
  11967. $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
  11968. $relative_prefix = '../../';
  11969. $editorConfig = [
  11970. 'ToolbarSet' => 'LearningPathDocuments',
  11971. 'Width' => '100%',
  11972. 'Height' => '500',
  11973. 'FullPage' => true,
  11974. 'CreateDocumentDir' => $relative_prefix,
  11975. 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
  11976. 'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
  11977. ];
  11978. $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
  11979. 'type' => 'document',
  11980. 'lp_id' => $this->lp_id,
  11981. ]);
  11982. $form = new FormValidator('final_item', 'POST', $url);
  11983. $form->addText('title', get_lang('Title'));
  11984. $form->addButtonSave($buttonText);
  11985. $form->addHtml(
  11986. Display::return_message(
  11987. 'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
  11988. 'normal',
  11989. false
  11990. )
  11991. );
  11992. $renderer = $form->defaultRenderer();
  11993. $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
  11994. $form->addHtmlEditor(
  11995. 'content_lp_certificate',
  11996. null,
  11997. true,
  11998. false,
  11999. $editorConfig,
  12000. true
  12001. );
  12002. $form->addHidden('action', 'add_final_item');
  12003. $form->addHidden('path', Session::read('pathItem'));
  12004. $form->addHidden('previous', $this->get_last());
  12005. $form->setDefaults(
  12006. ['title' => $title, 'content_lp_certificate' => $content]
  12007. );
  12008. if ($form->validate()) {
  12009. $values = $form->exportValues();
  12010. $lastItemId = $this->get_last();
  12011. if (!$finalItem) {
  12012. $documentId = $this->create_document(
  12013. $this->course_info,
  12014. $values['content_lp_certificate'],
  12015. $values['title']
  12016. );
  12017. $this->add_item(
  12018. 0,
  12019. $lastItemId,
  12020. 'final_item',
  12021. $documentId,
  12022. $values['title'],
  12023. ''
  12024. );
  12025. Display::addFlash(
  12026. Display::return_message(get_lang('Added'))
  12027. );
  12028. } else {
  12029. $this->edit_document($this->course_info);
  12030. }
  12031. }
  12032. return $form->returnForm();
  12033. }
  12034. /**
  12035. * Check if the current lp item is first, both, last or none from lp list.
  12036. *
  12037. * @param int $currentItemId
  12038. *
  12039. * @return string
  12040. */
  12041. public function isFirstOrLastItem($currentItemId)
  12042. {
  12043. if ($this->debug > 0) {
  12044. error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
  12045. }
  12046. $lpItemId = [];
  12047. $typeListNotToVerify = self::getChapterTypes();
  12048. // Using get_toc() function instead $this->items because returns the correct order of the items
  12049. foreach ($this->get_toc() as $item) {
  12050. if (!in_array($item['type'], $typeListNotToVerify)) {
  12051. $lpItemId[] = $item['id'];
  12052. }
  12053. }
  12054. $lastLpItemIndex = count($lpItemId) - 1;
  12055. $position = array_search($currentItemId, $lpItemId);
  12056. switch ($position) {
  12057. case 0:
  12058. if (!$lastLpItemIndex) {
  12059. $answer = 'both';
  12060. break;
  12061. }
  12062. $answer = 'first';
  12063. break;
  12064. case $lastLpItemIndex:
  12065. $answer = 'last';
  12066. break;
  12067. default:
  12068. $answer = 'none';
  12069. }
  12070. return $answer;
  12071. }
  12072. /**
  12073. * Get whether this is a learning path with the accumulated SCORM time or not.
  12074. *
  12075. * @return int
  12076. */
  12077. public function getAccumulateScormTime()
  12078. {
  12079. return $this->accumulateScormTime;
  12080. }
  12081. /**
  12082. * Set whether this is a learning path with the accumulated SCORM time or not.
  12083. *
  12084. * @param int $value (0 = false, 1 = true)
  12085. *
  12086. * @return bool Always returns true
  12087. */
  12088. public function setAccumulateScormTime($value)
  12089. {
  12090. if ($this->debug > 0) {
  12091. error_log('In learnpath::setAccumulateScormTime()', 0);
  12092. }
  12093. $this->accumulateScormTime = intval($value);
  12094. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  12095. $lp_id = $this->get_id();
  12096. $sql = "UPDATE $lp_table
  12097. SET accumulate_scorm_time = ".$this->accumulateScormTime."
  12098. WHERE iid = $lp_id";
  12099. Database::query($sql);
  12100. return true;
  12101. }
  12102. /**
  12103. * Returns an HTML-formatted link to a resource, to incorporate directly into
  12104. * the new learning path tool.
  12105. *
  12106. * The function is a big switch on tool type.
  12107. * In each case, we query the corresponding table for information and build the link
  12108. * with that information.
  12109. *
  12110. * @author Yannick Warnier <ywarnier@beeznest.org> - rebranding based on
  12111. * previous work (display_addedresource_link_in_learnpath())
  12112. *
  12113. * @param int $course_id Course code
  12114. * @param int $learningPathId The learning path ID (in lp table)
  12115. * @param int $id_in_path the unique index in the items table
  12116. * @param int $lpViewId
  12117. *
  12118. * @return string
  12119. */
  12120. public static function rl_get_resource_link_for_learnpath(
  12121. $course_id,
  12122. $learningPathId,
  12123. $id_in_path,
  12124. $lpViewId
  12125. ) {
  12126. $session_id = api_get_session_id();
  12127. $course_info = api_get_course_info_by_id($course_id);
  12128. $learningPathId = (int) $learningPathId;
  12129. $id_in_path = (int) $id_in_path;
  12130. $lpViewId = (int) $lpViewId;
  12131. $em = Database::getManager();
  12132. $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
  12133. /** @var CLpItem $rowItem */
  12134. $rowItem = $lpItemRepo->findOneBy([
  12135. 'cId' => $course_id,
  12136. 'lpId' => $learningPathId,
  12137. 'iid' => $id_in_path,
  12138. ]);
  12139. if (!$rowItem) {
  12140. // Try one more time with "id"
  12141. /** @var CLpItem $rowItem */
  12142. $rowItem = $lpItemRepo->findOneBy([
  12143. 'cId' => $course_id,
  12144. 'lpId' => $learningPathId,
  12145. 'id' => $id_in_path,
  12146. ]);
  12147. if (!$rowItem) {
  12148. return -1;
  12149. }
  12150. }
  12151. $course_code = $course_info['code'];
  12152. $type = $rowItem->getItemType();
  12153. $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
  12154. $main_dir_path = api_get_path(WEB_CODE_PATH);
  12155. $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
  12156. $link = '';
  12157. $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
  12158. switch ($type) {
  12159. case 'dir':
  12160. return $main_dir_path.'lp/blank.php';
  12161. case TOOL_CALENDAR_EVENT:
  12162. return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
  12163. case TOOL_ANNOUNCEMENT:
  12164. return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
  12165. case TOOL_LINK:
  12166. $linkInfo = Link::getLinkInfo($id);
  12167. if (isset($linkInfo['url'])) {
  12168. return $linkInfo['url'];
  12169. }
  12170. return '';
  12171. case TOOL_QUIZ:
  12172. if (empty($id)) {
  12173. return '';
  12174. }
  12175. // Get the lp_item_view with the highest view_count.
  12176. $learnpathItemViewResult = $em
  12177. ->getRepository('ChamiloCourseBundle:CLpItemView')
  12178. ->findBy(
  12179. ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
  12180. ['viewCount' => 'DESC'],
  12181. 1
  12182. );
  12183. /** @var CLpItemView $learnpathItemViewData */
  12184. $learnpathItemViewData = current($learnpathItemViewResult);
  12185. $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
  12186. return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
  12187. .http_build_query([
  12188. 'lp_init' => 1,
  12189. 'learnpath_item_view_id' => $learnpathItemViewId,
  12190. 'learnpath_id' => $learningPathId,
  12191. 'learnpath_item_id' => $id_in_path,
  12192. 'exerciseId' => $id,
  12193. ]);
  12194. case TOOL_HOTPOTATOES: //lowercase because of strtolower above
  12195. $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
  12196. $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
  12197. $myrow = Database::fetch_array($result);
  12198. $path = $myrow['path'];
  12199. return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
  12200. .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
  12201. .'&lp_view_id='.$lpViewId.'&'.$extraParams;
  12202. case TOOL_FORUM:
  12203. return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
  12204. case TOOL_THREAD:
  12205. // forum post
  12206. $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
  12207. if (empty($id)) {
  12208. return '';
  12209. }
  12210. $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
  12211. $result = Database::query($sql);
  12212. $myrow = Database::fetch_array($result);
  12213. return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
  12214. .$extraParams;
  12215. case TOOL_POST:
  12216. $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
  12217. $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
  12218. $myrow = Database::fetch_array($result);
  12219. return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
  12220. .$myrow['forum_id'].'&lp=true&'.$extraParams;
  12221. case TOOL_READOUT_TEXT:
  12222. return api_get_path(WEB_CODE_PATH).
  12223. 'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
  12224. case TOOL_DOCUMENT:
  12225. $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
  12226. $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
  12227. if (empty($document)) {
  12228. // Try with normal id
  12229. $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
  12230. if (empty($document)) {
  12231. return '';
  12232. }
  12233. }
  12234. $documentPathInfo = pathinfo($document->getPath());
  12235. $jplayerSupportedFiles = ['mp4', 'ogv', 'flv', 'm4v'];
  12236. $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
  12237. $showDirectUrl = !in_array($extension, $jplayerSupportedFiles);
  12238. $openmethod = 2;
  12239. $officedoc = false;
  12240. Session::write('openmethod', $openmethod);
  12241. Session::write('officedoc', $officedoc);
  12242. if ($showDirectUrl) {
  12243. $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
  12244. if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
  12245. if (Link::isPdfLink($file)) {
  12246. $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
  12247. return $pdfUrl;
  12248. }
  12249. }
  12250. return $file;
  12251. }
  12252. return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
  12253. case TOOL_LP_FINAL_ITEM:
  12254. return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
  12255. .$extraParams;
  12256. case 'assignments':
  12257. return $main_dir_path.'work/work.php?'.$extraParams;
  12258. case TOOL_DROPBOX:
  12259. return $main_dir_path.'dropbox/index.php?'.$extraParams;
  12260. case 'introduction_text': //DEPRECATED
  12261. return '';
  12262. case TOOL_COURSE_DESCRIPTION:
  12263. return $main_dir_path.'course_description?'.$extraParams;
  12264. case TOOL_GROUP:
  12265. return $main_dir_path.'group/group.php?'.$extraParams;
  12266. case TOOL_USER:
  12267. return $main_dir_path.'user/user.php?'.$extraParams;
  12268. case TOOL_STUDENTPUBLICATION:
  12269. if (!empty($rowItem->getPath())) {
  12270. return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
  12271. }
  12272. return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
  12273. }
  12274. return $link;
  12275. }
  12276. /**
  12277. * Gets the name of a resource (generally used in learnpath when no name is provided).
  12278. *
  12279. * @author Yannick Warnier <ywarnier@beeznest.org>
  12280. *
  12281. * @param string $course_code Course code
  12282. * @param int $learningPathId
  12283. * @param int $id_in_path The resource ID
  12284. *
  12285. * @return string
  12286. */
  12287. public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
  12288. {
  12289. $_course = api_get_course_info($course_code);
  12290. $course_id = $_course['real_id'];
  12291. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  12292. $learningPathId = (int) $learningPathId;
  12293. $id_in_path = (int) $id_in_path;
  12294. $sql = "SELECT item_type, title, ref
  12295. FROM $tbl_lp_item
  12296. WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
  12297. $res_item = Database::query($sql);
  12298. if (Database::num_rows($res_item) < 1) {
  12299. return '';
  12300. }
  12301. $row_item = Database::fetch_array($res_item);
  12302. $type = strtolower($row_item['item_type']);
  12303. $id = $row_item['ref'];
  12304. $output = '';
  12305. switch ($type) {
  12306. case TOOL_CALENDAR_EVENT:
  12307. $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
  12308. $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
  12309. $myrow = Database::fetch_array($result);
  12310. $output = $myrow['title'];
  12311. break;
  12312. case TOOL_ANNOUNCEMENT:
  12313. $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
  12314. $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
  12315. $myrow = Database::fetch_array($result);
  12316. $output = $myrow['title'];
  12317. break;
  12318. case TOOL_LINK:
  12319. // Doesn't take $target into account.
  12320. $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
  12321. $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
  12322. $myrow = Database::fetch_array($result);
  12323. $output = $myrow['title'];
  12324. break;
  12325. case TOOL_QUIZ:
  12326. $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
  12327. $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
  12328. $myrow = Database::fetch_array($result);
  12329. $output = $myrow['title'];
  12330. break;
  12331. case TOOL_FORUM:
  12332. $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
  12333. $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
  12334. $myrow = Database::fetch_array($result);
  12335. $output = $myrow['forum_name'];
  12336. break;
  12337. case TOOL_THREAD: //=topics
  12338. $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
  12339. // Grabbing the title of the post.
  12340. $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
  12341. $result_title = Database::query($sql_title);
  12342. $myrow_title = Database::fetch_array($result_title);
  12343. $output = $myrow_title['post_title'];
  12344. break;
  12345. case TOOL_POST:
  12346. $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
  12347. //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
  12348. $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
  12349. $result = Database::query($sql);
  12350. $post = Database::fetch_array($result);
  12351. $output = $post['post_title'];
  12352. break;
  12353. case 'dir':
  12354. $title = $row_item['title'];
  12355. if (!empty($title)) {
  12356. $output = $title;
  12357. } else {
  12358. $output = '-';
  12359. }
  12360. break;
  12361. case TOOL_DOCUMENT:
  12362. $title = $row_item['title'];
  12363. if (!empty($title)) {
  12364. $output = $title;
  12365. } else {
  12366. $output = '-';
  12367. }
  12368. break;
  12369. case 'hotpotatoes':
  12370. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  12371. $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
  12372. $myrow = Database::fetch_array($result);
  12373. $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
  12374. $last = count($pathname) - 1; // Making a correct name for the link.
  12375. $filename = $pathname[$last]; // Making a correct name for the link.
  12376. $ext = explode('.', $filename);
  12377. $ext = strtolower($ext[sizeof($ext) - 1]);
  12378. $myrow['path'] = rawurlencode($myrow['path']);
  12379. $output = $filename;
  12380. break;
  12381. }
  12382. return stripslashes($output);
  12383. }
  12384. /**
  12385. * Get the parent names for the current item.
  12386. *
  12387. * @param int $newItemId Optional. The item ID
  12388. *
  12389. * @return array
  12390. */
  12391. public function getCurrentItemParentNames($newItemId = 0)
  12392. {
  12393. $newItemId = $newItemId ?: $this->get_current_item_id();
  12394. $return = [];
  12395. $item = $this->getItem($newItemId);
  12396. $parent = $this->getItem($item->get_parent());
  12397. while ($parent) {
  12398. $return[] = $parent->get_title();
  12399. $parent = $this->getItem($parent->get_parent());
  12400. }
  12401. return array_reverse($return);
  12402. }
  12403. /**
  12404. * Reads and process "lp_subscription_settings" setting.
  12405. *
  12406. * @return array
  12407. */
  12408. public static function getSubscriptionSettings()
  12409. {
  12410. $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
  12411. if (empty($subscriptionSettings)) {
  12412. // By default allow both settings
  12413. $subscriptionSettings = [
  12414. 'allow_add_users_to_lp' => true,
  12415. 'allow_add_users_to_lp_category' => true,
  12416. ];
  12417. } else {
  12418. $subscriptionSettings = $subscriptionSettings['options'];
  12419. }
  12420. return $subscriptionSettings;
  12421. }
  12422. /**
  12423. * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
  12424. */
  12425. public function exportToCourseBuildFormat()
  12426. {
  12427. if (!api_is_allowed_to_edit()) {
  12428. return false;
  12429. }
  12430. $courseBuilder = new CourseBuilder();
  12431. $itemList = [];
  12432. /** @var learnpathItem $item */
  12433. foreach ($this->items as $item) {
  12434. $itemList[$item->get_type()][] = $item->get_path();
  12435. }
  12436. if (empty($itemList)) {
  12437. return false;
  12438. }
  12439. if (isset($itemList['document'])) {
  12440. // Get parents
  12441. foreach ($itemList['document'] as $documentId) {
  12442. $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
  12443. if (!empty($documentInfo['parents'])) {
  12444. foreach ($documentInfo['parents'] as $parentInfo) {
  12445. if (in_array($parentInfo['iid'], $itemList['document'])) {
  12446. continue;
  12447. }
  12448. $itemList['document'][] = $parentInfo['iid'];
  12449. }
  12450. }
  12451. }
  12452. $courseInfo = api_get_course_info();
  12453. foreach ($itemList['document'] as $documentId) {
  12454. $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
  12455. $items = DocumentManager::get_resources_from_source_html(
  12456. $documentInfo['absolute_path'],
  12457. true,
  12458. TOOL_DOCUMENT
  12459. );
  12460. if (!empty($items)) {
  12461. foreach ($items as $item) {
  12462. // Get information about source url
  12463. $url = $item[0]; // url
  12464. $scope = $item[1]; // scope (local, remote)
  12465. $type = $item[2]; // type (rel, abs, url)
  12466. $origParseUrl = parse_url($url);
  12467. $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
  12468. if ($scope == 'local') {
  12469. if ($type == 'abs' || $type == 'rel') {
  12470. $documentFile = strstr($realOrigPath, 'document');
  12471. if (strpos($realOrigPath, $documentFile) !== false) {
  12472. $documentFile = str_replace('document', '', $documentFile);
  12473. $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
  12474. // Document found! Add it to the list
  12475. if ($itemDocumentId) {
  12476. $itemList['document'][] = $itemDocumentId;
  12477. }
  12478. }
  12479. }
  12480. }
  12481. }
  12482. }
  12483. }
  12484. $courseBuilder->build_documents(
  12485. api_get_session_id(),
  12486. $this->get_course_int_id(),
  12487. true,
  12488. $itemList['document']
  12489. );
  12490. }
  12491. if (isset($itemList['quiz'])) {
  12492. $courseBuilder->build_quizzes(
  12493. api_get_session_id(),
  12494. $this->get_course_int_id(),
  12495. true,
  12496. $itemList['quiz']
  12497. );
  12498. }
  12499. require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
  12500. /*if (!empty($itemList['thread'])) {
  12501. $postList = [];
  12502. foreach ($itemList['thread'] as $postId) {
  12503. $post = get_post_information($postId);
  12504. if ($post) {
  12505. if (!isset($itemList['forum'])) {
  12506. $itemList['forum'] = [];
  12507. }
  12508. $itemList['forum'][] = $post['forum_id'];
  12509. $postList[] = $postId;
  12510. }
  12511. }
  12512. if (!empty($postList)) {
  12513. $courseBuilder->build_forum_posts(
  12514. $this->get_course_int_id(),
  12515. null,
  12516. null,
  12517. $postList
  12518. );
  12519. }
  12520. }*/
  12521. if (!empty($itemList['thread'])) {
  12522. $threadList = [];
  12523. $em = Database::getManager();
  12524. $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
  12525. foreach ($itemList['thread'] as $threadId) {
  12526. /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
  12527. $thread = $repo->find($threadId);
  12528. if ($thread) {
  12529. $itemList['forum'][] = $thread->getForumId();
  12530. $threadList[] = $thread->getIid();
  12531. }
  12532. }
  12533. if (!empty($threadList)) {
  12534. $courseBuilder->build_forum_topics(
  12535. api_get_session_id(),
  12536. $this->get_course_int_id(),
  12537. null,
  12538. $threadList
  12539. );
  12540. }
  12541. }
  12542. $forumCategoryList = [];
  12543. if (isset($itemList['forum'])) {
  12544. foreach ($itemList['forum'] as $forumId) {
  12545. $forumInfo = get_forums($forumId);
  12546. $forumCategoryList[] = $forumInfo['forum_category'];
  12547. }
  12548. }
  12549. if (!empty($forumCategoryList)) {
  12550. $courseBuilder->build_forum_category(
  12551. api_get_session_id(),
  12552. $this->get_course_int_id(),
  12553. true,
  12554. $forumCategoryList
  12555. );
  12556. }
  12557. if (!empty($itemList['forum'])) {
  12558. $courseBuilder->build_forums(
  12559. api_get_session_id(),
  12560. $this->get_course_int_id(),
  12561. true,
  12562. $itemList['forum']
  12563. );
  12564. }
  12565. if (isset($itemList['link'])) {
  12566. $courseBuilder->build_links(
  12567. api_get_session_id(),
  12568. $this->get_course_int_id(),
  12569. true,
  12570. $itemList['link']
  12571. );
  12572. }
  12573. if (!empty($itemList['student_publication'])) {
  12574. $courseBuilder->build_works(
  12575. api_get_session_id(),
  12576. $this->get_course_int_id(),
  12577. true,
  12578. $itemList['student_publication']
  12579. );
  12580. }
  12581. $courseBuilder->build_learnpaths(
  12582. api_get_session_id(),
  12583. $this->get_course_int_id(),
  12584. true,
  12585. [$this->get_id()],
  12586. false
  12587. );
  12588. $courseBuilder->restoreDocumentsFromList();
  12589. $zipFile = CourseArchiver::createBackup($courseBuilder->course);
  12590. $zipPath = CourseArchiver::getBackupDir().$zipFile;
  12591. $result = DocumentManager::file_send_for_download(
  12592. $zipPath,
  12593. true,
  12594. $this->get_name().'.zip'
  12595. );
  12596. if ($result) {
  12597. api_not_allowed();
  12598. }
  12599. return true;
  12600. }
  12601. /**
  12602. * Get whether this is a learning path with the accumulated work time or not.
  12603. *
  12604. * @return int
  12605. */
  12606. public function getAccumulateWorkTime()
  12607. {
  12608. return (int) $this->accumulateWorkTime;
  12609. }
  12610. /**
  12611. * Get whether this is a learning path with the accumulated work time or not.
  12612. *
  12613. * @return int
  12614. */
  12615. public function getAccumulateWorkTimeTotalCourse()
  12616. {
  12617. $table = Database::get_course_table(TABLE_LP_MAIN);
  12618. $sql = "SELECT SUM(accumulate_work_time) AS total
  12619. FROM $table
  12620. WHERE c_id = ".$this->course_int_id;
  12621. $result = Database::query($sql);
  12622. $row = Database::fetch_array($result);
  12623. return (int) $row['total'];
  12624. }
  12625. /**
  12626. * Set whether this is a learning path with the accumulated work time or not.
  12627. *
  12628. * @param int $value (0 = false, 1 = true)
  12629. *
  12630. * @return bool
  12631. */
  12632. public function setAccumulateWorkTime($value)
  12633. {
  12634. if (!api_get_configuration_value('lp_minimum_time')) {
  12635. return false;
  12636. }
  12637. $this->accumulateWorkTime = (int) $value;
  12638. $table = Database::get_course_table(TABLE_LP_MAIN);
  12639. $lp_id = $this->get_id();
  12640. $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
  12641. WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
  12642. Database::query($sql);
  12643. return true;
  12644. }
  12645. /**
  12646. * @param int $lpId
  12647. * @param int $courseId
  12648. *
  12649. * @return mixed
  12650. */
  12651. public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
  12652. {
  12653. $lpId = (int) $lpId;
  12654. $courseId = (int) $courseId;
  12655. $table = Database::get_course_table(TABLE_LP_MAIN);
  12656. $sql = "SELECT accumulate_work_time
  12657. FROM $table
  12658. WHERE c_id = $courseId AND id = $lpId";
  12659. $result = Database::query($sql);
  12660. $row = Database::fetch_array($result);
  12661. return $row['accumulate_work_time'];
  12662. }
  12663. /**
  12664. * @param int $courseId
  12665. *
  12666. * @return int
  12667. */
  12668. public static function getAccumulateWorkTimeTotal($courseId)
  12669. {
  12670. $table = Database::get_course_table(TABLE_LP_MAIN);
  12671. $courseId = (int) $courseId;
  12672. $sql = "SELECT SUM(accumulate_work_time) AS total
  12673. FROM $table
  12674. WHERE c_id = $courseId";
  12675. $result = Database::query($sql);
  12676. $row = Database::fetch_array($result);
  12677. return (int) $row['total'];
  12678. }
  12679. /**
  12680. * In order to use the lp icon option you need to create the "lp_icon" LP extra field
  12681. * and put the images in.
  12682. *
  12683. * @return array
  12684. */
  12685. public static function getIconSelect()
  12686. {
  12687. $theme = api_get_visual_theme();
  12688. $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
  12689. $icons = ['' => get_lang('SelectAnOption')];
  12690. if (is_dir($path)) {
  12691. $finder = new Finder();
  12692. $finder->files()->in($path);
  12693. $allowedExtensions = ['jpeg', 'jpg', 'png'];
  12694. /** @var SplFileInfo $file */
  12695. foreach ($finder as $file) {
  12696. if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
  12697. $icons[$file->getFilename()] = $file->getFilename();
  12698. }
  12699. }
  12700. }
  12701. return $icons;
  12702. }
  12703. /**
  12704. * @param int $lpId
  12705. *
  12706. * @return string
  12707. */
  12708. public static function getSelectedIcon($lpId)
  12709. {
  12710. $extraFieldValue = new ExtraFieldValue('lp');
  12711. $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
  12712. $icon = '';
  12713. if (!empty($lpIcon) && isset($lpIcon['value'])) {
  12714. $icon = $lpIcon['value'];
  12715. }
  12716. return $icon;
  12717. }
  12718. public static function getSelectedIconHtml($lpId)
  12719. {
  12720. $icon = self::getSelectedIcon($lpId);
  12721. if (empty($icon)) {
  12722. return '';
  12723. }
  12724. $theme = api_get_visual_theme();
  12725. $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
  12726. return Display::img($path);
  12727. }
  12728. /**
  12729. * Get the depth level of LP item.
  12730. *
  12731. * @param array $items
  12732. * @param int $currentItemId
  12733. *
  12734. * @return int
  12735. */
  12736. private static function get_level_for_item($items, $currentItemId)
  12737. {
  12738. $parentItemId = 0;
  12739. if (isset($items[$currentItemId])) {
  12740. $parentItemId = $items[$currentItemId]->parent;
  12741. }
  12742. if ($parentItemId == 0) {
  12743. return 0;
  12744. } else {
  12745. return self::get_level_for_item($items, $parentItemId) + 1;
  12746. }
  12747. }
  12748. /**
  12749. * Generate the link for a learnpath category as course tool.
  12750. *
  12751. * @param int $categoryId
  12752. *
  12753. * @return string
  12754. */
  12755. private static function getCategoryLinkForTool($categoryId)
  12756. {
  12757. $categoryId = (int) $categoryId;
  12758. $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
  12759. .http_build_query(
  12760. [
  12761. 'action' => 'view_category',
  12762. 'id' => $categoryId,
  12763. ]
  12764. );
  12765. return $link;
  12766. }
  12767. /**
  12768. * Return the scorm item type object with spaces replaced with _
  12769. * The return result is use to build a css classname like scorm_type_$return.
  12770. *
  12771. * @param $in_type
  12772. *
  12773. * @return mixed
  12774. */
  12775. private static function format_scorm_type_item($in_type)
  12776. {
  12777. return str_replace(' ', '_', $in_type);
  12778. }
  12779. /**
  12780. * Check and obtain the lp final item if exist.
  12781. *
  12782. * @return learnpathItem
  12783. */
  12784. private function getFinalItem()
  12785. {
  12786. if (empty($this->items)) {
  12787. return null;
  12788. }
  12789. foreach ($this->items as $item) {
  12790. if ($item->type !== 'final_item') {
  12791. continue;
  12792. }
  12793. return $item;
  12794. }
  12795. }
  12796. /**
  12797. * Get the LP Final Item Template.
  12798. *
  12799. * @return string
  12800. */
  12801. private function getFinalItemTemplate()
  12802. {
  12803. return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
  12804. }
  12805. /**
  12806. * Get the LP Final Item Url.
  12807. *
  12808. * @return string
  12809. */
  12810. private function getSavedFinalItem()
  12811. {
  12812. $finalItem = $this->getFinalItem();
  12813. $doc = DocumentManager::get_document_data_by_id(
  12814. $finalItem->path,
  12815. $this->cc
  12816. );
  12817. if ($doc && file_exists($doc['absolute_path'])) {
  12818. return file_get_contents($doc['absolute_path']);
  12819. }
  12820. return '';
  12821. }
  12822. }