learnpath.class.php 511 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680
  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. $lp_item_id_list = [];
  241. while ($row = Database::fetch_array($res)) {
  242. $lp_item_id_list[] = $row['iid'];
  243. switch ($this->type) {
  244. case 3: //aicc
  245. $oItem = new aiccItem('db', $row['iid'], $course_id);
  246. if (is_object($oItem)) {
  247. $my_item_id = $oItem->get_id();
  248. $oItem->set_lp_view($this->lp_view_id, $course_id);
  249. $oItem->set_prevent_reinit($this->prevent_reinit);
  250. // Don't use reference here as the next loop will make the pointed object change.
  251. $this->items[$my_item_id] = $oItem;
  252. $this->refs_list[$oItem->ref] = $my_item_id;
  253. if ($debug) {
  254. error_log(
  255. 'learnpath::__construct() - '.
  256. 'aicc object with id '.$my_item_id.
  257. ' set in items[]',
  258. 0
  259. );
  260. }
  261. }
  262. break;
  263. case 2:
  264. $oItem = new scormItem('db', $row['iid'], $course_id);
  265. if (is_object($oItem)) {
  266. $my_item_id = $oItem->get_id();
  267. $oItem->set_lp_view($this->lp_view_id, $course_id);
  268. $oItem->set_prevent_reinit($this->prevent_reinit);
  269. // Don't use reference here as the next loop will make the pointed object change.
  270. $this->items[$my_item_id] = $oItem;
  271. $this->refs_list[$oItem->ref] = $my_item_id;
  272. if ($debug) {
  273. error_log('object with id '.$my_item_id.' set in items[]');
  274. }
  275. }
  276. break;
  277. case 1:
  278. default:
  279. if ($debug) {
  280. error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
  281. }
  282. $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
  283. if ($debug) {
  284. error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
  285. }
  286. if (is_object($oItem)) {
  287. $my_item_id = $oItem->get_id();
  288. // Moved down to when we are sure the item_view exists.
  289. //$oItem->set_lp_view($this->lp_view_id);
  290. $oItem->set_prevent_reinit($this->prevent_reinit);
  291. // Don't use reference here as the next loop will make the pointed object change.
  292. $this->items[$my_item_id] = $oItem;
  293. $this->refs_list[$my_item_id] = $my_item_id;
  294. if ($debug) {
  295. error_log(
  296. 'learnpath::__construct() '.__LINE__.
  297. ' - object with id '.$my_item_id.' set in items[]'
  298. );
  299. }
  300. }
  301. break;
  302. }
  303. // Setting the object level with variable $this->items[$i][parent]
  304. foreach ($this->items as $itemLPObject) {
  305. $level = self::get_level_for_item(
  306. $this->items,
  307. $itemLPObject->db_id
  308. );
  309. $itemLPObject->level = $level;
  310. }
  311. // Setting the view in the item object.
  312. if (is_object($this->items[$row['iid']])) {
  313. $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
  314. if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
  315. $this->items[$row['iid']]->current_start_time = 0;
  316. $this->items[$row['iid']]->current_stop_time = 0;
  317. }
  318. }
  319. }
  320. if (!empty($lp_item_id_list)) {
  321. $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
  322. if (!empty($lp_item_id_list_to_string)) {
  323. // Get last viewing vars.
  324. $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  325. // This query should only return one or zero result.
  326. $sql = "SELECT lp_item_id, status
  327. FROM $itemViewTable
  328. WHERE
  329. c_id = $course_id AND
  330. lp_view_id = ".$this->get_view_id()." AND
  331. lp_item_id IN ('".$lp_item_id_list_to_string."')
  332. ORDER BY view_count DESC ";
  333. $status_list = [];
  334. $res = Database::query($sql);
  335. while ($row = Database:: fetch_array($res)) {
  336. $status_list[$row['lp_item_id']] = $row['status'];
  337. }
  338. foreach ($lp_item_id_list as $item_id) {
  339. if (isset($status_list[$item_id])) {
  340. $status = $status_list[$item_id];
  341. if (is_object($this->items[$item_id])) {
  342. $this->items[$item_id]->set_status($status);
  343. if (empty($status)) {
  344. $this->items[$item_id]->set_status(
  345. $this->default_status
  346. );
  347. }
  348. }
  349. } else {
  350. if (!api_is_invitee()) {
  351. if (is_object($this->items[$item_id])) {
  352. $this->items[$item_id]->set_status(
  353. $this->default_status
  354. );
  355. }
  356. if (!empty($this->lp_view_id)) {
  357. // Add that row to the lp_item_view table so that
  358. // we have something to show in the stats page.
  359. $params = [
  360. 'c_id' => $course_id,
  361. 'lp_item_id' => $item_id,
  362. 'lp_view_id' => $this->lp_view_id,
  363. 'view_count' => 1,
  364. 'status' => 'not attempted',
  365. 'start_time' => time(),
  366. 'total_time' => 0,
  367. 'score' => 0,
  368. ];
  369. $insertId = Database::insert($itemViewTable, $params);
  370. if ($insertId) {
  371. $sql = "UPDATE $itemViewTable SET id = iid
  372. WHERE iid = $insertId";
  373. Database::query($sql);
  374. }
  375. $this->items[$item_id]->set_lp_view(
  376. $this->lp_view_id,
  377. $course_id
  378. );
  379. }
  380. }
  381. }
  382. }
  383. }
  384. }
  385. $this->ordered_items = self::get_flat_ordered_items_list(
  386. $this->get_id(),
  387. 0,
  388. $course_id
  389. );
  390. $this->max_ordered_items = 0;
  391. foreach ($this->ordered_items as $index => $dummy) {
  392. if ($index > $this->max_ordered_items && !empty($dummy)) {
  393. $this->max_ordered_items = $index;
  394. }
  395. }
  396. // TODO: Define the current item better.
  397. $this->first();
  398. if ($debug) {
  399. error_log('lp_view_session_id '.$this->lp_view_session_id);
  400. error_log('End of learnpath constructor for learnpath '.$this->get_id());
  401. }
  402. } else {
  403. $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
  404. }
  405. }
  406. }
  407. /**
  408. * @return string
  409. */
  410. public function getCourseCode()
  411. {
  412. return $this->cc;
  413. }
  414. /**
  415. * @return int
  416. */
  417. public function get_course_int_id()
  418. {
  419. return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
  420. }
  421. /**
  422. * @param $course_id
  423. *
  424. * @return int
  425. */
  426. public function set_course_int_id($course_id)
  427. {
  428. return $this->course_int_id = (int) $course_id;
  429. }
  430. /**
  431. * Function rewritten based on old_add_item() from Yannick Warnier.
  432. * Due the fact that users can decide where the item should come, I had to overlook this function and
  433. * I found it better to rewrite it. Old function is still available.
  434. * Added also the possibility to add a description.
  435. *
  436. * @param int $parent
  437. * @param int $previous
  438. * @param string $type
  439. * @param int $id resource ID (ref)
  440. * @param string $title
  441. * @param string $description
  442. * @param int $prerequisites
  443. * @param int $max_time_allowed
  444. * @param int $userId
  445. *
  446. * @return int
  447. */
  448. public function add_item(
  449. $parent,
  450. $previous,
  451. $type = 'dir',
  452. $id,
  453. $title,
  454. $description,
  455. $prerequisites = 0,
  456. $max_time_allowed = 0,
  457. $userId = 0
  458. ) {
  459. $course_id = $this->course_info['real_id'];
  460. if (empty($course_id)) {
  461. // Sometimes Oogie doesn't catch the course info but sets $this->cc
  462. $this->course_info = api_get_course_info($this->cc);
  463. $course_id = $this->course_info['real_id'];
  464. }
  465. $userId = empty($userId) ? api_get_user_id() : $userId;
  466. $sessionId = api_get_session_id();
  467. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  468. $_course = $this->course_info;
  469. $parent = (int) $parent;
  470. $previous = (int) $previous;
  471. $id = (int) $id;
  472. $max_time_allowed = htmlentities($max_time_allowed);
  473. if (empty($max_time_allowed)) {
  474. $max_time_allowed = 0;
  475. }
  476. $sql = "SELECT COUNT(iid) AS num
  477. FROM $tbl_lp_item
  478. WHERE
  479. c_id = $course_id AND
  480. lp_id = ".$this->get_id()." AND
  481. parent_item_id = $parent ";
  482. $res_count = Database::query($sql);
  483. $row = Database::fetch_array($res_count);
  484. $num = $row['num'];
  485. $tmp_previous = 0;
  486. $display_order = 0;
  487. $next = 0;
  488. if ($num > 0) {
  489. if (empty($previous)) {
  490. $sql = "SELECT iid, next_item_id, display_order
  491. FROM $tbl_lp_item
  492. WHERE
  493. c_id = $course_id AND
  494. lp_id = ".$this->get_id()." AND
  495. parent_item_id = $parent AND
  496. previous_item_id = 0 OR
  497. previous_item_id = $parent";
  498. $result = Database::query($sql);
  499. $row = Database::fetch_array($result);
  500. if ($row) {
  501. $next = $row['iid'];
  502. }
  503. } else {
  504. $previous = (int) $previous;
  505. $sql = "SELECT iid, previous_item_id, next_item_id, display_order
  506. FROM $tbl_lp_item
  507. WHERE
  508. c_id = $course_id AND
  509. lp_id = ".$this->get_id()." AND
  510. id = $previous";
  511. $result = Database::query($sql);
  512. $row = Database::fetch_array($result);
  513. if ($row) {
  514. $tmp_previous = $row['iid'];
  515. $next = $row['next_item_id'];
  516. $display_order = $row['display_order'];
  517. }
  518. }
  519. }
  520. $id = (int) $id;
  521. $typeCleaned = Database::escape_string($type);
  522. $max_score = 100;
  523. if ($type === 'quiz') {
  524. $sql = 'SELECT SUM(ponderation)
  525. FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
  526. INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
  527. ON
  528. quiz_question.id = quiz_rel_question.question_id AND
  529. quiz_question.c_id = quiz_rel_question.c_id
  530. WHERE
  531. quiz_rel_question.exercice_id = '.$id." AND
  532. quiz_question.c_id = $course_id AND
  533. quiz_rel_question.c_id = $course_id ";
  534. $rsQuiz = Database::query($sql);
  535. $max_score = Database::result($rsQuiz, 0, 0);
  536. // Disabling the exercise if we add it inside a LP
  537. $exercise = new Exercise($course_id);
  538. $exercise->read($id);
  539. $exercise->disable();
  540. $exercise->save();
  541. }
  542. $params = [
  543. 'c_id' => $course_id,
  544. 'lp_id' => $this->get_id(),
  545. 'item_type' => $typeCleaned,
  546. 'ref' => '',
  547. 'title' => $title,
  548. 'description' => $description,
  549. 'path' => $id,
  550. 'max_score' => $max_score,
  551. 'parent_item_id' => $parent,
  552. 'previous_item_id' => $previous,
  553. 'next_item_id' => (int) $next,
  554. 'display_order' => $display_order + 1,
  555. 'prerequisite' => $prerequisites,
  556. 'max_time_allowed' => $max_time_allowed,
  557. 'min_score' => 0,
  558. 'launch_data' => '',
  559. ];
  560. if ($prerequisites != 0) {
  561. $params['prerequisite'] = $prerequisites;
  562. }
  563. $new_item_id = Database::insert($tbl_lp_item, $params);
  564. if ($new_item_id) {
  565. $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
  566. Database::query($sql);
  567. if (!empty($next)) {
  568. $sql = "UPDATE $tbl_lp_item
  569. SET previous_item_id = $new_item_id
  570. WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
  571. Database::query($sql);
  572. }
  573. // Update the item that should be before the new item.
  574. if (!empty($tmp_previous)) {
  575. $sql = "UPDATE $tbl_lp_item
  576. SET next_item_id = $new_item_id
  577. WHERE c_id = $course_id AND id = $tmp_previous";
  578. Database::query($sql);
  579. }
  580. // Update all the items after the new item.
  581. $sql = "UPDATE $tbl_lp_item
  582. SET display_order = display_order + 1
  583. WHERE
  584. c_id = $course_id AND
  585. lp_id = ".$this->get_id()." AND
  586. iid <> $new_item_id AND
  587. parent_item_id = $parent AND
  588. display_order > $display_order";
  589. Database::query($sql);
  590. // Update the item that should come after the new item.
  591. $sql = "UPDATE $tbl_lp_item
  592. SET ref = $new_item_id
  593. WHERE c_id = $course_id AND iid = $new_item_id";
  594. Database::query($sql);
  595. $sql = "UPDATE $tbl_lp_item
  596. SET previous_item_id = ".$this->getLastInFirstLevel()."
  597. WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
  598. Database::query($sql);
  599. // Upload audio.
  600. if (!empty($_FILES['mp3']['name'])) {
  601. // Create the audio folder if it does not exist yet.
  602. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
  603. if (!is_dir($filepath.'audio')) {
  604. mkdir(
  605. $filepath.'audio',
  606. api_get_permissions_for_new_directories()
  607. );
  608. $audio_id = add_document(
  609. $_course,
  610. '/audio',
  611. 'folder',
  612. 0,
  613. 'audio',
  614. '',
  615. 0,
  616. true,
  617. null,
  618. $sessionId,
  619. $userId
  620. );
  621. api_item_property_update(
  622. $_course,
  623. TOOL_DOCUMENT,
  624. $audio_id,
  625. 'FolderCreated',
  626. $userId,
  627. null,
  628. null,
  629. null,
  630. null,
  631. $sessionId
  632. );
  633. api_item_property_update(
  634. $_course,
  635. TOOL_DOCUMENT,
  636. $audio_id,
  637. 'invisible',
  638. $userId,
  639. null,
  640. null,
  641. null,
  642. null,
  643. $sessionId
  644. );
  645. }
  646. $file_path = handle_uploaded_document(
  647. $_course,
  648. $_FILES['mp3'],
  649. api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
  650. '/audio',
  651. $userId,
  652. '',
  653. '',
  654. '',
  655. '',
  656. false
  657. );
  658. // Getting the filename only.
  659. $file_components = explode('/', $file_path);
  660. $file = $file_components[count($file_components) - 1];
  661. // Store the mp3 file in the lp_item table.
  662. $sql = "UPDATE $tbl_lp_item SET
  663. audio = '".Database::escape_string($file)."'
  664. WHERE iid = '".intval($new_item_id)."'";
  665. Database::query($sql);
  666. }
  667. }
  668. return $new_item_id;
  669. }
  670. /**
  671. * Static admin function allowing addition of a learnpath to a course.
  672. *
  673. * @param string $courseCode
  674. * @param string $name
  675. * @param string $description
  676. * @param string $learnpath
  677. * @param string $origin
  678. * @param string $zipname Zip file containing the learnpath or directory containing the learnpath
  679. * @param string $publicated_on
  680. * @param string $expired_on
  681. * @param int $categoryId
  682. * @param int $userId
  683. *
  684. * @return int The new learnpath ID on success, 0 on failure
  685. */
  686. public static function add_lp(
  687. $courseCode,
  688. $name,
  689. $description = '',
  690. $learnpath = 'guess',
  691. $origin = 'zip',
  692. $zipname = '',
  693. $publicated_on = '',
  694. $expired_on = '',
  695. $categoryId = 0,
  696. $userId = 0
  697. ) {
  698. global $charset;
  699. if (!empty($courseCode)) {
  700. $courseInfo = api_get_course_info($courseCode);
  701. $course_id = $courseInfo['real_id'];
  702. } else {
  703. $course_id = api_get_course_int_id();
  704. $courseInfo = api_get_course_info();
  705. }
  706. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  707. // Check course code exists.
  708. // Check lp_name doesn't exist, otherwise append something.
  709. $i = 0;
  710. $categoryId = (int) $categoryId;
  711. // Session id.
  712. $session_id = api_get_session_id();
  713. $userId = empty($userId) ? api_get_user_id() : $userId;
  714. if (empty($publicated_on)) {
  715. $publicated_on = null;
  716. } else {
  717. $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
  718. }
  719. if (empty($expired_on)) {
  720. $expired_on = null;
  721. } else {
  722. $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
  723. }
  724. $check_name = "SELECT * FROM $tbl_lp
  725. WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
  726. $res_name = Database::query($check_name);
  727. while (Database::num_rows($res_name)) {
  728. // There is already one such name, update the current one a bit.
  729. $i++;
  730. $name = $name.' - '.$i;
  731. $check_name = "SELECT * FROM $tbl_lp
  732. WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
  733. $res_name = Database::query($check_name);
  734. }
  735. // New name does not exist yet; keep it.
  736. // Escape description.
  737. // Kevin: added htmlentities().
  738. $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
  739. $type = 1;
  740. switch ($learnpath) {
  741. case 'guess':
  742. break;
  743. case 'dokeos':
  744. case 'chamilo':
  745. $type = 1;
  746. break;
  747. case 'aicc':
  748. break;
  749. }
  750. switch ($origin) {
  751. case 'zip':
  752. // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
  753. break;
  754. case 'manual':
  755. default:
  756. $get_max = "SELECT MAX(display_order)
  757. FROM $tbl_lp WHERE c_id = $course_id";
  758. $res_max = Database::query($get_max);
  759. if (Database::num_rows($res_max) < 1) {
  760. $dsp = 1;
  761. } else {
  762. $row = Database::fetch_array($res_max);
  763. $dsp = $row[0] + 1;
  764. }
  765. $params = [
  766. 'c_id' => $course_id,
  767. 'lp_type' => $type,
  768. 'name' => $name,
  769. 'description' => $description,
  770. 'path' => '',
  771. 'default_view_mod' => 'embedded',
  772. 'default_encoding' => 'UTF-8',
  773. 'display_order' => $dsp,
  774. 'content_maker' => 'Chamilo',
  775. 'content_local' => 'local',
  776. 'js_lib' => '',
  777. 'session_id' => $session_id,
  778. 'created_on' => api_get_utc_datetime(),
  779. 'modified_on' => api_get_utc_datetime(),
  780. 'publicated_on' => $publicated_on,
  781. 'expired_on' => $expired_on,
  782. 'category_id' => $categoryId,
  783. 'force_commit' => 0,
  784. 'content_license' => '',
  785. 'debug' => 0,
  786. 'theme' => '',
  787. 'preview_image' => '',
  788. 'author' => '',
  789. 'prerequisite' => 0,
  790. 'hide_toc_frame' => 0,
  791. 'seriousgame_mode' => 0,
  792. 'autolaunch' => 0,
  793. 'max_attempts' => 0,
  794. 'subscribe_users' => 0,
  795. 'accumulate_scorm_time' => 1,
  796. ];
  797. $id = Database::insert($tbl_lp, $params);
  798. if ($id > 0) {
  799. $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
  800. Database::query($sql);
  801. // Insert into item_property.
  802. api_item_property_update(
  803. $courseInfo,
  804. TOOL_LEARNPATH,
  805. $id,
  806. 'LearnpathAdded',
  807. $userId
  808. );
  809. api_set_default_visibility(
  810. $id,
  811. TOOL_LEARNPATH,
  812. 0,
  813. $courseInfo,
  814. $session_id,
  815. $userId
  816. );
  817. return $id;
  818. }
  819. break;
  820. }
  821. }
  822. /**
  823. * Auto completes the parents of an item in case it's been completed or passed.
  824. *
  825. * @param int $item Optional ID of the item from which to look for parents
  826. */
  827. public function autocomplete_parents($item)
  828. {
  829. $debug = $this->debug;
  830. if (empty($item)) {
  831. $item = $this->current;
  832. }
  833. $currentItem = $this->getItem($item);
  834. if ($currentItem) {
  835. $parent_id = $currentItem->get_parent();
  836. $parent = $this->getItem($parent_id);
  837. if ($parent) {
  838. // if $item points to an object and there is a parent.
  839. if ($debug) {
  840. error_log(
  841. 'Autocompleting parent of item '.$item.' '.
  842. $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
  843. 0
  844. );
  845. }
  846. // New experiment including failed and browsed in completed status.
  847. //$current_status = $currentItem->get_status();
  848. //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
  849. // Fixes chapter auto complete
  850. if (true) {
  851. // If the current item is completed or passes or succeeded.
  852. $updateParentStatus = true;
  853. if ($debug) {
  854. error_log('Status of current item is alright');
  855. }
  856. foreach ($parent->get_children() as $childItemId) {
  857. $childItem = $this->getItem($childItemId);
  858. // If children was not set try to get the info
  859. if (empty($childItem->db_item_view_id)) {
  860. $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
  861. }
  862. // Check all his brothers (parent's children) for completion status.
  863. if ($childItemId != $item) {
  864. if ($debug) {
  865. error_log(
  866. 'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
  867. 0
  868. );
  869. }
  870. // Trying completing parents of failed and browsed items as well.
  871. if ($childItem->status_is(
  872. [
  873. 'completed',
  874. 'passed',
  875. 'succeeded',
  876. 'browsed',
  877. 'failed',
  878. ]
  879. )
  880. ) {
  881. // Keep completion status to true.
  882. continue;
  883. } else {
  884. if ($debug > 2) {
  885. error_log(
  886. '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,
  887. 0
  888. );
  889. }
  890. $updateParentStatus = false;
  891. break;
  892. }
  893. }
  894. }
  895. if ($updateParentStatus) {
  896. // If all the children were completed:
  897. $parent->set_status('completed');
  898. $parent->save(false, $this->prerequisites_match($parent->get_id()));
  899. // Force the status to "completed"
  900. //$this->update_queue[$parent->get_id()] = $parent->get_status();
  901. $this->update_queue[$parent->get_id()] = 'completed';
  902. if ($debug) {
  903. error_log(
  904. 'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
  905. print_r($this->update_queue, 1),
  906. 0
  907. );
  908. }
  909. // Recursive call.
  910. $this->autocomplete_parents($parent->get_id());
  911. }
  912. }
  913. } else {
  914. if ($debug) {
  915. error_log("Parent #$parent_id does not exists");
  916. }
  917. }
  918. } else {
  919. if ($debug) {
  920. error_log("#$item is an item that doesn't have parents");
  921. }
  922. }
  923. }
  924. /**
  925. * Closes the current resource.
  926. *
  927. * Stops the timer
  928. * Saves into the database if required
  929. * Clears the current resource data from this object
  930. *
  931. * @return bool True on success, false on failure
  932. */
  933. public function close()
  934. {
  935. if (empty($this->lp_id)) {
  936. $this->error = 'Trying to close this learnpath but no ID is set';
  937. return false;
  938. }
  939. $this->current_time_stop = time();
  940. $this->ordered_items = [];
  941. $this->index = 0;
  942. unset($this->lp_id);
  943. //unset other stuff
  944. return true;
  945. }
  946. /**
  947. * Static admin function allowing removal of a learnpath.
  948. *
  949. * @param array $courseInfo
  950. * @param int $id Learnpath ID
  951. * @param string $delete Whether to delete data or keep it (default: 'keep', others: 'remove')
  952. *
  953. * @return bool True on success, false on failure (might change that to return number of elements deleted)
  954. */
  955. public function delete($courseInfo = null, $id = null, $delete = 'keep')
  956. {
  957. $course_id = api_get_course_int_id();
  958. if (!empty($courseInfo)) {
  959. $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
  960. }
  961. // TODO: Implement a way of getting this to work when the current object is not set.
  962. // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
  963. // If an ID is specifically given and the current LP is not the same, prevent delete.
  964. if (!empty($id) && ($id != $this->lp_id)) {
  965. return false;
  966. }
  967. $lp = Database::get_course_table(TABLE_LP_MAIN);
  968. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  969. $lp_view = Database::get_course_table(TABLE_LP_VIEW);
  970. $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  971. // Delete lp item id.
  972. foreach ($this->items as $lpItemId => $dummy) {
  973. $sql = "DELETE FROM $lp_item_view
  974. WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
  975. Database::query($sql);
  976. }
  977. // Proposed by Christophe (nickname: clefevre)
  978. $sql = "DELETE FROM $lp_item
  979. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  980. Database::query($sql);
  981. $sql = "DELETE FROM $lp_view
  982. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  983. Database::query($sql);
  984. self::toggle_publish($this->lp_id, 'i');
  985. if ($this->type == 2 || $this->type == 3) {
  986. // This is a scorm learning path, delete the files as well.
  987. $sql = "SELECT path FROM $lp
  988. WHERE iid = ".$this->lp_id;
  989. $res = Database::query($sql);
  990. if (Database::num_rows($res) > 0) {
  991. $row = Database::fetch_array($res);
  992. $path = $row['path'];
  993. $sql = "SELECT id FROM $lp
  994. WHERE
  995. c_id = $course_id AND
  996. path = '$path' AND
  997. iid != ".$this->lp_id;
  998. $res = Database::query($sql);
  999. if (Database::num_rows($res) > 0) {
  1000. // Another learning path uses this directory, so don't delete it.
  1001. if ($this->debug > 2) {
  1002. error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
  1003. }
  1004. } else {
  1005. // No other LP uses that directory, delete it.
  1006. $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
  1007. // The absolute system path for this course.
  1008. $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
  1009. if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
  1010. if ($this->debug > 2) {
  1011. error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
  1012. }
  1013. // Proposed by Christophe (clefevre).
  1014. if (strcmp(substr($path, -2), "/.") == 0) {
  1015. $path = substr($path, 0, -1); // Remove "." at the end.
  1016. }
  1017. //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
  1018. rmdirr($course_scorm_dir.$path);
  1019. }
  1020. }
  1021. }
  1022. }
  1023. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  1024. $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
  1025. // Delete tools
  1026. $sql = "DELETE FROM $tbl_tool
  1027. WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
  1028. Database::query($sql);
  1029. $sql = "DELETE FROM $lp
  1030. WHERE iid = ".$this->lp_id;
  1031. Database::query($sql);
  1032. // Updates the display order of all lps.
  1033. $this->update_display_order();
  1034. api_item_property_update(
  1035. api_get_course_info(),
  1036. TOOL_LEARNPATH,
  1037. $this->lp_id,
  1038. 'delete',
  1039. api_get_user_id()
  1040. );
  1041. $link_info = GradebookUtils::isResourceInCourseGradebook(
  1042. api_get_course_id(),
  1043. 4,
  1044. $id,
  1045. api_get_session_id()
  1046. );
  1047. if ($link_info !== false) {
  1048. GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
  1049. }
  1050. if (api_get_setting('search_enabled') == 'true') {
  1051. require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
  1052. delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
  1053. }
  1054. }
  1055. /**
  1056. * Removes all the children of one item - dangerous!
  1057. *
  1058. * @param int $id Element ID of which children have to be removed
  1059. *
  1060. * @return int Total number of children removed
  1061. */
  1062. public function delete_children_items($id)
  1063. {
  1064. $course_id = $this->course_info['real_id'];
  1065. $num = 0;
  1066. $id = (int) $id;
  1067. if (empty($id) || empty($course_id)) {
  1068. return false;
  1069. }
  1070. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1071. $sql = "SELECT * FROM $lp_item
  1072. WHERE c_id = $course_id AND parent_item_id = $id";
  1073. $res = Database::query($sql);
  1074. while ($row = Database::fetch_array($res)) {
  1075. $num += $this->delete_children_items($row['iid']);
  1076. $sql = "DELETE FROM $lp_item
  1077. WHERE c_id = $course_id AND iid = ".$row['iid'];
  1078. Database::query($sql);
  1079. $num++;
  1080. }
  1081. return $num;
  1082. }
  1083. /**
  1084. * Removes an item from the current learnpath.
  1085. *
  1086. * @param int $id Elem ID (0 if first)
  1087. *
  1088. * @return int Number of elements moved
  1089. *
  1090. * @todo implement resource removal
  1091. */
  1092. public function delete_item($id)
  1093. {
  1094. $course_id = api_get_course_int_id();
  1095. $id = (int) $id;
  1096. // TODO: Implement the resource removal.
  1097. if (empty($id) || empty($course_id)) {
  1098. return false;
  1099. }
  1100. // First select item to get previous, next, and display order.
  1101. $lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1102. $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
  1103. $res_sel = Database::query($sql_sel);
  1104. if (Database::num_rows($res_sel) < 1) {
  1105. return false;
  1106. }
  1107. $row = Database::fetch_array($res_sel);
  1108. $previous = $row['previous_item_id'];
  1109. $next = $row['next_item_id'];
  1110. $display = $row['display_order'];
  1111. $parent = $row['parent_item_id'];
  1112. $lp = $row['lp_id'];
  1113. // Delete children items.
  1114. $this->delete_children_items($id);
  1115. // Now delete the item.
  1116. $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
  1117. Database::query($sql_del);
  1118. // Now update surrounding items.
  1119. $sql_upd = "UPDATE $lp_item SET next_item_id = $next
  1120. WHERE iid = $previous";
  1121. Database::query($sql_upd);
  1122. $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
  1123. WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
  1124. Database::query($sql_upd);
  1125. // Now update all following items with new display order.
  1126. $sql_all = "UPDATE $lp_item SET display_order = display_order-1
  1127. WHERE
  1128. c_id = $course_id AND
  1129. lp_id = $lp AND
  1130. parent_item_id = $parent AND
  1131. display_order > $display";
  1132. Database::query($sql_all);
  1133. //Removing prerequisites since the item will not longer exist
  1134. $sql_all = "UPDATE $lp_item SET prerequisite = ''
  1135. WHERE c_id = $course_id AND prerequisite = $id";
  1136. Database::query($sql_all);
  1137. $sql = "UPDATE $lp_item
  1138. SET previous_item_id = ".$this->getLastInFirstLevel()."
  1139. WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
  1140. Database::query($sql);
  1141. // Remove from search engine if enabled.
  1142. if (api_get_setting('search_enabled') === 'true') {
  1143. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  1144. $sql = 'SELECT * FROM %s
  1145. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
  1146. LIMIT 1';
  1147. $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
  1148. $res = Database::query($sql);
  1149. if (Database::num_rows($res) > 0) {
  1150. $row2 = Database::fetch_array($res);
  1151. $di = new ChamiloIndexer();
  1152. $di->remove_document($row2['search_did']);
  1153. }
  1154. $sql = 'DELETE FROM %s
  1155. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
  1156. LIMIT 1';
  1157. $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
  1158. Database::query($sql);
  1159. }
  1160. }
  1161. /**
  1162. * Updates an item's content in place.
  1163. *
  1164. * @param int $id Element ID
  1165. * @param int $parent Parent item ID
  1166. * @param int $previous Previous item ID
  1167. * @param string $title Item title
  1168. * @param string $description Item description
  1169. * @param string $prerequisites Prerequisites (optional)
  1170. * @param array $audio The array resulting of the $_FILES[mp3] element
  1171. * @param int $max_time_allowed
  1172. * @param string $url
  1173. *
  1174. * @return bool True on success, false on error
  1175. */
  1176. public function edit_item(
  1177. $id,
  1178. $parent,
  1179. $previous,
  1180. $title,
  1181. $description,
  1182. $prerequisites = '0',
  1183. $audio = [],
  1184. $max_time_allowed = 0,
  1185. $url = ''
  1186. ) {
  1187. $course_id = api_get_course_int_id();
  1188. $_course = api_get_course_info();
  1189. $id = (int) $id;
  1190. if (empty($max_time_allowed)) {
  1191. $max_time_allowed = 0;
  1192. }
  1193. if (empty($id) || empty($_course)) {
  1194. return false;
  1195. }
  1196. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1197. $sql = "SELECT * FROM $tbl_lp_item
  1198. WHERE iid = $id";
  1199. $res_select = Database::query($sql);
  1200. $row_select = Database::fetch_array($res_select);
  1201. $audio_update_sql = '';
  1202. if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
  1203. // Create the audio folder if it does not exist yet.
  1204. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
  1205. if (!is_dir($filepath.'audio')) {
  1206. mkdir($filepath.'audio', api_get_permissions_for_new_directories());
  1207. $audio_id = add_document(
  1208. $_course,
  1209. '/audio',
  1210. 'folder',
  1211. 0,
  1212. 'audio'
  1213. );
  1214. api_item_property_update(
  1215. $_course,
  1216. TOOL_DOCUMENT,
  1217. $audio_id,
  1218. 'FolderCreated',
  1219. api_get_user_id(),
  1220. null,
  1221. null,
  1222. null,
  1223. null,
  1224. api_get_session_id()
  1225. );
  1226. api_item_property_update(
  1227. $_course,
  1228. TOOL_DOCUMENT,
  1229. $audio_id,
  1230. 'invisible',
  1231. api_get_user_id(),
  1232. null,
  1233. null,
  1234. null,
  1235. null,
  1236. api_get_session_id()
  1237. );
  1238. }
  1239. // Upload file in documents.
  1240. $pi = pathinfo($audio['name']);
  1241. if ($pi['extension'] === 'mp3') {
  1242. $c_det = api_get_course_info($this->cc);
  1243. $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
  1244. $path = handle_uploaded_document(
  1245. $c_det,
  1246. $audio,
  1247. $bp,
  1248. '/audio',
  1249. api_get_user_id(),
  1250. 0,
  1251. null,
  1252. 0,
  1253. 'rename',
  1254. false,
  1255. 0
  1256. );
  1257. $path = substr($path, 7);
  1258. // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
  1259. $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
  1260. }
  1261. }
  1262. $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
  1263. $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
  1264. // TODO: htmlspecialchars to be checked for encoding related problems.
  1265. if ($same_parent && $same_previous) {
  1266. // Only update title and description.
  1267. $sql = "UPDATE $tbl_lp_item
  1268. SET title = '".Database::escape_string($title)."',
  1269. prerequisite = '".$prerequisites."',
  1270. description = '".Database::escape_string($description)."'
  1271. ".$audio_update_sql.",
  1272. max_time_allowed = '".Database::escape_string($max_time_allowed)."'
  1273. WHERE iid = $id";
  1274. Database::query($sql);
  1275. } else {
  1276. $old_parent = $row_select['parent_item_id'];
  1277. $old_previous = $row_select['previous_item_id'];
  1278. $old_next = $row_select['next_item_id'];
  1279. $old_order = $row_select['display_order'];
  1280. $old_prerequisite = $row_select['prerequisite'];
  1281. $old_max_time_allowed = $row_select['max_time_allowed'];
  1282. /* BEGIN -- virtually remove the current item id */
  1283. /* for the next and previous item it is like the current item doesn't exist anymore */
  1284. if ($old_previous != 0) {
  1285. // Next
  1286. $sql = "UPDATE $tbl_lp_item
  1287. SET next_item_id = $old_next
  1288. WHERE iid = $old_previous";
  1289. Database::query($sql);
  1290. }
  1291. if (!empty($old_next)) {
  1292. // Previous
  1293. $sql = "UPDATE $tbl_lp_item
  1294. SET previous_item_id = $old_previous
  1295. WHERE iid = $old_next";
  1296. Database::query($sql);
  1297. }
  1298. // display_order - 1 for every item with a display_order
  1299. // bigger then the display_order of the current item.
  1300. $sql = "UPDATE $tbl_lp_item
  1301. SET display_order = display_order - 1
  1302. WHERE
  1303. c_id = $course_id AND
  1304. display_order > $old_order AND
  1305. lp_id = ".$this->lp_id." AND
  1306. parent_item_id = $old_parent";
  1307. Database::query($sql);
  1308. /* END -- virtually remove the current item id */
  1309. /* BEGIN -- update the current item id to his new location */
  1310. if ($previous == 0) {
  1311. // Select the data of the item that should come after the current item.
  1312. $sql = "SELECT id, display_order
  1313. FROM $tbl_lp_item
  1314. WHERE
  1315. c_id = $course_id AND
  1316. lp_id = ".$this->lp_id." AND
  1317. parent_item_id = $parent AND
  1318. previous_item_id = $previous";
  1319. $res_select_old = Database::query($sql);
  1320. $row_select_old = Database::fetch_array($res_select_old);
  1321. // If the new parent didn't have children before.
  1322. if (Database::num_rows($res_select_old) == 0) {
  1323. $new_next = 0;
  1324. $new_order = 1;
  1325. } else {
  1326. $new_next = $row_select_old['id'];
  1327. $new_order = $row_select_old['display_order'];
  1328. }
  1329. } else {
  1330. // Select the data of the item that should come before the current item.
  1331. $sql = "SELECT next_item_id, display_order
  1332. FROM $tbl_lp_item
  1333. WHERE iid = $previous";
  1334. $res_select_old = Database::query($sql);
  1335. $row_select_old = Database::fetch_array($res_select_old);
  1336. $new_next = $row_select_old['next_item_id'];
  1337. $new_order = $row_select_old['display_order'] + 1;
  1338. }
  1339. // TODO: htmlspecialchars to be checked for encoding related problems.
  1340. // Update the current item with the new data.
  1341. $sql = "UPDATE $tbl_lp_item
  1342. SET
  1343. title = '".Database::escape_string($title)."',
  1344. description = '".Database::escape_string($description)."',
  1345. parent_item_id = $parent,
  1346. previous_item_id = $previous,
  1347. next_item_id = $new_next,
  1348. display_order = $new_order
  1349. $audio_update_sql
  1350. WHERE iid = $id";
  1351. Database::query($sql);
  1352. if ($previous != 0) {
  1353. // Update the previous item's next_item_id.
  1354. $sql = "UPDATE $tbl_lp_item
  1355. SET next_item_id = $id
  1356. WHERE iid = $previous";
  1357. Database::query($sql);
  1358. }
  1359. if (!empty($new_next)) {
  1360. // Update the next item's previous_item_id.
  1361. $sql = "UPDATE $tbl_lp_item
  1362. SET previous_item_id = $id
  1363. WHERE iid = $new_next";
  1364. Database::query($sql);
  1365. }
  1366. if ($old_prerequisite != $prerequisites) {
  1367. $sql = "UPDATE $tbl_lp_item
  1368. SET prerequisite = '$prerequisites'
  1369. WHERE iid = $id";
  1370. Database::query($sql);
  1371. }
  1372. if ($old_max_time_allowed != $max_time_allowed) {
  1373. // update max time allowed
  1374. $sql = "UPDATE $tbl_lp_item
  1375. SET max_time_allowed = $max_time_allowed
  1376. WHERE iid = $id";
  1377. Database::query($sql);
  1378. }
  1379. // Update all the items with the same or a bigger display_order than the current item.
  1380. $sql = "UPDATE $tbl_lp_item
  1381. SET display_order = display_order + 1
  1382. WHERE
  1383. c_id = $course_id AND
  1384. lp_id = ".$this->get_id()." AND
  1385. iid <> $id AND
  1386. parent_item_id = $parent AND
  1387. display_order >= $new_order";
  1388. Database::query($sql);
  1389. }
  1390. if ($row_select['item_type'] == 'link') {
  1391. $link = new Link();
  1392. $linkId = $row_select['path'];
  1393. $link->updateLink($linkId, $url);
  1394. }
  1395. }
  1396. /**
  1397. * Updates an item's prereq in place.
  1398. *
  1399. * @param int $id Element ID
  1400. * @param string $prerequisite_id Prerequisite Element ID
  1401. * @param int $minScore Prerequisite min score
  1402. * @param int $maxScore Prerequisite max score
  1403. *
  1404. * @return bool True on success, false on error
  1405. */
  1406. public function edit_item_prereq(
  1407. $id,
  1408. $prerequisite_id,
  1409. $minScore = 0,
  1410. $maxScore = 100
  1411. ) {
  1412. $id = (int) $id;
  1413. $prerequisite_id = (int) $prerequisite_id;
  1414. if (empty($id)) {
  1415. return false;
  1416. }
  1417. if (empty($minScore) || $minScore < 0) {
  1418. $minScore = 0;
  1419. }
  1420. if (empty($maxScore) || $maxScore < 0) {
  1421. $maxScore = 100;
  1422. }
  1423. $minScore = floatval($minScore);
  1424. $maxScore = floatval($maxScore);
  1425. if (empty($prerequisite_id)) {
  1426. $prerequisite_id = 'NULL';
  1427. $minScore = 0;
  1428. $maxScore = 100;
  1429. }
  1430. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1431. $sql = " UPDATE $tbl_lp_item
  1432. SET
  1433. prerequisite = $prerequisite_id ,
  1434. prerequisite_min_score = $minScore ,
  1435. prerequisite_max_score = $maxScore
  1436. WHERE iid = $id";
  1437. Database::query($sql);
  1438. return true;
  1439. }
  1440. /**
  1441. * Get the specific prefix index terms of this learning path.
  1442. *
  1443. * @param string $prefix
  1444. *
  1445. * @return array Array of terms
  1446. */
  1447. public function get_common_index_terms_by_prefix($prefix)
  1448. {
  1449. require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
  1450. $terms = get_specific_field_values_list_by_prefix(
  1451. $prefix,
  1452. $this->cc,
  1453. TOOL_LEARNPATH,
  1454. $this->lp_id
  1455. );
  1456. $prefix_terms = [];
  1457. if (!empty($terms)) {
  1458. foreach ($terms as $term) {
  1459. $prefix_terms[] = $term['value'];
  1460. }
  1461. }
  1462. return $prefix_terms;
  1463. }
  1464. /**
  1465. * Gets the number of items currently completed.
  1466. *
  1467. * @param bool $failedStatusException flag to determine the failed status is not considered progressed
  1468. *
  1469. * @return int The number of items currently completed
  1470. */
  1471. public function get_complete_items_count($failedStatusException = false)
  1472. {
  1473. $i = 0;
  1474. $completedStatusList = [
  1475. 'completed',
  1476. 'passed',
  1477. 'succeeded',
  1478. 'browsed',
  1479. ];
  1480. if (!$failedStatusException) {
  1481. $completedStatusList[] = 'failed';
  1482. }
  1483. foreach ($this->items as $id => $dummy) {
  1484. // Trying failed and browsed considered "progressed" as well.
  1485. if ($this->items[$id]->status_is($completedStatusList) &&
  1486. $this->items[$id]->get_type() != 'dir'
  1487. ) {
  1488. $i++;
  1489. }
  1490. }
  1491. return $i;
  1492. }
  1493. /**
  1494. * Gets the current item ID.
  1495. *
  1496. * @return int The current learnpath item id
  1497. */
  1498. public function get_current_item_id()
  1499. {
  1500. $current = 0;
  1501. if (!empty($this->current)) {
  1502. $current = (int) $this->current;
  1503. }
  1504. return $current;
  1505. }
  1506. /**
  1507. * Force to get the first learnpath item id.
  1508. *
  1509. * @return int The current learnpath item id
  1510. */
  1511. public function get_first_item_id()
  1512. {
  1513. $current = 0;
  1514. if (is_array($this->ordered_items)) {
  1515. $current = $this->ordered_items[0];
  1516. }
  1517. return $current;
  1518. }
  1519. /**
  1520. * Gets the total number of items available for viewing in this SCORM.
  1521. *
  1522. * @return int The total number of items
  1523. */
  1524. public function get_total_items_count()
  1525. {
  1526. return count($this->items);
  1527. }
  1528. /**
  1529. * Gets the total number of items available for viewing in this SCORM but without chapters.
  1530. *
  1531. * @return int The total no-chapters number of items
  1532. */
  1533. public function getTotalItemsCountWithoutDirs()
  1534. {
  1535. $total = 0;
  1536. $typeListNotToCount = self::getChapterTypes();
  1537. foreach ($this->items as $temp2) {
  1538. if (!in_array($temp2->get_type(), $typeListNotToCount)) {
  1539. $total++;
  1540. }
  1541. }
  1542. return $total;
  1543. }
  1544. /**
  1545. * Sets the first element URL.
  1546. */
  1547. public function first()
  1548. {
  1549. if ($this->debug > 0) {
  1550. error_log('In learnpath::first()', 0);
  1551. error_log('$this->last_item_seen '.$this->last_item_seen);
  1552. }
  1553. // Test if the last_item_seen exists and is not a dir.
  1554. if (count($this->ordered_items) == 0) {
  1555. $this->index = 0;
  1556. }
  1557. if (!empty($this->last_item_seen) &&
  1558. !empty($this->items[$this->last_item_seen]) &&
  1559. $this->items[$this->last_item_seen]->get_type() != 'dir'
  1560. //with this change (below) the LP will NOT go to the next item, it will take lp item we left
  1561. //&& !$this->items[$this->last_item_seen]->is_done()
  1562. ) {
  1563. if ($this->debug > 2) {
  1564. error_log(
  1565. 'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
  1566. $this->items[$this->last_item_seen]->get_type()
  1567. );
  1568. }
  1569. $index = -1;
  1570. foreach ($this->ordered_items as $myindex => $item_id) {
  1571. if ($item_id == $this->last_item_seen) {
  1572. $index = $myindex;
  1573. break;
  1574. }
  1575. }
  1576. if ($index == -1) {
  1577. // Index hasn't changed, so item not found - panic (this shouldn't happen).
  1578. if ($this->debug > 2) {
  1579. error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
  1580. }
  1581. return false;
  1582. } else {
  1583. $this->last = $this->last_item_seen;
  1584. $this->current = $this->last_item_seen;
  1585. $this->index = $index;
  1586. }
  1587. } else {
  1588. if ($this->debug > 2) {
  1589. error_log('In learnpath::first() - No last item seen', 0);
  1590. }
  1591. $index = 0;
  1592. // Loop through all ordered items and stop at the first item that is
  1593. // not a directory *and* that has not been completed yet.
  1594. while (!empty($this->ordered_items[$index]) &&
  1595. is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
  1596. (
  1597. $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
  1598. $this->items[$this->ordered_items[$index]]->is_done() === true
  1599. ) && $index < $this->max_ordered_items) {
  1600. $index++;
  1601. }
  1602. $this->last = $this->current;
  1603. // current is
  1604. $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
  1605. $this->index = $index;
  1606. if ($this->debug > 2) {
  1607. error_log('$index '.$index);
  1608. error_log('In learnpath::first() - No last item seen');
  1609. error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
  1610. }
  1611. }
  1612. if ($this->debug > 2) {
  1613. error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
  1614. }
  1615. }
  1616. /**
  1617. * Gets the js library from the database.
  1618. *
  1619. * @return string The name of the javascript library to be used
  1620. */
  1621. public function get_js_lib()
  1622. {
  1623. $lib = '';
  1624. if (!empty($this->js_lib)) {
  1625. $lib = $this->js_lib;
  1626. }
  1627. return $lib;
  1628. }
  1629. /**
  1630. * Gets the learnpath database ID.
  1631. *
  1632. * @return int Learnpath ID in the lp table
  1633. */
  1634. public function get_id()
  1635. {
  1636. if (!empty($this->lp_id)) {
  1637. return (int) $this->lp_id;
  1638. }
  1639. return 0;
  1640. }
  1641. /**
  1642. * Gets the last element URL.
  1643. *
  1644. * @return string URL to load into the viewer
  1645. */
  1646. public function get_last()
  1647. {
  1648. // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
  1649. if (count($this->ordered_items) > 0) {
  1650. $this->index = count($this->ordered_items) - 1;
  1651. return $this->ordered_items[$this->index];
  1652. }
  1653. return false;
  1654. }
  1655. /**
  1656. * Get the last element in the first level.
  1657. * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
  1658. *
  1659. * @return mixed
  1660. */
  1661. public function getLastInFirstLevel()
  1662. {
  1663. try {
  1664. $lastId = Database::getManager()
  1665. ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
  1666. WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
  1667. ->setMaxResults(1)
  1668. ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
  1669. ->getSingleScalarResult();
  1670. return $lastId;
  1671. } catch (Exception $exception) {
  1672. return 0;
  1673. }
  1674. }
  1675. /**
  1676. * Gets the navigation bar for the learnpath display screen.
  1677. *
  1678. * @param string $barId
  1679. *
  1680. * @return string The HTML string to use as a navigation bar
  1681. */
  1682. public function get_navigation_bar($barId = '')
  1683. {
  1684. if (empty($barId)) {
  1685. $barId = 'control-top';
  1686. }
  1687. $lpId = $this->lp_id;
  1688. $mycurrentitemid = $this->get_current_item_id();
  1689. $reportingText = get_lang('Reporting');
  1690. $previousText = get_lang('ScormPrevious');
  1691. $nextText = get_lang('ScormNext');
  1692. $fullScreenText = get_lang('ScormExitFullScreen');
  1693. $settings = api_get_configuration_value('lp_view_settings');
  1694. $display = isset($settings['display']) ? $settings['display'] : false;
  1695. $reportingIcon = '
  1696. <a class="icon-toolbar"
  1697. id="stats_link"
  1698. href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
  1699. onclick="window.parent.API.save_asset(); return true;"
  1700. target="content_name" title="'.$reportingText.'">
  1701. <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
  1702. </a>';
  1703. if (!empty($display)) {
  1704. $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
  1705. if ($showReporting === false) {
  1706. $reportingIcon = '';
  1707. }
  1708. }
  1709. $hideArrows = false;
  1710. if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
  1711. $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
  1712. }
  1713. $previousIcon = '';
  1714. $nextIcon = '';
  1715. if ($hideArrows === false) {
  1716. $previousIcon = '
  1717. <a class="icon-toolbar" id="scorm-previous" href="#"
  1718. onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
  1719. <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
  1720. </a>';
  1721. $nextIcon = '
  1722. <a class="icon-toolbar" id="scorm-next" href="#"
  1723. onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
  1724. <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
  1725. </a>';
  1726. }
  1727. if ($this->mode === 'fullscreen') {
  1728. $navbar = '
  1729. <span id="'.$barId.'" class="buttons">
  1730. '.$reportingIcon.'
  1731. '.$previousIcon.'
  1732. '.$nextIcon.'
  1733. <a class="icon-toolbar" id="view-embedded"
  1734. href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
  1735. <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
  1736. </a>
  1737. </span>';
  1738. } else {
  1739. $navbar = '
  1740. <span id="'.$barId.'" class="buttons text-right">
  1741. '.$reportingIcon.'
  1742. '.$previousIcon.'
  1743. '.$nextIcon.'
  1744. </span>';
  1745. }
  1746. return $navbar;
  1747. }
  1748. /**
  1749. * Gets the next resource in queue (url).
  1750. *
  1751. * @return string URL to load into the viewer
  1752. */
  1753. public function get_next_index()
  1754. {
  1755. // TODO
  1756. $index = $this->index;
  1757. $index++;
  1758. while (
  1759. !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
  1760. $index < $this->max_ordered_items
  1761. ) {
  1762. $index++;
  1763. if ($index == $this->max_ordered_items) {
  1764. if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
  1765. return $this->index;
  1766. }
  1767. return $index;
  1768. }
  1769. }
  1770. if (empty($this->ordered_items[$index])) {
  1771. return $this->index;
  1772. }
  1773. return $index;
  1774. }
  1775. /**
  1776. * Gets item_id for the next element.
  1777. *
  1778. * @return int Next item (DB) ID
  1779. */
  1780. public function get_next_item_id()
  1781. {
  1782. $new_index = $this->get_next_index();
  1783. if (!empty($new_index)) {
  1784. if (isset($this->ordered_items[$new_index])) {
  1785. return $this->ordered_items[$new_index];
  1786. }
  1787. }
  1788. return 0;
  1789. }
  1790. /**
  1791. * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
  1792. *
  1793. * Generally, the package provided is in the form of a zip file, so the function
  1794. * has been written to test a zip file. If not a zip, the function will return the
  1795. * default return value: ''
  1796. *
  1797. * @param string $file_path the path to the file
  1798. * @param string $file_name the original name of the file
  1799. *
  1800. * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
  1801. */
  1802. public static function get_package_type($file_path, $file_name)
  1803. {
  1804. // Get name of the zip file without the extension.
  1805. $file_info = pathinfo($file_name);
  1806. $extension = $file_info['extension']; // Extension only.
  1807. if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
  1808. 'dll',
  1809. 'exe',
  1810. ])) {
  1811. return 'oogie';
  1812. }
  1813. if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
  1814. 'dll',
  1815. 'exe',
  1816. ])) {
  1817. return 'woogie';
  1818. }
  1819. $zipFile = new PclZip($file_path);
  1820. // Check the zip content (real size and file extension).
  1821. $zipContentArray = $zipFile->listContent();
  1822. $package_type = '';
  1823. $manifest = '';
  1824. $aicc_match_crs = 0;
  1825. $aicc_match_au = 0;
  1826. $aicc_match_des = 0;
  1827. $aicc_match_cst = 0;
  1828. // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
  1829. if (is_array($zipContentArray) && count($zipContentArray) > 0) {
  1830. foreach ($zipContentArray as $thisContent) {
  1831. if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
  1832. // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
  1833. } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
  1834. $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
  1835. $package_type = 'scorm';
  1836. break; // Exit the foreach loop.
  1837. } elseif (
  1838. preg_match('/aicc\//i', $thisContent['filename']) ||
  1839. in_array(
  1840. strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
  1841. ['crs', 'au', 'des', 'cst']
  1842. )
  1843. ) {
  1844. $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
  1845. switch ($ext) {
  1846. case 'crs':
  1847. $aicc_match_crs = 1;
  1848. break;
  1849. case 'au':
  1850. $aicc_match_au = 1;
  1851. break;
  1852. case 'des':
  1853. $aicc_match_des = 1;
  1854. break;
  1855. case 'cst':
  1856. $aicc_match_cst = 1;
  1857. break;
  1858. default:
  1859. break;
  1860. }
  1861. //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
  1862. } else {
  1863. $package_type = '';
  1864. }
  1865. }
  1866. }
  1867. if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
  1868. // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
  1869. $package_type = 'aicc';
  1870. }
  1871. // Try with chamilo course builder
  1872. if (empty($package_type)) {
  1873. $package_type = 'chamilo';
  1874. }
  1875. return $package_type;
  1876. }
  1877. /**
  1878. * Gets the previous resource in queue (url). Also initialises time values for this viewing.
  1879. *
  1880. * @return string URL to load into the viewer
  1881. */
  1882. public function get_previous_index()
  1883. {
  1884. $index = $this->index;
  1885. if (isset($this->ordered_items[$index - 1])) {
  1886. $index--;
  1887. while (isset($this->ordered_items[$index]) &&
  1888. ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
  1889. ) {
  1890. $index--;
  1891. if ($index < 0) {
  1892. return $this->index;
  1893. }
  1894. }
  1895. }
  1896. return $index;
  1897. }
  1898. /**
  1899. * Gets item_id for the next element.
  1900. *
  1901. * @return int Previous item (DB) ID
  1902. */
  1903. public function get_previous_item_id()
  1904. {
  1905. $index = $this->get_previous_index();
  1906. return $this->ordered_items[$index];
  1907. }
  1908. /**
  1909. * Returns the HTML necessary to print a mediaplayer block inside a page.
  1910. *
  1911. * @param int $lpItemId
  1912. * @param string $autostart
  1913. *
  1914. * @return string The mediaplayer HTML
  1915. */
  1916. public function get_mediaplayer($lpItemId, $autostart = 'true')
  1917. {
  1918. $course_id = api_get_course_int_id();
  1919. $_course = api_get_course_info();
  1920. if (empty($_course)) {
  1921. return '';
  1922. }
  1923. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  1924. $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  1925. $lpItemId = (int) $lpItemId;
  1926. /** @var learnpathItem $item */
  1927. $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
  1928. $itemViewId = 0;
  1929. if ($item) {
  1930. $itemViewId = (int) $item->db_item_view_id;
  1931. }
  1932. // Getting all the information about the item.
  1933. $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
  1934. FROM $tbl_lp_item as lpi
  1935. INNER JOIN $tbl_lp_item_view as lp_view
  1936. ON (lpi.iid = lp_view.lp_item_id)
  1937. WHERE
  1938. lp_view.iid = $itemViewId AND
  1939. lpi.iid = $lpItemId AND
  1940. lp_view.c_id = $course_id";
  1941. $result = Database::query($sql);
  1942. $row = Database::fetch_assoc($result);
  1943. $output = '';
  1944. if (!empty($row['audio'])) {
  1945. $list = $_SESSION['oLP']->get_toc();
  1946. switch ($row['item_type']) {
  1947. case 'quiz':
  1948. $type_quiz = false;
  1949. foreach ($list as $toc) {
  1950. if ($toc['id'] == $_SESSION['oLP']->current) {
  1951. $type_quiz = true;
  1952. }
  1953. }
  1954. if ($type_quiz) {
  1955. if ($_SESSION['oLP']->prevent_reinit == 1) {
  1956. $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
  1957. } else {
  1958. $autostart_audio = $autostart;
  1959. }
  1960. }
  1961. break;
  1962. case TOOL_READOUT_TEXT:;
  1963. $autostart_audio = 'false';
  1964. break;
  1965. default:
  1966. $autostart_audio = 'true';
  1967. }
  1968. $courseInfo = api_get_course_info();
  1969. $audio = $row['audio'];
  1970. $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
  1971. $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
  1972. if (!file_exists($file)) {
  1973. $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
  1974. $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
  1975. $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
  1976. }
  1977. $player = Display::getMediaPlayer(
  1978. $file,
  1979. [
  1980. 'id' => 'lp_audio_media_player',
  1981. 'url' => $url,
  1982. 'autoplay' => $autostart_audio,
  1983. 'width' => '100%',
  1984. ]
  1985. );
  1986. // The mp3 player.
  1987. $output = '<div id="container">';
  1988. $output .= $player;
  1989. $output .= '</div>';
  1990. }
  1991. return $output;
  1992. }
  1993. /**
  1994. * @param int $studentId
  1995. * @param int $prerequisite
  1996. * @param array $courseInfo
  1997. * @param int $sessionId
  1998. *
  1999. * @return bool
  2000. */
  2001. public static function isBlockedByPrerequisite(
  2002. $studentId,
  2003. $prerequisite,
  2004. $courseInfo,
  2005. $sessionId
  2006. ) {
  2007. if (empty($courseInfo)) {
  2008. return false;
  2009. }
  2010. $courseId = $courseInfo['real_id'];
  2011. $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
  2012. if ($allow) {
  2013. if (api_is_allowed_to_edit() ||
  2014. api_is_platform_admin(true) ||
  2015. api_is_drh() ||
  2016. api_is_coach($sessionId, $courseId, false)
  2017. ) {
  2018. return false;
  2019. }
  2020. }
  2021. $isBlocked = false;
  2022. if (!empty($prerequisite)) {
  2023. $progress = self::getProgress(
  2024. $prerequisite,
  2025. $studentId,
  2026. $courseId,
  2027. $sessionId
  2028. );
  2029. if ($progress < 100) {
  2030. $isBlocked = true;
  2031. }
  2032. if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
  2033. // Block if it does not exceed minimum time
  2034. // Minimum time (in minutes) to pass the learning path
  2035. $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
  2036. if ($accumulateWorkTime > 0) {
  2037. // Total time in course (sum of times in learning paths from course)
  2038. $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
  2039. // Connect with the plugin_licences_course_session table
  2040. // which indicates what percentage of the time applies
  2041. // Minimum connection percentage
  2042. $perc = 100;
  2043. // Time from the course
  2044. $tc = $accumulateWorkTimeTotal;
  2045. // Percentage of the learning paths
  2046. $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
  2047. // Minimum time for each learning path
  2048. $accumulateWorkTime = ($pl * $tc * $perc / 100);
  2049. // Spent time (in seconds) so far in the learning path
  2050. $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
  2051. $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
  2052. if ($lpTime < ($accumulateWorkTime * 60)) {
  2053. $isBlocked = true;
  2054. }
  2055. }
  2056. }
  2057. }
  2058. return $isBlocked;
  2059. }
  2060. /**
  2061. * Checks if the learning path is visible for student after the progress
  2062. * of its prerequisite is completed, considering the time availability and
  2063. * the LP visibility.
  2064. *
  2065. * @param int $lp_id
  2066. * @param int $student_id
  2067. * @param array $courseInfo
  2068. * @param int $sessionId
  2069. *
  2070. * @return bool
  2071. */
  2072. public static function is_lp_visible_for_student(
  2073. $lp_id,
  2074. $student_id,
  2075. $courseInfo = [],
  2076. $sessionId = 0
  2077. ) {
  2078. $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
  2079. $lp_id = (int) $lp_id;
  2080. $sessionId = (int) $sessionId;
  2081. if (empty($courseInfo)) {
  2082. return false;
  2083. }
  2084. if (empty($sessionId)) {
  2085. $sessionId = api_get_session_id();
  2086. }
  2087. $courseId = $courseInfo['real_id'];
  2088. $itemInfo = api_get_item_property_info(
  2089. $courseId,
  2090. TOOL_LEARNPATH,
  2091. $lp_id,
  2092. $sessionId
  2093. );
  2094. // If the item was deleted.
  2095. if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
  2096. return false;
  2097. }
  2098. // @todo remove this query and load the row info as a parameter
  2099. $table = Database::get_course_table(TABLE_LP_MAIN);
  2100. // Get current prerequisite
  2101. $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
  2102. FROM $table
  2103. WHERE iid = $lp_id";
  2104. $rs = Database::query($sql);
  2105. $now = time();
  2106. if (Database::num_rows($rs) > 0) {
  2107. $row = Database::fetch_array($rs, 'ASSOC');
  2108. if (!empty($row['category_id'])) {
  2109. $em = Database::getManager();
  2110. $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
  2111. if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
  2112. return false;
  2113. }
  2114. }
  2115. $prerequisite = $row['prerequisite'];
  2116. $is_visible = true;
  2117. $isBlocked = self::isBlockedByPrerequisite(
  2118. $student_id,
  2119. $prerequisite,
  2120. $courseInfo,
  2121. $sessionId
  2122. );
  2123. if ($isBlocked) {
  2124. $is_visible = false;
  2125. }
  2126. // Also check the time availability of the LP
  2127. if ($is_visible) {
  2128. // Adding visibility restrictions
  2129. if (!empty($row['publicated_on'])) {
  2130. if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
  2131. $is_visible = false;
  2132. }
  2133. }
  2134. // Blocking empty start times see BT#2800
  2135. global $_custom;
  2136. if (isset($_custom['lps_hidden_when_no_start_date']) &&
  2137. $_custom['lps_hidden_when_no_start_date']
  2138. ) {
  2139. if (empty($row['publicated_on'])) {
  2140. $is_visible = false;
  2141. }
  2142. }
  2143. if (!empty($row['expired_on'])) {
  2144. if ($now > api_strtotime($row['expired_on'], 'UTC')) {
  2145. $is_visible = false;
  2146. }
  2147. }
  2148. }
  2149. if ($is_visible) {
  2150. $subscriptionSettings = self::getSubscriptionSettings();
  2151. // Check if the subscription users/group to a LP is ON
  2152. if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
  2153. $subscriptionSettings['allow_add_users_to_lp'] === true
  2154. ) {
  2155. // Try group
  2156. $is_visible = false;
  2157. // Checking only the user visibility
  2158. $userVisibility = api_get_item_visibility(
  2159. $courseInfo,
  2160. 'learnpath',
  2161. $row['id'],
  2162. $sessionId,
  2163. $student_id,
  2164. 'LearnpathSubscription'
  2165. );
  2166. if ($userVisibility == 1) {
  2167. $is_visible = true;
  2168. } else {
  2169. $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
  2170. if (!empty($userGroups)) {
  2171. foreach ($userGroups as $groupInfo) {
  2172. $groupId = $groupInfo['iid'];
  2173. $userVisibility = api_get_item_visibility(
  2174. $courseInfo,
  2175. 'learnpath',
  2176. $row['id'],
  2177. $sessionId,
  2178. null,
  2179. 'LearnpathSubscription',
  2180. $groupId
  2181. );
  2182. if ($userVisibility == 1) {
  2183. $is_visible = true;
  2184. break;
  2185. }
  2186. }
  2187. }
  2188. }
  2189. }
  2190. }
  2191. return $is_visible;
  2192. }
  2193. return false;
  2194. }
  2195. /**
  2196. * @param int $lpId
  2197. * @param int $userId
  2198. * @param int $courseId
  2199. * @param int $sessionId
  2200. *
  2201. * @return int
  2202. */
  2203. public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
  2204. {
  2205. $lpId = (int) $lpId;
  2206. $userId = (int) $userId;
  2207. $courseId = (int) $courseId;
  2208. $sessionId = (int) $sessionId;
  2209. $sessionCondition = api_get_session_condition($sessionId);
  2210. $table = Database::get_course_table(TABLE_LP_VIEW);
  2211. $sql = "SELECT progress FROM $table
  2212. WHERE
  2213. c_id = $courseId AND
  2214. lp_id = $lpId AND
  2215. user_id = $userId $sessionCondition ";
  2216. $res = Database::query($sql);
  2217. $progress = 0;
  2218. if (Database::num_rows($res) > 0) {
  2219. $row = Database::fetch_array($res);
  2220. $progress = (int) $row['progress'];
  2221. }
  2222. return $progress;
  2223. }
  2224. /**
  2225. * @param array $lpList
  2226. * @param int $userId
  2227. * @param int $courseId
  2228. * @param int $sessionId
  2229. *
  2230. * @return array
  2231. */
  2232. public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
  2233. {
  2234. $lpList = array_map('intval', $lpList);
  2235. if (empty($lpList)) {
  2236. return [];
  2237. }
  2238. $lpList = implode("','", $lpList);
  2239. $userId = (int) $userId;
  2240. $courseId = (int) $courseId;
  2241. $sessionId = (int) $sessionId;
  2242. $sessionCondition = api_get_session_condition($sessionId);
  2243. $table = Database::get_course_table(TABLE_LP_VIEW);
  2244. $sql = "SELECT lp_id, progress FROM $table
  2245. WHERE
  2246. c_id = $courseId AND
  2247. lp_id IN ('".$lpList."') AND
  2248. user_id = $userId $sessionCondition ";
  2249. $res = Database::query($sql);
  2250. if (Database::num_rows($res) > 0) {
  2251. $list = [];
  2252. while ($row = Database::fetch_array($res)) {
  2253. $list[$row['lp_id']] = $row['progress'];
  2254. }
  2255. return $list;
  2256. }
  2257. return [];
  2258. }
  2259. /**
  2260. * Displays a progress bar
  2261. * completed so far.
  2262. *
  2263. * @param int $percentage Progress value to display
  2264. * @param string $text_add Text to display near the progress value
  2265. *
  2266. * @return string HTML string containing the progress bar
  2267. */
  2268. public static function get_progress_bar($percentage = -1, $text_add = '')
  2269. {
  2270. $text = $percentage.$text_add;
  2271. $output = '<div class="progress">
  2272. <div id="progress_bar_value"
  2273. class="progress-bar progress-bar-success" role="progressbar"
  2274. aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
  2275. '.$text.'
  2276. </div>
  2277. </div>';
  2278. return $output;
  2279. }
  2280. /**
  2281. * @param string $mode can be '%' or 'abs'
  2282. * otherwise this value will be used $this->progress_bar_mode
  2283. *
  2284. * @return string
  2285. */
  2286. public function getProgressBar($mode = null)
  2287. {
  2288. list($percentage, $text_add) = $this->get_progress_bar_text($mode);
  2289. return self::get_progress_bar($percentage, $text_add);
  2290. }
  2291. /**
  2292. * Gets the progress bar info to display inside the progress bar.
  2293. * Also used by scorm_api.php.
  2294. *
  2295. * @param string $mode Mode of display (can be '%' or 'abs').abs means
  2296. * we display a number of completed elements per total elements
  2297. * @param int $add Additional steps to fake as completed
  2298. *
  2299. * @return array Percentage or number and symbol (% or /xx)
  2300. */
  2301. public function get_progress_bar_text($mode = '', $add = 0)
  2302. {
  2303. if (empty($mode)) {
  2304. $mode = $this->progress_bar_mode;
  2305. }
  2306. $total_items = $this->getTotalItemsCountWithoutDirs();
  2307. $completeItems = $this->get_complete_items_count();
  2308. if ($add != 0) {
  2309. $completeItems += $add;
  2310. }
  2311. $text = '';
  2312. if ($completeItems > $total_items) {
  2313. $completeItems = $total_items;
  2314. }
  2315. $percentage = 0;
  2316. if ($mode == '%') {
  2317. if ($total_items > 0) {
  2318. $percentage = ((float) $completeItems / (float) $total_items) * 100;
  2319. }
  2320. $percentage = number_format($percentage, 0);
  2321. $text = '%';
  2322. } elseif ($mode === 'abs') {
  2323. $percentage = $completeItems;
  2324. $text = '/'.$total_items;
  2325. }
  2326. return [
  2327. $percentage,
  2328. $text,
  2329. ];
  2330. }
  2331. /**
  2332. * Gets the progress bar mode.
  2333. *
  2334. * @return string The progress bar mode attribute
  2335. */
  2336. public function get_progress_bar_mode()
  2337. {
  2338. if (!empty($this->progress_bar_mode)) {
  2339. return $this->progress_bar_mode;
  2340. }
  2341. return '%';
  2342. }
  2343. /**
  2344. * Gets the learnpath theme (remote or local).
  2345. *
  2346. * @return string Learnpath theme
  2347. */
  2348. public function get_theme()
  2349. {
  2350. if (!empty($this->theme)) {
  2351. return $this->theme;
  2352. }
  2353. return '';
  2354. }
  2355. /**
  2356. * Gets the learnpath session id.
  2357. *
  2358. * @return int
  2359. */
  2360. public function get_lp_session_id()
  2361. {
  2362. if (!empty($this->lp_session_id)) {
  2363. return (int) $this->lp_session_id;
  2364. }
  2365. return 0;
  2366. }
  2367. /**
  2368. * Gets the learnpath image.
  2369. *
  2370. * @return string Web URL of the LP image
  2371. */
  2372. public function get_preview_image()
  2373. {
  2374. if (!empty($this->preview_image)) {
  2375. return $this->preview_image;
  2376. }
  2377. return '';
  2378. }
  2379. /**
  2380. * @param string $size
  2381. * @param string $path_type
  2382. *
  2383. * @return bool|string
  2384. */
  2385. public function get_preview_image_path($size = null, $path_type = 'web')
  2386. {
  2387. $preview_image = $this->get_preview_image();
  2388. if (isset($preview_image) && !empty($preview_image)) {
  2389. $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
  2390. $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
  2391. if (isset($size)) {
  2392. $info = pathinfo($preview_image);
  2393. $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
  2394. if (file_exists($image_sys_path.$image_custom_size)) {
  2395. if ($path_type == 'web') {
  2396. return $image_path.$image_custom_size;
  2397. } else {
  2398. return $image_sys_path.$image_custom_size;
  2399. }
  2400. }
  2401. } else {
  2402. if ($path_type == 'web') {
  2403. return $image_path.$preview_image;
  2404. } else {
  2405. return $image_sys_path.$preview_image;
  2406. }
  2407. }
  2408. }
  2409. return false;
  2410. }
  2411. /**
  2412. * Gets the learnpath author.
  2413. *
  2414. * @return string LP's author
  2415. */
  2416. public function get_author()
  2417. {
  2418. if (!empty($this->author)) {
  2419. return $this->author;
  2420. }
  2421. return '';
  2422. }
  2423. /**
  2424. * Gets hide table of contents.
  2425. *
  2426. * @return int
  2427. */
  2428. public function getHideTableOfContents()
  2429. {
  2430. return (int) $this->hide_toc_frame;
  2431. }
  2432. /**
  2433. * Generate a new prerequisites string for a given item. If this item was a sco and
  2434. * its prerequisites were strings (instead of IDs), then transform those strings into
  2435. * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
  2436. * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
  2437. * same rule as the scormExport() method.
  2438. *
  2439. * @param int $item_id Item ID
  2440. *
  2441. * @return string Prerequisites string ready for the export as SCORM
  2442. */
  2443. public function get_scorm_prereq_string($item_id)
  2444. {
  2445. if ($this->debug > 0) {
  2446. error_log('In learnpath::get_scorm_prereq_string()');
  2447. }
  2448. if (!is_object($this->items[$item_id])) {
  2449. return false;
  2450. }
  2451. /** @var learnpathItem $oItem */
  2452. $oItem = $this->items[$item_id];
  2453. $prereq = $oItem->get_prereq_string();
  2454. if (empty($prereq)) {
  2455. return '';
  2456. }
  2457. if (preg_match('/^\d+$/', $prereq) &&
  2458. isset($this->items[$prereq]) &&
  2459. is_object($this->items[$prereq])
  2460. ) {
  2461. // If the prerequisite is a simple integer ID and this ID exists as an item ID,
  2462. // then simply return it (with the ITEM_ prefix).
  2463. //return 'ITEM_' . $prereq;
  2464. return $this->items[$prereq]->ref;
  2465. } else {
  2466. if (isset($this->refs_list[$prereq])) {
  2467. // It's a simple string item from which the ID can be found in the refs list,
  2468. // so we can transform it directly to an ID for export.
  2469. return $this->items[$this->refs_list[$prereq]]->ref;
  2470. } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
  2471. return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
  2472. } else {
  2473. // The last case, if it's a complex form, then find all the IDs (SCORM strings)
  2474. // and replace them, one by one, by the internal IDs (chamilo db)
  2475. // TODO: Modify the '*' replacement to replace the multiplier in front of it
  2476. // by a space as well.
  2477. $find = [
  2478. '&',
  2479. '|',
  2480. '~',
  2481. '=',
  2482. '<>',
  2483. '{',
  2484. '}',
  2485. '*',
  2486. '(',
  2487. ')',
  2488. ];
  2489. $replace = [
  2490. ' ',
  2491. ' ',
  2492. ' ',
  2493. ' ',
  2494. ' ',
  2495. ' ',
  2496. ' ',
  2497. ' ',
  2498. ' ',
  2499. ' ',
  2500. ];
  2501. $prereq_mod = str_replace($find, $replace, $prereq);
  2502. $ids = explode(' ', $prereq_mod);
  2503. foreach ($ids as $id) {
  2504. $id = trim($id);
  2505. if (isset($this->refs_list[$id])) {
  2506. $prereq = preg_replace(
  2507. '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
  2508. 'ITEM_'.$this->refs_list[$id],
  2509. $prereq
  2510. );
  2511. }
  2512. }
  2513. return $prereq;
  2514. }
  2515. }
  2516. }
  2517. /**
  2518. * Returns the XML DOM document's node.
  2519. *
  2520. * @param resource $children Reference to a list of objects to search for the given ITEM_*
  2521. * @param string $id The identifier to look for
  2522. *
  2523. * @return mixed The reference to the element found with that identifier. False if not found
  2524. */
  2525. public function get_scorm_xml_node(&$children, $id)
  2526. {
  2527. for ($i = 0; $i < $children->length; $i++) {
  2528. $item_temp = $children->item($i);
  2529. if ($item_temp->nodeName == 'item') {
  2530. if ($item_temp->getAttribute('identifier') == $id) {
  2531. return $item_temp;
  2532. }
  2533. }
  2534. $subchildren = $item_temp->childNodes;
  2535. if ($subchildren && $subchildren->length > 0) {
  2536. $val = $this->get_scorm_xml_node($subchildren, $id);
  2537. if (is_object($val)) {
  2538. return $val;
  2539. }
  2540. }
  2541. }
  2542. return false;
  2543. }
  2544. /**
  2545. * Gets the status list for all LP's items.
  2546. *
  2547. * @return array Array of [index] => [item ID => current status]
  2548. */
  2549. public function get_items_status_list()
  2550. {
  2551. $list = [];
  2552. foreach ($this->ordered_items as $item_id) {
  2553. $list[] = [
  2554. $item_id => $this->items[$item_id]->get_status(),
  2555. ];
  2556. }
  2557. return $list;
  2558. }
  2559. /**
  2560. * Return the number of interactions for the given learnpath Item View ID.
  2561. * This method can be used as static.
  2562. *
  2563. * @param int $lp_iv_id Item View ID
  2564. * @param int $course_id course id
  2565. *
  2566. * @return int
  2567. */
  2568. public static function get_interactions_count_from_db($lp_iv_id, $course_id)
  2569. {
  2570. $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
  2571. $lp_iv_id = (int) $lp_iv_id;
  2572. $course_id = (int) $course_id;
  2573. $sql = "SELECT count(*) FROM $table
  2574. WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
  2575. $res = Database::query($sql);
  2576. $num = 0;
  2577. if (Database::num_rows($res)) {
  2578. $row = Database::fetch_array($res);
  2579. $num = $row[0];
  2580. }
  2581. return $num;
  2582. }
  2583. /**
  2584. * Return the interactions as an array for the given lp_iv_id.
  2585. * This method can be used as static.
  2586. *
  2587. * @param int $lp_iv_id Learnpath Item View ID
  2588. *
  2589. * @return array
  2590. *
  2591. * @todo Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
  2592. */
  2593. public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
  2594. {
  2595. $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
  2596. $list = [];
  2597. $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
  2598. $lp_iv_id = (int) $lp_iv_id;
  2599. if (empty($lp_iv_id) || empty($course_id)) {
  2600. return [];
  2601. }
  2602. $sql = "SELECT * FROM $table
  2603. WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
  2604. ORDER BY order_id ASC";
  2605. $res = Database::query($sql);
  2606. $num = Database::num_rows($res);
  2607. if ($num > 0) {
  2608. $list[] = [
  2609. 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
  2610. 'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
  2611. 'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
  2612. 'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
  2613. 'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
  2614. 'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
  2615. 'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
  2616. 'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
  2617. 'student_response_formatted' => '',
  2618. ];
  2619. while ($row = Database::fetch_array($res)) {
  2620. $studentResponseFormatted = urldecode($row['student_response']);
  2621. $content_student_response = explode('__|', $studentResponseFormatted);
  2622. if (count($content_student_response) > 0) {
  2623. if (count($content_student_response) >= 3) {
  2624. // Pop the element off the end of array.
  2625. array_pop($content_student_response);
  2626. }
  2627. $studentResponseFormatted = implode(',', $content_student_response);
  2628. }
  2629. $list[] = [
  2630. 'order_id' => $row['order_id'] + 1,
  2631. 'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
  2632. 'type' => $row['interaction_type'],
  2633. 'time' => $row['completion_time'],
  2634. 'correct_responses' => '', // Hide correct responses from students.
  2635. 'student_response' => $row['student_response'],
  2636. 'result' => $row['result'],
  2637. 'latency' => $row['latency'],
  2638. 'student_response_formatted' => $studentResponseFormatted,
  2639. ];
  2640. }
  2641. }
  2642. return $list;
  2643. }
  2644. /**
  2645. * Return the number of objectives for the given learnpath Item View ID.
  2646. * This method can be used as static.
  2647. *
  2648. * @param int $lp_iv_id Item View ID
  2649. * @param int $course_id Course ID
  2650. *
  2651. * @return int Number of objectives
  2652. */
  2653. public static function get_objectives_count_from_db($lp_iv_id, $course_id)
  2654. {
  2655. $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
  2656. $course_id = (int) $course_id;
  2657. $lp_iv_id = (int) $lp_iv_id;
  2658. $sql = "SELECT count(*) FROM $table
  2659. WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
  2660. //@todo seems that this always returns 0
  2661. $res = Database::query($sql);
  2662. $num = 0;
  2663. if (Database::num_rows($res)) {
  2664. $row = Database::fetch_array($res);
  2665. $num = $row[0];
  2666. }
  2667. return $num;
  2668. }
  2669. /**
  2670. * Return the objectives as an array for the given lp_iv_id.
  2671. * This method can be used as static.
  2672. *
  2673. * @param int $lpItemViewId Learnpath Item View ID
  2674. * @param int $course_id
  2675. *
  2676. * @return array
  2677. *
  2678. * @todo Translate labels
  2679. */
  2680. public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
  2681. {
  2682. $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
  2683. $lpItemViewId = (int) $lpItemViewId;
  2684. if (empty($course_id) || empty($lpItemViewId)) {
  2685. return [];
  2686. }
  2687. $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
  2688. $sql = "SELECT * FROM $table
  2689. WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
  2690. ORDER BY order_id ASC";
  2691. $res = Database::query($sql);
  2692. $num = Database::num_rows($res);
  2693. $list = [];
  2694. if ($num > 0) {
  2695. $list[] = [
  2696. 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
  2697. 'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
  2698. 'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
  2699. 'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
  2700. 'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
  2701. 'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
  2702. ];
  2703. while ($row = Database::fetch_array($res)) {
  2704. $list[] = [
  2705. 'order_id' => $row['order_id'] + 1,
  2706. 'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
  2707. 'score_raw' => $row['score_raw'],
  2708. 'score_max' => $row['score_max'],
  2709. 'score_min' => $row['score_min'],
  2710. 'status' => $row['status'],
  2711. ];
  2712. }
  2713. }
  2714. return $list;
  2715. }
  2716. /**
  2717. * Generate and return the table of contents for this learnpath. The (flat) table returned can be
  2718. * used by get_html_toc() to be ready to display.
  2719. *
  2720. * @return array TOC as a table with 4 elements per row: title, link, status and level
  2721. */
  2722. public function get_toc()
  2723. {
  2724. $toc = [];
  2725. foreach ($this->ordered_items as $item_id) {
  2726. // TODO: Change this link generation and use new function instead.
  2727. $toc[] = [
  2728. 'id' => $item_id,
  2729. 'title' => $this->items[$item_id]->get_title(),
  2730. 'status' => $this->items[$item_id]->get_status(),
  2731. 'level' => $this->items[$item_id]->get_level(),
  2732. 'type' => $this->items[$item_id]->get_type(),
  2733. 'description' => $this->items[$item_id]->get_description(),
  2734. 'path' => $this->items[$item_id]->get_path(),
  2735. 'parent' => $this->items[$item_id]->get_parent(),
  2736. ];
  2737. }
  2738. return $toc;
  2739. }
  2740. /**
  2741. * Generate and return the table of contents for this learnpath. The JS
  2742. * table returned is used inside of scorm_api.php.
  2743. *
  2744. * @param string $varname
  2745. *
  2746. * @return string A JS array variable construction
  2747. */
  2748. public function get_items_details_as_js($varname = 'olms.lms_item_types')
  2749. {
  2750. $toc = $varname.' = new Array();';
  2751. foreach ($this->ordered_items as $item_id) {
  2752. $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
  2753. }
  2754. return $toc;
  2755. }
  2756. /**
  2757. * Gets the learning path type.
  2758. *
  2759. * @param bool $get_name Return the name? If false, return the ID. Default is false.
  2760. *
  2761. * @return mixed Type ID or name, depending on the parameter
  2762. */
  2763. public function get_type($get_name = false)
  2764. {
  2765. $res = false;
  2766. if (!empty($this->type) && (!$get_name)) {
  2767. $res = $this->type;
  2768. }
  2769. return $res;
  2770. }
  2771. /**
  2772. * Gets the learning path type as static method.
  2773. *
  2774. * @param int $lp_id
  2775. *
  2776. * @return mixed Type ID or name, depending on the parameter
  2777. */
  2778. public static function get_type_static($lp_id = 0)
  2779. {
  2780. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  2781. $lp_id = (int) $lp_id;
  2782. $sql = "SELECT lp_type FROM $tbl_lp
  2783. WHERE iid = $lp_id";
  2784. $res = Database::query($sql);
  2785. if ($res === false) {
  2786. return null;
  2787. }
  2788. if (Database::num_rows($res) <= 0) {
  2789. return null;
  2790. }
  2791. $row = Database::fetch_array($res);
  2792. return $row['lp_type'];
  2793. }
  2794. /**
  2795. * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
  2796. * This method can be used as abstract and is recursive.
  2797. *
  2798. * @param int $lp Learnpath ID
  2799. * @param int $parent Parent ID of the items to look for
  2800. * @param int $course_id
  2801. *
  2802. * @return array Ordered list of item IDs (empty array on error)
  2803. */
  2804. public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
  2805. {
  2806. if (empty($course_id)) {
  2807. $course_id = api_get_course_int_id();
  2808. } else {
  2809. $course_id = (int) $course_id;
  2810. }
  2811. $list = [];
  2812. if (empty($lp)) {
  2813. return $list;
  2814. }
  2815. $lp = (int) $lp;
  2816. $parent = (int) $parent;
  2817. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  2818. $sql = "SELECT iid FROM $tbl_lp_item
  2819. WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
  2820. ORDER BY display_order";
  2821. $res = Database::query($sql);
  2822. while ($row = Database::fetch_array($res)) {
  2823. $sublist = self::get_flat_ordered_items_list(
  2824. $lp,
  2825. $row['iid'],
  2826. $course_id
  2827. );
  2828. $list[] = $row['iid'];
  2829. foreach ($sublist as $item) {
  2830. $list[] = $item;
  2831. }
  2832. }
  2833. return $list;
  2834. }
  2835. /**
  2836. * @return array
  2837. */
  2838. public static function getChapterTypes()
  2839. {
  2840. return [
  2841. 'dir',
  2842. ];
  2843. }
  2844. /**
  2845. * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
  2846. *
  2847. * @param $tree
  2848. *
  2849. * @return array HTML TOC ready to display
  2850. */
  2851. public function getParentToc($tree)
  2852. {
  2853. if (empty($tree)) {
  2854. $tree = $this->get_toc();
  2855. }
  2856. $dirTypes = self::getChapterTypes();
  2857. $myCurrentId = $this->get_current_item_id();
  2858. $listParent = [];
  2859. $listChildren = [];
  2860. $listNotParent = [];
  2861. $list = [];
  2862. foreach ($tree as $subtree) {
  2863. if (in_array($subtree['type'], $dirTypes)) {
  2864. $listChildren = $this->getChildrenToc($tree, $subtree['id']);
  2865. $subtree['children'] = $listChildren;
  2866. if (!empty($subtree['children'])) {
  2867. foreach ($subtree['children'] as $subItem) {
  2868. if ($subItem['id'] == $this->current) {
  2869. $subtree['parent_current'] = 'in';
  2870. $subtree['current'] = 'on';
  2871. }
  2872. }
  2873. }
  2874. $listParent[] = $subtree;
  2875. }
  2876. if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
  2877. $classStatus = [
  2878. 'not attempted' => 'scorm_not_attempted',
  2879. 'incomplete' => 'scorm_not_attempted',
  2880. 'failed' => 'scorm_failed',
  2881. 'completed' => 'scorm_completed',
  2882. 'passed' => 'scorm_completed',
  2883. 'succeeded' => 'scorm_completed',
  2884. 'browsed' => 'scorm_completed',
  2885. ];
  2886. if (isset($classStatus[$subtree['status']])) {
  2887. $cssStatus = $classStatus[$subtree['status']];
  2888. }
  2889. $title = Security::remove_XSS($subtree['title']);
  2890. unset($subtree['title']);
  2891. if (empty($title)) {
  2892. $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
  2893. }
  2894. $classStyle = null;
  2895. if ($subtree['id'] == $this->current) {
  2896. $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
  2897. } elseif (!in_array($subtree['type'], $dirTypes)) {
  2898. $classStyle = 'scorm_item_normal '.$classStyle.' ';
  2899. }
  2900. $subtree['title'] = $title;
  2901. $subtree['class'] = $classStyle.' '.$cssStatus;
  2902. $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
  2903. $subtree['current_id'] = $myCurrentId;
  2904. $listNotParent[] = $subtree;
  2905. }
  2906. }
  2907. $list['are_parents'] = $listParent;
  2908. $list['not_parents'] = $listNotParent;
  2909. return $list;
  2910. }
  2911. /**
  2912. * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
  2913. *
  2914. * @param array $tree
  2915. * @param int $id
  2916. * @param bool $parent
  2917. *
  2918. * @return array HTML TOC ready to display
  2919. */
  2920. public function getChildrenToc($tree, $id, $parent = true)
  2921. {
  2922. if (empty($tree)) {
  2923. $tree = $this->get_toc();
  2924. }
  2925. $dirTypes = self::getChapterTypes();
  2926. $currentItemId = $this->get_current_item_id();
  2927. $list = [];
  2928. $classStatus = [
  2929. 'not attempted' => 'scorm_not_attempted',
  2930. 'incomplete' => 'scorm_not_attempted',
  2931. 'failed' => 'scorm_failed',
  2932. 'completed' => 'scorm_completed',
  2933. 'passed' => 'scorm_completed',
  2934. 'succeeded' => 'scorm_completed',
  2935. 'browsed' => 'scorm_completed',
  2936. ];
  2937. foreach ($tree as $subtree) {
  2938. $subtree['tree'] = null;
  2939. if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
  2940. if ($subtree['id'] == $this->current) {
  2941. $subtree['current'] = 'active';
  2942. } else {
  2943. $subtree['current'] = null;
  2944. }
  2945. if (isset($classStatus[$subtree['status']])) {
  2946. $cssStatus = $classStatus[$subtree['status']];
  2947. }
  2948. $title = Security::remove_XSS($subtree['title']);
  2949. unset($subtree['title']);
  2950. if (empty($title)) {
  2951. $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
  2952. }
  2953. $classStyle = null;
  2954. if ($subtree['id'] == $this->current) {
  2955. $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
  2956. } elseif (!in_array($subtree['type'], $dirTypes)) {
  2957. $classStyle = 'scorm_item_normal '.$classStyle.' ';
  2958. }
  2959. if (in_array($subtree['type'], $dirTypes)) {
  2960. $subtree['title'] = stripslashes($title);
  2961. } else {
  2962. $subtree['title'] = $title;
  2963. $subtree['class'] = $classStyle.' '.$cssStatus;
  2964. $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
  2965. $subtree['current_id'] = $currentItemId;
  2966. }
  2967. $list[] = $subtree;
  2968. }
  2969. }
  2970. return $list;
  2971. }
  2972. /**
  2973. * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
  2974. *
  2975. * @param array $toc_list
  2976. *
  2977. * @return array HTML TOC ready to display
  2978. */
  2979. public function getListArrayToc($toc_list = [])
  2980. {
  2981. if (empty($toc_list)) {
  2982. $toc_list = $this->get_toc();
  2983. }
  2984. // Temporary variables.
  2985. $currentItemId = $this->get_current_item_id();
  2986. $list = [];
  2987. $arrayList = [];
  2988. $classStatus = [
  2989. 'not attempted' => 'scorm_not_attempted',
  2990. 'incomplete' => 'scorm_not_attempted',
  2991. 'failed' => 'scorm_failed',
  2992. 'completed' => 'scorm_completed',
  2993. 'passed' => 'scorm_completed',
  2994. 'succeeded' => 'scorm_completed',
  2995. 'browsed' => 'scorm_completed',
  2996. ];
  2997. foreach ($toc_list as $item) {
  2998. $list['id'] = $item['id'];
  2999. $list['status'] = $item['status'];
  3000. $cssStatus = null;
  3001. if (isset($classStatus[$item['status']])) {
  3002. $cssStatus = $classStatus[$item['status']];
  3003. }
  3004. $classStyle = ' ';
  3005. $dirTypes = self::getChapterTypes();
  3006. if (in_array($item['type'], $dirTypes)) {
  3007. $classStyle = 'scorm_item_section ';
  3008. }
  3009. if ($item['id'] == $this->current) {
  3010. $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
  3011. } elseif (!in_array($item['type'], $dirTypes)) {
  3012. $classStyle = 'scorm_item_normal '.$classStyle.' ';
  3013. }
  3014. $title = $item['title'];
  3015. if (empty($title)) {
  3016. $title = self::rl_get_resource_name(
  3017. api_get_course_id(),
  3018. $this->get_id(),
  3019. $item['id']
  3020. );
  3021. }
  3022. $title = Security::remove_XSS($item['title']);
  3023. if (empty($item['description'])) {
  3024. $list['description'] = $title;
  3025. } else {
  3026. $list['description'] = $item['description'];
  3027. }
  3028. $list['class'] = $classStyle.' '.$cssStatus;
  3029. $list['level'] = $item['level'];
  3030. $list['type'] = $item['type'];
  3031. if (in_array($item['type'], $dirTypes)) {
  3032. $list['css_level'] = 'level_'.$item['level'];
  3033. } else {
  3034. $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
  3035. }
  3036. if (in_array($item['type'], $dirTypes)) {
  3037. $list['title'] = stripslashes($title);
  3038. } else {
  3039. $list['title'] = stripslashes($title);
  3040. $list['url'] = $this->get_link('http', $item['id'], $toc_list);
  3041. $list['current_id'] = $currentItemId;
  3042. }
  3043. $arrayList[] = $list;
  3044. }
  3045. return $arrayList;
  3046. }
  3047. /**
  3048. * Returns an HTML-formatted string ready to display with teacher buttons
  3049. * in LP view menu.
  3050. *
  3051. * @return string HTML TOC ready to display
  3052. */
  3053. public function get_teacher_toc_buttons()
  3054. {
  3055. $isAllow = api_is_allowed_to_edit(null, true, false, false);
  3056. $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
  3057. $html = '';
  3058. if ($isAllow && $hideIcons == false) {
  3059. if ($this->get_lp_session_id() == api_get_session_id()) {
  3060. $html .= '<div id="actions_lp" class="actions_lp"><hr>';
  3061. $html .= '<div class="btn-group">';
  3062. $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'>".
  3063. Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
  3064. $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'>".
  3065. Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
  3066. $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
  3067. Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
  3068. $html .= '</div>';
  3069. $html .= '</div>';
  3070. }
  3071. }
  3072. return $html;
  3073. }
  3074. /**
  3075. * Gets the learnpath maker name - generally the editor's name.
  3076. *
  3077. * @return string Learnpath maker name
  3078. */
  3079. public function get_maker()
  3080. {
  3081. if (!empty($this->maker)) {
  3082. return $this->maker;
  3083. }
  3084. return '';
  3085. }
  3086. /**
  3087. * Gets the learnpath name/title.
  3088. *
  3089. * @return string Learnpath name/title
  3090. */
  3091. public function get_name()
  3092. {
  3093. if (!empty($this->name)) {
  3094. return $this->name;
  3095. }
  3096. return 'N/A';
  3097. }
  3098. /**
  3099. * @return string
  3100. */
  3101. public function getNameNoTags()
  3102. {
  3103. return strip_tags($this->get_name());
  3104. }
  3105. /**
  3106. * Gets a link to the resource from the present location, depending on item ID.
  3107. *
  3108. * @param string $type Type of link expected
  3109. * @param int $item_id Learnpath item ID
  3110. * @param bool $provided_toc
  3111. *
  3112. * @return string $provided_toc Link to the lp_item resource
  3113. */
  3114. public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
  3115. {
  3116. $course_id = $this->get_course_int_id();
  3117. $item_id = (int) $item_id;
  3118. if (empty($item_id)) {
  3119. $item_id = $this->get_current_item_id();
  3120. if (empty($item_id)) {
  3121. //still empty, this means there was no item_id given and we are not in an object context or
  3122. //the object property is empty, return empty link
  3123. $this->first();
  3124. return '';
  3125. }
  3126. }
  3127. $file = '';
  3128. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  3129. $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
  3130. $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
  3131. $sql = "SELECT
  3132. l.lp_type as ltype,
  3133. l.path as lpath,
  3134. li.item_type as litype,
  3135. li.path as lipath,
  3136. li.parameters as liparams
  3137. FROM $lp_table l
  3138. INNER JOIN $lp_item_table li
  3139. ON (li.lp_id = l.iid)
  3140. WHERE
  3141. li.iid = $item_id
  3142. ";
  3143. $res = Database::query($sql);
  3144. if (Database::num_rows($res) > 0) {
  3145. $row = Database::fetch_array($res);
  3146. $lp_type = $row['ltype'];
  3147. $lp_path = $row['lpath'];
  3148. $lp_item_type = $row['litype'];
  3149. $lp_item_path = $row['lipath'];
  3150. $lp_item_params = $row['liparams'];
  3151. if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
  3152. list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
  3153. }
  3154. $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
  3155. if ($type === 'http') {
  3156. //web path
  3157. $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
  3158. } else {
  3159. $course_path = $sys_course_path; //system path
  3160. }
  3161. // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
  3162. // then change the lp type to thread it as a normal Chamilo LP not a SCO.
  3163. if (in_array(
  3164. $lp_item_type,
  3165. ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
  3166. )
  3167. ) {
  3168. $lp_type = 1;
  3169. }
  3170. // Now go through the specific cases to get the end of the path
  3171. // @todo Use constants instead of int values.
  3172. switch ($lp_type) {
  3173. case 1:
  3174. $file = self::rl_get_resource_link_for_learnpath(
  3175. $course_id,
  3176. $this->get_id(),
  3177. $item_id,
  3178. $this->get_view_id()
  3179. );
  3180. switch ($lp_item_type) {
  3181. case 'document':
  3182. // Shows a button to download the file instead of just downloading the file directly.
  3183. $documentPathInfo = pathinfo($file);
  3184. if (isset($documentPathInfo['extension'])) {
  3185. $parsed = parse_url($documentPathInfo['extension']);
  3186. if (isset($parsed['path'])) {
  3187. $extension = $parsed['path'];
  3188. $extensionsToDownload = [
  3189. 'zip',
  3190. 'ppt',
  3191. 'pptx',
  3192. 'ods',
  3193. 'xlsx',
  3194. 'xls',
  3195. 'csv',
  3196. 'doc',
  3197. 'docx',
  3198. 'dot',
  3199. ];
  3200. if (in_array($extension, $extensionsToDownload)) {
  3201. $file = api_get_path(WEB_CODE_PATH).
  3202. 'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
  3203. }
  3204. }
  3205. }
  3206. break;
  3207. case 'dir':
  3208. $file = 'lp_content.php?type=dir';
  3209. break;
  3210. case 'link':
  3211. if (Link::is_youtube_link($file)) {
  3212. $src = Link::get_youtube_video_id($file);
  3213. $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
  3214. } elseif (Link::isVimeoLink($file)) {
  3215. $src = Link::getVimeoLinkId($file);
  3216. $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
  3217. } else {
  3218. // If the current site is HTTPS and the link is
  3219. // HTTP, browsers will refuse opening the link
  3220. $urlId = api_get_current_access_url_id();
  3221. $url = api_get_access_url($urlId, false);
  3222. $protocol = substr($url['url'], 0, 5);
  3223. if ($protocol === 'https') {
  3224. $linkProtocol = substr($file, 0, 5);
  3225. if ($linkProtocol === 'http:') {
  3226. //this is the special intervention case
  3227. $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
  3228. }
  3229. }
  3230. }
  3231. break;
  3232. case 'quiz':
  3233. // Check how much attempts of a exercise exits in lp
  3234. $lp_item_id = $this->get_current_item_id();
  3235. $lp_view_id = $this->get_view_id();
  3236. $prevent_reinit = null;
  3237. if (isset($this->items[$this->current])) {
  3238. $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
  3239. }
  3240. if (empty($provided_toc)) {
  3241. if ($this->debug > 0) {
  3242. error_log('In learnpath::get_link() Loading get_toc ', 0);
  3243. }
  3244. $list = $this->get_toc();
  3245. } else {
  3246. if ($this->debug > 0) {
  3247. error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
  3248. }
  3249. $list = $provided_toc;
  3250. }
  3251. $type_quiz = false;
  3252. foreach ($list as $toc) {
  3253. if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
  3254. $type_quiz = true;
  3255. }
  3256. }
  3257. if ($type_quiz) {
  3258. $lp_item_id = (int) $lp_item_id;
  3259. $lp_view_id = (int) $lp_view_id;
  3260. $sql = "SELECT count(*) FROM $lp_item_view_table
  3261. WHERE
  3262. c_id = $course_id AND
  3263. lp_item_id='".$lp_item_id."' AND
  3264. lp_view_id ='".$lp_view_id."' AND
  3265. status='completed'";
  3266. $result = Database::query($sql);
  3267. $row_count = Database:: fetch_row($result);
  3268. $count_item_view = (int) $row_count[0];
  3269. $not_multiple_attempt = 0;
  3270. if ($prevent_reinit === 1 && $count_item_view > 0) {
  3271. $not_multiple_attempt = 1;
  3272. }
  3273. $file .= '&not_multiple_attempt='.$not_multiple_attempt;
  3274. }
  3275. break;
  3276. }
  3277. $tmp_array = explode('/', $file);
  3278. $document_name = $tmp_array[count($tmp_array) - 1];
  3279. if (strpos($document_name, '_DELETED_')) {
  3280. $file = 'blank.php?error=document_deleted';
  3281. }
  3282. break;
  3283. case 2:
  3284. if ($this->debug > 2) {
  3285. error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
  3286. }
  3287. if ($lp_item_type != 'dir') {
  3288. // Quite complex here:
  3289. // We want to make sure 'http://' (and similar) links can
  3290. // be loaded as is (withouth the Chamilo path in front) but
  3291. // some contents use this form: resource.htm?resource=http://blablabla
  3292. // which means we have to find a protocol at the path's start, otherwise
  3293. // it should not be considered as an external URL.
  3294. // if ($this->prerequisites_match($item_id)) {
  3295. if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
  3296. if ($this->debug > 2) {
  3297. error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
  3298. }
  3299. // Distant url, return as is.
  3300. $file = $lp_item_path;
  3301. } else {
  3302. if ($this->debug > 2) {
  3303. error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
  3304. }
  3305. // Prevent getting untranslatable urls.
  3306. $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
  3307. $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
  3308. // Prepare the path.
  3309. $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
  3310. // TODO: Fix this for urls with protocol header.
  3311. $file = str_replace('//', '/', $file);
  3312. $file = str_replace(':/', '://', $file);
  3313. if (substr($lp_path, -1) == '/') {
  3314. $lp_path = substr($lp_path, 0, -1);
  3315. }
  3316. if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
  3317. // if file not found.
  3318. $decoded = html_entity_decode($lp_item_path);
  3319. list($decoded) = explode('?', $decoded);
  3320. if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
  3321. $file = self::rl_get_resource_link_for_learnpath(
  3322. $course_id,
  3323. $this->get_id(),
  3324. $item_id,
  3325. $this->get_view_id()
  3326. );
  3327. if (empty($file)) {
  3328. $file = 'blank.php?error=document_not_found';
  3329. } else {
  3330. $tmp_array = explode('/', $file);
  3331. $document_name = $tmp_array[count($tmp_array) - 1];
  3332. if (strpos($document_name, '_DELETED_')) {
  3333. $file = 'blank.php?error=document_deleted';
  3334. } else {
  3335. $file = 'blank.php?error=document_not_found';
  3336. }
  3337. }
  3338. } else {
  3339. $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
  3340. }
  3341. }
  3342. }
  3343. // We want to use parameters if they were defined in the imsmanifest
  3344. if (strpos($file, 'blank.php') === false) {
  3345. $lp_item_params = ltrim($lp_item_params, '?');
  3346. $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
  3347. }
  3348. } else {
  3349. $file = 'lp_content.php?type=dir';
  3350. }
  3351. break;
  3352. case 3:
  3353. if ($this->debug > 2) {
  3354. error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
  3355. }
  3356. // Formatting AICC HACP append URL.
  3357. $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
  3358. if (!empty($lp_item_params)) {
  3359. $aicc_append .= $lp_item_params.'&';
  3360. }
  3361. if ($lp_item_type != 'dir') {
  3362. // Quite complex here:
  3363. // We want to make sure 'http://' (and similar) links can
  3364. // be loaded as is (withouth the Chamilo path in front) but
  3365. // some contents use this form: resource.htm?resource=http://blablabla
  3366. // which means we have to find a protocol at the path's start, otherwise
  3367. // it should not be considered as an external URL.
  3368. if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
  3369. if ($this->debug > 2) {
  3370. error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
  3371. }
  3372. // Distant url, return as is.
  3373. $file = $lp_item_path;
  3374. // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
  3375. /*
  3376. if (stristr($file,'<servername>') !== false) {
  3377. $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
  3378. }
  3379. */
  3380. if (stripos($file, '<servername>') !== false) {
  3381. //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
  3382. $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
  3383. $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
  3384. }
  3385. $file .= $aicc_append;
  3386. } else {
  3387. if ($this->debug > 2) {
  3388. error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
  3389. }
  3390. // Prevent getting untranslatable urls.
  3391. $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
  3392. $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
  3393. // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
  3394. $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
  3395. // TODO: Fix this for urls with protocol header.
  3396. $file = str_replace('//', '/', $file);
  3397. $file = str_replace(':/', '://', $file);
  3398. $file .= $aicc_append;
  3399. }
  3400. } else {
  3401. $file = 'lp_content.php?type=dir';
  3402. }
  3403. break;
  3404. case 4:
  3405. break;
  3406. default:
  3407. break;
  3408. }
  3409. // Replace &amp; by & because &amp; will break URL with params
  3410. $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
  3411. }
  3412. if ($this->debug > 2) {
  3413. error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
  3414. }
  3415. return $file;
  3416. }
  3417. /**
  3418. * Gets the latest usable view or generate a new one.
  3419. *
  3420. * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
  3421. *
  3422. * @return int DB lp_view id
  3423. */
  3424. public function get_view($attempt_num = 0)
  3425. {
  3426. $search = '';
  3427. // Use $attempt_num to enable multi-views management (disabled so far).
  3428. if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
  3429. $search = 'AND view_count = '.$attempt_num;
  3430. }
  3431. // When missing $attempt_num, search for a unique lp_view record for this lp and user.
  3432. $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
  3433. $course_id = api_get_course_int_id();
  3434. $sessionId = api_get_session_id();
  3435. $sql = "SELECT iid, view_count FROM $lp_view_table
  3436. WHERE
  3437. c_id = $course_id AND
  3438. lp_id = ".$this->get_id()." AND
  3439. user_id = ".$this->get_user_id()." AND
  3440. session_id = $sessionId
  3441. $search
  3442. ORDER BY view_count DESC";
  3443. $res = Database::query($sql);
  3444. if (Database::num_rows($res) > 0) {
  3445. $row = Database::fetch_array($res);
  3446. $this->lp_view_id = $row['iid'];
  3447. } elseif (!api_is_invitee()) {
  3448. // There is no database record, create one.
  3449. $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
  3450. ($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
  3451. Database::query($sql);
  3452. $id = Database::insert_id();
  3453. $this->lp_view_id = $id;
  3454. $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
  3455. Database::query($sql);
  3456. }
  3457. return $this->lp_view_id;
  3458. }
  3459. /**
  3460. * Gets the current view id.
  3461. *
  3462. * @return int View ID (from lp_view)
  3463. */
  3464. public function get_view_id()
  3465. {
  3466. if (!empty($this->lp_view_id)) {
  3467. return (int) $this->lp_view_id;
  3468. }
  3469. return 0;
  3470. }
  3471. /**
  3472. * Gets the update queue.
  3473. *
  3474. * @return array Array containing IDs of items to be updated by JavaScript
  3475. */
  3476. public function get_update_queue()
  3477. {
  3478. return $this->update_queue;
  3479. }
  3480. /**
  3481. * Gets the user ID.
  3482. *
  3483. * @return int User ID
  3484. */
  3485. public function get_user_id()
  3486. {
  3487. if (!empty($this->user_id)) {
  3488. return (int) $this->user_id;
  3489. }
  3490. return false;
  3491. }
  3492. /**
  3493. * Checks if any of the items has an audio element attached.
  3494. *
  3495. * @return bool True or false
  3496. */
  3497. public function has_audio()
  3498. {
  3499. $has = false;
  3500. foreach ($this->items as $i => $item) {
  3501. if (!empty($this->items[$i]->audio)) {
  3502. $has = true;
  3503. break;
  3504. }
  3505. }
  3506. return $has;
  3507. }
  3508. /**
  3509. * Moves an item up and down at its level.
  3510. *
  3511. * @param int $id Item to move up and down
  3512. * @param string $direction Direction 'up' or 'down'
  3513. *
  3514. * @return bool|int
  3515. */
  3516. public function move_item($id, $direction)
  3517. {
  3518. $course_id = api_get_course_int_id();
  3519. if (empty($id) || empty($direction)) {
  3520. return false;
  3521. }
  3522. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  3523. $sql_sel = "SELECT *
  3524. FROM $tbl_lp_item
  3525. WHERE
  3526. iid = $id
  3527. ";
  3528. $res_sel = Database::query($sql_sel);
  3529. // Check if elem exists.
  3530. if (Database::num_rows($res_sel) < 1) {
  3531. return false;
  3532. }
  3533. // Gather data.
  3534. $row = Database::fetch_array($res_sel);
  3535. $previous = $row['previous_item_id'];
  3536. $next = $row['next_item_id'];
  3537. $display = $row['display_order'];
  3538. $parent = $row['parent_item_id'];
  3539. $lp = $row['lp_id'];
  3540. // Update the item (switch with previous/next one).
  3541. switch ($direction) {
  3542. case 'up':
  3543. if ($display > 1) {
  3544. $sql_sel2 = "SELECT * FROM $tbl_lp_item
  3545. WHERE iid = $previous";
  3546. $res_sel2 = Database::query($sql_sel2);
  3547. if (Database::num_rows($res_sel2) < 1) {
  3548. $previous_previous = 0;
  3549. }
  3550. // Gather data.
  3551. $row2 = Database::fetch_array($res_sel2);
  3552. $previous_previous = $row2['previous_item_id'];
  3553. // Update previous_previous item (switch "next" with current).
  3554. if ($previous_previous != 0) {
  3555. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3556. next_item_id = $id
  3557. WHERE iid = $previous_previous";
  3558. Database::query($sql_upd2);
  3559. }
  3560. // Update previous item (switch with current).
  3561. if ($previous != 0) {
  3562. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3563. next_item_id = $next,
  3564. previous_item_id = $id,
  3565. display_order = display_order +1
  3566. WHERE iid = $previous";
  3567. Database::query($sql_upd2);
  3568. }
  3569. // Update current item (switch with previous).
  3570. if ($id != 0) {
  3571. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3572. next_item_id = $previous,
  3573. previous_item_id = $previous_previous,
  3574. display_order = display_order-1
  3575. WHERE c_id = ".$course_id." AND id = $id";
  3576. Database::query($sql_upd2);
  3577. }
  3578. // Update next item (new previous item).
  3579. if (!empty($next)) {
  3580. $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
  3581. WHERE iid = $next";
  3582. Database::query($sql_upd2);
  3583. }
  3584. $display = $display - 1;
  3585. }
  3586. break;
  3587. case 'down':
  3588. if ($next != 0) {
  3589. $sql_sel2 = "SELECT * FROM $tbl_lp_item
  3590. WHERE iid = $next";
  3591. $res_sel2 = Database::query($sql_sel2);
  3592. if (Database::num_rows($res_sel2) < 1) {
  3593. $next_next = 0;
  3594. }
  3595. // Gather data.
  3596. $row2 = Database::fetch_array($res_sel2);
  3597. $next_next = $row2['next_item_id'];
  3598. // Update previous item (switch with current).
  3599. if ($previous != 0) {
  3600. $sql_upd2 = "UPDATE $tbl_lp_item
  3601. SET next_item_id = $next
  3602. WHERE iid = $previous";
  3603. Database::query($sql_upd2);
  3604. }
  3605. // Update current item (switch with previous).
  3606. if ($id != 0) {
  3607. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3608. previous_item_id = $next,
  3609. next_item_id = $next_next,
  3610. display_order = display_order + 1
  3611. WHERE iid = $id";
  3612. Database::query($sql_upd2);
  3613. }
  3614. // Update next item (new previous item).
  3615. if ($next != 0) {
  3616. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3617. previous_item_id = $previous,
  3618. next_item_id = $id,
  3619. display_order = display_order-1
  3620. WHERE iid = $next";
  3621. Database::query($sql_upd2);
  3622. }
  3623. // Update next_next item (switch "previous" with current).
  3624. if ($next_next != 0) {
  3625. $sql_upd2 = "UPDATE $tbl_lp_item SET
  3626. previous_item_id = $id
  3627. WHERE iid = $next_next";
  3628. Database::query($sql_upd2);
  3629. }
  3630. $display = $display + 1;
  3631. }
  3632. break;
  3633. default:
  3634. return false;
  3635. }
  3636. return $display;
  3637. }
  3638. /**
  3639. * Move a LP up (display_order).
  3640. *
  3641. * @param int $lp_id Learnpath ID
  3642. * @param int $categoryId Category ID
  3643. *
  3644. * @return bool
  3645. */
  3646. public static function move_up($lp_id, $categoryId = 0)
  3647. {
  3648. $courseId = api_get_course_int_id();
  3649. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  3650. $categoryCondition = '';
  3651. if (!empty($categoryId)) {
  3652. $categoryId = (int) $categoryId;
  3653. $categoryCondition = " AND category_id = $categoryId";
  3654. }
  3655. $sql = "SELECT * FROM $lp_table
  3656. WHERE c_id = $courseId
  3657. $categoryCondition
  3658. ORDER BY display_order";
  3659. $res = Database::query($sql);
  3660. if ($res === false) {
  3661. return false;
  3662. }
  3663. $lps = [];
  3664. $lp_order = [];
  3665. $num = Database::num_rows($res);
  3666. // First check the order is correct, globally (might be wrong because
  3667. // of versions < 1.8.4)
  3668. if ($num > 0) {
  3669. $i = 1;
  3670. while ($row = Database::fetch_array($res)) {
  3671. if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
  3672. $sql = "UPDATE $lp_table SET display_order = $i
  3673. WHERE iid = ".$row['iid'];
  3674. Database::query($sql);
  3675. }
  3676. $row['display_order'] = $i;
  3677. $lps[$row['iid']] = $row;
  3678. $lp_order[$i] = $row['iid'];
  3679. $i++;
  3680. }
  3681. }
  3682. if ($num > 1) { // If there's only one element, no need to sort.
  3683. $order = $lps[$lp_id]['display_order'];
  3684. if ($order > 1) { // If it's the first element, no need to move up.
  3685. $sql = "UPDATE $lp_table SET display_order = $order
  3686. WHERE iid = ".$lp_order[$order - 1];
  3687. Database::query($sql);
  3688. $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
  3689. WHERE iid = $lp_id";
  3690. Database::query($sql);
  3691. }
  3692. }
  3693. return true;
  3694. }
  3695. /**
  3696. * Move a learnpath down (display_order).
  3697. *
  3698. * @param int $lp_id Learnpath ID
  3699. * @param int $categoryId Category ID
  3700. *
  3701. * @return bool
  3702. */
  3703. public static function move_down($lp_id, $categoryId = 0)
  3704. {
  3705. $courseId = api_get_course_int_id();
  3706. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  3707. $categoryCondition = '';
  3708. if (!empty($categoryId)) {
  3709. $categoryId = (int) $categoryId;
  3710. $categoryCondition = " AND category_id = $categoryId";
  3711. }
  3712. $sql = "SELECT * FROM $lp_table
  3713. WHERE c_id = $courseId
  3714. $categoryCondition
  3715. ORDER BY display_order";
  3716. $res = Database::query($sql);
  3717. if ($res === false) {
  3718. return false;
  3719. }
  3720. $lps = [];
  3721. $lp_order = [];
  3722. $num = Database::num_rows($res);
  3723. $max = 0;
  3724. // First check the order is correct, globally (might be wrong because
  3725. // of versions < 1.8.4).
  3726. if ($num > 0) {
  3727. $i = 1;
  3728. while ($row = Database::fetch_array($res)) {
  3729. $max = $i;
  3730. if ($row['display_order'] != $i) {
  3731. // If we find a gap in the order, we need to fix it.
  3732. $sql = "UPDATE $lp_table SET display_order = $i
  3733. WHERE iid = ".$row['iid'];
  3734. Database::query($sql);
  3735. }
  3736. $row['display_order'] = $i;
  3737. $lps[$row['iid']] = $row;
  3738. $lp_order[$i] = $row['iid'];
  3739. $i++;
  3740. }
  3741. }
  3742. if ($num > 1) { // If there's only one element, no need to sort.
  3743. $order = $lps[$lp_id]['display_order'];
  3744. if ($order < $max) { // If it's the first element, no need to move up.
  3745. $sql = "UPDATE $lp_table SET display_order = $order
  3746. WHERE iid = ".$lp_order[$order + 1];
  3747. Database::query($sql);
  3748. $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
  3749. WHERE iid = $lp_id";
  3750. Database::query($sql);
  3751. }
  3752. }
  3753. return true;
  3754. }
  3755. /**
  3756. * Updates learnpath attributes to point to the next element
  3757. * The last part is similar to set_current_item but processing the other way around.
  3758. */
  3759. public function next()
  3760. {
  3761. if ($this->debug > 0) {
  3762. error_log('In learnpath::next()', 0);
  3763. }
  3764. $this->last = $this->get_current_item_id();
  3765. $this->items[$this->last]->save(
  3766. false,
  3767. $this->prerequisites_match($this->last)
  3768. );
  3769. $this->autocomplete_parents($this->last);
  3770. $new_index = $this->get_next_index();
  3771. if ($this->debug > 2) {
  3772. error_log('New index: '.$new_index, 0);
  3773. }
  3774. $this->index = $new_index;
  3775. if ($this->debug > 2) {
  3776. error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
  3777. }
  3778. $this->current = $this->ordered_items[$new_index];
  3779. if ($this->debug > 2) {
  3780. error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
  3781. }
  3782. }
  3783. /**
  3784. * Open a resource = initialise all local variables relative to this resource. Depending on the child
  3785. * class, this might be redefined to allow several behaviours depending on the document type.
  3786. *
  3787. * @param int $id Resource ID
  3788. */
  3789. public function open($id)
  3790. {
  3791. // TODO:
  3792. // set the current resource attribute to this resource
  3793. // switch on element type (redefine in child class?)
  3794. // set status for this item to "opened"
  3795. // start timer
  3796. // initialise score
  3797. $this->index = 0; //or = the last item seen (see $this->last)
  3798. }
  3799. /**
  3800. * Check that all prerequisites are fulfilled. Returns true and an
  3801. * empty string on success, returns false
  3802. * and the prerequisite string on error.
  3803. * This function is based on the rules for aicc_script language as
  3804. * described in the SCORM 1.2 CAM documentation page 108.
  3805. *
  3806. * @param int $itemId Optional item ID. If none given, uses the current open item.
  3807. *
  3808. * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
  3809. * string otherwise
  3810. */
  3811. public function prerequisites_match($itemId = null)
  3812. {
  3813. $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
  3814. if ($allow) {
  3815. if (api_is_allowed_to_edit() ||
  3816. api_is_platform_admin(true) ||
  3817. api_is_drh() ||
  3818. api_is_coach(api_get_session_id(), api_get_course_int_id())
  3819. ) {
  3820. return true;
  3821. }
  3822. }
  3823. $debug = $this->debug;
  3824. if ($debug > 0) {
  3825. error_log('In learnpath::prerequisites_match()');
  3826. }
  3827. if (empty($itemId)) {
  3828. $itemId = $this->current;
  3829. }
  3830. $currentItem = $this->getItem($itemId);
  3831. if ($currentItem) {
  3832. if ($this->type == 2) {
  3833. // Getting prereq from scorm
  3834. $prereq_string = $this->get_scorm_prereq_string($itemId);
  3835. } else {
  3836. $prereq_string = $currentItem->get_prereq_string();
  3837. }
  3838. if (empty($prereq_string)) {
  3839. if ($debug > 0) {
  3840. error_log('Found prereq_string is empty return true');
  3841. }
  3842. return true;
  3843. }
  3844. // Clean spaces.
  3845. $prereq_string = str_replace(' ', '', $prereq_string);
  3846. if ($debug > 0) {
  3847. error_log('Found prereq_string: '.$prereq_string, 0);
  3848. }
  3849. // Now send to the parse_prereq() function that will check this component's prerequisites.
  3850. $result = $currentItem->parse_prereq(
  3851. $prereq_string,
  3852. $this->items,
  3853. $this->refs_list,
  3854. $this->get_user_id()
  3855. );
  3856. if ($result === false) {
  3857. $this->set_error_msg($currentItem->prereq_alert);
  3858. }
  3859. } else {
  3860. $result = true;
  3861. if ($debug > 1) {
  3862. error_log('$this->items['.$itemId.'] was not an object', 0);
  3863. }
  3864. }
  3865. if ($debug > 1) {
  3866. error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
  3867. }
  3868. return $result;
  3869. }
  3870. /**
  3871. * Updates learnpath attributes to point to the previous element
  3872. * The last part is similar to set_current_item but processing the other way around.
  3873. */
  3874. public function previous()
  3875. {
  3876. $this->last = $this->get_current_item_id();
  3877. $this->items[$this->last]->save(
  3878. false,
  3879. $this->prerequisites_match($this->last)
  3880. );
  3881. $this->autocomplete_parents($this->last);
  3882. $new_index = $this->get_previous_index();
  3883. $this->index = $new_index;
  3884. $this->current = $this->ordered_items[$new_index];
  3885. }
  3886. /**
  3887. * Publishes a learnpath. This basically means show or hide the learnpath
  3888. * to normal users.
  3889. * Can be used as abstract.
  3890. *
  3891. * @param int $lp_id Learnpath ID
  3892. * @param int $set_visibility New visibility
  3893. *
  3894. * @return bool
  3895. */
  3896. public static function toggle_visibility($lp_id, $set_visibility = 1)
  3897. {
  3898. $action = 'visible';
  3899. if ($set_visibility != 1) {
  3900. $action = 'invisible';
  3901. self::toggle_publish($lp_id, 'i');
  3902. }
  3903. return api_item_property_update(
  3904. api_get_course_info(),
  3905. TOOL_LEARNPATH,
  3906. $lp_id,
  3907. $action,
  3908. api_get_user_id()
  3909. );
  3910. }
  3911. /**
  3912. * Publishes a learnpath category.
  3913. * This basically means show or hide the learnpath category to normal users.
  3914. *
  3915. * @param int $id
  3916. * @param int $visibility
  3917. *
  3918. * @throws \Doctrine\ORM\NonUniqueResultException
  3919. * @throws \Doctrine\ORM\ORMException
  3920. * @throws \Doctrine\ORM\OptimisticLockException
  3921. * @throws \Doctrine\ORM\TransactionRequiredException
  3922. *
  3923. * @return bool
  3924. */
  3925. public static function toggleCategoryVisibility($id, $visibility = 1)
  3926. {
  3927. $action = 'visible';
  3928. if ($visibility != 1) {
  3929. self::toggleCategoryPublish($id, 0);
  3930. $action = 'invisible';
  3931. }
  3932. return api_item_property_update(
  3933. api_get_course_info(),
  3934. TOOL_LEARNPATH_CATEGORY,
  3935. $id,
  3936. $action,
  3937. api_get_user_id()
  3938. );
  3939. }
  3940. /**
  3941. * Publishes a learnpath. This basically means show or hide the learnpath
  3942. * on the course homepage
  3943. * Can be used as abstract.
  3944. *
  3945. * @param int $lp_id Learnpath id
  3946. * @param string $set_visibility New visibility (v/i - visible/invisible)
  3947. *
  3948. * @return bool
  3949. */
  3950. public static function toggle_publish($lp_id, $set_visibility = 'v')
  3951. {
  3952. $course_id = api_get_course_int_id();
  3953. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  3954. $lp_id = (int) $lp_id;
  3955. $sql = "SELECT * FROM $tbl_lp
  3956. WHERE iid = $lp_id";
  3957. $result = Database::query($sql);
  3958. if (Database::num_rows($result)) {
  3959. $row = Database::fetch_array($result);
  3960. $name = Database::escape_string($row['name']);
  3961. if ($set_visibility == 'i') {
  3962. $v = 0;
  3963. }
  3964. if ($set_visibility == 'v') {
  3965. $v = 1;
  3966. }
  3967. $session_id = api_get_session_id();
  3968. $session_condition = api_get_session_condition($session_id);
  3969. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  3970. $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
  3971. $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
  3972. $sql = "SELECT * FROM $tbl_tool
  3973. WHERE
  3974. c_id = $course_id AND
  3975. (link = '$link' OR link = '$oldLink') AND
  3976. image = 'scormbuilder.gif' AND
  3977. (
  3978. link LIKE '$link%' OR
  3979. link LIKE '$oldLink%'
  3980. )
  3981. $session_condition
  3982. ";
  3983. $result = Database::query($sql);
  3984. $num = Database::num_rows($result);
  3985. if ($set_visibility == 'i' && $num > 0) {
  3986. $sql = "DELETE FROM $tbl_tool
  3987. WHERE
  3988. c_id = $course_id AND
  3989. (link = '$link' OR link = '$oldLink') AND
  3990. image='scormbuilder.gif'
  3991. $session_condition";
  3992. Database::query($sql);
  3993. } elseif ($set_visibility == 'v' && $num == 0) {
  3994. $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
  3995. ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
  3996. Database::query($sql);
  3997. $insertId = Database::insert_id();
  3998. if ($insertId) {
  3999. $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
  4000. Database::query($sql);
  4001. }
  4002. } elseif ($set_visibility == 'v' && $num > 0) {
  4003. $sql = "UPDATE $tbl_tool SET
  4004. c_id = $course_id,
  4005. name = '$name',
  4006. link = '$link',
  4007. image = 'scormbuilder.gif',
  4008. visibility = '$v',
  4009. admin = '0',
  4010. address = 'pastillegris.gif',
  4011. added_tool = 0,
  4012. session_id = $session_id
  4013. WHERE
  4014. c_id = ".$course_id." AND
  4015. (link = '$link' OR link = '$oldLink') AND
  4016. image='scormbuilder.gif'
  4017. $session_condition
  4018. ";
  4019. Database::query($sql);
  4020. } else {
  4021. // Parameter and database incompatible, do nothing, exit.
  4022. return false;
  4023. }
  4024. } else {
  4025. return false;
  4026. }
  4027. }
  4028. /**
  4029. * Publishes a learnpath.
  4030. * Show or hide the learnpath category on the course homepage.
  4031. *
  4032. * @param int $id
  4033. * @param int $setVisibility
  4034. *
  4035. * @throws \Doctrine\ORM\NonUniqueResultException
  4036. * @throws \Doctrine\ORM\ORMException
  4037. * @throws \Doctrine\ORM\OptimisticLockException
  4038. * @throws \Doctrine\ORM\TransactionRequiredException
  4039. *
  4040. * @return bool
  4041. */
  4042. public static function toggleCategoryPublish($id, $setVisibility = 1)
  4043. {
  4044. $courseId = api_get_course_int_id();
  4045. $sessionId = api_get_session_id();
  4046. $sessionCondition = api_get_session_condition(
  4047. $sessionId,
  4048. true,
  4049. false,
  4050. 't.sessionId'
  4051. );
  4052. $em = Database::getManager();
  4053. /** @var CLpCategory $category */
  4054. $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  4055. if (!$category) {
  4056. return false;
  4057. }
  4058. if (empty($courseId)) {
  4059. return false;
  4060. }
  4061. $link = self::getCategoryLinkForTool($id);
  4062. /** @var CTool $tool */
  4063. $tool = $em->createQuery("
  4064. SELECT t FROM ChamiloCourseBundle:CTool t
  4065. WHERE
  4066. t.cId = :course AND
  4067. t.link = :link1 AND
  4068. t.image = 'lp_category.gif' AND
  4069. t.link LIKE :link2
  4070. $sessionCondition
  4071. ")
  4072. ->setParameters([
  4073. 'course' => $courseId,
  4074. 'link1' => $link,
  4075. 'link2' => "$link%",
  4076. ])
  4077. ->getOneOrNullResult();
  4078. if ($setVisibility == 0 && $tool) {
  4079. $em->remove($tool);
  4080. $em->flush();
  4081. return true;
  4082. }
  4083. if ($setVisibility == 1 && !$tool) {
  4084. $tool = new CTool();
  4085. $tool
  4086. ->setCategory('authoring')
  4087. ->setCId($courseId)
  4088. ->setName(strip_tags($category->getName()))
  4089. ->setLink($link)
  4090. ->setImage('lp_category.gif')
  4091. ->setVisibility(1)
  4092. ->setAdmin(0)
  4093. ->setAddress('pastillegris.gif')
  4094. ->setAddedTool(0)
  4095. ->setSessionId($sessionId)
  4096. ->setTarget('_self');
  4097. $em->persist($tool);
  4098. $em->flush();
  4099. $tool->setId($tool->getIid());
  4100. $em->persist($tool);
  4101. $em->flush();
  4102. return true;
  4103. }
  4104. if ($setVisibility == 1 && $tool) {
  4105. $tool
  4106. ->setName(strip_tags($category->getName()))
  4107. ->setVisibility(1);
  4108. $em->persist($tool);
  4109. $em->flush();
  4110. return true;
  4111. }
  4112. return false;
  4113. }
  4114. /**
  4115. * Check if the learnpath category is visible for a user.
  4116. *
  4117. * @param CLpCategory $category
  4118. * @param User $user
  4119. * @param int
  4120. * @param int
  4121. *
  4122. * @return bool
  4123. */
  4124. public static function categoryIsVisibleForStudent(
  4125. CLpCategory $category,
  4126. User $user,
  4127. $courseId = 0,
  4128. $sessionId = 0
  4129. ) {
  4130. if (empty($category)) {
  4131. return false;
  4132. }
  4133. $isAllowedToEdit = api_is_allowed_to_edit(null, true);
  4134. if ($isAllowedToEdit) {
  4135. return true;
  4136. }
  4137. $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
  4138. $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
  4139. $courseInfo = api_get_course_info_by_id($courseId);
  4140. $categoryVisibility = api_get_item_visibility(
  4141. $courseInfo,
  4142. TOOL_LEARNPATH_CATEGORY,
  4143. $category->getId(),
  4144. $sessionId
  4145. );
  4146. if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
  4147. return false;
  4148. }
  4149. $subscriptionSettings = self::getSubscriptionSettings();
  4150. if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
  4151. return true;
  4152. }
  4153. $users = $category->getUsers();
  4154. if (empty($users) || !$users->count()) {
  4155. return true;
  4156. }
  4157. if ($category->hasUserAdded($user)) {
  4158. return true;
  4159. }
  4160. $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
  4161. if (!empty($groups)) {
  4162. $em = Database::getManager();
  4163. /** @var ItemPropertyRepository $itemRepo */
  4164. $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
  4165. /** @var CourseRepository $courseRepo */
  4166. $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
  4167. $session = null;
  4168. if (!empty($sessionId)) {
  4169. $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
  4170. }
  4171. $course = $courseRepo->find($courseId);
  4172. // Subscribed groups to a LP
  4173. $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
  4174. TOOL_LEARNPATH_CATEGORY,
  4175. $category->getId(),
  4176. $course,
  4177. $session
  4178. );
  4179. if (!empty($subscribedGroupsInLp)) {
  4180. $groups = array_column($groups, 'iid');
  4181. /** @var CItemProperty $item */
  4182. foreach ($subscribedGroupsInLp as $item) {
  4183. if ($item->getGroup() &&
  4184. in_array($item->getGroup()->getId(), $groups)
  4185. ) {
  4186. return true;
  4187. }
  4188. }
  4189. }
  4190. }
  4191. return false;
  4192. }
  4193. /**
  4194. * Check if a learnpath category is published as course tool.
  4195. *
  4196. * @param CLpCategory $category
  4197. * @param int $courseId
  4198. *
  4199. * @return bool
  4200. */
  4201. public static function categoryIsPublished(
  4202. CLpCategory $category,
  4203. $courseId
  4204. ) {
  4205. $link = self::getCategoryLinkForTool($category->getId());
  4206. $em = Database::getManager();
  4207. $tools = $em
  4208. ->createQuery("
  4209. SELECT t FROM ChamiloCourseBundle:CTool t
  4210. WHERE t.cId = :course AND
  4211. t.name = :name AND
  4212. t.image = 'lp_category.gif' AND
  4213. t.link LIKE :link
  4214. ")
  4215. ->setParameters([
  4216. 'course' => $courseId,
  4217. 'name' => strip_tags($category->getName()),
  4218. 'link' => "$link%",
  4219. ])
  4220. ->getResult();
  4221. /** @var CTool $tool */
  4222. $tool = current($tools);
  4223. return $tool ? $tool->getVisibility() : false;
  4224. }
  4225. /**
  4226. * Restart the whole learnpath. Return the URL of the first element.
  4227. * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
  4228. * To use a similar method statically, use the create_new_attempt() method.
  4229. *
  4230. * @return bool
  4231. */
  4232. public function restart()
  4233. {
  4234. if ($this->debug > 0) {
  4235. error_log('In learnpath::restart()', 0);
  4236. }
  4237. // TODO
  4238. // Call autosave method to save the current progress.
  4239. //$this->index = 0;
  4240. if (api_is_invitee()) {
  4241. return false;
  4242. }
  4243. $session_id = api_get_session_id();
  4244. $course_id = api_get_course_int_id();
  4245. $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
  4246. $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
  4247. VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
  4248. if ($this->debug > 2) {
  4249. error_log('Inserting new lp_view for restart: '.$sql, 0);
  4250. }
  4251. Database::query($sql);
  4252. $view_id = Database::insert_id();
  4253. if ($view_id) {
  4254. $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
  4255. Database::query($sql);
  4256. $this->lp_view_id = $view_id;
  4257. $this->attempt = $this->attempt + 1;
  4258. } else {
  4259. $this->error = 'Could not insert into item_view table...';
  4260. return false;
  4261. }
  4262. $this->autocomplete_parents($this->current);
  4263. foreach ($this->items as $index => $dummy) {
  4264. $this->items[$index]->restart();
  4265. $this->items[$index]->set_lp_view($this->lp_view_id);
  4266. }
  4267. $this->first();
  4268. return true;
  4269. }
  4270. /**
  4271. * Saves the current item.
  4272. *
  4273. * @return bool
  4274. */
  4275. public function save_current()
  4276. {
  4277. $debug = $this->debug;
  4278. // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
  4279. // on $ordered_items[] but not sure it's always safe to use with $items[]).
  4280. if ($debug) {
  4281. error_log('save_current() saving item '.$this->current, 0);
  4282. error_log(''.print_r($this->items, true), 0);
  4283. }
  4284. if (isset($this->items[$this->current]) &&
  4285. is_object($this->items[$this->current])
  4286. ) {
  4287. if ($debug) {
  4288. error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
  4289. }
  4290. $res = $this->items[$this->current]->save(
  4291. false,
  4292. $this->prerequisites_match($this->current)
  4293. );
  4294. $this->autocomplete_parents($this->current);
  4295. $status = $this->items[$this->current]->get_status();
  4296. $this->update_queue[$this->current] = $status;
  4297. if ($debug) {
  4298. error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
  4299. }
  4300. return $res;
  4301. }
  4302. return false;
  4303. }
  4304. /**
  4305. * Saves the given item.
  4306. *
  4307. * @param int $item_id Optional (will take from $_REQUEST if null)
  4308. * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
  4309. *
  4310. * @return bool
  4311. */
  4312. public function save_item($item_id = null, $from_outside = true)
  4313. {
  4314. $debug = $this->debug;
  4315. if ($debug) {
  4316. error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
  4317. }
  4318. // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
  4319. // on $ordered_items[] but not sure it's always safe to use with $items[]).
  4320. if (empty($item_id)) {
  4321. $item_id = (int) $_REQUEST['id'];
  4322. }
  4323. if (empty($item_id)) {
  4324. $item_id = $this->get_current_item_id();
  4325. }
  4326. if (isset($this->items[$item_id]) &&
  4327. is_object($this->items[$item_id])
  4328. ) {
  4329. if ($debug) {
  4330. error_log('Object exists');
  4331. }
  4332. // Saving the item.
  4333. $res = $this->items[$item_id]->save(
  4334. $from_outside,
  4335. $this->prerequisites_match($item_id)
  4336. );
  4337. if ($debug) {
  4338. error_log('update_queue before:');
  4339. error_log(print_r($this->update_queue, 1));
  4340. }
  4341. $this->autocomplete_parents($item_id);
  4342. $status = $this->items[$item_id]->get_status();
  4343. $this->update_queue[$item_id] = $status;
  4344. if ($debug) {
  4345. error_log('get_status(): '.$status);
  4346. error_log('update_queue after:');
  4347. error_log(print_r($this->update_queue, 1));
  4348. }
  4349. return $res;
  4350. }
  4351. return false;
  4352. }
  4353. /**
  4354. * Saves the last item seen's ID only in case.
  4355. */
  4356. public function save_last()
  4357. {
  4358. $course_id = api_get_course_int_id();
  4359. $debug = $this->debug;
  4360. if ($debug) {
  4361. error_log('In learnpath::save_last()', 0);
  4362. }
  4363. $session_condition = api_get_session_condition(
  4364. api_get_session_id(),
  4365. true,
  4366. false
  4367. );
  4368. $table = Database::get_course_table(TABLE_LP_VIEW);
  4369. if (isset($this->current) && !api_is_invitee()) {
  4370. if ($debug) {
  4371. error_log('Saving current item ('.$this->current.') for later review', 0);
  4372. }
  4373. $sql = "UPDATE $table SET
  4374. last_item = ".$this->get_current_item_id()."
  4375. WHERE
  4376. c_id = $course_id AND
  4377. lp_id = ".$this->get_id()." AND
  4378. user_id = ".$this->get_user_id()." ".$session_condition;
  4379. if ($debug) {
  4380. error_log('Saving last item seen : '.$sql, 0);
  4381. }
  4382. Database::query($sql);
  4383. }
  4384. if (!api_is_invitee()) {
  4385. // Save progress.
  4386. list($progress) = $this->get_progress_bar_text('%');
  4387. if ($progress >= 0 && $progress <= 100) {
  4388. $progress = (int) $progress;
  4389. $sql = "UPDATE $table SET
  4390. progress = $progress
  4391. WHERE
  4392. c_id = $course_id AND
  4393. lp_id = ".$this->get_id()." AND
  4394. user_id = ".$this->get_user_id()." ".$session_condition;
  4395. // Ignore errors as some tables might not have the progress field just yet.
  4396. Database::query($sql);
  4397. $this->progress_db = $progress;
  4398. }
  4399. }
  4400. }
  4401. /**
  4402. * Sets the current item ID (checks if valid and authorized first).
  4403. *
  4404. * @param int $item_id New item ID. If not given or not authorized, defaults to current
  4405. */
  4406. public function set_current_item($item_id = null)
  4407. {
  4408. $debug = $this->debug;
  4409. if ($debug) {
  4410. error_log('In learnpath::set_current_item('.$item_id.')', 0);
  4411. }
  4412. if (empty($item_id)) {
  4413. if ($debug) {
  4414. error_log('No new current item given, ignore...', 0);
  4415. }
  4416. // Do nothing.
  4417. } else {
  4418. if ($debug) {
  4419. error_log('New current item given is '.$item_id.'...', 0);
  4420. }
  4421. if (is_numeric($item_id)) {
  4422. $item_id = (int) $item_id;
  4423. // TODO: Check in database here.
  4424. $this->last = $this->current;
  4425. $this->current = $item_id;
  4426. // TODO: Update $this->index as well.
  4427. foreach ($this->ordered_items as $index => $item) {
  4428. if ($item == $this->current) {
  4429. $this->index = $index;
  4430. break;
  4431. }
  4432. }
  4433. if ($debug) {
  4434. error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
  4435. }
  4436. } else {
  4437. if ($debug) {
  4438. error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
  4439. }
  4440. }
  4441. }
  4442. }
  4443. /**
  4444. * Sets the encoding.
  4445. *
  4446. * @param string $enc New encoding
  4447. *
  4448. * @return bool
  4449. *
  4450. * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
  4451. */
  4452. public function set_encoding($enc = 'UTF-8')
  4453. {
  4454. $enc = api_refine_encoding_id($enc);
  4455. if (empty($enc)) {
  4456. $enc = api_get_system_encoding();
  4457. }
  4458. if (api_is_encoding_supported($enc)) {
  4459. $lp = $this->get_id();
  4460. if ($lp != 0) {
  4461. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  4462. $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
  4463. WHERE iid = ".$lp;
  4464. $res = Database::query($sql);
  4465. return $res;
  4466. }
  4467. }
  4468. return false;
  4469. }
  4470. /**
  4471. * Sets the JS lib setting in the database directly.
  4472. * This is the JavaScript library file this lp needs to load on startup.
  4473. *
  4474. * @param string $lib Proximity setting
  4475. *
  4476. * @return bool True on update success. False otherwise.
  4477. */
  4478. public function set_jslib($lib = '')
  4479. {
  4480. $lp = $this->get_id();
  4481. if ($lp != 0) {
  4482. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  4483. $lib = Database::escape_string($lib);
  4484. $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
  4485. WHERE iid = $lp";
  4486. $res = Database::query($sql);
  4487. return $res;
  4488. }
  4489. return false;
  4490. }
  4491. /**
  4492. * Sets the name of the LP maker (publisher) (and save).
  4493. *
  4494. * @param string $name Optional string giving the new content_maker of this learnpath
  4495. *
  4496. * @return bool True
  4497. */
  4498. public function set_maker($name = '')
  4499. {
  4500. if (empty($name)) {
  4501. return false;
  4502. }
  4503. $this->maker = $name;
  4504. $table = Database::get_course_table(TABLE_LP_MAIN);
  4505. $lp_id = $this->get_id();
  4506. $sql = "UPDATE $table SET
  4507. content_maker = '".Database::escape_string($this->maker)."'
  4508. WHERE iid = $lp_id";
  4509. Database::query($sql);
  4510. return true;
  4511. }
  4512. /**
  4513. * Sets the name of the current learnpath (and save).
  4514. *
  4515. * @param string $name Optional string giving the new name of this learnpath
  4516. *
  4517. * @return bool True/False
  4518. */
  4519. public function set_name($name = null)
  4520. {
  4521. if (empty($name)) {
  4522. return false;
  4523. }
  4524. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  4525. $name = Database::escape_string($name);
  4526. $this->name = $name;
  4527. $lp_id = $this->get_id();
  4528. $course_id = $this->course_info['real_id'];
  4529. $sql = "UPDATE $lp_table SET
  4530. name = '$name'
  4531. WHERE iid = $lp_id";
  4532. $result = Database::query($sql);
  4533. // If the lp is visible on the homepage, change his name there.
  4534. if (Database::affected_rows($result)) {
  4535. $session_id = api_get_session_id();
  4536. $session_condition = api_get_session_condition($session_id);
  4537. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  4538. $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
  4539. $sql = "UPDATE $tbl_tool SET name = '$name'
  4540. WHERE
  4541. c_id = $course_id AND
  4542. (link='$link' AND image='scormbuilder.gif' $session_condition)";
  4543. Database::query($sql);
  4544. return true;
  4545. }
  4546. return false;
  4547. }
  4548. /**
  4549. * Set index specified prefix terms for all items in this path.
  4550. *
  4551. * @param string $terms_string Comma-separated list of terms
  4552. * @param string $prefix Xapian term prefix
  4553. *
  4554. * @return bool False on error, true otherwise
  4555. */
  4556. public function set_terms_by_prefix($terms_string, $prefix)
  4557. {
  4558. $course_id = api_get_course_int_id();
  4559. if (api_get_setting('search_enabled') !== 'true') {
  4560. return false;
  4561. }
  4562. if (!extension_loaded('xapian')) {
  4563. return false;
  4564. }
  4565. $terms_string = trim($terms_string);
  4566. $terms = explode(',', $terms_string);
  4567. array_walk($terms, 'trim_value');
  4568. $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
  4569. // Don't do anything if no change, verify only at DB, not the search engine.
  4570. if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
  4571. return false;
  4572. }
  4573. require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
  4574. require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
  4575. $items_table = Database::get_course_table(TABLE_LP_ITEM);
  4576. // TODO: Make query secure agains XSS : use member attr instead of post var.
  4577. $lp_id = (int) $_POST['lp_id'];
  4578. $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
  4579. $result = Database::query($sql);
  4580. $di = new ChamiloIndexer();
  4581. while ($lp_item = Database::fetch_array($result)) {
  4582. // Get search_did.
  4583. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  4584. $sql = 'SELECT * FROM %s
  4585. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
  4586. LIMIT 1';
  4587. $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
  4588. //echo $sql; echo '<br>';
  4589. $res = Database::query($sql);
  4590. if (Database::num_rows($res) > 0) {
  4591. $se_ref = Database::fetch_array($res);
  4592. // Compare terms.
  4593. $doc = $di->get_document($se_ref['search_did']);
  4594. $xapian_terms = xapian_get_doc_terms($doc, $prefix);
  4595. $xterms = [];
  4596. foreach ($xapian_terms as $xapian_term) {
  4597. $xterms[] = substr($xapian_term['name'], 1);
  4598. }
  4599. $dterms = $terms;
  4600. $missing_terms = array_diff($dterms, $xterms);
  4601. $deprecated_terms = array_diff($xterms, $dterms);
  4602. // Save it to search engine.
  4603. foreach ($missing_terms as $term) {
  4604. $doc->add_term($prefix.$term, 1);
  4605. }
  4606. foreach ($deprecated_terms as $term) {
  4607. $doc->remove_term($prefix.$term);
  4608. }
  4609. $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
  4610. $di->getDb()->flush();
  4611. }
  4612. }
  4613. return true;
  4614. }
  4615. /**
  4616. * Sets the theme of the LP (local/remote) (and save).
  4617. *
  4618. * @param string $name Optional string giving the new theme of this learnpath
  4619. *
  4620. * @return bool Returns true if theme name is not empty
  4621. */
  4622. public function set_theme($name = '')
  4623. {
  4624. $this->theme = $name;
  4625. $table = Database::get_course_table(TABLE_LP_MAIN);
  4626. $lp_id = $this->get_id();
  4627. $sql = "UPDATE $table
  4628. SET theme = '".Database::escape_string($this->theme)."'
  4629. WHERE iid = $lp_id";
  4630. Database::query($sql);
  4631. return true;
  4632. }
  4633. /**
  4634. * Sets the image of an LP (and save).
  4635. *
  4636. * @param string $name Optional string giving the new image of this learnpath
  4637. *
  4638. * @return bool Returns true if theme name is not empty
  4639. */
  4640. public function set_preview_image($name = '')
  4641. {
  4642. $this->preview_image = $name;
  4643. $table = Database::get_course_table(TABLE_LP_MAIN);
  4644. $lp_id = $this->get_id();
  4645. $sql = "UPDATE $table SET
  4646. preview_image = '".Database::escape_string($this->preview_image)."'
  4647. WHERE iid = $lp_id";
  4648. Database::query($sql);
  4649. return true;
  4650. }
  4651. /**
  4652. * Sets the author of a LP (and save).
  4653. *
  4654. * @param string $name Optional string giving the new author of this learnpath
  4655. *
  4656. * @return bool Returns true if author's name is not empty
  4657. */
  4658. public function set_author($name = '')
  4659. {
  4660. $this->author = $name;
  4661. $table = Database::get_course_table(TABLE_LP_MAIN);
  4662. $lp_id = $this->get_id();
  4663. $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
  4664. WHERE iid = $lp_id";
  4665. Database::query($sql);
  4666. return true;
  4667. }
  4668. /**
  4669. * Sets the hide_toc_frame parameter of a LP (and save).
  4670. *
  4671. * @param int $hide 1 if frame is hidden 0 then else
  4672. *
  4673. * @return bool Returns true if author's name is not empty
  4674. */
  4675. public function set_hide_toc_frame($hide)
  4676. {
  4677. if (intval($hide) == $hide) {
  4678. $this->hide_toc_frame = $hide;
  4679. $table = Database::get_course_table(TABLE_LP_MAIN);
  4680. $lp_id = $this->get_id();
  4681. $sql = "UPDATE $table SET
  4682. hide_toc_frame = '".(int) $this->hide_toc_frame."'
  4683. WHERE iid = $lp_id";
  4684. Database::query($sql);
  4685. return true;
  4686. }
  4687. return false;
  4688. }
  4689. /**
  4690. * Sets the prerequisite of a LP (and save).
  4691. *
  4692. * @param int $prerequisite integer giving the new prerequisite of this learnpath
  4693. *
  4694. * @return bool returns true if prerequisite is not empty
  4695. */
  4696. public function set_prerequisite($prerequisite)
  4697. {
  4698. $this->prerequisite = (int) $prerequisite;
  4699. $table = Database::get_course_table(TABLE_LP_MAIN);
  4700. $lp_id = $this->get_id();
  4701. $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
  4702. WHERE iid = $lp_id";
  4703. Database::query($sql);
  4704. return true;
  4705. }
  4706. /**
  4707. * Sets the location/proximity of the LP (local/remote) (and save).
  4708. *
  4709. * @param string $name Optional string giving the new location of this learnpath
  4710. *
  4711. * @return bool True on success / False on error
  4712. */
  4713. public function set_proximity($name = '')
  4714. {
  4715. if (empty($name)) {
  4716. return false;
  4717. }
  4718. $this->proximity = $name;
  4719. $table = Database::get_course_table(TABLE_LP_MAIN);
  4720. $lp_id = $this->get_id();
  4721. $sql = "UPDATE $table SET
  4722. content_local = '".Database::escape_string($name)."'
  4723. WHERE iid = $lp_id";
  4724. Database::query($sql);
  4725. return true;
  4726. }
  4727. /**
  4728. * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
  4729. *
  4730. * @param int $id DB ID of the item
  4731. */
  4732. public function set_previous_item($id)
  4733. {
  4734. if ($this->debug > 0) {
  4735. error_log('In learnpath::set_previous_item()', 0);
  4736. }
  4737. $this->last = $id;
  4738. }
  4739. /**
  4740. * Sets use_max_score.
  4741. *
  4742. * @param int $use_max_score Optional string giving the new location of this learnpath
  4743. *
  4744. * @return bool True on success / False on error
  4745. */
  4746. public function set_use_max_score($use_max_score = 1)
  4747. {
  4748. $use_max_score = (int) $use_max_score;
  4749. $this->use_max_score = $use_max_score;
  4750. $table = Database::get_course_table(TABLE_LP_MAIN);
  4751. $lp_id = $this->get_id();
  4752. $sql = "UPDATE $table SET
  4753. use_max_score = '".$this->use_max_score."'
  4754. WHERE iid = $lp_id";
  4755. Database::query($sql);
  4756. return true;
  4757. }
  4758. /**
  4759. * Sets and saves the expired_on date.
  4760. *
  4761. * @param string $expired_on Optional string giving the new author of this learnpath
  4762. *
  4763. * @throws \Doctrine\ORM\OptimisticLockException
  4764. *
  4765. * @return bool Returns true if author's name is not empty
  4766. */
  4767. public function set_expired_on($expired_on)
  4768. {
  4769. $em = Database::getManager();
  4770. /** @var CLp $lp */
  4771. $lp = $em
  4772. ->getRepository('ChamiloCourseBundle:CLp')
  4773. ->findOneBy(
  4774. [
  4775. 'iid' => $this->get_id(),
  4776. ]
  4777. );
  4778. if (!$lp) {
  4779. return false;
  4780. }
  4781. $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
  4782. $lp->setExpiredOn($this->expired_on);
  4783. $em->persist($lp);
  4784. $em->flush();
  4785. return true;
  4786. }
  4787. /**
  4788. * Sets and saves the publicated_on date.
  4789. *
  4790. * @param string $publicated_on Optional string giving the new author of this learnpath
  4791. *
  4792. * @throws \Doctrine\ORM\OptimisticLockException
  4793. *
  4794. * @return bool Returns true if author's name is not empty
  4795. */
  4796. public function set_publicated_on($publicated_on)
  4797. {
  4798. $em = Database::getManager();
  4799. /** @var CLp $lp */
  4800. $lp = $em
  4801. ->getRepository('ChamiloCourseBundle:CLp')
  4802. ->findOneBy(
  4803. [
  4804. 'iid' => $this->get_id(),
  4805. ]
  4806. );
  4807. if (!$lp) {
  4808. return false;
  4809. }
  4810. $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
  4811. $lp->setPublicatedOn($this->publicated_on);
  4812. $em->persist($lp);
  4813. $em->flush();
  4814. return true;
  4815. }
  4816. /**
  4817. * Sets and saves the expired_on date.
  4818. *
  4819. * @return bool Returns true if author's name is not empty
  4820. */
  4821. public function set_modified_on()
  4822. {
  4823. $this->modified_on = api_get_utc_datetime();
  4824. $table = Database::get_course_table(TABLE_LP_MAIN);
  4825. $lp_id = $this->get_id();
  4826. $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
  4827. WHERE iid = $lp_id";
  4828. Database::query($sql);
  4829. return true;
  4830. }
  4831. /**
  4832. * Sets the object's error message.
  4833. *
  4834. * @param string $error Error message. If empty, reinits the error string
  4835. */
  4836. public function set_error_msg($error = '')
  4837. {
  4838. if ($this->debug > 0) {
  4839. error_log('In learnpath::set_error_msg()', 0);
  4840. }
  4841. if (empty($error)) {
  4842. $this->error = '';
  4843. } else {
  4844. $this->error .= $error;
  4845. }
  4846. }
  4847. /**
  4848. * Launches the current item if not 'sco'
  4849. * (starts timer and make sure there is a record ready in the DB).
  4850. *
  4851. * @param bool $allow_new_attempt Whether to allow a new attempt or not
  4852. *
  4853. * @return bool
  4854. */
  4855. public function start_current_item($allow_new_attempt = false)
  4856. {
  4857. $debug = $this->debug;
  4858. if ($debug) {
  4859. error_log('In learnpath::start_current_item()');
  4860. error_log('current: '.$this->current);
  4861. }
  4862. if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
  4863. $type = $this->get_type();
  4864. $item_type = $this->items[$this->current]->get_type();
  4865. if (($type == 2 && $item_type != 'sco') ||
  4866. ($type == 3 && $item_type != 'au') ||
  4867. ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
  4868. ) {
  4869. if ($debug) {
  4870. error_log('item type: '.$item_type);
  4871. error_log('lp type: '.$type);
  4872. }
  4873. $this->items[$this->current]->open($allow_new_attempt);
  4874. $this->autocomplete_parents($this->current);
  4875. $prereq_check = $this->prerequisites_match($this->current);
  4876. if ($debug) {
  4877. error_log('start_current_item will save item with prereq: '.$prereq_check);
  4878. }
  4879. $this->items[$this->current]->save(false, $prereq_check);
  4880. }
  4881. // If sco, then it is supposed to have been updated by some other call.
  4882. if ($item_type == 'sco') {
  4883. $this->items[$this->current]->restart();
  4884. }
  4885. }
  4886. if ($debug) {
  4887. error_log('lp_view_session_id');
  4888. error_log($this->lp_view_session_id);
  4889. error_log('api session id');
  4890. error_log(api_get_session_id());
  4891. error_log('End of learnpath::start_current_item()');
  4892. }
  4893. return true;
  4894. }
  4895. /**
  4896. * Stops the processing and counters for the old item (as held in $this->last).
  4897. *
  4898. * @return bool True/False
  4899. */
  4900. public function stop_previous_item()
  4901. {
  4902. $debug = $this->debug;
  4903. if ($debug) {
  4904. error_log('In learnpath::stop_previous_item()', 0);
  4905. }
  4906. if ($this->last != 0 && $this->last != $this->current &&
  4907. isset($this->items[$this->last]) && is_object($this->items[$this->last])
  4908. ) {
  4909. if ($debug) {
  4910. error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
  4911. }
  4912. switch ($this->get_type()) {
  4913. case '3':
  4914. if ($this->items[$this->last]->get_type() != 'au') {
  4915. if ($debug) {
  4916. error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
  4917. }
  4918. $this->items[$this->last]->close();
  4919. } else {
  4920. if ($debug) {
  4921. error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
  4922. }
  4923. }
  4924. break;
  4925. case '2':
  4926. if ($this->items[$this->last]->get_type() != 'sco') {
  4927. if ($debug) {
  4928. error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
  4929. }
  4930. $this->items[$this->last]->close();
  4931. } else {
  4932. if ($debug) {
  4933. error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
  4934. }
  4935. }
  4936. break;
  4937. case '1':
  4938. default:
  4939. if ($debug) {
  4940. error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
  4941. }
  4942. $this->items[$this->last]->close();
  4943. break;
  4944. }
  4945. } else {
  4946. if ($debug) {
  4947. error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
  4948. }
  4949. return false;
  4950. }
  4951. return true;
  4952. }
  4953. /**
  4954. * Updates the default view mode from fullscreen to embedded and inversely.
  4955. *
  4956. * @return string The current default view mode ('fullscreen' or 'embedded')
  4957. */
  4958. public function update_default_view_mode()
  4959. {
  4960. $table = Database::get_course_table(TABLE_LP_MAIN);
  4961. $sql = "SELECT * FROM $table
  4962. WHERE iid = ".$this->get_id();
  4963. $res = Database::query($sql);
  4964. if (Database::num_rows($res) > 0) {
  4965. $row = Database::fetch_array($res);
  4966. $default_view_mode = $row['default_view_mod'];
  4967. $view_mode = $default_view_mode;
  4968. switch ($default_view_mode) {
  4969. case 'fullscreen': // default with popup
  4970. $view_mode = 'embedded';
  4971. break;
  4972. case 'embedded': // default view with left menu
  4973. $view_mode = 'embedframe';
  4974. break;
  4975. case 'embedframe': //folded menu
  4976. $view_mode = 'impress';
  4977. break;
  4978. case 'impress':
  4979. $view_mode = 'fullscreen';
  4980. break;
  4981. }
  4982. $sql = "UPDATE $table SET default_view_mod = '$view_mode'
  4983. WHERE iid = ".$this->get_id();
  4984. Database::query($sql);
  4985. $this->mode = $view_mode;
  4986. return $view_mode;
  4987. }
  4988. return -1;
  4989. }
  4990. /**
  4991. * Updates the default behaviour about auto-commiting SCORM updates.
  4992. *
  4993. * @return bool True if auto-commit has been set to 'on', false otherwise
  4994. */
  4995. public function update_default_scorm_commit()
  4996. {
  4997. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  4998. $sql = "SELECT * FROM $lp_table
  4999. WHERE iid = ".$this->get_id();
  5000. $res = Database::query($sql);
  5001. if (Database::num_rows($res) > 0) {
  5002. $row = Database::fetch_array($res);
  5003. $force = $row['force_commit'];
  5004. if ($force == 1) {
  5005. $force = 0;
  5006. $force_return = false;
  5007. } elseif ($force == 0) {
  5008. $force = 1;
  5009. $force_return = true;
  5010. }
  5011. $sql = "UPDATE $lp_table SET force_commit = $force
  5012. WHERE iid = ".$this->get_id();
  5013. Database::query($sql);
  5014. $this->force_commit = $force_return;
  5015. return $force_return;
  5016. }
  5017. return -1;
  5018. }
  5019. /**
  5020. * Updates the order of learning paths (goes through all of them by order and fills the gaps).
  5021. *
  5022. * @return bool True on success, false on failure
  5023. */
  5024. public function update_display_order()
  5025. {
  5026. $course_id = api_get_course_int_id();
  5027. $table = Database::get_course_table(TABLE_LP_MAIN);
  5028. $sql = "SELECT * FROM $table
  5029. WHERE c_id = $course_id
  5030. ORDER BY display_order";
  5031. $res = Database::query($sql);
  5032. if ($res === false) {
  5033. return false;
  5034. }
  5035. $num = Database::num_rows($res);
  5036. // First check the order is correct, globally (might be wrong because
  5037. // of versions < 1.8.4).
  5038. if ($num > 0) {
  5039. $i = 1;
  5040. while ($row = Database::fetch_array($res)) {
  5041. if ($row['display_order'] != $i) {
  5042. // If we find a gap in the order, we need to fix it.
  5043. $sql = "UPDATE $table SET display_order = $i
  5044. WHERE iid = ".$row['iid'];
  5045. Database::query($sql);
  5046. }
  5047. $i++;
  5048. }
  5049. }
  5050. return true;
  5051. }
  5052. /**
  5053. * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
  5054. *
  5055. * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
  5056. */
  5057. public function update_reinit()
  5058. {
  5059. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5060. $sql = "SELECT * FROM $lp_table
  5061. WHERE iid = ".$this->get_id();
  5062. $res = Database::query($sql);
  5063. if (Database::num_rows($res) > 0) {
  5064. $row = Database::fetch_array($res);
  5065. $force = $row['prevent_reinit'];
  5066. if ($force == 1) {
  5067. $force = 0;
  5068. } elseif ($force == 0) {
  5069. $force = 1;
  5070. }
  5071. $sql = "UPDATE $lp_table SET prevent_reinit = $force
  5072. WHERE iid = ".$this->get_id();
  5073. Database::query($sql);
  5074. $this->prevent_reinit = $force;
  5075. return $force;
  5076. }
  5077. return -1;
  5078. }
  5079. /**
  5080. * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
  5081. *
  5082. * @return string 'single', 'multi' or 'seriousgame'
  5083. *
  5084. * @author ndiechburg <noel@cblue.be>
  5085. */
  5086. public function get_attempt_mode()
  5087. {
  5088. //Set default value for seriousgame_mode
  5089. if (!isset($this->seriousgame_mode)) {
  5090. $this->seriousgame_mode = 0;
  5091. }
  5092. // Set default value for prevent_reinit
  5093. if (!isset($this->prevent_reinit)) {
  5094. $this->prevent_reinit = 1;
  5095. }
  5096. if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
  5097. return 'seriousgame';
  5098. }
  5099. if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
  5100. return 'single';
  5101. }
  5102. if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
  5103. return 'multiple';
  5104. }
  5105. return 'single';
  5106. }
  5107. /**
  5108. * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
  5109. *
  5110. * @param string 'seriousgame', 'single' or 'multiple'
  5111. *
  5112. * @return bool
  5113. *
  5114. * @author ndiechburg <noel@cblue.be>
  5115. */
  5116. public function set_attempt_mode($mode)
  5117. {
  5118. switch ($mode) {
  5119. case 'seriousgame':
  5120. $sg_mode = 1;
  5121. $prevent_reinit = 1;
  5122. break;
  5123. case 'single':
  5124. $sg_mode = 0;
  5125. $prevent_reinit = 1;
  5126. break;
  5127. case 'multiple':
  5128. $sg_mode = 0;
  5129. $prevent_reinit = 0;
  5130. break;
  5131. default:
  5132. $sg_mode = 0;
  5133. $prevent_reinit = 0;
  5134. break;
  5135. }
  5136. $this->prevent_reinit = $prevent_reinit;
  5137. $this->seriousgame_mode = $sg_mode;
  5138. $table = Database::get_course_table(TABLE_LP_MAIN);
  5139. $sql = "UPDATE $table SET
  5140. prevent_reinit = $prevent_reinit ,
  5141. seriousgame_mode = $sg_mode
  5142. WHERE iid = ".$this->get_id();
  5143. $res = Database::query($sql);
  5144. if ($res) {
  5145. return true;
  5146. } else {
  5147. return false;
  5148. }
  5149. }
  5150. /**
  5151. * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
  5152. *
  5153. * @author ndiechburg <noel@cblue.be>
  5154. */
  5155. public function switch_attempt_mode()
  5156. {
  5157. $mode = $this->get_attempt_mode();
  5158. switch ($mode) {
  5159. case 'single':
  5160. $next_mode = 'multiple';
  5161. break;
  5162. case 'multiple':
  5163. $next_mode = 'seriousgame';
  5164. break;
  5165. case 'seriousgame':
  5166. default:
  5167. $next_mode = 'single';
  5168. break;
  5169. }
  5170. $this->set_attempt_mode($next_mode);
  5171. }
  5172. /**
  5173. * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
  5174. * but possibility to do again a completed item.
  5175. *
  5176. * @return bool true if seriousgame_mode has been set to 1, false otherwise
  5177. *
  5178. * @author ndiechburg <noel@cblue.be>
  5179. */
  5180. public function set_seriousgame_mode()
  5181. {
  5182. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5183. $sql = "SELECT * FROM $lp_table
  5184. WHERE iid = ".$this->get_id();
  5185. $res = Database::query($sql);
  5186. if (Database::num_rows($res) > 0) {
  5187. $row = Database::fetch_array($res);
  5188. $force = $row['seriousgame_mode'];
  5189. if ($force == 1) {
  5190. $force = 0;
  5191. } elseif ($force == 0) {
  5192. $force = 1;
  5193. }
  5194. $sql = "UPDATE $lp_table SET seriousgame_mode = $force
  5195. WHERE iid = ".$this->get_id();
  5196. Database::query($sql);
  5197. $this->seriousgame_mode = $force;
  5198. return $force;
  5199. }
  5200. return -1;
  5201. }
  5202. /**
  5203. * Updates the "scorm_debug" value that shows or hide the debug window.
  5204. *
  5205. * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
  5206. */
  5207. public function update_scorm_debug()
  5208. {
  5209. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  5210. $sql = "SELECT * FROM $lp_table
  5211. WHERE iid = ".$this->get_id();
  5212. $res = Database::query($sql);
  5213. if (Database::num_rows($res) > 0) {
  5214. $row = Database::fetch_array($res);
  5215. $force = $row['debug'];
  5216. if ($force == 1) {
  5217. $force = 0;
  5218. } elseif ($force == 0) {
  5219. $force = 1;
  5220. }
  5221. $sql = "UPDATE $lp_table SET debug = $force
  5222. WHERE iid = ".$this->get_id();
  5223. Database::query($sql);
  5224. $this->scorm_debug = $force;
  5225. return $force;
  5226. }
  5227. return -1;
  5228. }
  5229. /**
  5230. * Function that makes a call to the function sort_tree_array and create_tree_array.
  5231. *
  5232. * @author Kevin Van Den Haute
  5233. *
  5234. * @param array
  5235. */
  5236. public function tree_array($array)
  5237. {
  5238. $array = $this->sort_tree_array($array);
  5239. $this->create_tree_array($array);
  5240. }
  5241. /**
  5242. * Creates an array with the elements of the learning path tree in it.
  5243. *
  5244. * @author Kevin Van Den Haute
  5245. *
  5246. * @param array $array
  5247. * @param int $parent
  5248. * @param int $depth
  5249. * @param array $tmp
  5250. */
  5251. public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
  5252. {
  5253. if (is_array($array)) {
  5254. for ($i = 0; $i < count($array); $i++) {
  5255. if ($array[$i]['parent_item_id'] == $parent) {
  5256. if (!in_array($array[$i]['parent_item_id'], $tmp)) {
  5257. $tmp[] = $array[$i]['parent_item_id'];
  5258. $depth++;
  5259. }
  5260. $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
  5261. $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
  5262. $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
  5263. $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
  5264. $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
  5265. $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
  5266. $this->arrMenu[] = [
  5267. 'id' => $array[$i]['id'],
  5268. 'ref' => $ref,
  5269. 'item_type' => $array[$i]['item_type'],
  5270. 'title' => $array[$i]['title'],
  5271. 'title_raw' => $array[$i]['title_raw'],
  5272. 'path' => $path,
  5273. 'description' => $array[$i]['description'],
  5274. 'parent_item_id' => $array[$i]['parent_item_id'],
  5275. 'previous_item_id' => $array[$i]['previous_item_id'],
  5276. 'next_item_id' => $array[$i]['next_item_id'],
  5277. 'min_score' => $array[$i]['min_score'],
  5278. 'max_score' => $array[$i]['max_score'],
  5279. 'mastery_score' => $array[$i]['mastery_score'],
  5280. 'display_order' => $array[$i]['display_order'],
  5281. 'prerequisite' => $preq,
  5282. 'depth' => $depth,
  5283. 'audio' => $audio,
  5284. 'prerequisite_min_score' => $prerequisiteMinScore,
  5285. 'prerequisite_max_score' => $prerequisiteMaxScore,
  5286. ];
  5287. $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
  5288. }
  5289. }
  5290. }
  5291. }
  5292. /**
  5293. * Sorts a multi dimensional array by parent id and display order.
  5294. *
  5295. * @author Kevin Van Den Haute
  5296. *
  5297. * @param array $array (array with al the learning path items in it)
  5298. *
  5299. * @return array
  5300. */
  5301. public function sort_tree_array($array)
  5302. {
  5303. foreach ($array as $key => $row) {
  5304. $parent[$key] = $row['parent_item_id'];
  5305. $position[$key] = $row['display_order'];
  5306. }
  5307. if (count($array) > 0) {
  5308. array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
  5309. }
  5310. return $array;
  5311. }
  5312. /**
  5313. * Function that creates a html list of learning path items so that we can add audio files to them.
  5314. *
  5315. * @author Kevin Van Den Haute
  5316. *
  5317. * @return string
  5318. */
  5319. public function overview()
  5320. {
  5321. $return = '';
  5322. $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
  5323. // we need to start a form when we want to update all the mp3 files
  5324. if ($update_audio == 'true') {
  5325. $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">';
  5326. }
  5327. $return .= '<div id="message"></div>';
  5328. if (count($this->items) == 0) {
  5329. $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
  5330. } else {
  5331. $return_audio = '<table class="data_table">';
  5332. $return_audio .= '<tr>';
  5333. $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
  5334. $return_audio .= '<th>'.get_lang('Audio').'</th>';
  5335. $return_audio .= '</tr>';
  5336. if ($update_audio != 'true') {
  5337. $return .= '<div class="col-md-12">';
  5338. $return .= self::return_new_tree($update_audio);
  5339. $return .= '</div>';
  5340. $return .= Display::div(
  5341. Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
  5342. ['style' => 'float:left; margin-top:15px;width:100%']
  5343. );
  5344. } else {
  5345. $return_audio .= self::return_new_tree($update_audio);
  5346. $return .= $return_audio.'</table>';
  5347. }
  5348. // We need to close the form when we are updating the mp3 files.
  5349. if ($update_audio == 'true') {
  5350. $return .= '<div class="footer-audio">';
  5351. $return .= Display::button(
  5352. 'save_audio',
  5353. '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
  5354. ['class' => 'btn btn-primary', 'type' => 'submit']
  5355. );
  5356. $return .= '</div>';
  5357. }
  5358. }
  5359. // We need to close the form when we are updating the mp3 files.
  5360. if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
  5361. $return .= '</form>';
  5362. }
  5363. return $return;
  5364. }
  5365. /**
  5366. * @param string $update_audio
  5367. *
  5368. * @return array
  5369. */
  5370. public function processBuildMenuElements($update_audio = 'false')
  5371. {
  5372. $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
  5373. $arrLP = $this->getItemsForForm();
  5374. $this->tree_array($arrLP);
  5375. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  5376. unset($this->arrMenu);
  5377. $default_data = null;
  5378. $default_content = null;
  5379. $elements = [];
  5380. $return_audio = null;
  5381. $iconPath = api_get_path(SYS_CODE_PATH).'img/';
  5382. $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
  5383. $countItems = count($arrLP);
  5384. $upIcon = Display::return_icon(
  5385. 'up.png',
  5386. get_lang('Up'),
  5387. [],
  5388. ICON_SIZE_TINY
  5389. );
  5390. $disableUpIcon = Display::return_icon(
  5391. 'up_na.png',
  5392. get_lang('Up'),
  5393. [],
  5394. ICON_SIZE_TINY
  5395. );
  5396. $downIcon = Display::return_icon(
  5397. 'down.png',
  5398. get_lang('Down'),
  5399. [],
  5400. ICON_SIZE_TINY
  5401. );
  5402. $disableDownIcon = Display::return_icon(
  5403. 'down_na.png',
  5404. get_lang('Down'),
  5405. [],
  5406. ICON_SIZE_TINY
  5407. );
  5408. $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
  5409. $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
  5410. $plugin = null;
  5411. if ($pluginCalendar) {
  5412. $plugin = LearningCalendarPlugin::create();
  5413. }
  5414. for ($i = 0; $i < $countItems; $i++) {
  5415. $parent_id = $arrLP[$i]['parent_item_id'];
  5416. $title = $arrLP[$i]['title'];
  5417. $title_cut = $arrLP[$i]['title_raw'];
  5418. if ($show === false) {
  5419. $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
  5420. }
  5421. // Link for the documents
  5422. if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
  5423. $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5424. $title_cut = Display::url(
  5425. $title_cut,
  5426. $url,
  5427. [
  5428. 'class' => 'ajax moved',
  5429. 'data-title' => $title,
  5430. 'title' => $title,
  5431. ]
  5432. );
  5433. }
  5434. // Detect if type is FINAL_ITEM to set path_id to SESSION
  5435. if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
  5436. Session::write('pathItem', $arrLP[$i]['path']);
  5437. }
  5438. $oddClass = 'row_even';
  5439. if (($i % 2) == 0) {
  5440. $oddClass = 'row_odd';
  5441. }
  5442. $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
  5443. $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
  5444. if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
  5445. $icon = Display::return_icon('lp_'.$icon_name.'.png');
  5446. } else {
  5447. if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
  5448. $icon = Display::return_icon('lp_'.$icon_name.'.gif');
  5449. } else {
  5450. if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
  5451. $icon = Display::return_icon('certificate.png');
  5452. } else {
  5453. $icon = Display::return_icon('folder_document.gif');
  5454. }
  5455. }
  5456. }
  5457. // The audio column.
  5458. $return_audio .= '<td align="left" style="padding-left:10px;">';
  5459. $audio = '';
  5460. if (!$update_audio || $update_audio != 'true') {
  5461. if (empty($arrLP[$i]['audio'])) {
  5462. $audio .= '';
  5463. }
  5464. } else {
  5465. $types = self::getChapterTypes();
  5466. if (!in_array($arrLP[$i]['item_type'], $types)) {
  5467. $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
  5468. if (!empty($arrLP[$i]['audio'])) {
  5469. $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
  5470. <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
  5471. }
  5472. }
  5473. }
  5474. $return_audio .= Display::span($icon.' '.$title).
  5475. Display::tag(
  5476. 'td',
  5477. $audio,
  5478. ['style' => '']
  5479. );
  5480. $return_audio .= '</td>';
  5481. $move_icon = '';
  5482. $move_item_icon = '';
  5483. $edit_icon = '';
  5484. $delete_icon = '';
  5485. $audio_icon = '';
  5486. $prerequisities_icon = '';
  5487. $forumIcon = '';
  5488. $previewIcon = '';
  5489. $pluginCalendarIcon = '';
  5490. $orderIcons = '';
  5491. $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
  5492. if ($is_allowed_to_edit) {
  5493. if (!$update_audio || $update_audio != 'true') {
  5494. if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
  5495. $move_icon .= '<a class="moved" href="#">';
  5496. $move_icon .= Display::return_icon(
  5497. 'move_everywhere.png',
  5498. get_lang('Move'),
  5499. [],
  5500. ICON_SIZE_TINY
  5501. );
  5502. $move_icon .= '</a>';
  5503. }
  5504. }
  5505. // No edit for this item types
  5506. if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
  5507. if ($arrLP[$i]['item_type'] != 'dir') {
  5508. $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">';
  5509. $edit_icon .= Display::return_icon(
  5510. 'edit.png',
  5511. get_lang('LearnpathEditModule'),
  5512. [],
  5513. ICON_SIZE_TINY
  5514. );
  5515. $edit_icon .= '</a>';
  5516. if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
  5517. $forumThread = null;
  5518. if (isset($this->items[$arrLP[$i]['id']])) {
  5519. $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
  5520. $this->course_int_id,
  5521. $this->lp_session_id
  5522. );
  5523. }
  5524. if ($forumThread) {
  5525. $forumIconUrl = $mainUrl.'&'.http_build_query([
  5526. 'action' => 'dissociate_forum',
  5527. 'id' => $arrLP[$i]['id'],
  5528. 'lp_id' => $this->lp_id,
  5529. ]);
  5530. $forumIcon = Display::url(
  5531. Display::return_icon(
  5532. 'forum.png',
  5533. get_lang('DissociateForumToLPItem'),
  5534. [],
  5535. ICON_SIZE_TINY
  5536. ),
  5537. $forumIconUrl,
  5538. ['class' => 'btn btn-default lp-btn-dissociate-forum']
  5539. );
  5540. } else {
  5541. $forumIconUrl = $mainUrl.'&'.http_build_query([
  5542. 'action' => 'create_forum',
  5543. 'id' => $arrLP[$i]['id'],
  5544. 'lp_id' => $this->lp_id,
  5545. ]);
  5546. $forumIcon = Display::url(
  5547. Display::return_icon(
  5548. 'forum.png',
  5549. get_lang('AssociateForumToLPItem'),
  5550. [],
  5551. ICON_SIZE_TINY
  5552. ),
  5553. $forumIconUrl,
  5554. ['class' => 'btn btn-default lp-btn-associate-forum']
  5555. );
  5556. }
  5557. }
  5558. } else {
  5559. $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">';
  5560. $edit_icon .= Display::return_icon(
  5561. 'edit.png',
  5562. get_lang('LearnpathEditModule'),
  5563. [],
  5564. ICON_SIZE_TINY
  5565. );
  5566. $edit_icon .= '</a>';
  5567. }
  5568. } else {
  5569. if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
  5570. $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
  5571. $edit_icon .= Display::return_icon(
  5572. 'edit.png',
  5573. get_lang('Edit'),
  5574. [],
  5575. ICON_SIZE_TINY
  5576. );
  5577. $edit_icon .= '</a>';
  5578. }
  5579. }
  5580. if ($pluginCalendar) {
  5581. $pluginLink = $pluginUrl.
  5582. '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5583. $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
  5584. $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
  5585. if ($itemInfo && $itemInfo['value'] == 1) {
  5586. $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
  5587. }
  5588. $pluginCalendarIcon = Display::url(
  5589. $iconCalendar,
  5590. $pluginLink,
  5591. ['class' => 'btn btn-default']
  5592. );
  5593. }
  5594. if ($arrLP[$i]['item_type'] != 'final_item') {
  5595. $orderIcons = Display::url(
  5596. $upIcon,
  5597. 'javascript:void(0)',
  5598. ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
  5599. );
  5600. $orderIcons .= Display::url(
  5601. $downIcon,
  5602. 'javascript:void(0)',
  5603. ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
  5604. );
  5605. }
  5606. $delete_icon .= ' <a
  5607. href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
  5608. onclick="return confirmation(\''.addslashes($title).'\');"
  5609. class="btn btn-default">';
  5610. $delete_icon .= Display::return_icon(
  5611. 'delete.png',
  5612. get_lang('LearnpathDeleteModule'),
  5613. [],
  5614. ICON_SIZE_TINY
  5615. );
  5616. $delete_icon .= '</a>';
  5617. $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5618. $previewImage = Display::return_icon(
  5619. 'preview_view.png',
  5620. get_lang('Preview'),
  5621. [],
  5622. ICON_SIZE_TINY
  5623. );
  5624. switch ($arrLP[$i]['item_type']) {
  5625. case TOOL_DOCUMENT:
  5626. case TOOL_LP_FINAL_ITEM:
  5627. case TOOL_READOUT_TEXT:
  5628. $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
  5629. $previewIcon = Display::url(
  5630. $previewImage,
  5631. $urlPreviewLink,
  5632. [
  5633. 'target' => '_blank',
  5634. 'class' => 'btn btn-default',
  5635. 'data-title' => $arrLP[$i]['title'],
  5636. 'title' => $arrLP[$i]['title'],
  5637. ]
  5638. );
  5639. break;
  5640. case TOOL_THREAD:
  5641. case TOOL_FORUM:
  5642. case TOOL_QUIZ:
  5643. case TOOL_STUDENTPUBLICATION:
  5644. case TOOL_LP_FINAL_ITEM:
  5645. case TOOL_LINK:
  5646. $class = 'btn btn-default';
  5647. $target = '_blank';
  5648. $link = self::rl_get_resource_link_for_learnpath(
  5649. $this->course_int_id,
  5650. $this->lp_id,
  5651. $arrLP[$i]['id'],
  5652. 0
  5653. );
  5654. $previewIcon = Display::url(
  5655. $previewImage,
  5656. $link,
  5657. [
  5658. 'class' => $class,
  5659. 'data-title' => $arrLP[$i]['title'],
  5660. 'title' => $arrLP[$i]['title'],
  5661. 'target' => $target,
  5662. ]
  5663. );
  5664. break;
  5665. default:
  5666. $previewIcon = Display::url(
  5667. $previewImage,
  5668. $url.'&action=view_item',
  5669. ['class' => 'btn btn-default', 'target' => '_blank']
  5670. );
  5671. break;
  5672. }
  5673. if ($arrLP[$i]['item_type'] != 'dir') {
  5674. $prerequisities_icon = Display::url(
  5675. Display::return_icon(
  5676. 'accept.png',
  5677. get_lang('LearnpathPrerequisites'),
  5678. [],
  5679. ICON_SIZE_TINY
  5680. ),
  5681. $url.'&action=edit_item_prereq',
  5682. ['class' => 'btn btn-default']
  5683. );
  5684. if ($arrLP[$i]['item_type'] != 'final_item') {
  5685. $move_item_icon = Display::url(
  5686. Display::return_icon(
  5687. 'move.png',
  5688. get_lang('Move'),
  5689. [],
  5690. ICON_SIZE_TINY
  5691. ),
  5692. $url.'&action=move_item',
  5693. ['class' => 'btn btn-default']
  5694. );
  5695. }
  5696. $audio_icon = Display::url(
  5697. Display::return_icon(
  5698. 'audio.png',
  5699. get_lang('UplUpload'),
  5700. [],
  5701. ICON_SIZE_TINY
  5702. ),
  5703. $url.'&action=add_audio',
  5704. ['class' => 'btn btn-default']
  5705. );
  5706. }
  5707. }
  5708. if ($update_audio != 'true') {
  5709. $row = $move_icon.' '.$icon.
  5710. Display::span($title_cut).
  5711. Display::tag(
  5712. 'div',
  5713. "<div class=\"btn-group btn-group-xs\">
  5714. $previewIcon
  5715. $audio
  5716. $edit_icon
  5717. $pluginCalendarIcon
  5718. $forumIcon
  5719. $prerequisities_icon
  5720. $move_item_icon
  5721. $audio_icon
  5722. $orderIcons
  5723. $delete_icon
  5724. </div>",
  5725. ['class' => 'btn-toolbar button_actions']
  5726. );
  5727. } else {
  5728. $row =
  5729. Display::span($title.$icon).
  5730. Display::span($audio, ['class' => 'button_actions']);
  5731. }
  5732. $default_data[$arrLP[$i]['id']] = $row;
  5733. $default_content[$arrLP[$i]['id']] = $arrLP[$i];
  5734. if (empty($parent_id)) {
  5735. $elements[$arrLP[$i]['id']]['data'] = $row;
  5736. $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
  5737. } else {
  5738. $parent_arrays = [];
  5739. if ($arrLP[$i]['depth'] > 1) {
  5740. // Getting list of parents
  5741. for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
  5742. foreach ($arrLP as $item) {
  5743. if ($item['id'] == $parent_id) {
  5744. if ($item['parent_item_id'] == 0) {
  5745. $parent_id = $item['id'];
  5746. break;
  5747. } else {
  5748. $parent_id = $item['parent_item_id'];
  5749. if (empty($parent_arrays)) {
  5750. $parent_arrays[] = intval($item['id']);
  5751. }
  5752. $parent_arrays[] = $parent_id;
  5753. break;
  5754. }
  5755. }
  5756. }
  5757. }
  5758. }
  5759. if (!empty($parent_arrays)) {
  5760. $parent_arrays = array_reverse($parent_arrays);
  5761. $val = '$elements';
  5762. $x = 0;
  5763. foreach ($parent_arrays as $item) {
  5764. if ($x != count($parent_arrays) - 1) {
  5765. $val .= '["'.$item.'"]["children"]';
  5766. } else {
  5767. $val .= '["'.$item.'"]["children"]';
  5768. }
  5769. $x++;
  5770. }
  5771. $val .= "";
  5772. $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
  5773. eval($code_str);
  5774. } else {
  5775. $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
  5776. $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
  5777. }
  5778. }
  5779. }
  5780. return [
  5781. 'elements' => $elements,
  5782. 'default_data' => $default_data,
  5783. 'default_content' => $default_content,
  5784. 'return_audio' => $return_audio,
  5785. ];
  5786. }
  5787. /**
  5788. * @param string $updateAudio true/false strings
  5789. *
  5790. * @return string
  5791. */
  5792. public function returnLpItemList($updateAudio)
  5793. {
  5794. $result = $this->processBuildMenuElements($updateAudio);
  5795. $html = self::print_recursive(
  5796. $result['elements'],
  5797. $result['default_data'],
  5798. $result['default_content']
  5799. );
  5800. if (!empty($html)) {
  5801. $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
  5802. }
  5803. return $html;
  5804. }
  5805. /**
  5806. * @param string $update_audio
  5807. * @param bool $drop_element_here
  5808. *
  5809. * @return string
  5810. */
  5811. public function return_new_tree($update_audio = 'false', $drop_element_here = false)
  5812. {
  5813. $result = $this->processBuildMenuElements($update_audio);
  5814. $list = '<ul id="lp_item_list">';
  5815. $tree = $this->print_recursive(
  5816. $result['elements'],
  5817. $result['default_data'],
  5818. $result['default_content']
  5819. );
  5820. if (!empty($tree)) {
  5821. $list .= $tree;
  5822. } else {
  5823. if ($drop_element_here) {
  5824. $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
  5825. }
  5826. }
  5827. $list .= '</ul>';
  5828. $return = Display::panelCollapse(
  5829. $this->name,
  5830. $list,
  5831. 'scorm-list',
  5832. null,
  5833. 'scorm-list-accordion',
  5834. 'scorm-list-collapse'
  5835. );
  5836. if ($update_audio === 'true') {
  5837. $return = $result['return_audio'];
  5838. }
  5839. return $return;
  5840. }
  5841. /**
  5842. * @param array $elements
  5843. * @param array $default_data
  5844. * @param array $default_content
  5845. *
  5846. * @return string
  5847. */
  5848. public function print_recursive($elements, $default_data, $default_content)
  5849. {
  5850. $return = '';
  5851. foreach ($elements as $key => $item) {
  5852. if (isset($item['load_data']) || empty($item['data'])) {
  5853. $item['data'] = $default_data[$item['load_data']];
  5854. $item['type'] = $default_content[$item['load_data']]['item_type'];
  5855. }
  5856. $sub_list = '';
  5857. if (isset($item['type']) && $item['type'] === 'dir') {
  5858. // empty value
  5859. $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
  5860. }
  5861. if (empty($item['children'])) {
  5862. $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
  5863. $active = null;
  5864. if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
  5865. $active = 'active';
  5866. }
  5867. $return .= Display::tag(
  5868. 'li',
  5869. Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
  5870. ['id' => $key, 'class' => 'record li_container']
  5871. );
  5872. } else {
  5873. // Sections
  5874. $data = '';
  5875. if (isset($item['children'])) {
  5876. $data = self::print_recursive($item['children'], $default_data, $default_content);
  5877. }
  5878. $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
  5879. $return .= Display::tag(
  5880. 'li',
  5881. Display::div($item['data'], ['class' => 'item_data']).$sub_list,
  5882. ['id' => $key, 'class' => 'record li_container']
  5883. );
  5884. }
  5885. }
  5886. return $return;
  5887. }
  5888. /**
  5889. * This function builds the action menu.
  5890. *
  5891. * @param bool $returnContent Optional
  5892. * @param bool $showRequirementButtons Optional. Allow show the requirements button
  5893. * @param bool $isConfigPage Optional. If is the config page, show the edit button
  5894. * @param bool $allowExpand Optional. Allow show the expand/contract button
  5895. *
  5896. * @return string
  5897. */
  5898. public function build_action_menu(
  5899. $returnContent = false,
  5900. $showRequirementButtons = true,
  5901. $isConfigPage = false,
  5902. $allowExpand = true
  5903. ) {
  5904. $actionsRight = '';
  5905. $actionsLeft = Display::url(
  5906. Display::return_icon(
  5907. 'back.png',
  5908. get_lang('ReturnToLearningPaths'),
  5909. '',
  5910. ICON_SIZE_MEDIUM
  5911. ),
  5912. 'lp_controller.php?'.api_get_cidreq()
  5913. );
  5914. $actionsLeft .= Display::url(
  5915. Display::return_icon(
  5916. 'preview_view.png',
  5917. get_lang('Preview'),
  5918. '',
  5919. ICON_SIZE_MEDIUM
  5920. ),
  5921. 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  5922. 'action' => 'view',
  5923. 'lp_id' => $this->lp_id,
  5924. 'isStudentView' => 'true',
  5925. ])
  5926. );
  5927. $actionsLeft .= Display::url(
  5928. Display::return_icon(
  5929. 'upload_audio.png',
  5930. get_lang('UpdateAllAudioFragments'),
  5931. '',
  5932. ICON_SIZE_MEDIUM
  5933. ),
  5934. 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  5935. 'action' => 'admin_view',
  5936. 'lp_id' => $this->lp_id,
  5937. 'updateaudio' => 'true',
  5938. ])
  5939. );
  5940. $subscriptionSettings = self::getSubscriptionSettings();
  5941. $request = api_request_uri();
  5942. if (strpos($request, 'edit') === false) {
  5943. $actionsLeft .= Display::url(
  5944. Display::return_icon(
  5945. 'settings.png',
  5946. get_lang('CourseSettings'),
  5947. '',
  5948. ICON_SIZE_MEDIUM
  5949. ),
  5950. 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  5951. 'action' => 'edit',
  5952. 'lp_id' => $this->lp_id,
  5953. ])
  5954. );
  5955. }
  5956. if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
  5957. $actionsLeft .= Display::url(
  5958. Display::return_icon(
  5959. 'edit.png',
  5960. get_lang('Edit'),
  5961. '',
  5962. ICON_SIZE_MEDIUM
  5963. ),
  5964. 'lp_controller.php?'.http_build_query([
  5965. 'action' => 'build',
  5966. 'lp_id' => $this->lp_id,
  5967. ]).'&'.api_get_cidreq()
  5968. );
  5969. }
  5970. if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
  5971. if ($this->subscribeUsers == 1 &&
  5972. $subscriptionSettings['allow_add_users_to_lp']) {
  5973. $actionsLeft .= Display::url(
  5974. Display::return_icon(
  5975. 'user.png',
  5976. get_lang('SubscribeUsersToLp'),
  5977. '',
  5978. ICON_SIZE_MEDIUM
  5979. ),
  5980. api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
  5981. );
  5982. }
  5983. }
  5984. if ($allowExpand) {
  5985. $actionsLeft .= Display::url(
  5986. Display::return_icon(
  5987. 'expand.png',
  5988. get_lang('Expand'),
  5989. ['id' => 'expand'],
  5990. ICON_SIZE_MEDIUM
  5991. ).
  5992. Display::return_icon(
  5993. 'contract.png',
  5994. get_lang('Collapse'),
  5995. ['id' => 'contract', 'class' => 'hide'],
  5996. ICON_SIZE_MEDIUM
  5997. ),
  5998. '#',
  5999. ['role' => 'button', 'id' => 'hide_bar_template']
  6000. );
  6001. }
  6002. if ($showRequirementButtons) {
  6003. $buttons = [
  6004. [
  6005. 'title' => get_lang('SetPrerequisiteForEachItem'),
  6006. 'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6007. 'action' => 'set_previous_step_as_prerequisite',
  6008. 'lp_id' => $this->lp_id,
  6009. ]),
  6010. ],
  6011. [
  6012. 'title' => get_lang('ClearAllPrerequisites'),
  6013. 'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
  6014. 'action' => 'clear_prerequisites',
  6015. 'lp_id' => $this->lp_id,
  6016. ]),
  6017. ],
  6018. ];
  6019. $actionsRight = Display::groupButtonWithDropDown(
  6020. get_lang('PrerequisitesOptions'),
  6021. $buttons,
  6022. true
  6023. );
  6024. }
  6025. $toolbar = Display::toolbarAction(
  6026. 'actions-lp-controller',
  6027. [$actionsLeft, $actionsRight]
  6028. );
  6029. if ($returnContent) {
  6030. return $toolbar;
  6031. }
  6032. echo $toolbar;
  6033. }
  6034. /**
  6035. * Creates the default learning path folder.
  6036. *
  6037. * @param array $course
  6038. * @param int $creatorId
  6039. *
  6040. * @return bool
  6041. */
  6042. public static function generate_learning_path_folder($course, $creatorId = 0)
  6043. {
  6044. // Creating learning_path folder
  6045. $dir = '/learning_path';
  6046. $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
  6047. $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
  6048. $folder = false;
  6049. if (!is_dir($filepath.'/'.$dir)) {
  6050. $folderData = create_unexisting_directory(
  6051. $course,
  6052. $creatorId,
  6053. 0,
  6054. null,
  6055. 0,
  6056. $filepath,
  6057. $dir,
  6058. get_lang('LearningPaths'),
  6059. 0
  6060. );
  6061. if (!empty($folderData)) {
  6062. $folder = true;
  6063. }
  6064. } else {
  6065. $folder = true;
  6066. }
  6067. return $folder;
  6068. }
  6069. /**
  6070. * @param array $course
  6071. * @param string $lp_name
  6072. * @param int $creatorId
  6073. *
  6074. * @return array
  6075. */
  6076. public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
  6077. {
  6078. $filepath = '';
  6079. $dir = '/learning_path/';
  6080. if (empty($lp_name)) {
  6081. $lp_name = $this->name;
  6082. }
  6083. $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
  6084. $folder = self::generate_learning_path_folder($course, $creatorId);
  6085. // Limits title size
  6086. $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
  6087. $dir = $dir.$title;
  6088. // Creating LP folder
  6089. $documentId = null;
  6090. if ($folder) {
  6091. $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
  6092. if (!is_dir($filepath.'/'.$dir)) {
  6093. $folderData = create_unexisting_directory(
  6094. $course,
  6095. $creatorId,
  6096. 0,
  6097. 0,
  6098. 0,
  6099. $filepath,
  6100. $dir,
  6101. $lp_name
  6102. );
  6103. if (!empty($folderData)) {
  6104. $folder = true;
  6105. }
  6106. $documentId = $folderData['id'];
  6107. } else {
  6108. $folder = true;
  6109. }
  6110. $dir = $dir.'/';
  6111. if ($folder) {
  6112. $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
  6113. }
  6114. }
  6115. if (empty($documentId)) {
  6116. $dir = api_remove_trailing_slash($dir);
  6117. $documentId = DocumentManager::get_document_id($course, $dir, 0);
  6118. }
  6119. $array = [
  6120. 'dir' => $dir,
  6121. 'filepath' => $filepath,
  6122. 'folder' => $folder,
  6123. 'id' => $documentId,
  6124. ];
  6125. return $array;
  6126. }
  6127. /**
  6128. * Create a new document //still needs some finetuning.
  6129. *
  6130. * @param array $courseInfo
  6131. * @param string $content
  6132. * @param string $title
  6133. * @param string $extension
  6134. * @param int $parentId
  6135. * @param int $creatorId creator id
  6136. *
  6137. * @return int
  6138. */
  6139. public function create_document(
  6140. $courseInfo,
  6141. $content = '',
  6142. $title = '',
  6143. $extension = 'html',
  6144. $parentId = 0,
  6145. $creatorId = 0
  6146. ) {
  6147. if (!empty($courseInfo)) {
  6148. $course_id = $courseInfo['real_id'];
  6149. } else {
  6150. $course_id = api_get_course_int_id();
  6151. }
  6152. $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
  6153. $sessionId = api_get_session_id();
  6154. // Generates folder
  6155. $result = $this->generate_lp_folder($courseInfo);
  6156. $dir = $result['dir'];
  6157. if (empty($parentId) || $parentId == '/') {
  6158. $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
  6159. $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
  6160. if ($parentId === '/') {
  6161. $dir = '/';
  6162. }
  6163. // Please, do not modify this dirname formatting.
  6164. if (strstr($dir, '..')) {
  6165. $dir = '/';
  6166. }
  6167. if (!empty($dir[0]) && $dir[0] == '.') {
  6168. $dir = substr($dir, 1);
  6169. }
  6170. if (!empty($dir[0]) && $dir[0] != '/') {
  6171. $dir = '/'.$dir;
  6172. }
  6173. if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
  6174. $dir .= '/';
  6175. }
  6176. } else {
  6177. $parentInfo = DocumentManager::get_document_data_by_id(
  6178. $parentId,
  6179. $courseInfo['code']
  6180. );
  6181. if (!empty($parentInfo)) {
  6182. $dir = $parentInfo['path'].'/';
  6183. }
  6184. }
  6185. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  6186. if (!is_dir($filepath)) {
  6187. $dir = '/';
  6188. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  6189. }
  6190. // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
  6191. // is already escaped twice when it gets here.
  6192. $originalTitle = !empty($title) ? $title : $_POST['title'];
  6193. if (!empty($title)) {
  6194. $title = api_replace_dangerous_char(stripslashes($title));
  6195. } else {
  6196. $title = api_replace_dangerous_char(stripslashes($_POST['title']));
  6197. }
  6198. $title = disable_dangerous_file($title);
  6199. $filename = $title;
  6200. $content = !empty($content) ? $content : $_POST['content_lp'];
  6201. $tmp_filename = $filename;
  6202. $i = 0;
  6203. while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
  6204. $tmp_filename = $filename.'_'.++$i;
  6205. }
  6206. $filename = $tmp_filename.'.'.$extension;
  6207. if ($extension == 'html') {
  6208. $content = stripslashes($content);
  6209. $content = str_replace(
  6210. api_get_path(WEB_COURSE_PATH),
  6211. api_get_path(REL_PATH).'courses/',
  6212. $content
  6213. );
  6214. // Change the path of mp3 to absolute.
  6215. // The first regexp deals with :// urls.
  6216. $content = preg_replace(
  6217. "|(flashvars=\"file=)([^:/]+)/|",
  6218. "$1".api_get_path(
  6219. REL_COURSE_PATH
  6220. ).$courseInfo['path'].'/document/',
  6221. $content
  6222. );
  6223. // The second regexp deals with audio/ urls.
  6224. $content = preg_replace(
  6225. "|(flashvars=\"file=)([^/]+)/|",
  6226. "$1".api_get_path(
  6227. REL_COURSE_PATH
  6228. ).$courseInfo['path'].'/document/$2/',
  6229. $content
  6230. );
  6231. // For flv player: To prevent edition problem with firefox,
  6232. // we have to use a strange tip (don't blame me please).
  6233. $content = str_replace(
  6234. '</body>',
  6235. '<style type="text/css">body{}</style></body>',
  6236. $content
  6237. );
  6238. }
  6239. if (!file_exists($filepath.$filename)) {
  6240. if ($fp = @fopen($filepath.$filename, 'w')) {
  6241. fputs($fp, $content);
  6242. fclose($fp);
  6243. $file_size = filesize($filepath.$filename);
  6244. $save_file_path = $dir.$filename;
  6245. $document_id = add_document(
  6246. $courseInfo,
  6247. $save_file_path,
  6248. 'file',
  6249. $file_size,
  6250. $tmp_filename,
  6251. '',
  6252. 0, //readonly
  6253. true,
  6254. null,
  6255. $sessionId,
  6256. $creatorId
  6257. );
  6258. if ($document_id) {
  6259. api_item_property_update(
  6260. $courseInfo,
  6261. TOOL_DOCUMENT,
  6262. $document_id,
  6263. 'DocumentAdded',
  6264. $creatorId,
  6265. null,
  6266. null,
  6267. null,
  6268. null,
  6269. $sessionId
  6270. );
  6271. $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
  6272. $new_title = $originalTitle;
  6273. if ($new_comment || $new_title) {
  6274. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6275. $ct = '';
  6276. if ($new_comment) {
  6277. $ct .= ", comment='".Database::escape_string($new_comment)."'";
  6278. }
  6279. if ($new_title) {
  6280. $ct .= ", title='".Database::escape_string($new_title)."' ";
  6281. }
  6282. $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
  6283. WHERE c_id = ".$course_id." AND id = ".$document_id;
  6284. Database::query($sql);
  6285. }
  6286. }
  6287. return $document_id;
  6288. }
  6289. }
  6290. }
  6291. /**
  6292. * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
  6293. *
  6294. * @param array $_course array
  6295. */
  6296. public function edit_document($_course)
  6297. {
  6298. $course_id = api_get_course_int_id();
  6299. $urlAppend = api_get_configuration_value('url_append');
  6300. // Please, do not modify this dirname formatting.
  6301. $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
  6302. $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
  6303. if (strstr($dir, '..')) {
  6304. $dir = '/';
  6305. }
  6306. if (isset($dir[0]) && $dir[0] == '.') {
  6307. $dir = substr($dir, 1);
  6308. }
  6309. if (isset($dir[0]) && $dir[0] != '/') {
  6310. $dir = '/'.$dir;
  6311. }
  6312. if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
  6313. $dir .= '/';
  6314. }
  6315. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
  6316. if (!is_dir($filepath)) {
  6317. $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
  6318. }
  6319. $table_doc = Database::get_course_table(TABLE_DOCUMENT);
  6320. if (isset($_POST['path']) && !empty($_POST['path'])) {
  6321. $document_id = (int) $_POST['path'];
  6322. $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
  6323. if (empty($documentInfo)) {
  6324. // Try with iid
  6325. $table = Database::get_course_table(TABLE_DOCUMENT);
  6326. $sql = "SELECT id, path FROM $table
  6327. WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
  6328. $res_doc = Database::query($sql);
  6329. $row = Database::fetch_array($res_doc);
  6330. if ($row) {
  6331. $document_id = $row['id'];
  6332. $documentPath = $row['path'];
  6333. }
  6334. } else {
  6335. $documentPath = $documentInfo['path'];
  6336. }
  6337. $content = stripslashes($_POST['content_lp']);
  6338. $file = $filepath.$documentPath;
  6339. if (!file_exists($file)) {
  6340. return false;
  6341. }
  6342. if ($fp = @fopen($file, 'w')) {
  6343. $content = str_replace(
  6344. api_get_path(WEB_COURSE_PATH),
  6345. $urlAppend.api_get_path(REL_COURSE_PATH),
  6346. $content
  6347. );
  6348. // Change the path of mp3 to absolute.
  6349. // The first regexp deals with :// urls.
  6350. $content = preg_replace(
  6351. "|(flashvars=\"file=)([^:/]+)/|",
  6352. "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
  6353. $content
  6354. );
  6355. // The second regexp deals with audio/ urls.
  6356. $content = preg_replace(
  6357. "|(flashvars=\"file=)([^:/]+)/|",
  6358. "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
  6359. $content
  6360. );
  6361. fputs($fp, $content);
  6362. fclose($fp);
  6363. $sql = "UPDATE $table_doc SET
  6364. title='".Database::escape_string($_POST['title'])."'
  6365. WHERE c_id = $course_id AND id = ".$document_id;
  6366. Database::query($sql);
  6367. }
  6368. }
  6369. }
  6370. /**
  6371. * Displays the selected item, with a panel for manipulating the item.
  6372. *
  6373. * @param int $item_id
  6374. * @param string $msg
  6375. * @param bool $show_actions
  6376. *
  6377. * @return string
  6378. */
  6379. public function display_item($item_id, $msg = null, $show_actions = true)
  6380. {
  6381. $course_id = api_get_course_int_id();
  6382. $return = '';
  6383. if (is_numeric($item_id)) {
  6384. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  6385. $sql = "SELECT lp.* FROM $tbl_lp_item as lp
  6386. WHERE lp.iid = ".intval($item_id);
  6387. $result = Database::query($sql);
  6388. while ($row = Database::fetch_array($result, 'ASSOC')) {
  6389. $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
  6390. // Prevents wrong parent selection for document, see Bug#1251.
  6391. if ($row['item_type'] != 'dir') {
  6392. $_SESSION['parent_item_id'] = $row['parent_item_id'];
  6393. }
  6394. if ($show_actions) {
  6395. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6396. }
  6397. $return .= '<div style="padding:10px;">';
  6398. if ($msg != '') {
  6399. $return .= $msg;
  6400. }
  6401. $return .= '<h3>'.$row['title'].'</h3>';
  6402. switch ($row['item_type']) {
  6403. case TOOL_THREAD:
  6404. $link = $this->rl_get_resource_link_for_learnpath(
  6405. $course_id,
  6406. $row['lp_id'],
  6407. $item_id,
  6408. 0
  6409. );
  6410. $return .= Display::url(
  6411. get_lang('GoToThread'),
  6412. $link,
  6413. ['class' => 'btn btn-primary']
  6414. );
  6415. break;
  6416. case TOOL_FORUM:
  6417. $return .= Display::url(
  6418. get_lang('GoToForum'),
  6419. api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
  6420. ['class' => 'btn btn-primary']
  6421. );
  6422. break;
  6423. case TOOL_QUIZ:
  6424. if (!empty($row['path'])) {
  6425. $exercise = new Exercise();
  6426. $exercise->read($row['path']);
  6427. $return .= $exercise->description.'<br />';
  6428. $return .= Display::url(
  6429. get_lang('GoToExercise'),
  6430. api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
  6431. ['class' => 'btn btn-primary']
  6432. );
  6433. }
  6434. break;
  6435. case TOOL_LP_FINAL_ITEM:
  6436. $return .= $this->getSavedFinalItem();
  6437. break;
  6438. case TOOL_DOCUMENT:
  6439. case TOOL_READOUT_TEXT:
  6440. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6441. $sql_doc = "SELECT path FROM $tbl_doc
  6442. WHERE c_id = $course_id AND iid = ".intval($row['path']);
  6443. $result = Database::query($sql_doc);
  6444. $path_file = Database::result($result, 0, 0);
  6445. $path_parts = pathinfo($path_file);
  6446. // TODO: Correct the following naive comparisons.
  6447. if (in_array($path_parts['extension'], [
  6448. 'html',
  6449. 'txt',
  6450. 'png',
  6451. 'jpg',
  6452. 'JPG',
  6453. 'jpeg',
  6454. 'JPEG',
  6455. 'gif',
  6456. 'swf',
  6457. 'pdf',
  6458. 'htm',
  6459. ])) {
  6460. $return .= $this->display_document($row['path'], true, true);
  6461. }
  6462. break;
  6463. case TOOL_HOTPOTATOES:
  6464. $return .= $this->display_document($row['path'], false, true);
  6465. break;
  6466. }
  6467. $return .= '</div>';
  6468. }
  6469. }
  6470. return $return;
  6471. }
  6472. /**
  6473. * Shows the needed forms for editing a specific item.
  6474. *
  6475. * @param int $item_id
  6476. *
  6477. * @throws Exception
  6478. * @throws HTML_QuickForm_Error
  6479. *
  6480. * @return string
  6481. */
  6482. public function display_edit_item($item_id)
  6483. {
  6484. $course_id = api_get_course_int_id();
  6485. $return = '';
  6486. $item_id = (int) $item_id;
  6487. if (empty($item_id)) {
  6488. return '';
  6489. }
  6490. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  6491. $sql = "SELECT * FROM $tbl_lp_item
  6492. WHERE iid = ".$item_id;
  6493. $res = Database::query($sql);
  6494. $row = Database::fetch_array($res);
  6495. switch ($row['item_type']) {
  6496. case 'dir':
  6497. case 'asset':
  6498. case 'sco':
  6499. if (isset($_GET['view']) && $_GET['view'] == 'build') {
  6500. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6501. $return .= $this->display_item_form(
  6502. $row['item_type'],
  6503. get_lang('EditCurrentChapter').' :',
  6504. 'edit',
  6505. $item_id,
  6506. $row
  6507. );
  6508. } else {
  6509. $return .= $this->display_item_form(
  6510. $row['item_type'],
  6511. get_lang('EditCurrentChapter').' :',
  6512. 'edit_item',
  6513. $item_id,
  6514. $row
  6515. );
  6516. }
  6517. break;
  6518. case TOOL_DOCUMENT:
  6519. case TOOL_READOUT_TEXT:
  6520. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6521. $sql = "SELECT lp.*, doc.path as dir
  6522. FROM $tbl_lp_item as lp
  6523. LEFT JOIN $tbl_doc as doc
  6524. ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
  6525. WHERE
  6526. doc.c_id = $course_id AND
  6527. lp.iid = ".$item_id;
  6528. $res_step = Database::query($sql);
  6529. $row_step = Database::fetch_array($res_step, 'ASSOC');
  6530. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6531. if ($row['item_type'] === TOOL_DOCUMENT) {
  6532. $return .= $this->display_document_form('edit', $item_id, $row_step);
  6533. }
  6534. if ($row['item_type'] === TOOL_READOUT_TEXT) {
  6535. $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
  6536. }
  6537. break;
  6538. case TOOL_LINK:
  6539. $linkId = (int) $row['path'];
  6540. if (!empty($linkId)) {
  6541. $table = Database::get_course_table(TABLE_LINK);
  6542. $sql = 'SELECT url FROM '.$table.'
  6543. WHERE c_id = '.$course_id.' AND iid = '.$linkId;
  6544. $res_link = Database::query($sql);
  6545. $row_link = Database::fetch_array($res_link);
  6546. if (empty($row_link)) {
  6547. // Try with id
  6548. $sql = 'SELECT url FROM '.$table.'
  6549. WHERE c_id = '.$course_id.' AND id = '.$linkId;
  6550. $res_link = Database::query($sql);
  6551. $row_link = Database::fetch_array($res_link);
  6552. }
  6553. if (is_array($row_link)) {
  6554. $row['url'] = $row_link['url'];
  6555. }
  6556. }
  6557. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6558. $return .= $this->display_link_form('edit', $item_id, $row);
  6559. break;
  6560. case TOOL_LP_FINAL_ITEM:
  6561. Session::write('finalItem', true);
  6562. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  6563. $sql = "SELECT lp.*, doc.path as dir
  6564. FROM $tbl_lp_item as lp
  6565. LEFT JOIN $tbl_doc as doc
  6566. ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
  6567. WHERE
  6568. doc.c_id = $course_id AND
  6569. lp.iid = ".$item_id;
  6570. $res_step = Database::query($sql);
  6571. $row_step = Database::fetch_array($res_step, 'ASSOC');
  6572. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6573. $return .= $this->display_document_form('edit', $item_id, $row_step);
  6574. break;
  6575. case TOOL_QUIZ:
  6576. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6577. $return .= $this->display_quiz_form('edit', $item_id, $row);
  6578. break;
  6579. case TOOL_HOTPOTATOES:
  6580. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6581. $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
  6582. break;
  6583. case TOOL_STUDENTPUBLICATION:
  6584. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6585. $return .= $this->display_student_publication_form('edit', $item_id, $row);
  6586. break;
  6587. case TOOL_FORUM:
  6588. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6589. $return .= $this->display_forum_form('edit', $item_id, $row);
  6590. break;
  6591. case TOOL_THREAD:
  6592. $return .= $this->display_manipulate($item_id, $row['item_type']);
  6593. $return .= $this->display_thread_form('edit', $item_id, $row);
  6594. break;
  6595. }
  6596. return $return;
  6597. }
  6598. /**
  6599. * Function that displays a list with al the resources that
  6600. * could be added to the learning path.
  6601. *
  6602. * @throws Exception
  6603. * @throws HTML_QuickForm_Error
  6604. *
  6605. * @return bool
  6606. */
  6607. public function display_resources()
  6608. {
  6609. $course_code = api_get_course_id();
  6610. // Get all the docs.
  6611. $documents = $this->get_documents(true);
  6612. // Get all the exercises.
  6613. $exercises = $this->get_exercises();
  6614. // Get all the links.
  6615. $links = $this->get_links();
  6616. // Get all the student publications.
  6617. $works = $this->get_student_publications();
  6618. // Get all the forums.
  6619. $forums = $this->get_forums(null, $course_code);
  6620. // Get the final item form (see BT#11048) .
  6621. $finish = $this->getFinalItemForm();
  6622. $headers = [
  6623. Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
  6624. Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
  6625. Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
  6626. Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
  6627. Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
  6628. Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
  6629. Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
  6630. ];
  6631. echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
  6632. $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
  6633. $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
  6634. echo Display::tabs(
  6635. $headers,
  6636. [
  6637. $documents,
  6638. $exercises,
  6639. $links,
  6640. $works,
  6641. $forums,
  6642. $dir,
  6643. $finish,
  6644. ],
  6645. 'resource_tab',
  6646. [],
  6647. [],
  6648. $selected
  6649. );
  6650. return true;
  6651. }
  6652. /**
  6653. * Returns the extension of a document.
  6654. *
  6655. * @param string $filename
  6656. *
  6657. * @return string Extension (part after the last dot)
  6658. */
  6659. public function get_extension($filename)
  6660. {
  6661. $explode = explode('.', $filename);
  6662. return $explode[count($explode) - 1];
  6663. }
  6664. /**
  6665. * Displays a document by id.
  6666. *
  6667. * @param int $id
  6668. * @param bool $show_title
  6669. * @param bool $iframe
  6670. * @param bool $edit_link
  6671. *
  6672. * @return string
  6673. */
  6674. public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
  6675. {
  6676. $_course = api_get_course_info();
  6677. $course_id = api_get_course_int_id();
  6678. $id = (int) $id;
  6679. $return = '';
  6680. $table = Database::get_course_table(TABLE_DOCUMENT);
  6681. $sql_doc = "SELECT * FROM $table
  6682. WHERE c_id = $course_id AND iid = $id";
  6683. $res_doc = Database::query($sql_doc);
  6684. $row_doc = Database::fetch_array($res_doc);
  6685. // TODO: Add a path filter.
  6686. if ($iframe) {
  6687. $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>';
  6688. } else {
  6689. $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
  6690. }
  6691. return $return;
  6692. }
  6693. /**
  6694. * Return HTML form to add/edit a quiz.
  6695. *
  6696. * @param string $action Action (add/edit)
  6697. * @param int $id Item ID if already exists
  6698. * @param mixed $extra_info Extra information (quiz ID if integer)
  6699. *
  6700. * @throws Exception
  6701. *
  6702. * @return string HTML form
  6703. */
  6704. public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
  6705. {
  6706. $course_id = api_get_course_int_id();
  6707. $id = (int) $id;
  6708. $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
  6709. if ($id != 0 && is_array($extra_info)) {
  6710. $item_title = $extra_info['title'];
  6711. $item_description = $extra_info['description'];
  6712. } elseif (is_numeric($extra_info)) {
  6713. $sql = "SELECT title, description
  6714. FROM $tbl_quiz
  6715. WHERE c_id = $course_id AND iid = ".$extra_info;
  6716. $result = Database::query($sql);
  6717. $row = Database::fetch_array($result);
  6718. $item_title = $row['title'];
  6719. $item_description = $row['description'];
  6720. } else {
  6721. $item_title = '';
  6722. $item_description = '';
  6723. }
  6724. $item_title = Security::remove_XSS($item_title);
  6725. $item_description = Security::remove_XSS($item_description);
  6726. $parent = 0;
  6727. if ($id != 0 && is_array($extra_info)) {
  6728. $parent = $extra_info['parent_item_id'];
  6729. }
  6730. $arrLP = $this->getItemsForForm();
  6731. $this->tree_array($arrLP);
  6732. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  6733. unset($this->arrMenu);
  6734. $form = new FormValidator(
  6735. 'quiz_form',
  6736. 'POST',
  6737. $this->getCurrentBuildingModeURL()
  6738. );
  6739. $defaults = [];
  6740. if ($action === 'add') {
  6741. $legend = get_lang('CreateTheExercise');
  6742. } elseif ($action === 'move') {
  6743. $legend = get_lang('MoveTheCurrentExercise');
  6744. } else {
  6745. $legend = get_lang('EditCurrentExecice');
  6746. }
  6747. if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
  6748. $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
  6749. }
  6750. $form->addHeader($legend);
  6751. if ($action != 'move') {
  6752. $this->setItemTitle($form);
  6753. $defaults['title'] = $item_title;
  6754. }
  6755. // Select for Parent item, root or chapter
  6756. $selectParent = $form->addSelect(
  6757. 'parent',
  6758. get_lang('Parent'),
  6759. [],
  6760. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
  6761. );
  6762. $selectParent->addOption($this->name, 0);
  6763. $arrHide = [
  6764. $id,
  6765. ];
  6766. for ($i = 0; $i < count($arrLP); $i++) {
  6767. if ($action != 'add') {
  6768. if (
  6769. ($arrLP[$i]['item_type'] == 'dir') &&
  6770. !in_array($arrLP[$i]['id'], $arrHide) &&
  6771. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  6772. ) {
  6773. $selectParent->addOption(
  6774. $arrLP[$i]['title'],
  6775. $arrLP[$i]['id'],
  6776. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  6777. );
  6778. if ($parent == $arrLP[$i]['id']) {
  6779. $selectParent->setSelected($arrLP[$i]['id']);
  6780. }
  6781. } else {
  6782. $arrHide[] = $arrLP[$i]['id'];
  6783. }
  6784. } else {
  6785. if ($arrLP[$i]['item_type'] == 'dir') {
  6786. $selectParent->addOption(
  6787. $arrLP[$i]['title'],
  6788. $arrLP[$i]['id'],
  6789. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  6790. );
  6791. if ($parent == $arrLP[$i]['id']) {
  6792. $selectParent->setSelected($arrLP[$i]['id']);
  6793. }
  6794. }
  6795. }
  6796. }
  6797. if (is_array($arrLP)) {
  6798. reset($arrLP);
  6799. }
  6800. $selectPrevious = $form->addSelect(
  6801. 'previous',
  6802. get_lang('Position'),
  6803. [],
  6804. ['id' => 'previous']
  6805. );
  6806. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  6807. for ($i = 0; $i < count($arrLP); $i++) {
  6808. if ($arrLP[$i]['parent_item_id'] == $parent &&
  6809. $arrLP[$i]['id'] != $id
  6810. ) {
  6811. $selectPrevious->addOption(
  6812. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  6813. $arrLP[$i]['id']
  6814. );
  6815. if (is_array($extra_info)) {
  6816. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  6817. $selectPrevious->setSelected($arrLP[$i]['id']);
  6818. }
  6819. } elseif ($action == 'add') {
  6820. $selectPrevious->setSelected($arrLP[$i]['id']);
  6821. }
  6822. }
  6823. }
  6824. if ($action != 'move') {
  6825. $arrHide = [];
  6826. for ($i = 0; $i < count($arrLP); $i++) {
  6827. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  6828. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  6829. }
  6830. }
  6831. }
  6832. if ($action === 'add') {
  6833. $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
  6834. } else {
  6835. $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
  6836. }
  6837. if ($action === 'move') {
  6838. $form->addHidden('title', $item_title);
  6839. $form->addHidden('description', $item_description);
  6840. }
  6841. if (is_numeric($extra_info)) {
  6842. $form->addHidden('path', $extra_info);
  6843. } elseif (is_array($extra_info)) {
  6844. $form->addHidden('path', $extra_info['path']);
  6845. }
  6846. $form->addHidden('type', TOOL_QUIZ);
  6847. $form->addHidden('post_time', time());
  6848. $form->setDefaults($defaults);
  6849. return '<div class="sectioncomment">'.$form->returnForm().'</div>';
  6850. }
  6851. /**
  6852. * Addition of Hotpotatoes tests.
  6853. *
  6854. * @param string $action
  6855. * @param int $id Internal ID of the item
  6856. * @param string $extra_info
  6857. *
  6858. * @return string HTML structure to display the hotpotatoes addition formular
  6859. */
  6860. public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
  6861. {
  6862. $course_id = api_get_course_int_id();
  6863. $uploadPath = DIR_HOTPOTATOES;
  6864. if ($id != 0 && is_array($extra_info)) {
  6865. $item_title = stripslashes($extra_info['title']);
  6866. $item_description = stripslashes($extra_info['description']);
  6867. } elseif (is_numeric($extra_info)) {
  6868. $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
  6869. $sql = "SELECT * FROM $TBL_DOCUMENT
  6870. WHERE
  6871. c_id = $course_id AND
  6872. path LIKE '".$uploadPath."/%/%htm%' AND
  6873. iid = ".(int) $extra_info."
  6874. ORDER BY iid ASC";
  6875. $res_hot = Database::query($sql);
  6876. $row = Database::fetch_array($res_hot);
  6877. $item_title = $row['title'];
  6878. $item_description = $row['description'];
  6879. if (!empty($row['comment'])) {
  6880. $item_title = $row['comment'];
  6881. }
  6882. } else {
  6883. $item_title = '';
  6884. $item_description = '';
  6885. }
  6886. $parent = 0;
  6887. if ($id != 0 && is_array($extra_info)) {
  6888. $parent = $extra_info['parent_item_id'];
  6889. }
  6890. $arrLP = $this->getItemsForForm();
  6891. $legend = '<legend>';
  6892. if ($action == 'add') {
  6893. $legend .= get_lang('CreateTheExercise');
  6894. } elseif ($action == 'move') {
  6895. $legend .= get_lang('MoveTheCurrentExercise');
  6896. } else {
  6897. $legend .= get_lang('EditCurrentExecice');
  6898. }
  6899. if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
  6900. $legend .= Display:: return_message(
  6901. get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
  6902. );
  6903. }
  6904. $legend .= '</legend>';
  6905. $return = '<form method="POST">';
  6906. $return .= $legend;
  6907. $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
  6908. $return .= '<tr>';
  6909. $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
  6910. $return .= '<td class="input">';
  6911. $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
  6912. $return .= '<option class="top" value="0">'.$this->name.'</option>';
  6913. $arrHide = [$id];
  6914. if (count($arrLP) > 0) {
  6915. for ($i = 0; $i < count($arrLP); $i++) {
  6916. if ($action != 'add') {
  6917. if ($arrLP[$i]['item_type'] == 'dir' &&
  6918. !in_array($arrLP[$i]['id'], $arrHide) &&
  6919. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  6920. ) {
  6921. $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
  6922. } else {
  6923. $arrHide[] = $arrLP[$i]['id'];
  6924. }
  6925. } else {
  6926. if ($arrLP[$i]['item_type'] == 'dir') {
  6927. $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
  6928. }
  6929. }
  6930. }
  6931. reset($arrLP);
  6932. }
  6933. $return .= '</select>';
  6934. $return .= '</td>';
  6935. $return .= '</tr>';
  6936. $return .= '<tr>';
  6937. $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
  6938. $return .= '<td class="input">';
  6939. $return .= '<select id="previous" name="previous" size="1">';
  6940. $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
  6941. for ($i = 0; $i < count($arrLP); $i++) {
  6942. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  6943. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  6944. $selected = 'selected="selected" ';
  6945. } elseif ($action == 'add') {
  6946. $selected = 'selected="selected" ';
  6947. } else {
  6948. $selected = '';
  6949. }
  6950. $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
  6951. get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
  6952. }
  6953. }
  6954. $return .= '</select>';
  6955. $return .= '</td>';
  6956. $return .= '</tr>';
  6957. if ($action != 'move') {
  6958. $return .= '<tr>';
  6959. $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
  6960. $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
  6961. $return .= '</tr>';
  6962. $id_prerequisite = 0;
  6963. if (is_array($arrLP) && count($arrLP) > 0) {
  6964. foreach ($arrLP as $key => $value) {
  6965. if ($value['id'] == $id) {
  6966. $id_prerequisite = $value['prerequisite'];
  6967. break;
  6968. }
  6969. }
  6970. $arrHide = [];
  6971. for ($i = 0; $i < count($arrLP); $i++) {
  6972. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  6973. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  6974. }
  6975. }
  6976. }
  6977. }
  6978. $return .= '<tr>';
  6979. $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
  6980. get_lang('SaveHotpotatoes').'</button></td>';
  6981. $return .= '</tr>';
  6982. $return .= '</table>';
  6983. if ($action == 'move') {
  6984. $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
  6985. $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
  6986. }
  6987. if (is_numeric($extra_info)) {
  6988. $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
  6989. } elseif (is_array($extra_info)) {
  6990. $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
  6991. }
  6992. $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
  6993. $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
  6994. $return .= '</form>';
  6995. return $return;
  6996. }
  6997. /**
  6998. * Return the form to display the forum edit/add option.
  6999. *
  7000. * @param string $action
  7001. * @param int $id ID of the lp_item if already exists
  7002. * @param string $extra_info
  7003. *
  7004. * @throws Exception
  7005. *
  7006. * @return string HTML form
  7007. */
  7008. public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
  7009. {
  7010. $course_id = api_get_course_int_id();
  7011. $tbl_forum = Database::get_course_table(TABLE_FORUM);
  7012. $item_title = '';
  7013. $item_description = '';
  7014. if ($id != 0 && is_array($extra_info)) {
  7015. $item_title = stripslashes($extra_info['title']);
  7016. } elseif (is_numeric($extra_info)) {
  7017. $sql = "SELECT forum_title as title, forum_comment as comment
  7018. FROM $tbl_forum
  7019. WHERE c_id = $course_id AND forum_id = ".$extra_info;
  7020. $result = Database::query($sql);
  7021. $row = Database::fetch_array($result);
  7022. $item_title = $row['title'];
  7023. $item_description = $row['comment'];
  7024. }
  7025. $parent = 0;
  7026. if ($id != 0 && is_array($extra_info)) {
  7027. $parent = $extra_info['parent_item_id'];
  7028. }
  7029. $arrLP = $this->getItemsForForm();
  7030. $this->tree_array($arrLP);
  7031. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7032. unset($this->arrMenu);
  7033. if ($action == 'add') {
  7034. $legend = get_lang('CreateTheForum');
  7035. } elseif ($action == 'move') {
  7036. $legend = get_lang('MoveTheCurrentForum');
  7037. } else {
  7038. $legend = get_lang('EditCurrentForum');
  7039. }
  7040. $form = new FormValidator(
  7041. 'forum_form',
  7042. 'POST',
  7043. $this->getCurrentBuildingModeURL()
  7044. );
  7045. $defaults = [];
  7046. $form->addHeader($legend);
  7047. if ($action != 'move') {
  7048. $this->setItemTitle($form);
  7049. $defaults['title'] = $item_title;
  7050. }
  7051. $selectParent = $form->addSelect(
  7052. 'parent',
  7053. get_lang('Parent'),
  7054. [],
  7055. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
  7056. );
  7057. $selectParent->addOption($this->name, 0);
  7058. $arrHide = [
  7059. $id,
  7060. ];
  7061. for ($i = 0; $i < count($arrLP); $i++) {
  7062. if ($action != 'add') {
  7063. if ($arrLP[$i]['item_type'] == 'dir' &&
  7064. !in_array($arrLP[$i]['id'], $arrHide) &&
  7065. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7066. ) {
  7067. $selectParent->addOption(
  7068. $arrLP[$i]['title'],
  7069. $arrLP[$i]['id'],
  7070. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7071. );
  7072. if ($parent == $arrLP[$i]['id']) {
  7073. $selectParent->setSelected($arrLP[$i]['id']);
  7074. }
  7075. } else {
  7076. $arrHide[] = $arrLP[$i]['id'];
  7077. }
  7078. } else {
  7079. if ($arrLP[$i]['item_type'] == 'dir') {
  7080. $selectParent->addOption(
  7081. $arrLP[$i]['title'],
  7082. $arrLP[$i]['id'],
  7083. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7084. );
  7085. if ($parent == $arrLP[$i]['id']) {
  7086. $selectParent->setSelected($arrLP[$i]['id']);
  7087. }
  7088. }
  7089. }
  7090. }
  7091. if (is_array($arrLP)) {
  7092. reset($arrLP);
  7093. }
  7094. $selectPrevious = $form->addSelect(
  7095. 'previous',
  7096. get_lang('Position'),
  7097. [],
  7098. ['id' => 'previous', 'class' => 'learnpath_item_form']
  7099. );
  7100. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  7101. for ($i = 0; $i < count($arrLP); $i++) {
  7102. if ($arrLP[$i]['parent_item_id'] == $parent &&
  7103. $arrLP[$i]['id'] != $id
  7104. ) {
  7105. $selectPrevious->addOption(
  7106. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  7107. $arrLP[$i]['id']
  7108. );
  7109. if (isset($extra_info['previous_item_id']) &&
  7110. $extra_info['previous_item_id'] == $arrLP[$i]['id']
  7111. ) {
  7112. $selectPrevious->setSelected($arrLP[$i]['id']);
  7113. } elseif ($action == 'add') {
  7114. $selectPrevious->setSelected($arrLP[$i]['id']);
  7115. }
  7116. }
  7117. }
  7118. if ($action != 'move') {
  7119. $id_prerequisite = 0;
  7120. if (is_array($arrLP)) {
  7121. foreach ($arrLP as $key => $value) {
  7122. if ($value['id'] == $id) {
  7123. $id_prerequisite = $value['prerequisite'];
  7124. break;
  7125. }
  7126. }
  7127. }
  7128. $arrHide = [];
  7129. for ($i = 0; $i < count($arrLP); $i++) {
  7130. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  7131. if (isset($extra_info['previous_item_id']) &&
  7132. $extra_info['previous_item_id'] == $arrLP[$i]['id']
  7133. ) {
  7134. $s_selected_position = $arrLP[$i]['id'];
  7135. } elseif ($action == 'add') {
  7136. $s_selected_position = 0;
  7137. }
  7138. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7139. }
  7140. }
  7141. }
  7142. if ($action == 'add') {
  7143. $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
  7144. } else {
  7145. $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
  7146. }
  7147. if ($action == 'move') {
  7148. $form->addHidden('title', $item_title);
  7149. $form->addHidden('description', $item_description);
  7150. }
  7151. if (is_numeric($extra_info)) {
  7152. $form->addHidden('path', $extra_info);
  7153. } elseif (is_array($extra_info)) {
  7154. $form->addHidden('path', $extra_info['path']);
  7155. }
  7156. $form->addHidden('type', TOOL_FORUM);
  7157. $form->addHidden('post_time', time());
  7158. $form->setDefaults($defaults);
  7159. return '<div class="sectioncomment">'.$form->returnForm().'</div>';
  7160. }
  7161. /**
  7162. * Return HTML form to add/edit forum threads.
  7163. *
  7164. * @param string $action
  7165. * @param int $id Item ID if already exists in learning path
  7166. * @param string $extra_info
  7167. *
  7168. * @throws Exception
  7169. *
  7170. * @return string HTML form
  7171. */
  7172. public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
  7173. {
  7174. $course_id = api_get_course_int_id();
  7175. if (empty($course_id)) {
  7176. return null;
  7177. }
  7178. $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
  7179. $item_title = '';
  7180. $item_description = '';
  7181. if ($id != 0 && is_array($extra_info)) {
  7182. $item_title = stripslashes($extra_info['title']);
  7183. } elseif (is_numeric($extra_info)) {
  7184. $sql = "SELECT thread_title as title FROM $tbl_forum
  7185. WHERE c_id = $course_id AND thread_id = ".$extra_info;
  7186. $result = Database::query($sql);
  7187. $row = Database::fetch_array($result);
  7188. $item_title = $row['title'];
  7189. $item_description = '';
  7190. }
  7191. $parent = 0;
  7192. if ($id != 0 && is_array($extra_info)) {
  7193. $parent = $extra_info['parent_item_id'];
  7194. }
  7195. $arrLP = $this->getItemsForForm();
  7196. $this->tree_array($arrLP);
  7197. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7198. unset($this->arrMenu);
  7199. $form = new FormValidator(
  7200. 'thread_form',
  7201. 'POST',
  7202. $this->getCurrentBuildingModeURL()
  7203. );
  7204. $defaults = [];
  7205. if ($action == 'add') {
  7206. $legend = get_lang('CreateTheForum');
  7207. } elseif ($action == 'move') {
  7208. $legend = get_lang('MoveTheCurrentForum');
  7209. } else {
  7210. $legend = get_lang('EditCurrentForum');
  7211. }
  7212. $form->addHeader($legend);
  7213. $selectParent = $form->addSelect(
  7214. 'parent',
  7215. get_lang('Parent'),
  7216. [],
  7217. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
  7218. );
  7219. $selectParent->addOption($this->name, 0);
  7220. $arrHide = [
  7221. $id,
  7222. ];
  7223. for ($i = 0; $i < count($arrLP); $i++) {
  7224. if ($action != 'add') {
  7225. if (
  7226. ($arrLP[$i]['item_type'] == 'dir') &&
  7227. !in_array($arrLP[$i]['id'], $arrHide) &&
  7228. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7229. ) {
  7230. $selectParent->addOption(
  7231. $arrLP[$i]['title'],
  7232. $arrLP[$i]['id'],
  7233. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7234. );
  7235. if ($parent == $arrLP[$i]['id']) {
  7236. $selectParent->setSelected($arrLP[$i]['id']);
  7237. }
  7238. } else {
  7239. $arrHide[] = $arrLP[$i]['id'];
  7240. }
  7241. } else {
  7242. if ($arrLP[$i]['item_type'] == 'dir') {
  7243. $selectParent->addOption(
  7244. $arrLP[$i]['title'],
  7245. $arrLP[$i]['id'],
  7246. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  7247. );
  7248. if ($parent == $arrLP[$i]['id']) {
  7249. $selectParent->setSelected($arrLP[$i]['id']);
  7250. }
  7251. }
  7252. }
  7253. }
  7254. if ($arrLP != null) {
  7255. reset($arrLP);
  7256. }
  7257. $selectPrevious = $form->addSelect(
  7258. 'previous',
  7259. get_lang('Position'),
  7260. [],
  7261. ['id' => 'previous']
  7262. );
  7263. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  7264. for ($i = 0; $i < count($arrLP); $i++) {
  7265. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  7266. $selectPrevious->addOption(
  7267. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  7268. $arrLP[$i]['id']
  7269. );
  7270. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  7271. $selectPrevious->setSelected($arrLP[$i]['id']);
  7272. } elseif ($action == 'add') {
  7273. $selectPrevious->setSelected($arrLP[$i]['id']);
  7274. }
  7275. }
  7276. }
  7277. if ($action != 'move') {
  7278. $this->setItemTitle($form);
  7279. $defaults['title'] = $item_title;
  7280. $id_prerequisite = 0;
  7281. if ($arrLP != null) {
  7282. foreach ($arrLP as $key => $value) {
  7283. if ($value['id'] == $id) {
  7284. $id_prerequisite = $value['prerequisite'];
  7285. break;
  7286. }
  7287. }
  7288. }
  7289. $arrHide = [];
  7290. $s_selected_position = 0;
  7291. for ($i = 0; $i < count($arrLP); $i++) {
  7292. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  7293. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  7294. $s_selected_position = $arrLP[$i]['id'];
  7295. } elseif ($action == 'add') {
  7296. $s_selected_position = 0;
  7297. }
  7298. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7299. }
  7300. }
  7301. $selectPrerequisites = $form->addSelect(
  7302. 'prerequisites',
  7303. get_lang('LearnpathPrerequisites'),
  7304. [],
  7305. ['id' => 'prerequisites']
  7306. );
  7307. $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
  7308. foreach ($arrHide as $key => $value) {
  7309. $selectPrerequisites->addOption($value['value'], $key);
  7310. if ($key == $s_selected_position && $action == 'add') {
  7311. $selectPrerequisites->setSelected($key);
  7312. } elseif ($key == $id_prerequisite && $action == 'edit') {
  7313. $selectPrerequisites->setSelected($key);
  7314. }
  7315. }
  7316. }
  7317. $form->addButtonSave(get_lang('Ok'), 'submit_button');
  7318. if ($action == 'move') {
  7319. $form->addHidden('title', $item_title);
  7320. $form->addHidden('description', $item_description);
  7321. }
  7322. if (is_numeric($extra_info)) {
  7323. $form->addHidden('path', $extra_info);
  7324. } elseif (is_array($extra_info)) {
  7325. $form->addHidden('path', $extra_info['path']);
  7326. }
  7327. $form->addHidden('type', TOOL_THREAD);
  7328. $form->addHidden('post_time', time());
  7329. $form->setDefaults($defaults);
  7330. return $form->returnForm();
  7331. }
  7332. /**
  7333. * Return the HTML form to display an item (generally a dir item).
  7334. *
  7335. * @param string $item_type
  7336. * @param string $title
  7337. * @param string $action
  7338. * @param int $id
  7339. * @param string $extra_info
  7340. *
  7341. * @throws Exception
  7342. * @throws HTML_QuickForm_Error
  7343. *
  7344. * @return string HTML form
  7345. */
  7346. public function display_item_form(
  7347. $item_type,
  7348. $title = '',
  7349. $action = 'add_item',
  7350. $id = 0,
  7351. $extra_info = 'new'
  7352. ) {
  7353. $_course = api_get_course_info();
  7354. global $charset;
  7355. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  7356. $item_title = '';
  7357. $item_description = '';
  7358. $item_path_fck = '';
  7359. if ($id != 0 && is_array($extra_info)) {
  7360. $item_title = $extra_info['title'];
  7361. $item_description = $extra_info['description'];
  7362. $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
  7363. $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
  7364. }
  7365. $parent = 0;
  7366. if ($id != 0 && is_array($extra_info)) {
  7367. $parent = $extra_info['parent_item_id'];
  7368. }
  7369. $id = (int) $id;
  7370. $sql = "SELECT * FROM $tbl_lp_item
  7371. WHERE
  7372. lp_id = ".$this->lp_id." AND
  7373. iid != $id";
  7374. if ($item_type == 'dir') {
  7375. $sql .= " AND parent_item_id = 0";
  7376. }
  7377. $result = Database::query($sql);
  7378. $arrLP = [];
  7379. while ($row = Database::fetch_array($result)) {
  7380. $arrLP[] = [
  7381. 'id' => $row['iid'],
  7382. 'item_type' => $row['item_type'],
  7383. 'title' => $this->cleanItemTitle($row['title']),
  7384. 'title_raw' => $row['title'],
  7385. 'path' => $row['path'],
  7386. 'description' => $row['description'],
  7387. 'parent_item_id' => $row['parent_item_id'],
  7388. 'previous_item_id' => $row['previous_item_id'],
  7389. 'next_item_id' => $row['next_item_id'],
  7390. 'max_score' => $row['max_score'],
  7391. 'min_score' => $row['min_score'],
  7392. 'mastery_score' => $row['mastery_score'],
  7393. 'prerequisite' => $row['prerequisite'],
  7394. 'display_order' => $row['display_order'],
  7395. ];
  7396. }
  7397. $this->tree_array($arrLP);
  7398. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7399. unset($this->arrMenu);
  7400. $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
  7401. $form = new FormValidator('form_'.$item_type, 'POST', $url);
  7402. $defaults['title'] = api_html_entity_decode(
  7403. $item_title,
  7404. ENT_QUOTES,
  7405. $charset
  7406. );
  7407. $defaults['description'] = $item_description;
  7408. $form->addHeader($title);
  7409. $arrHide[0]['value'] = Security::remove_XSS($this->name);
  7410. $arrHide[0]['padding'] = 20;
  7411. $charset = api_get_system_encoding();
  7412. for ($i = 0; $i < count($arrLP); $i++) {
  7413. if ($action != 'add') {
  7414. if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
  7415. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7416. ) {
  7417. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7418. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  7419. if ($parent == $arrLP[$i]['id']) {
  7420. $s_selected_parent = $arrHide[$arrLP[$i]['id']];
  7421. }
  7422. }
  7423. } else {
  7424. if ($arrLP[$i]['item_type'] === 'dir') {
  7425. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7426. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  7427. if ($parent == $arrLP[$i]['id']) {
  7428. $s_selected_parent = $arrHide[$arrLP[$i]['id']];
  7429. }
  7430. }
  7431. }
  7432. }
  7433. if ($action != 'move') {
  7434. $this->setItemTitle($form);
  7435. } else {
  7436. $form->addElement('hidden', 'title');
  7437. }
  7438. $parentSelect = $form->addElement(
  7439. 'select',
  7440. 'parent',
  7441. get_lang('Parent'),
  7442. '',
  7443. [
  7444. 'id' => 'idParent',
  7445. 'onchange' => 'javascript: load_cbo(this.value);',
  7446. ]
  7447. );
  7448. foreach ($arrHide as $key => $value) {
  7449. $parentSelect->addOption(
  7450. $value['value'],
  7451. $key,
  7452. 'style="padding-left:'.$value['padding'].'px;"'
  7453. );
  7454. $lastPosition = $key;
  7455. }
  7456. if (!empty($s_selected_parent)) {
  7457. $parentSelect->setSelected($s_selected_parent);
  7458. }
  7459. if (is_array($arrLP)) {
  7460. reset($arrLP);
  7461. }
  7462. $arrHide = [];
  7463. // POSITION
  7464. for ($i = 0; $i < count($arrLP); $i++) {
  7465. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
  7466. $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
  7467. //this is the same!
  7468. if (isset($extra_info['previous_item_id']) &&
  7469. $extra_info['previous_item_id'] == $arrLP[$i]['id']
  7470. ) {
  7471. $s_selected_position = $arrLP[$i]['id'];
  7472. } elseif ($action == 'add') {
  7473. $s_selected_position = $arrLP[$i]['id'];
  7474. }
  7475. $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
  7476. }
  7477. }
  7478. $position = $form->addElement(
  7479. 'select',
  7480. 'previous',
  7481. get_lang('Position'),
  7482. '',
  7483. ['id' => 'previous']
  7484. );
  7485. $padding = isset($value['padding']) ? $value['padding'] : 0;
  7486. $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
  7487. $lastPosition = null;
  7488. foreach ($arrHide as $key => $value) {
  7489. $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
  7490. $lastPosition = $key;
  7491. }
  7492. if (!empty($s_selected_position)) {
  7493. $position->setSelected($s_selected_position);
  7494. }
  7495. // When new chapter add at the end
  7496. if ($action === 'add_item') {
  7497. $position->setSelected($lastPosition);
  7498. }
  7499. if (is_array($arrLP)) {
  7500. reset($arrLP);
  7501. }
  7502. $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
  7503. //fix in order to use the tab
  7504. if ($item_type === 'dir') {
  7505. $form->addElement('hidden', 'type', 'dir');
  7506. }
  7507. $extension = null;
  7508. if (!empty($item_path)) {
  7509. $extension = pathinfo($item_path, PATHINFO_EXTENSION);
  7510. }
  7511. //assets can't be modified
  7512. //$item_type == 'asset' ||
  7513. if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
  7514. if ($item_type == 'sco') {
  7515. $form->addElement(
  7516. 'html',
  7517. '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
  7518. );
  7519. }
  7520. $renderer = $form->defaultRenderer();
  7521. $renderer->setElementTemplate(
  7522. '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
  7523. 'content_lp'
  7524. );
  7525. $relative_prefix = '';
  7526. $editor_config = [
  7527. 'ToolbarSet' => 'LearningPathDocuments',
  7528. 'Width' => '100%',
  7529. 'Height' => '500',
  7530. 'FullPage' => true,
  7531. 'CreateDocumentDir' => $relative_prefix,
  7532. 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
  7533. 'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
  7534. ];
  7535. $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
  7536. $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
  7537. $defaults['content_lp'] = file_get_contents($content_path);
  7538. }
  7539. if (!empty($id)) {
  7540. $form->addHidden('id', $id);
  7541. }
  7542. $form->addElement('hidden', 'type', $item_type);
  7543. $form->addElement('hidden', 'post_time', time());
  7544. $form->setDefaults($defaults);
  7545. return $form->returnForm();
  7546. }
  7547. /**
  7548. * @return string
  7549. */
  7550. public function getCurrentBuildingModeURL()
  7551. {
  7552. $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
  7553. $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
  7554. $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
  7555. $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
  7556. $currentUrl = api_get_self().'?'.api_get_cidreq().
  7557. '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
  7558. return $currentUrl;
  7559. }
  7560. /**
  7561. * Returns the form to update or create a document.
  7562. *
  7563. * @param string $action (add/edit)
  7564. * @param int $id ID of the lp_item (if already exists)
  7565. * @param mixed $extra_info Integer if document ID, string if info ('new')
  7566. *
  7567. * @throws Exception
  7568. * @throws HTML_QuickForm_Error
  7569. *
  7570. * @return string HTML form
  7571. */
  7572. public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
  7573. {
  7574. $course_id = api_get_course_int_id();
  7575. $_course = api_get_course_info();
  7576. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  7577. $no_display_edit_textarea = false;
  7578. $item_description = '';
  7579. //If action==edit document
  7580. //We don't display the document form if it's not an editable document (html or txt file)
  7581. if ($action === 'edit') {
  7582. if (is_array($extra_info)) {
  7583. $path_parts = pathinfo($extra_info['dir']);
  7584. if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
  7585. $no_display_edit_textarea = true;
  7586. }
  7587. }
  7588. }
  7589. $no_display_add = false;
  7590. // If action==add an existing document
  7591. // We don't display the document form if it's not an editable document (html or txt file).
  7592. if ($action === 'add') {
  7593. if (is_numeric($extra_info)) {
  7594. $extra_info = (int) $extra_info;
  7595. $sql_doc = "SELECT path FROM $tbl_doc
  7596. WHERE c_id = $course_id AND iid = ".$extra_info;
  7597. $result = Database::query($sql_doc);
  7598. $path_file = Database::result($result, 0, 0);
  7599. $path_parts = pathinfo($path_file);
  7600. if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
  7601. $no_display_add = true;
  7602. }
  7603. }
  7604. }
  7605. $item_title = '';
  7606. $item_description = '';
  7607. if ($id != 0 && is_array($extra_info)) {
  7608. $item_title = stripslashes($extra_info['title']);
  7609. $item_description = stripslashes($extra_info['description']);
  7610. if (empty($item_title)) {
  7611. $path_parts = pathinfo($extra_info['path']);
  7612. $item_title = stripslashes($path_parts['filename']);
  7613. }
  7614. } elseif (is_numeric($extra_info)) {
  7615. $sql = "SELECT path, title FROM $tbl_doc
  7616. WHERE
  7617. c_id = ".$course_id." AND
  7618. iid = ".intval($extra_info);
  7619. $result = Database::query($sql);
  7620. $row = Database::fetch_array($result);
  7621. $item_title = $row['title'];
  7622. $item_title = str_replace('_', ' ', $item_title);
  7623. if (empty($item_title)) {
  7624. $path_parts = pathinfo($row['path']);
  7625. $item_title = stripslashes($path_parts['filename']);
  7626. }
  7627. }
  7628. $return = '<legend>';
  7629. $parent = 0;
  7630. if ($id != 0 && is_array($extra_info)) {
  7631. $parent = $extra_info['parent_item_id'];
  7632. }
  7633. $arrLP = $this->getItemsForForm();
  7634. $this->tree_array($arrLP);
  7635. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7636. unset($this->arrMenu);
  7637. if ($action == 'add') {
  7638. $return .= get_lang('CreateTheDocument');
  7639. } elseif ($action == 'move') {
  7640. $return .= get_lang('MoveTheCurrentDocument');
  7641. } else {
  7642. $return .= get_lang('EditTheCurrentDocument');
  7643. }
  7644. $return .= '</legend>';
  7645. if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
  7646. $return .= Display::return_message(
  7647. '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
  7648. false
  7649. );
  7650. }
  7651. $form = new FormValidator(
  7652. 'form',
  7653. 'POST',
  7654. $this->getCurrentBuildingModeURL(),
  7655. '',
  7656. ['enctype' => 'multipart/form-data']
  7657. );
  7658. $defaults['title'] = Security::remove_XSS($item_title);
  7659. if (empty($item_title)) {
  7660. $defaults['title'] = Security::remove_XSS($item_title);
  7661. }
  7662. $defaults['description'] = $item_description;
  7663. $form->addElement('html', $return);
  7664. if ($action != 'move') {
  7665. $data = $this->generate_lp_folder($_course);
  7666. if ($action != 'edit') {
  7667. $folders = DocumentManager::get_all_document_folders(
  7668. $_course,
  7669. 0,
  7670. true
  7671. );
  7672. DocumentManager::build_directory_selector(
  7673. $folders,
  7674. '',
  7675. [],
  7676. true,
  7677. $form,
  7678. 'directory_parent_id'
  7679. );
  7680. }
  7681. if (isset($data['id'])) {
  7682. $defaults['directory_parent_id'] = $data['id'];
  7683. }
  7684. $this->setItemTitle($form);
  7685. }
  7686. $arrHide[0]['value'] = $this->name;
  7687. $arrHide[0]['padding'] = 20;
  7688. for ($i = 0; $i < count($arrLP); $i++) {
  7689. if ($action != 'add') {
  7690. if ($arrLP[$i]['item_type'] == 'dir' &&
  7691. !in_array($arrLP[$i]['id'], $arrHide) &&
  7692. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  7693. ) {
  7694. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7695. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  7696. }
  7697. } else {
  7698. if ($arrLP[$i]['item_type'] == 'dir') {
  7699. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7700. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  7701. }
  7702. }
  7703. }
  7704. $parentSelect = $form->addSelect(
  7705. 'parent',
  7706. get_lang('Parent'),
  7707. [],
  7708. [
  7709. 'id' => 'idParent',
  7710. 'onchange' => 'javascript: load_cbo(this.value);',
  7711. ]
  7712. );
  7713. $my_count = 0;
  7714. foreach ($arrHide as $key => $value) {
  7715. if ($my_count != 0) {
  7716. // The LP name is also the first section and is not in the same charset like the other sections.
  7717. $value['value'] = Security::remove_XSS($value['value']);
  7718. $parentSelect->addOption(
  7719. $value['value'],
  7720. $key,
  7721. 'style="padding-left:'.$value['padding'].'px;"'
  7722. );
  7723. } else {
  7724. $value['value'] = Security::remove_XSS($value['value']);
  7725. $parentSelect->addOption(
  7726. $value['value'],
  7727. $key,
  7728. 'style="padding-left:'.$value['padding'].'px;"'
  7729. );
  7730. }
  7731. $my_count++;
  7732. }
  7733. if (!empty($id)) {
  7734. $parentSelect->setSelected($parent);
  7735. } else {
  7736. $parent_item_id = Session::read('parent_item_id', 0);
  7737. $parentSelect->setSelected($parent_item_id);
  7738. }
  7739. if (is_array($arrLP)) {
  7740. reset($arrLP);
  7741. }
  7742. $arrHide = [];
  7743. // POSITION
  7744. for ($i = 0; $i < count($arrLP); $i++) {
  7745. if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
  7746. $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
  7747. ) {
  7748. $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
  7749. }
  7750. }
  7751. $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
  7752. $position = $form->addSelect(
  7753. 'previous',
  7754. get_lang('Position'),
  7755. [],
  7756. ['id' => 'previous']
  7757. );
  7758. $position->addOption(get_lang('FirstPosition'), 0);
  7759. foreach ($arrHide as $key => $value) {
  7760. $padding = isset($value['padding']) ? $value['padding'] : 20;
  7761. $position->addOption(
  7762. $value['value'],
  7763. $key,
  7764. 'style="padding-left:'.$padding.'px;"'
  7765. );
  7766. }
  7767. $position->setSelected($selectedPosition);
  7768. if (is_array($arrLP)) {
  7769. reset($arrLP);
  7770. }
  7771. if ($action != 'move') {
  7772. $arrHide = [];
  7773. for ($i = 0; $i < count($arrLP); $i++) {
  7774. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
  7775. $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
  7776. ) {
  7777. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  7778. }
  7779. }
  7780. if (!$no_display_add) {
  7781. $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
  7782. $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
  7783. if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
  7784. $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
  7785. ) {
  7786. if (isset($_POST['content'])) {
  7787. $content = stripslashes($_POST['content']);
  7788. } elseif (is_array($extra_info)) {
  7789. //If it's an html document or a text file
  7790. if (!$no_display_edit_textarea) {
  7791. $content = $this->display_document(
  7792. $extra_info['path'],
  7793. false,
  7794. false
  7795. );
  7796. }
  7797. } elseif (is_numeric($extra_info)) {
  7798. $content = $this->display_document(
  7799. $extra_info,
  7800. false,
  7801. false
  7802. );
  7803. } else {
  7804. $content = '';
  7805. }
  7806. if (!$no_display_edit_textarea) {
  7807. // We need to calculate here some specific settings for the online editor.
  7808. // The calculated settings work for documents in the Documents tool
  7809. // (on the root or in subfolders).
  7810. // For documents in native scorm packages it is unclear whether the
  7811. // online editor should be activated or not.
  7812. // A new document, it is in the root of the repository.
  7813. $relative_path = '';
  7814. $relative_prefix = '';
  7815. if (is_array($extra_info) && $extra_info != 'new') {
  7816. // The document already exists. Whe have to determine its relative path towards the repository root.
  7817. $relative_path = explode('/', $extra_info['dir']);
  7818. $cnt = count($relative_path) - 2;
  7819. if ($cnt < 0) {
  7820. $cnt = 0;
  7821. }
  7822. $relative_prefix = str_repeat('../', $cnt);
  7823. $relative_path = array_slice($relative_path, 1, $cnt);
  7824. $relative_path = implode('/', $relative_path);
  7825. if (strlen($relative_path) > 0) {
  7826. $relative_path = $relative_path.'/';
  7827. }
  7828. } else {
  7829. $result = $this->generate_lp_folder($_course);
  7830. $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
  7831. $relative_prefix = '../../';
  7832. }
  7833. $editor_config = [
  7834. 'ToolbarSet' => 'LearningPathDocuments',
  7835. 'Width' => '100%',
  7836. 'Height' => '500',
  7837. 'FullPage' => true,
  7838. 'CreateDocumentDir' => $relative_prefix,
  7839. 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
  7840. 'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
  7841. ];
  7842. if ($_GET['action'] == 'add_item') {
  7843. $class = 'add';
  7844. $text = get_lang('LPCreateDocument');
  7845. } else {
  7846. if ($_GET['action'] == 'edit_item') {
  7847. $class = 'save';
  7848. $text = get_lang('SaveDocument');
  7849. }
  7850. }
  7851. $form->addButtonSave($text, 'submit_button');
  7852. $renderer = $form->defaultRenderer();
  7853. $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
  7854. $form->addElement('html', '<div class="editor-lp">');
  7855. $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
  7856. $form->addElement('html', '</div>');
  7857. $defaults['content_lp'] = $content;
  7858. }
  7859. } elseif (is_numeric($extra_info)) {
  7860. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  7861. $return = $this->display_document($extra_info, true, true, true);
  7862. $form->addElement('html', $return);
  7863. }
  7864. }
  7865. }
  7866. if (isset($extra_info['item_type']) &&
  7867. $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
  7868. ) {
  7869. $parentSelect->freeze();
  7870. $position->freeze();
  7871. }
  7872. if ($action == 'move') {
  7873. $form->addElement('hidden', 'title', $item_title);
  7874. $form->addElement('hidden', 'description', $item_description);
  7875. }
  7876. if (is_numeric($extra_info)) {
  7877. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  7878. $form->addElement('hidden', 'path', $extra_info);
  7879. } elseif (is_array($extra_info)) {
  7880. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  7881. $form->addElement('hidden', 'path', $extra_info['path']);
  7882. }
  7883. $form->addElement('hidden', 'type', TOOL_DOCUMENT);
  7884. $form->addElement('hidden', 'post_time', time());
  7885. $form->setDefaults($defaults);
  7886. return $form->returnForm();
  7887. }
  7888. /**
  7889. * Returns the form to update or create a read-out text.
  7890. *
  7891. * @param string $action "add" or "edit"
  7892. * @param int $id ID of the lp_item (if already exists)
  7893. * @param mixed $extra_info Integer if document ID, string if info ('new')
  7894. *
  7895. * @throws Exception
  7896. * @throws HTML_QuickForm_Error
  7897. *
  7898. * @return string HTML form
  7899. */
  7900. public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
  7901. {
  7902. $course_id = api_get_course_int_id();
  7903. $_course = api_get_course_info();
  7904. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  7905. $no_display_edit_textarea = false;
  7906. //If action==edit document
  7907. //We don't display the document form if it's not an editable document (html or txt file)
  7908. if ($action == 'edit') {
  7909. if (is_array($extra_info)) {
  7910. $path_parts = pathinfo($extra_info['dir']);
  7911. if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
  7912. $no_display_edit_textarea = true;
  7913. }
  7914. }
  7915. }
  7916. $no_display_add = false;
  7917. $item_title = '';
  7918. $item_description = '';
  7919. if ($id != 0 && is_array($extra_info)) {
  7920. $item_title = stripslashes($extra_info['title']);
  7921. $item_description = stripslashes($extra_info['description']);
  7922. $item_terms = stripslashes($extra_info['terms']);
  7923. if (empty($item_title)) {
  7924. $path_parts = pathinfo($extra_info['path']);
  7925. $item_title = stripslashes($path_parts['filename']);
  7926. }
  7927. } elseif (is_numeric($extra_info)) {
  7928. $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
  7929. $result = Database::query($sql);
  7930. $row = Database::fetch_array($result);
  7931. $item_title = $row['title'];
  7932. $item_title = str_replace('_', ' ', $item_title);
  7933. if (empty($item_title)) {
  7934. $path_parts = pathinfo($row['path']);
  7935. $item_title = stripslashes($path_parts['filename']);
  7936. }
  7937. }
  7938. $parent = 0;
  7939. if ($id != 0 && is_array($extra_info)) {
  7940. $parent = $extra_info['parent_item_id'];
  7941. }
  7942. $arrLP = $this->getItemsForForm();
  7943. $this->tree_array($arrLP);
  7944. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  7945. unset($this->arrMenu);
  7946. if ($action === 'add') {
  7947. $formHeader = get_lang('CreateTheDocument');
  7948. } else {
  7949. $formHeader = get_lang('EditTheCurrentDocument');
  7950. }
  7951. if ('edit' === $action) {
  7952. $urlAudioIcon = Display::url(
  7953. Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
  7954. api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
  7955. .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
  7956. );
  7957. } else {
  7958. $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
  7959. }
  7960. $form = new FormValidator(
  7961. 'frm_add_reading',
  7962. 'POST',
  7963. $this->getCurrentBuildingModeURL(),
  7964. '',
  7965. ['enctype' => 'multipart/form-data']
  7966. );
  7967. $form->addHeader($formHeader);
  7968. $form->addHtml(
  7969. Display::return_message(
  7970. sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
  7971. 'normal',
  7972. false
  7973. )
  7974. );
  7975. $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
  7976. $defaults['description'] = $item_description;
  7977. $data = $this->generate_lp_folder($_course);
  7978. if ($action != 'edit') {
  7979. $folders = DocumentManager::get_all_document_folders($_course, 0, true);
  7980. DocumentManager::build_directory_selector(
  7981. $folders,
  7982. '',
  7983. [],
  7984. true,
  7985. $form,
  7986. 'directory_parent_id'
  7987. );
  7988. }
  7989. if (isset($data['id'])) {
  7990. $defaults['directory_parent_id'] = $data['id'];
  7991. }
  7992. $this->setItemTitle($form);
  7993. $arrHide[0]['value'] = $this->name;
  7994. $arrHide[0]['padding'] = 20;
  7995. for ($i = 0; $i < count($arrLP); $i++) {
  7996. if ($action != 'add') {
  7997. if ($arrLP[$i]['item_type'] == 'dir' &&
  7998. !in_array($arrLP[$i]['id'], $arrHide) &&
  7999. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  8000. ) {
  8001. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8002. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  8003. }
  8004. } else {
  8005. if ($arrLP[$i]['item_type'] == 'dir') {
  8006. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8007. $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
  8008. }
  8009. }
  8010. }
  8011. $parent_select = $form->addSelect(
  8012. 'parent',
  8013. get_lang('Parent'),
  8014. [],
  8015. ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
  8016. );
  8017. $my_count = 0;
  8018. foreach ($arrHide as $key => $value) {
  8019. if ($my_count != 0) {
  8020. // The LP name is also the first section and is not in the same charset like the other sections.
  8021. $value['value'] = Security::remove_XSS($value['value']);
  8022. $parent_select->addOption(
  8023. $value['value'],
  8024. $key,
  8025. 'style="padding-left:'.$value['padding'].'px;"'
  8026. );
  8027. } else {
  8028. $value['value'] = Security::remove_XSS($value['value']);
  8029. $parent_select->addOption(
  8030. $value['value'],
  8031. $key,
  8032. 'style="padding-left:'.$value['padding'].'px;"'
  8033. );
  8034. }
  8035. $my_count++;
  8036. }
  8037. if (!empty($id)) {
  8038. $parent_select->setSelected($parent);
  8039. } else {
  8040. $parent_item_id = Session::read('parent_item_id', 0);
  8041. $parent_select->setSelected($parent_item_id);
  8042. }
  8043. if (is_array($arrLP)) {
  8044. reset($arrLP);
  8045. }
  8046. $arrHide = [];
  8047. $s_selected_position = null;
  8048. // POSITION
  8049. $lastPosition = null;
  8050. for ($i = 0; $i < count($arrLP); $i++) {
  8051. if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
  8052. $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
  8053. ) {
  8054. if ((isset($extra_info['previous_item_id']) &&
  8055. $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
  8056. ) {
  8057. $s_selected_position = $arrLP[$i]['id'];
  8058. }
  8059. $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
  8060. }
  8061. $lastPosition = $arrLP[$i]['id'];
  8062. }
  8063. if (empty($s_selected_position)) {
  8064. $s_selected_position = $lastPosition;
  8065. }
  8066. $position = $form->addSelect(
  8067. 'previous',
  8068. get_lang('Position'),
  8069. []
  8070. );
  8071. $position->addOption(get_lang('FirstPosition'), 0);
  8072. foreach ($arrHide as $key => $value) {
  8073. $padding = isset($value['padding']) ? $value['padding'] : 20;
  8074. $position->addOption(
  8075. $value['value'],
  8076. $key,
  8077. 'style="padding-left:'.$padding.'px;"'
  8078. );
  8079. }
  8080. $position->setSelected($s_selected_position);
  8081. if (is_array($arrLP)) {
  8082. reset($arrLP);
  8083. }
  8084. $arrHide = [];
  8085. for ($i = 0; $i < count($arrLP); $i++) {
  8086. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
  8087. $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
  8088. ) {
  8089. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8090. }
  8091. }
  8092. if (!$no_display_add) {
  8093. $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
  8094. $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
  8095. if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
  8096. if (!$no_display_edit_textarea) {
  8097. $content = '';
  8098. if (isset($_POST['content'])) {
  8099. $content = stripslashes($_POST['content']);
  8100. } elseif (is_array($extra_info)) {
  8101. $content = $this->display_document($extra_info['path'], false, false);
  8102. } elseif (is_numeric($extra_info)) {
  8103. $content = $this->display_document($extra_info, false, false);
  8104. }
  8105. // A new document, it is in the root of the repository.
  8106. if (is_array($extra_info) && $extra_info != 'new') {
  8107. } else {
  8108. $this->generate_lp_folder($_course);
  8109. }
  8110. if ($_GET['action'] == 'add_item') {
  8111. $text = get_lang('LPCreateDocument');
  8112. } else {
  8113. $text = get_lang('SaveDocument');
  8114. }
  8115. $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
  8116. $form
  8117. ->defaultRenderer()
  8118. ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
  8119. $form->addButtonSave($text, 'submit_button');
  8120. $defaults['content_lp'] = $content;
  8121. }
  8122. } elseif (is_numeric($extra_info)) {
  8123. $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
  8124. $return = $this->display_document($extra_info, true, true, true);
  8125. $form->addElement('html', $return);
  8126. }
  8127. }
  8128. if (is_numeric($extra_info)) {
  8129. $form->addElement('hidden', 'path', $extra_info);
  8130. } elseif (is_array($extra_info)) {
  8131. $form->addElement('hidden', 'path', $extra_info['path']);
  8132. }
  8133. $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
  8134. $form->addElement('hidden', 'post_time', time());
  8135. $form->setDefaults($defaults);
  8136. return $form->returnForm();
  8137. }
  8138. /**
  8139. * @param array $courseInfo
  8140. * @param string $content
  8141. * @param string $title
  8142. * @param int $parentId
  8143. *
  8144. * @throws \Doctrine\ORM\ORMException
  8145. * @throws \Doctrine\ORM\OptimisticLockException
  8146. * @throws \Doctrine\ORM\TransactionRequiredException
  8147. *
  8148. * @return int
  8149. */
  8150. public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
  8151. {
  8152. $creatorId = api_get_user_id();
  8153. $sessionId = api_get_session_id();
  8154. // Generates folder
  8155. $result = $this->generate_lp_folder($courseInfo);
  8156. $dir = $result['dir'];
  8157. if (empty($parentId) || $parentId == '/') {
  8158. $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
  8159. $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
  8160. if ($parentId === '/') {
  8161. $dir = '/';
  8162. }
  8163. // Please, do not modify this dirname formatting.
  8164. if (strstr($dir, '..')) {
  8165. $dir = '/';
  8166. }
  8167. if (!empty($dir[0]) && $dir[0] == '.') {
  8168. $dir = substr($dir, 1);
  8169. }
  8170. if (!empty($dir[0]) && $dir[0] != '/') {
  8171. $dir = '/'.$dir;
  8172. }
  8173. if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
  8174. $dir .= '/';
  8175. }
  8176. } else {
  8177. $parentInfo = DocumentManager::get_document_data_by_id(
  8178. $parentId,
  8179. $courseInfo['code']
  8180. );
  8181. if (!empty($parentInfo)) {
  8182. $dir = $parentInfo['path'].'/';
  8183. }
  8184. }
  8185. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  8186. if (!is_dir($filepath)) {
  8187. $dir = '/';
  8188. $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
  8189. }
  8190. $originalTitle = !empty($title) ? $title : $_POST['title'];
  8191. if (!empty($title)) {
  8192. $title = api_replace_dangerous_char(stripslashes($title));
  8193. } else {
  8194. $title = api_replace_dangerous_char(stripslashes($_POST['title']));
  8195. }
  8196. $title = disable_dangerous_file($title);
  8197. $filename = $title;
  8198. $content = !empty($content) ? $content : $_POST['content_lp'];
  8199. $tmpFileName = $filename;
  8200. $i = 0;
  8201. while (file_exists($filepath.$tmpFileName.'.html')) {
  8202. $tmpFileName = $filename.'_'.++$i;
  8203. }
  8204. $filename = $tmpFileName.'.html';
  8205. $content = stripslashes($content);
  8206. if (file_exists($filepath.$filename)) {
  8207. return 0;
  8208. }
  8209. $putContent = file_put_contents($filepath.$filename, $content);
  8210. if ($putContent === false) {
  8211. return 0;
  8212. }
  8213. $fileSize = filesize($filepath.$filename);
  8214. $saveFilePath = $dir.$filename;
  8215. $documentId = add_document(
  8216. $courseInfo,
  8217. $saveFilePath,
  8218. 'file',
  8219. $fileSize,
  8220. $tmpFileName,
  8221. '',
  8222. 0, //readonly
  8223. true,
  8224. null,
  8225. $sessionId,
  8226. $creatorId
  8227. );
  8228. if (!$documentId) {
  8229. return 0;
  8230. }
  8231. api_item_property_update(
  8232. $courseInfo,
  8233. TOOL_DOCUMENT,
  8234. $documentId,
  8235. 'DocumentAdded',
  8236. $creatorId,
  8237. null,
  8238. null,
  8239. null,
  8240. null,
  8241. $sessionId
  8242. );
  8243. $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
  8244. $newTitle = $originalTitle;
  8245. if ($newComment || $newTitle) {
  8246. $em = Database::getManager();
  8247. /** @var CDocument $doc */
  8248. $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
  8249. if ($newComment) {
  8250. $doc->setComment($newComment);
  8251. }
  8252. if ($newTitle) {
  8253. $doc->setTitle($newTitle);
  8254. }
  8255. $em->persist($doc);
  8256. $em->flush();
  8257. }
  8258. return $documentId;
  8259. }
  8260. /**
  8261. * Return HTML form to add/edit a link item.
  8262. *
  8263. * @param string $action (add/edit)
  8264. * @param int $id Item ID if exists
  8265. * @param mixed $extra_info
  8266. *
  8267. * @throws Exception
  8268. * @throws HTML_QuickForm_Error
  8269. *
  8270. * @return string HTML form
  8271. */
  8272. public function display_link_form($action = 'add', $id = 0, $extra_info = '')
  8273. {
  8274. $course_id = api_get_course_int_id();
  8275. $tbl_link = Database::get_course_table(TABLE_LINK);
  8276. $item_title = '';
  8277. $item_description = '';
  8278. $item_url = '';
  8279. if ($id != 0 && is_array($extra_info)) {
  8280. $item_title = stripslashes($extra_info['title']);
  8281. $item_description = stripslashes($extra_info['description']);
  8282. $item_url = stripslashes($extra_info['url']);
  8283. } elseif (is_numeric($extra_info)) {
  8284. $extra_info = (int) $extra_info;
  8285. $sql = "SELECT title, description, url
  8286. FROM $tbl_link
  8287. WHERE c_id = $course_id AND iid = $extra_info";
  8288. $result = Database::query($sql);
  8289. $row = Database::fetch_array($result);
  8290. $item_title = $row['title'];
  8291. $item_description = $row['description'];
  8292. $item_url = $row['url'];
  8293. }
  8294. $form = new FormValidator(
  8295. 'edit_link',
  8296. 'POST',
  8297. $this->getCurrentBuildingModeURL()
  8298. );
  8299. $defaults = [];
  8300. $parent = 0;
  8301. if ($id != 0 && is_array($extra_info)) {
  8302. $parent = $extra_info['parent_item_id'];
  8303. }
  8304. $arrLP = $this->getItemsForForm();
  8305. $this->tree_array($arrLP);
  8306. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  8307. unset($this->arrMenu);
  8308. if ($action == 'add') {
  8309. $legend = get_lang('CreateTheLink');
  8310. } elseif ($action == 'move') {
  8311. $legend = get_lang('MoveCurrentLink');
  8312. } else {
  8313. $legend = get_lang('EditCurrentLink');
  8314. }
  8315. $form->addHeader($legend);
  8316. if ($action != 'move') {
  8317. $this->setItemTitle($form);
  8318. $defaults['title'] = $item_title;
  8319. }
  8320. $selectParent = $form->addSelect(
  8321. 'parent',
  8322. get_lang('Parent'),
  8323. [],
  8324. ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
  8325. );
  8326. $selectParent->addOption($this->name, 0);
  8327. $arrHide = [
  8328. $id,
  8329. ];
  8330. $parent_item_id = Session::read('parent_item_id', 0);
  8331. for ($i = 0; $i < count($arrLP); $i++) {
  8332. if ($action != 'add') {
  8333. if (
  8334. ($arrLP[$i]['item_type'] == 'dir') &&
  8335. !in_array($arrLP[$i]['id'], $arrHide) &&
  8336. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  8337. ) {
  8338. $selectParent->addOption(
  8339. $arrLP[$i]['title'],
  8340. $arrLP[$i]['id'],
  8341. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
  8342. );
  8343. if ($parent == $arrLP[$i]['id']) {
  8344. $selectParent->setSelected($arrLP[$i]['id']);
  8345. }
  8346. } else {
  8347. $arrHide[] = $arrLP[$i]['id'];
  8348. }
  8349. } else {
  8350. if ($arrLP[$i]['item_type'] == 'dir') {
  8351. $selectParent->addOption(
  8352. $arrLP[$i]['title'],
  8353. $arrLP[$i]['id'],
  8354. ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
  8355. );
  8356. if ($parent_item_id == $arrLP[$i]['id']) {
  8357. $selectParent->setSelected($arrLP[$i]['id']);
  8358. }
  8359. }
  8360. }
  8361. }
  8362. if (is_array($arrLP)) {
  8363. reset($arrLP);
  8364. }
  8365. $selectPrevious = $form->addSelect(
  8366. 'previous',
  8367. get_lang('Position'),
  8368. [],
  8369. ['id' => 'previous', 'class' => 'learnpath_item_form']
  8370. );
  8371. $selectPrevious->addOption(get_lang('FirstPosition'), 0);
  8372. for ($i = 0; $i < count($arrLP); $i++) {
  8373. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  8374. $selectPrevious->addOption(
  8375. $arrLP[$i]['title'],
  8376. $arrLP[$i]['id']
  8377. );
  8378. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  8379. $selectPrevious->setSelected($arrLP[$i]['id']);
  8380. } elseif ($action == 'add') {
  8381. $selectPrevious->setSelected($arrLP[$i]['id']);
  8382. }
  8383. }
  8384. }
  8385. if ($action != 'move') {
  8386. $urlAttributes = ['class' => 'learnpath_item_form'];
  8387. if (is_numeric($extra_info)) {
  8388. $urlAttributes['disabled'] = 'disabled';
  8389. }
  8390. $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
  8391. $defaults['url'] = $item_url;
  8392. $arrHide = [];
  8393. for ($i = 0; $i < count($arrLP); $i++) {
  8394. if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
  8395. $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
  8396. }
  8397. }
  8398. }
  8399. if ($action == 'add') {
  8400. $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
  8401. } else {
  8402. $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
  8403. }
  8404. if ($action == 'move') {
  8405. $form->addHidden('title', $item_title);
  8406. $form->addHidden('description', $item_description);
  8407. }
  8408. if (is_numeric($extra_info)) {
  8409. $form->addHidden('path', $extra_info);
  8410. } elseif (is_array($extra_info)) {
  8411. $form->addHidden('path', $extra_info['path']);
  8412. }
  8413. $form->addHidden('type', TOOL_LINK);
  8414. $form->addHidden('post_time', time());
  8415. $form->setDefaults($defaults);
  8416. return '<div class="sectioncomment">'.$form->returnForm().'</div>';
  8417. }
  8418. /**
  8419. * Return HTML form to add/edit a student publication (work).
  8420. *
  8421. * @param string $action
  8422. * @param int $id Item ID if already exists
  8423. * @param string $extra_info
  8424. *
  8425. * @throws Exception
  8426. *
  8427. * @return string HTML form
  8428. */
  8429. public function display_student_publication_form(
  8430. $action = 'add',
  8431. $id = 0,
  8432. $extra_info = ''
  8433. ) {
  8434. $course_id = api_get_course_int_id();
  8435. $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
  8436. $item_title = get_lang('Student_publication');
  8437. if ($id != 0 && is_array($extra_info)) {
  8438. $item_title = stripslashes($extra_info['title']);
  8439. $item_description = stripslashes($extra_info['description']);
  8440. } elseif (is_numeric($extra_info)) {
  8441. $extra_info = (int) $extra_info;
  8442. $sql = "SELECT title, description
  8443. FROM $tbl_publication
  8444. WHERE c_id = $course_id AND id = ".$extra_info;
  8445. $result = Database::query($sql);
  8446. $row = Database::fetch_array($result);
  8447. if ($row) {
  8448. $item_title = $row['title'];
  8449. }
  8450. }
  8451. $parent = 0;
  8452. if ($id != 0 && is_array($extra_info)) {
  8453. $parent = $extra_info['parent_item_id'];
  8454. }
  8455. $arrLP = $this->getItemsForForm();
  8456. $this->tree_array($arrLP);
  8457. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  8458. unset($this->arrMenu);
  8459. $form = new FormValidator('frm_student_publication', 'post', '#');
  8460. if ($action == 'add') {
  8461. $form->addHeader(get_lang('Student_publication'));
  8462. } elseif ($action == 'move') {
  8463. $form->addHeader(get_lang('MoveCurrentStudentPublication'));
  8464. } else {
  8465. $form->addHeader(get_lang('EditCurrentStudentPublication'));
  8466. }
  8467. if ($action != 'move') {
  8468. $this->setItemTitle($form);
  8469. }
  8470. $parentSelect = $form->addSelect(
  8471. 'parent',
  8472. get_lang('Parent'),
  8473. ['0' => $this->name],
  8474. [
  8475. 'onchange' => 'javascript: load_cbo(this.value);',
  8476. 'class' => 'learnpath_item_form',
  8477. 'id' => 'idParent',
  8478. ]
  8479. );
  8480. $arrHide = [$id];
  8481. for ($i = 0; $i < count($arrLP); $i++) {
  8482. if ($action != 'add') {
  8483. if (
  8484. ($arrLP[$i]['item_type'] == 'dir') &&
  8485. !in_array($arrLP[$i]['id'], $arrHide) &&
  8486. !in_array($arrLP[$i]['parent_item_id'], $arrHide)
  8487. ) {
  8488. $parentSelect->addOption(
  8489. $arrLP[$i]['title'],
  8490. $arrLP[$i]['id'],
  8491. ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
  8492. );
  8493. if ($parent == $arrLP[$i]['id']) {
  8494. $parentSelect->setSelected($arrLP[$i]['id']);
  8495. }
  8496. } else {
  8497. $arrHide[] = $arrLP[$i]['id'];
  8498. }
  8499. } else {
  8500. if ($arrLP[$i]['item_type'] == 'dir') {
  8501. $parentSelect->addOption(
  8502. $arrLP[$i]['title'],
  8503. $arrLP[$i]['id'],
  8504. ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
  8505. );
  8506. if ($parent == $arrLP[$i]['id']) {
  8507. $parentSelect->setSelected($arrLP[$i]['id']);
  8508. }
  8509. }
  8510. }
  8511. }
  8512. if (is_array($arrLP)) {
  8513. reset($arrLP);
  8514. }
  8515. $previousSelect = $form->addSelect(
  8516. 'previous',
  8517. get_lang('Position'),
  8518. ['0' => get_lang('FirstPosition')],
  8519. ['id' => 'previous', 'class' => 'learnpath_item_form']
  8520. );
  8521. for ($i = 0; $i < count($arrLP); $i++) {
  8522. if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
  8523. $previousSelect->addOption(
  8524. get_lang('After').' "'.$arrLP[$i]['title'].'"',
  8525. $arrLP[$i]['id']
  8526. );
  8527. if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
  8528. $previousSelect->setSelected($arrLP[$i]['id']);
  8529. } elseif ($action == 'add') {
  8530. $previousSelect->setSelected($arrLP[$i]['id']);
  8531. }
  8532. }
  8533. }
  8534. if ($action == 'add') {
  8535. $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
  8536. } else {
  8537. $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
  8538. }
  8539. if ($action == 'move') {
  8540. $form->addHidden('title', $item_title);
  8541. $form->addHidden('description', $item_description);
  8542. }
  8543. if (is_numeric($extra_info)) {
  8544. $form->addHidden('path', $extra_info);
  8545. } elseif (is_array($extra_info)) {
  8546. $form->addHidden('path', $extra_info['path']);
  8547. }
  8548. $form->addHidden('type', TOOL_STUDENTPUBLICATION);
  8549. $form->addHidden('post_time', time());
  8550. $form->setDefaults(['title' => $item_title]);
  8551. $return = '<div class="sectioncomment">';
  8552. $return .= $form->returnForm();
  8553. $return .= '</div>';
  8554. return $return;
  8555. }
  8556. /**
  8557. * Displays the menu for manipulating a step.
  8558. *
  8559. * @param id $item_id
  8560. * @param string $item_type
  8561. *
  8562. * @return string
  8563. */
  8564. public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
  8565. {
  8566. $_course = api_get_course_info();
  8567. $course_code = api_get_course_id();
  8568. $item_id = (int) $item_id;
  8569. $return = '<div class="actions">';
  8570. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8571. $sql = "SELECT * FROM $tbl_lp_item
  8572. WHERE iid = ".$item_id;
  8573. $result = Database::query($sql);
  8574. $row = Database::fetch_assoc($result);
  8575. $audio_player = null;
  8576. // We display an audio player if needed.
  8577. if (!empty($row['audio'])) {
  8578. $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
  8579. $audio_player .= '<div class="lp_mediaplayer" id="container">'
  8580. .'<audio src="'.$webAudioPath.'" controls>'
  8581. .'</div><br>';
  8582. }
  8583. $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
  8584. if ($item_type != TOOL_LP_FINAL_ITEM) {
  8585. $return .= Display::url(
  8586. Display::return_icon(
  8587. 'edit.png',
  8588. get_lang('Edit'),
  8589. [],
  8590. ICON_SIZE_SMALL
  8591. ),
  8592. $url.'&action=edit_item&path_item='.$row['path']
  8593. );
  8594. $return .= Display::url(
  8595. Display::return_icon(
  8596. 'move.png',
  8597. get_lang('Move'),
  8598. [],
  8599. ICON_SIZE_SMALL
  8600. ),
  8601. $url.'&action=move_item'
  8602. );
  8603. }
  8604. // Commented for now as prerequisites cannot be added to chapters.
  8605. if ($item_type != 'dir') {
  8606. $return .= Display::url(
  8607. Display::return_icon(
  8608. 'accept.png',
  8609. get_lang('LearnpathPrerequisites'),
  8610. [],
  8611. ICON_SIZE_SMALL
  8612. ),
  8613. $url.'&action=edit_item_prereq'
  8614. );
  8615. }
  8616. $return .= Display::url(
  8617. Display::return_icon(
  8618. 'delete.png',
  8619. get_lang('Delete'),
  8620. [],
  8621. ICON_SIZE_SMALL
  8622. ),
  8623. $url.'&action=delete_item'
  8624. );
  8625. if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
  8626. $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
  8627. if (empty($documentData)) {
  8628. // Try with iid
  8629. $table = Database::get_course_table(TABLE_DOCUMENT);
  8630. $sql = "SELECT path FROM $table
  8631. WHERE
  8632. c_id = ".api_get_course_int_id()." AND
  8633. iid = ".$row['path']." AND
  8634. path NOT LIKE '%_DELETED_%'";
  8635. $result = Database::query($sql);
  8636. $documentData = Database::fetch_array($result);
  8637. if ($documentData) {
  8638. $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
  8639. }
  8640. }
  8641. if (isset($documentData['absolute_path_from_document'])) {
  8642. $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
  8643. }
  8644. }
  8645. $return .= '</div>';
  8646. if (!empty($audio_player)) {
  8647. $return .= $audio_player;
  8648. }
  8649. return $return;
  8650. }
  8651. /**
  8652. * Creates the javascript needed for filling up the checkboxes without page reload.
  8653. *
  8654. * @return string
  8655. */
  8656. public function get_js_dropdown_array()
  8657. {
  8658. $course_id = api_get_course_int_id();
  8659. $return = 'var child_name = new Array();'."\n";
  8660. $return .= 'var child_value = new Array();'."\n\n";
  8661. $return .= 'child_name[0] = new Array();'."\n";
  8662. $return .= 'child_value[0] = new Array();'."\n\n";
  8663. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8664. $sql = "SELECT * FROM ".$tbl_lp_item."
  8665. WHERE
  8666. c_id = $course_id AND
  8667. lp_id = ".$this->lp_id." AND
  8668. parent_item_id = 0
  8669. ORDER BY display_order ASC";
  8670. $res_zero = Database::query($sql);
  8671. $i = 0;
  8672. $list = $this->getItemsForForm(true);
  8673. foreach ($list as $row_zero) {
  8674. if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
  8675. if ($row_zero['item_type'] == TOOL_QUIZ) {
  8676. $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
  8677. }
  8678. $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
  8679. $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
  8680. $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
  8681. }
  8682. }
  8683. $return .= "\n";
  8684. $sql = "SELECT * FROM $tbl_lp_item
  8685. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  8686. $res = Database::query($sql);
  8687. while ($row = Database::fetch_array($res)) {
  8688. $sql_parent = "SELECT * FROM ".$tbl_lp_item."
  8689. WHERE
  8690. c_id = ".$course_id." AND
  8691. parent_item_id = ".$row['iid']."
  8692. ORDER BY display_order ASC";
  8693. $res_parent = Database::query($sql_parent);
  8694. $i = 0;
  8695. $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
  8696. $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
  8697. while ($row_parent = Database::fetch_array($res_parent)) {
  8698. $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
  8699. $return .= 'child_name['.$row['iid'].']['.$i.'] = '.$js_var.' ;'."\n";
  8700. $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
  8701. }
  8702. $return .= "\n";
  8703. }
  8704. $return .= "
  8705. function load_cbo(id) {
  8706. if (!id) {
  8707. return false;
  8708. }
  8709. var cbo = document.getElementById('previous');
  8710. for(var i = cbo.length - 1; i > 0; i--) {
  8711. cbo.options[i] = null;
  8712. }
  8713. var k=0;
  8714. for(var i = 1; i <= child_name[id].length; i++){
  8715. var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
  8716. option.style.paddingLeft = '40px';
  8717. cbo.options[i] = option;
  8718. k = i;
  8719. }
  8720. cbo.options[k].selected = true;
  8721. $('#previous').selectpicker('refresh');
  8722. }";
  8723. return $return;
  8724. }
  8725. /**
  8726. * Display the form to allow moving an item.
  8727. *
  8728. * @param int $item_id Item ID
  8729. *
  8730. * @throws Exception
  8731. * @throws HTML_QuickForm_Error
  8732. *
  8733. * @return string HTML form
  8734. */
  8735. public function display_move_item($item_id)
  8736. {
  8737. $return = '';
  8738. if (is_numeric($item_id)) {
  8739. $item_id = (int) $item_id;
  8740. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8741. $sql = "SELECT * FROM $tbl_lp_item
  8742. WHERE iid = $item_id";
  8743. $res = Database::query($sql);
  8744. $row = Database::fetch_array($res);
  8745. switch ($row['item_type']) {
  8746. case 'dir':
  8747. case 'asset':
  8748. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8749. $return .= $this->display_item_form(
  8750. $row['item_type'],
  8751. get_lang('MoveCurrentChapter'),
  8752. 'move',
  8753. $item_id,
  8754. $row
  8755. );
  8756. break;
  8757. case TOOL_DOCUMENT:
  8758. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8759. $return .= $this->display_document_form('move', $item_id, $row);
  8760. break;
  8761. case TOOL_LINK:
  8762. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8763. $return .= $this->display_link_form('move', $item_id, $row);
  8764. break;
  8765. case TOOL_HOTPOTATOES:
  8766. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8767. $return .= $this->display_link_form('move', $item_id, $row);
  8768. break;
  8769. case TOOL_QUIZ:
  8770. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8771. $return .= $this->display_quiz_form('move', $item_id, $row);
  8772. break;
  8773. case TOOL_STUDENTPUBLICATION:
  8774. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8775. $return .= $this->display_student_publication_form('move', $item_id, $row);
  8776. break;
  8777. case TOOL_FORUM:
  8778. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8779. $return .= $this->display_forum_form('move', $item_id, $row);
  8780. break;
  8781. case TOOL_THREAD:
  8782. $return .= $this->display_manipulate($item_id, $row['item_type']);
  8783. $return .= $this->display_forum_form('move', $item_id, $row);
  8784. break;
  8785. }
  8786. }
  8787. return $return;
  8788. }
  8789. /**
  8790. * Return HTML form to allow prerequisites selection.
  8791. *
  8792. * @todo use FormValidator
  8793. *
  8794. * @param int Item ID
  8795. *
  8796. * @return string HTML form
  8797. */
  8798. public function display_item_prerequisites_form($item_id = 0)
  8799. {
  8800. $course_id = api_get_course_int_id();
  8801. $item_id = (int) $item_id;
  8802. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  8803. /* Current prerequisite */
  8804. $sql = "SELECT * FROM $tbl_lp_item
  8805. WHERE iid = $item_id";
  8806. $result = Database::query($sql);
  8807. $row = Database::fetch_array($result);
  8808. $prerequisiteId = $row['prerequisite'];
  8809. $return = '<legend>';
  8810. $return .= get_lang('AddEditPrerequisites');
  8811. $return .= '</legend>';
  8812. $return .= '<form method="POST">';
  8813. $return .= '<div class="table-responsive">';
  8814. $return .= '<table class="table table-hover">';
  8815. $return .= '<thead>';
  8816. $return .= '<tr>';
  8817. $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
  8818. $return .= '<th width="140">'.get_lang('Minimum').'</th>';
  8819. $return .= '<th width="140">'.get_lang('Maximum').'</th>';
  8820. $return .= '</tr>';
  8821. $return .= '</thead>';
  8822. // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
  8823. $return .= '<tbody>';
  8824. $return .= '<tr>';
  8825. $return .= '<td colspan="3">';
  8826. $return .= '<div class="radio learnpath"><label for="idNone">';
  8827. $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
  8828. $return .= get_lang('None').'</label>';
  8829. $return .= '</div>';
  8830. $return .= '</tr>';
  8831. $sql = "SELECT * FROM $tbl_lp_item
  8832. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  8833. $result = Database::query($sql);
  8834. $selectedMinScore = [];
  8835. $selectedMaxScore = [];
  8836. $masteryScore = [];
  8837. while ($row = Database::fetch_array($result)) {
  8838. if ($row['iid'] == $item_id) {
  8839. $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
  8840. $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
  8841. }
  8842. $masteryScore[$row['iid']] = $row['mastery_score'];
  8843. }
  8844. $arrLP = $this->getItemsForForm();
  8845. $this->tree_array($arrLP);
  8846. $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
  8847. unset($this->arrMenu);
  8848. for ($i = 0; $i < count($arrLP); $i++) {
  8849. $item = $arrLP[$i];
  8850. if ($item['id'] == $item_id) {
  8851. break;
  8852. }
  8853. $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
  8854. $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
  8855. $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
  8856. $return .= '<tr>';
  8857. $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
  8858. $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
  8859. $return .= '<label for="id'.$item['id'].'">';
  8860. $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'].'" />';
  8861. $icon_name = str_replace(' ', '', $item['item_type']);
  8862. if (file_exists('../img/lp_'.$icon_name.'.png')) {
  8863. $return .= Display::return_icon('lp_'.$icon_name.'.png');
  8864. } else {
  8865. if (file_exists('../img/lp_'.$icon_name.'.png')) {
  8866. $return .= Display::return_icon('lp_'.$icon_name.'.png');
  8867. } else {
  8868. $return .= Display::return_icon('folder_document.png');
  8869. }
  8870. }
  8871. $return .= $item['title'].'</label>';
  8872. $return .= '</div>';
  8873. $return .= '</td>';
  8874. if ($item['item_type'] == TOOL_QUIZ) {
  8875. // lets update max_score Quiz information depending of the Quiz Advanced properties
  8876. $lpItemObj = new LpItem($course_id, $item['id']);
  8877. $exercise = new Exercise($course_id);
  8878. $exercise->read($lpItemObj->path);
  8879. $lpItemObj->max_score = $exercise->get_max_score();
  8880. $lpItemObj->update();
  8881. $item['max_score'] = $lpItemObj->max_score;
  8882. if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
  8883. // Backwards compatibility with 1.9.x use mastery_score as min value
  8884. $selectedMinScoreValue = $masteryScoreAsMinValue;
  8885. }
  8886. $return .= '<td>';
  8887. $return .= '<input
  8888. class="form-control"
  8889. size="4" maxlength="3"
  8890. name="min_'.$item['id'].'"
  8891. type="number"
  8892. min="0"
  8893. step="1"
  8894. max="'.$item['max_score'].'"
  8895. value="'.$selectedMinScoreValue.'"
  8896. />';
  8897. $return .= '</td>';
  8898. $return .= '<td>';
  8899. $return .= '<input
  8900. class="form-control"
  8901. size="4"
  8902. maxlength="3"
  8903. name="max_'.$item['id'].'"
  8904. type="number"
  8905. min="0"
  8906. step="1"
  8907. max="'.$item['max_score'].'"
  8908. value="'.$selectedMaxScoreValue.'"
  8909. />';
  8910. $return .= '</td>';
  8911. }
  8912. if ($item['item_type'] == TOOL_HOTPOTATOES) {
  8913. $return .= '<td>';
  8914. $return .= '<input
  8915. size="4"
  8916. maxlength="3"
  8917. name="min_'.$item['id'].'"
  8918. type="number"
  8919. min="0"
  8920. step="1"
  8921. max="'.$item['max_score'].'"
  8922. value="'.$selectedMinScoreValue.'"
  8923. />';
  8924. $return .= '</td>';
  8925. $return .= '<td>';
  8926. $return .= '<input
  8927. size="4"
  8928. maxlength="3"
  8929. name="max_'.$item['id'].'"
  8930. type="number"
  8931. min="0"
  8932. step="1"
  8933. max="'.$item['max_score'].'"
  8934. value="'.$selectedMaxScoreValue.'"
  8935. />';
  8936. $return .= '</td>';
  8937. }
  8938. $return .= '</tr>';
  8939. }
  8940. $return .= '<tr>';
  8941. $return .= '</tr>';
  8942. $return .= '</tbody>';
  8943. $return .= '</table>';
  8944. $return .= '</div>';
  8945. $return .= '<div class="form-group">';
  8946. $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
  8947. get_lang('ModifyPrerequisites').'</button>';
  8948. $return .= '</form>';
  8949. return $return;
  8950. }
  8951. /**
  8952. * Return HTML list to allow prerequisites selection for lp.
  8953. *
  8954. * @return string HTML form
  8955. */
  8956. public function display_lp_prerequisites_list()
  8957. {
  8958. $course_id = api_get_course_int_id();
  8959. $lp_id = $this->lp_id;
  8960. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  8961. // get current prerequisite
  8962. $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
  8963. $result = Database::query($sql);
  8964. $row = Database::fetch_array($result);
  8965. $prerequisiteId = $row['prerequisite'];
  8966. $session_id = api_get_session_id();
  8967. $session_condition = api_get_session_condition($session_id, true, true);
  8968. $sql = "SELECT * FROM $tbl_lp
  8969. WHERE c_id = $course_id $session_condition
  8970. ORDER BY display_order ";
  8971. $rs = Database::query($sql);
  8972. $return = '';
  8973. $return .= '<select name="prerequisites" class="form-control">';
  8974. $return .= '<option value="0">'.get_lang('None').'</option>';
  8975. if (Database::num_rows($rs) > 0) {
  8976. while ($row = Database::fetch_array($rs)) {
  8977. if ($row['id'] == $lp_id) {
  8978. continue;
  8979. }
  8980. $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
  8981. }
  8982. }
  8983. $return .= '</select>';
  8984. return $return;
  8985. }
  8986. /**
  8987. * Creates a list with all the documents in it.
  8988. *
  8989. * @param bool $showInvisibleFiles
  8990. *
  8991. * @throws Exception
  8992. * @throws HTML_QuickForm_Error
  8993. *
  8994. * @return string
  8995. */
  8996. public function get_documents($showInvisibleFiles = false)
  8997. {
  8998. $course_info = api_get_course_info();
  8999. $sessionId = api_get_session_id();
  9000. $documentTree = DocumentManager::get_document_preview(
  9001. $course_info,
  9002. $this->lp_id,
  9003. null,
  9004. $sessionId,
  9005. true,
  9006. null,
  9007. null,
  9008. $showInvisibleFiles,
  9009. true
  9010. );
  9011. $headers = [
  9012. get_lang('Files'),
  9013. get_lang('CreateTheDocument'),
  9014. get_lang('CreateReadOutText'),
  9015. get_lang('Upload'),
  9016. ];
  9017. $form = new FormValidator(
  9018. 'form_upload',
  9019. 'POST',
  9020. $this->getCurrentBuildingModeURL(),
  9021. '',
  9022. ['enctype' => 'multipart/form-data']
  9023. );
  9024. $folders = DocumentManager::get_all_document_folders(
  9025. api_get_course_info(),
  9026. 0,
  9027. true
  9028. );
  9029. $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
  9030. DocumentManager::build_directory_selector(
  9031. $folders,
  9032. $lpPathInfo['id'],
  9033. [],
  9034. true,
  9035. $form,
  9036. 'directory_parent_id'
  9037. );
  9038. $group = [
  9039. $form->createElement(
  9040. 'radio',
  9041. 'if_exists',
  9042. get_lang('UplWhatIfFileExists'),
  9043. get_lang('UplDoNothing'),
  9044. 'nothing'
  9045. ),
  9046. $form->createElement(
  9047. 'radio',
  9048. 'if_exists',
  9049. null,
  9050. get_lang('UplOverwriteLong'),
  9051. 'overwrite'
  9052. ),
  9053. $form->createElement(
  9054. 'radio',
  9055. 'if_exists',
  9056. null,
  9057. get_lang('UplRenameLong'),
  9058. 'rename'
  9059. ),
  9060. ];
  9061. $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
  9062. $fileExistsOption = api_get_setting('document_if_file_exists_option');
  9063. $defaultFileExistsOption = 'rename';
  9064. if (!empty($fileExistsOption)) {
  9065. $defaultFileExistsOption = $fileExistsOption;
  9066. }
  9067. $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
  9068. // Check box options
  9069. $form->addElement(
  9070. 'checkbox',
  9071. 'unzip',
  9072. get_lang('Options'),
  9073. get_lang('Uncompress')
  9074. );
  9075. $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
  9076. $form->addMultipleUpload($url);
  9077. $new = $this->display_document_form('add', 0);
  9078. $frmReadOutText = $this->displayFrmReadOutText('add');
  9079. $tabs = Display::tabs(
  9080. $headers,
  9081. [$documentTree, $new, $frmReadOutText, $form->returnForm()],
  9082. 'subtab'
  9083. );
  9084. return $tabs;
  9085. }
  9086. /**
  9087. * Creates a list with all the exercises (quiz) in it.
  9088. *
  9089. * @return string
  9090. */
  9091. public function get_exercises()
  9092. {
  9093. $course_id = api_get_course_int_id();
  9094. $session_id = api_get_session_id();
  9095. $userInfo = api_get_user_info();
  9096. // New for hotpotatoes.
  9097. $uploadPath = DIR_HOTPOTATOES; //defined in main_api
  9098. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  9099. $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
  9100. $condition_session = api_get_session_condition($session_id, true, true);
  9101. $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
  9102. $activeCondition = ' active <> -1 ';
  9103. if ($setting) {
  9104. $activeCondition = ' active = 1 ';
  9105. }
  9106. $categoryCondition = '';
  9107. $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
  9108. if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
  9109. $categoryCondition = " AND exercise_category_id = $categoryId ";
  9110. }
  9111. $keywordCondition = '';
  9112. $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
  9113. if (!empty($keyword)) {
  9114. $keyword = Database::escape_string($keyword);
  9115. $keywordCondition = " AND title LIKE '%$keyword%' ";
  9116. }
  9117. $sql_quiz = "SELECT * FROM $tbl_quiz
  9118. WHERE
  9119. c_id = $course_id AND
  9120. $activeCondition
  9121. $condition_session
  9122. $categoryCondition
  9123. $keywordCondition
  9124. ORDER BY title ASC";
  9125. $sql_hot = "SELECT * FROM $tbl_doc
  9126. WHERE
  9127. c_id = $course_id AND
  9128. path LIKE '".$uploadPath."/%/%htm%'
  9129. $condition_session
  9130. ORDER BY id ASC";
  9131. $res_quiz = Database::query($sql_quiz);
  9132. $res_hot = Database::query($sql_hot);
  9133. $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
  9134. // Create a search-box
  9135. $form = new FormValidator('search_simple', 'get', $currentUrl);
  9136. $form->addHidden('action', 'add_item');
  9137. $form->addHidden('type', 'step');
  9138. $form->addHidden('lp_id', $this->lp_id);
  9139. $form->addHidden('lp_build_selected', '2');
  9140. $form->addCourseHiddenParams();
  9141. $form->addText(
  9142. 'keyword',
  9143. get_lang('Search'),
  9144. false,
  9145. [
  9146. 'aria-label' => get_lang('Search'),
  9147. ]
  9148. );
  9149. if (api_get_configuration_value('allow_exercise_categories')) {
  9150. $manager = new ExerciseCategoryManager();
  9151. $options = $manager->getCategoriesForSelect(api_get_course_int_id());
  9152. if (!empty($options)) {
  9153. $form->addSelect(
  9154. 'category_id',
  9155. get_lang('Category'),
  9156. $options,
  9157. ['placeholder' => get_lang('SelectAnOption')]
  9158. );
  9159. }
  9160. }
  9161. $form->addButtonSearch(get_lang('Search'));
  9162. $return = $form->returnForm();
  9163. $return .= '<ul class="lp_resource">';
  9164. $return .= '<li class="lp_resource_element">';
  9165. $return .= Display::return_icon('new_exercice.png');
  9166. $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
  9167. get_lang('NewExercise').'</a>';
  9168. $return .= '</li>';
  9169. $previewIcon = Display::return_icon(
  9170. 'preview_view.png',
  9171. get_lang('Preview')
  9172. );
  9173. $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
  9174. $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
  9175. $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
  9176. // Display hotpotatoes
  9177. while ($row_hot = Database::fetch_array($res_hot)) {
  9178. $link = Display::url(
  9179. $previewIcon,
  9180. $exerciseUrl.'&file='.$row_hot['path'],
  9181. ['target' => '_blank']
  9182. );
  9183. $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
  9184. $return .= '<a class="moved" href="#">';
  9185. $return .= Display::return_icon(
  9186. 'move_everywhere.png',
  9187. get_lang('Move'),
  9188. [],
  9189. ICON_SIZE_TINY
  9190. );
  9191. $return .= '</a> ';
  9192. $return .= Display::return_icon('hotpotatoes_s.png');
  9193. $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
  9194. ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
  9195. $return .= '</li>';
  9196. }
  9197. $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
  9198. while ($row_quiz = Database::fetch_array($res_quiz)) {
  9199. $title = strip_tags(
  9200. api_html_entity_decode($row_quiz['title'])
  9201. );
  9202. $visibility = api_get_item_visibility(
  9203. ['real_id' => $course_id],
  9204. TOOL_QUIZ,
  9205. $row_quiz['iid'],
  9206. $session_id
  9207. );
  9208. $link = Display::url(
  9209. $previewIcon,
  9210. $exerciseUrl.'&exerciseId='.$row_quiz['id'],
  9211. ['target' => '_blank']
  9212. );
  9213. $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
  9214. $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
  9215. $return .= $quizIcon;
  9216. $sessionStar = api_get_session_image(
  9217. $row_quiz['session_id'],
  9218. $userInfo['status']
  9219. );
  9220. $return .= Display::url(
  9221. Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
  9222. api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
  9223. [
  9224. 'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
  9225. ]
  9226. );
  9227. $return .= '</li>';
  9228. }
  9229. $return .= '</ul>';
  9230. return $return;
  9231. }
  9232. /**
  9233. * Creates a list with all the links in it.
  9234. *
  9235. * @return string
  9236. */
  9237. public function get_links()
  9238. {
  9239. $selfUrl = api_get_self();
  9240. $courseIdReq = api_get_cidreq();
  9241. $course = api_get_course_info();
  9242. $userInfo = api_get_user_info();
  9243. $course_id = $course['real_id'];
  9244. $tbl_link = Database::get_course_table(TABLE_LINK);
  9245. $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
  9246. $moveEverywhereIcon = Display::return_icon(
  9247. 'move_everywhere.png',
  9248. get_lang('Move'),
  9249. [],
  9250. ICON_SIZE_TINY
  9251. );
  9252. $session_id = api_get_session_id();
  9253. $condition_session = api_get_session_condition(
  9254. $session_id,
  9255. true,
  9256. true,
  9257. 'link.session_id'
  9258. );
  9259. $sql = "SELECT
  9260. link.id as link_id,
  9261. link.title as link_title,
  9262. link.session_id as link_session_id,
  9263. link.category_id as category_id,
  9264. link_category.category_title as category_title
  9265. FROM $tbl_link as link
  9266. LEFT JOIN $linkCategoryTable as link_category
  9267. ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
  9268. WHERE link.c_id = $course_id $condition_session
  9269. ORDER BY link_category.category_title ASC, link.title ASC";
  9270. $result = Database::query($sql);
  9271. $categorizedLinks = [];
  9272. $categories = [];
  9273. while ($link = Database::fetch_array($result)) {
  9274. if (!$link['category_id']) {
  9275. $link['category_title'] = get_lang('Uncategorized');
  9276. }
  9277. $categories[$link['category_id']] = $link['category_title'];
  9278. $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
  9279. }
  9280. $linksHtmlCode =
  9281. '<script>
  9282. function toggle_tool(tool, id) {
  9283. if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
  9284. document.getElementById(tool+"_"+id+"_content").style.display = "block";
  9285. document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
  9286. } else {
  9287. document.getElementById(tool+"_"+id+"_content").style.display = "none";
  9288. document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
  9289. }
  9290. }
  9291. </script>
  9292. <ul class="lp_resource">
  9293. <li class="lp_resource_element">
  9294. '.Display::return_icon('linksnew.gif').'
  9295. <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
  9296. get_lang('LinkAdd').'
  9297. </a>
  9298. </li>';
  9299. foreach ($categorizedLinks as $categoryId => $links) {
  9300. $linkNodes = null;
  9301. foreach ($links as $key => $linkInfo) {
  9302. $title = $linkInfo['link_title'];
  9303. $linkSessionId = $linkInfo['link_session_id'];
  9304. $link = Display::url(
  9305. Display::return_icon('preview_view.png', get_lang('Preview')),
  9306. api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
  9307. ['target' => '_blank']
  9308. );
  9309. if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
  9310. $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
  9311. $linkNodes .=
  9312. '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
  9313. <a class="moved" href="#">'.
  9314. $moveEverywhereIcon.
  9315. '</a>
  9316. '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
  9317. <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
  9318. TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
  9319. Security::remove_XSS($title).$sessionStar.$link.
  9320. '</a>
  9321. </li>';
  9322. }
  9323. }
  9324. $linksHtmlCode .=
  9325. '<li>
  9326. <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
  9327. <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
  9328. align="absbottom" />
  9329. </a>
  9330. <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
  9331. </li>
  9332. <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
  9333. }
  9334. $linksHtmlCode .= '</ul>';
  9335. return $linksHtmlCode;
  9336. }
  9337. /**
  9338. * Creates a list with all the student publications in it.
  9339. *
  9340. * @return string
  9341. */
  9342. public function get_student_publications()
  9343. {
  9344. $return = '<ul class="lp_resource">';
  9345. $return .= '<li class="lp_resource_element">';
  9346. $return .= Display::return_icon('works_new.gif');
  9347. $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
  9348. get_lang('AddAssignmentPage').'</a>';
  9349. $return .= '</li>';
  9350. require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
  9351. $works = getWorkListTeacher(0, 100, null, null, null);
  9352. if (!empty($works)) {
  9353. foreach ($works as $work) {
  9354. $link = Display::url(
  9355. Display::return_icon('preview_view.png', get_lang('Preview')),
  9356. api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
  9357. ['target' => '_blank']
  9358. );
  9359. $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
  9360. $return .= '<a class="moved" href="#">';
  9361. $return .= Display::return_icon(
  9362. 'move_everywhere.png',
  9363. get_lang('Move'),
  9364. [],
  9365. ICON_SIZE_TINY
  9366. );
  9367. $return .= '</a> ';
  9368. $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
  9369. $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.'">'.
  9370. Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
  9371. </a>';
  9372. $return .= '</li>';
  9373. }
  9374. }
  9375. $return .= '</ul>';
  9376. return $return;
  9377. }
  9378. /**
  9379. * Creates a list with all the forums in it.
  9380. *
  9381. * @return string
  9382. */
  9383. public function get_forums()
  9384. {
  9385. require_once '../forum/forumfunction.inc.php';
  9386. $forumCategories = get_forum_categories();
  9387. $forumsInNoCategory = get_forums_in_category(0);
  9388. if (!empty($forumsInNoCategory)) {
  9389. $forumCategories = array_merge(
  9390. $forumCategories,
  9391. [
  9392. [
  9393. 'cat_id' => 0,
  9394. 'session_id' => 0,
  9395. 'visibility' => 1,
  9396. 'cat_comment' => null,
  9397. ],
  9398. ]
  9399. );
  9400. }
  9401. $forumList = get_forums();
  9402. $a_forums = [];
  9403. foreach ($forumCategories as $forumCategory) {
  9404. // The forums in this category.
  9405. $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
  9406. if (!empty($forumsInCategory)) {
  9407. foreach ($forumList as $forum) {
  9408. if (isset($forum['forum_category']) &&
  9409. $forum['forum_category'] == $forumCategory['cat_id']
  9410. ) {
  9411. $a_forums[] = $forum;
  9412. }
  9413. }
  9414. }
  9415. }
  9416. $return = '<ul class="lp_resource">';
  9417. // First add link
  9418. $return .= '<li class="lp_resource_element">';
  9419. $return .= Display::return_icon('new_forum.png');
  9420. $return .= Display::url(
  9421. get_lang('CreateANewForum'),
  9422. api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
  9423. 'action' => 'add',
  9424. 'content' => 'forum',
  9425. 'lp_id' => $this->lp_id,
  9426. ]),
  9427. ['title' => get_lang('CreateANewForum')]
  9428. );
  9429. $return .= '</li>';
  9430. $return .= '<script>
  9431. function toggle_forum(forum_id) {
  9432. if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
  9433. document.getElementById("forum_"+forum_id+"_content").style.display = "block";
  9434. document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
  9435. } else {
  9436. document.getElementById("forum_"+forum_id+"_content").style.display = "none";
  9437. document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
  9438. }
  9439. }
  9440. </script>';
  9441. foreach ($a_forums as $forum) {
  9442. if (!empty($forum['forum_id'])) {
  9443. $link = Display::url(
  9444. Display::return_icon('preview_view.png', get_lang('Preview')),
  9445. api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
  9446. ['target' => '_blank']
  9447. );
  9448. $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
  9449. $return .= '<a class="moved" href="#">';
  9450. $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
  9451. $return .= ' </a>';
  9452. $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
  9453. $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
  9454. <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
  9455. </a>
  9456. <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">'.
  9457. Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
  9458. $return .= '</li>';
  9459. $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
  9460. $a_threads = get_threads($forum['forum_id']);
  9461. if (is_array($a_threads)) {
  9462. foreach ($a_threads as $thread) {
  9463. $link = Display::url(
  9464. Display::return_icon('preview_view.png', get_lang('Preview')),
  9465. api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
  9466. ['target' => '_blank']
  9467. );
  9468. $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
  9469. $return .= '&nbsp;<a class="moved" href="#">';
  9470. $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
  9471. $return .= ' </a>';
  9472. $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
  9473. $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.'">'.
  9474. Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
  9475. $return .= '</li>';
  9476. }
  9477. }
  9478. $return .= '</div>';
  9479. }
  9480. }
  9481. $return .= '</ul>';
  9482. return $return;
  9483. }
  9484. /**
  9485. * // TODO: The output encoding should be equal to the system encoding.
  9486. *
  9487. * Exports the learning path as a SCORM package. This is the main function that
  9488. * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
  9489. * whole thing and returns the zip.
  9490. *
  9491. * This method needs to be called in PHP5, as it will fail with non-adequate
  9492. * XML package (like the ones for PHP4), and it is *not* a static method, so
  9493. * you need to call it on a learnpath object.
  9494. *
  9495. * @TODO The method might be redefined later on in the scorm class itself to avoid
  9496. * creating a SCORM structure if there is one already. However, if the initial SCORM
  9497. * path has been modified, it should use the generic method here below.
  9498. *
  9499. * @return string Returns the zip package string, or null if error
  9500. */
  9501. public function scormExport()
  9502. {
  9503. api_set_more_memory_and_time_limits();
  9504. $_course = api_get_course_info();
  9505. $course_id = $_course['real_id'];
  9506. // Create the zip handler (this will remain available throughout the method).
  9507. $archivePath = api_get_path(SYS_ARCHIVE_PATH);
  9508. $sys_course_path = api_get_path(SYS_COURSE_PATH);
  9509. $temp_dir_short = uniqid('scorm_export', true);
  9510. $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
  9511. $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
  9512. $zip_folder = new PclZip($temp_zip_file);
  9513. $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
  9514. $root_path = $main_path = api_get_path(SYS_PATH);
  9515. $files_cleanup = [];
  9516. // Place to temporarily stash the zip file.
  9517. // create the temp dir if it doesn't exist
  9518. // or do a cleanup before creating the zip file.
  9519. if (!is_dir($temp_zip_dir)) {
  9520. mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
  9521. } else {
  9522. // Cleanup: Check the temp dir for old files and delete them.
  9523. $handle = opendir($temp_zip_dir);
  9524. while (false !== ($file = readdir($handle))) {
  9525. if ($file != '.' && $file != '..') {
  9526. unlink("$temp_zip_dir/$file");
  9527. }
  9528. }
  9529. closedir($handle);
  9530. }
  9531. $zip_files = $zip_files_abs = $zip_files_dist = [];
  9532. if (is_dir($current_course_path.'/scorm/'.$this->path) &&
  9533. is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
  9534. ) {
  9535. // Remove the possible . at the end of the path.
  9536. $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
  9537. $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
  9538. mkdir(
  9539. $dest_path_to_scorm_folder,
  9540. api_get_permissions_for_new_directories(),
  9541. true
  9542. );
  9543. copyr(
  9544. $current_course_path.'/scorm/'.$this->path,
  9545. $dest_path_to_scorm_folder,
  9546. ['imsmanifest'],
  9547. $zip_files
  9548. );
  9549. }
  9550. // Build a dummy imsmanifest structure.
  9551. // Do not add to the zip yet (we still need it).
  9552. // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
  9553. // Aggregation Model official document, section "2.3 Content Packaging".
  9554. // We are going to build a UTF-8 encoded manifest.
  9555. // Later we will recode it to the desired (and supported) encoding.
  9556. $xmldoc = new DOMDocument('1.0');
  9557. $root = $xmldoc->createElement('manifest');
  9558. $root->setAttribute('identifier', 'SingleCourseManifest');
  9559. $root->setAttribute('version', '1.1');
  9560. $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
  9561. $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
  9562. $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  9563. $root->setAttribute(
  9564. 'xsi:schemaLocation',
  9565. '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'
  9566. );
  9567. // Build mandatory sub-root container elements.
  9568. $metadata = $xmldoc->createElement('metadata');
  9569. $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
  9570. $metadata->appendChild($md_schema);
  9571. $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
  9572. $metadata->appendChild($md_schemaversion);
  9573. $root->appendChild($metadata);
  9574. $organizations = $xmldoc->createElement('organizations');
  9575. $resources = $xmldoc->createElement('resources');
  9576. // Build the only organization we will use in building our learnpaths.
  9577. $organizations->setAttribute('default', 'chamilo_scorm_export');
  9578. $organization = $xmldoc->createElement('organization');
  9579. $organization->setAttribute('identifier', 'chamilo_scorm_export');
  9580. // To set the title of the SCORM entity (=organization), we take the name given
  9581. // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
  9582. // learning path charset) as it is the encoding that defines how it is stored
  9583. // in the database. Then we convert it to HTML entities again as the "&" character
  9584. // alone is not authorized in XML (must be &amp;).
  9585. // The title is then decoded twice when extracting (see scorm::parse_manifest).
  9586. $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
  9587. $organization->appendChild($org_title);
  9588. $folder_name = 'document';
  9589. // Removes the learning_path/scorm_folder path when exporting see #4841
  9590. $path_to_remove = '';
  9591. $path_to_replace = '';
  9592. $result = $this->generate_lp_folder($_course);
  9593. if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
  9594. $path_to_remove = 'document'.$result['dir'];
  9595. $path_to_replace = $folder_name.'/';
  9596. }
  9597. // Fixes chamilo scorm exports
  9598. if ($this->ref === 'chamilo_scorm_export') {
  9599. $path_to_remove = 'scorm/'.$this->path.'/document/';
  9600. }
  9601. // For each element, add it to the imsmanifest structure, then add it to the zip.
  9602. $link_updates = [];
  9603. $links_to_create = [];
  9604. foreach ($this->ordered_items as $index => $itemId) {
  9605. /** @var learnpathItem $item */
  9606. $item = $this->items[$itemId];
  9607. if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
  9608. // Get included documents from this item.
  9609. if ($item->type === 'sco') {
  9610. $inc_docs = $item->get_resources_from_source(
  9611. null,
  9612. $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
  9613. );
  9614. } else {
  9615. $inc_docs = $item->get_resources_from_source();
  9616. }
  9617. // Give a child element <item> to the <organization> element.
  9618. $my_item_id = $item->get_id();
  9619. $my_item = $xmldoc->createElement('item');
  9620. $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
  9621. $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
  9622. $my_item->setAttribute('isvisible', 'true');
  9623. // Give a child element <title> to the <item> element.
  9624. $my_title = $xmldoc->createElement(
  9625. 'title',
  9626. htmlspecialchars(
  9627. api_utf8_encode($item->get_title()),
  9628. ENT_QUOTES,
  9629. 'UTF-8'
  9630. )
  9631. );
  9632. $my_item->appendChild($my_title);
  9633. // Give a child element <adlcp:prerequisites> to the <item> element.
  9634. $my_prereqs = $xmldoc->createElement(
  9635. 'adlcp:prerequisites',
  9636. $this->get_scorm_prereq_string($my_item_id)
  9637. );
  9638. $my_prereqs->setAttribute('type', 'aicc_script');
  9639. $my_item->appendChild($my_prereqs);
  9640. // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
  9641. //$xmldoc->createElement('adlcp:maxtimeallowed','');
  9642. // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
  9643. //$xmldoc->createElement('adlcp:timelimitaction','');
  9644. // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
  9645. //$xmldoc->createElement('adlcp:datafromlms','');
  9646. // Give a child element <adlcp:masteryscore> to the <item> element.
  9647. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
  9648. $my_item->appendChild($my_masteryscore);
  9649. // Attach this item to the organization element or hits parent if there is one.
  9650. if (!empty($item->parent) && $item->parent != 0) {
  9651. $children = $organization->childNodes;
  9652. $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
  9653. if (is_object($possible_parent)) {
  9654. $possible_parent->appendChild($my_item);
  9655. } else {
  9656. if ($this->debug > 0) {
  9657. error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
  9658. }
  9659. }
  9660. } else {
  9661. if ($this->debug > 0) {
  9662. error_log('No parent');
  9663. }
  9664. $organization->appendChild($my_item);
  9665. }
  9666. // Get the path of the file(s) from the course directory root.
  9667. $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
  9668. $my_xml_file_path = $my_file_path;
  9669. if (!empty($path_to_remove)) {
  9670. // From docs
  9671. $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
  9672. // From quiz
  9673. if ($this->ref === 'chamilo_scorm_export') {
  9674. $path_to_remove = 'scorm/'.$this->path.'/';
  9675. $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
  9676. }
  9677. }
  9678. $my_sub_dir = dirname($my_file_path);
  9679. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  9680. $my_xml_sub_dir = $my_sub_dir;
  9681. // Give a <resource> child to the <resources> element
  9682. $my_resource = $xmldoc->createElement('resource');
  9683. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  9684. $my_resource->setAttribute('type', 'webcontent');
  9685. $my_resource->setAttribute('href', $my_xml_file_path);
  9686. // adlcp:scormtype can be either 'sco' or 'asset'.
  9687. if ($item->type === 'sco') {
  9688. $my_resource->setAttribute('adlcp:scormtype', 'sco');
  9689. } else {
  9690. $my_resource->setAttribute('adlcp:scormtype', 'asset');
  9691. }
  9692. // xml:base is the base directory to find the files declared in this resource.
  9693. $my_resource->setAttribute('xml:base', '');
  9694. // Give a <file> child to the <resource> element.
  9695. $my_file = $xmldoc->createElement('file');
  9696. $my_file->setAttribute('href', $my_xml_file_path);
  9697. $my_resource->appendChild($my_file);
  9698. // Dependency to other files - not yet supported.
  9699. $i = 1;
  9700. if ($inc_docs) {
  9701. foreach ($inc_docs as $doc_info) {
  9702. if (count($doc_info) < 1 || empty($doc_info[0])) {
  9703. continue;
  9704. }
  9705. $my_dep = $xmldoc->createElement('resource');
  9706. $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
  9707. $my_dep->setAttribute('identifier', $res_id);
  9708. $my_dep->setAttribute('type', 'webcontent');
  9709. $my_dep->setAttribute('adlcp:scormtype', 'asset');
  9710. $my_dep_file = $xmldoc->createElement('file');
  9711. // Check type of URL.
  9712. if ($doc_info[1] == 'remote') {
  9713. // Remote file. Save url as is.
  9714. $my_dep_file->setAttribute('href', $doc_info[0]);
  9715. $my_dep->setAttribute('xml:base', '');
  9716. } elseif ($doc_info[1] === 'local') {
  9717. switch ($doc_info[2]) {
  9718. case 'url':
  9719. // Local URL - save path as url for now, don't zip file.
  9720. $abs_path = api_get_path(SYS_PATH).
  9721. str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
  9722. $current_dir = dirname($abs_path);
  9723. $current_dir = str_replace('\\', '/', $current_dir);
  9724. $file_path = realpath($abs_path);
  9725. $file_path = str_replace('\\', '/', $file_path);
  9726. $my_dep_file->setAttribute('href', $file_path);
  9727. $my_dep->setAttribute('xml:base', '');
  9728. if (strstr($file_path, $main_path) !== false) {
  9729. // The calculated real path is really inside Chamilo's root path.
  9730. // Reduce file path to what's under the DocumentRoot.
  9731. $replace = $file_path;
  9732. $file_path = substr($file_path, strlen($root_path) - 1);
  9733. $destinationFile = $file_path;
  9734. if (strstr($file_path, 'upload/users') !== false) {
  9735. $pos = strpos($file_path, 'my_files/');
  9736. if ($pos !== false) {
  9737. $onlyDirectory = str_replace(
  9738. 'upload/users/',
  9739. '',
  9740. substr($file_path, $pos, strlen($file_path))
  9741. );
  9742. }
  9743. $replace = $onlyDirectory;
  9744. $destinationFile = $replace;
  9745. }
  9746. $zip_files_abs[] = $file_path;
  9747. $link_updates[$my_file_path][] = [
  9748. 'orig' => $doc_info[0],
  9749. 'dest' => $destinationFile,
  9750. 'replace' => $replace,
  9751. ];
  9752. $my_dep_file->setAttribute('href', $file_path);
  9753. $my_dep->setAttribute('xml:base', '');
  9754. } elseif (empty($file_path)) {
  9755. $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
  9756. $file_path = str_replace('//', '/', $file_path);
  9757. if (file_exists($file_path)) {
  9758. // We get the relative path.
  9759. $file_path = substr($file_path, strlen($current_dir));
  9760. $zip_files[] = $my_sub_dir.'/'.$file_path;
  9761. $link_updates[$my_file_path][] = [
  9762. 'orig' => $doc_info[0],
  9763. 'dest' => $file_path,
  9764. ];
  9765. $my_dep_file->setAttribute('href', $file_path);
  9766. $my_dep->setAttribute('xml:base', '');
  9767. }
  9768. }
  9769. break;
  9770. case 'abs':
  9771. // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
  9772. $my_dep_file->setAttribute('href', $doc_info[0]);
  9773. $my_dep->setAttribute('xml:base', '');
  9774. // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
  9775. // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
  9776. $abs_img_path_without_subdir = $doc_info[0];
  9777. $relp = api_get_path(REL_PATH); // The url-append config param.
  9778. $pos = strpos($abs_img_path_without_subdir, $relp);
  9779. if ($pos === 0) {
  9780. $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
  9781. }
  9782. $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
  9783. $file_path = str_replace(['\\', '//'], '/', $file_path);
  9784. // Prepare the current directory path (until just under 'document') with a trailing slash.
  9785. $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
  9786. // Check if the current document is in that path.
  9787. if (strstr($file_path, $cur_path) !== false) {
  9788. $destinationFile = substr($file_path, strlen($cur_path));
  9789. $filePathNoCoursePart = substr($file_path, strlen($cur_path));
  9790. $fileToTest = $cur_path.$my_file_path;
  9791. if (!empty($path_to_remove)) {
  9792. $fileToTest = str_replace(
  9793. $path_to_remove.'/',
  9794. $path_to_replace,
  9795. $cur_path.$my_file_path
  9796. );
  9797. }
  9798. $relative_path = api_get_relative_path($fileToTest, $file_path);
  9799. // Put the current document in the zip (this array is the array
  9800. // that will manage documents already in the course folder - relative).
  9801. $zip_files[] = $filePathNoCoursePart;
  9802. // Update the links to the current document in the
  9803. // containing document (make them relative).
  9804. $link_updates[$my_file_path][] = [
  9805. 'orig' => $doc_info[0],
  9806. 'dest' => $destinationFile,
  9807. 'replace' => $relative_path,
  9808. ];
  9809. $my_dep_file->setAttribute('href', $file_path);
  9810. $my_dep->setAttribute('xml:base', '');
  9811. } elseif (strstr($file_path, $main_path) !== false) {
  9812. // The calculated real path is really inside Chamilo's root path.
  9813. // Reduce file path to what's under the DocumentRoot.
  9814. $file_path = substr($file_path, strlen($root_path));
  9815. $zip_files_abs[] = $file_path;
  9816. $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
  9817. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  9818. $my_dep->setAttribute('xml:base', '');
  9819. } elseif (empty($file_path)) {
  9820. // Probably this is an image inside "/main" directory
  9821. $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
  9822. $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
  9823. if (file_exists($file_path)) {
  9824. if (strstr($file_path, 'main/default_course_document') !== false) {
  9825. // We get the relative path.
  9826. $pos = strpos($file_path, 'main/default_course_document/');
  9827. if ($pos !== false) {
  9828. $onlyDirectory = str_replace(
  9829. 'main/default_course_document/',
  9830. '',
  9831. substr($file_path, $pos, strlen($file_path))
  9832. );
  9833. }
  9834. $destinationFile = 'default_course_document/'.$onlyDirectory;
  9835. $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
  9836. $zip_files_abs[] = $fileAbs;
  9837. $link_updates[$my_file_path][] = [
  9838. 'orig' => $doc_info[0],
  9839. 'dest' => $destinationFile,
  9840. ];
  9841. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  9842. $my_dep->setAttribute('xml:base', '');
  9843. }
  9844. }
  9845. }
  9846. break;
  9847. case 'rel':
  9848. // Path relative to the current document.
  9849. // Save xml:base as current document's directory and save file in zip as subdir.file_path
  9850. if (substr($doc_info[0], 0, 2) === '..') {
  9851. // Relative path going up.
  9852. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
  9853. $current_dir = str_replace('\\', '/', $current_dir);
  9854. $file_path = realpath($current_dir.$doc_info[0]);
  9855. $file_path = str_replace('\\', '/', $file_path);
  9856. if (strstr($file_path, $main_path) !== false) {
  9857. // The calculated real path is really inside Chamilo's root path.
  9858. // Reduce file path to what's under the DocumentRoot.
  9859. $file_path = substr($file_path, strlen($root_path));
  9860. $zip_files_abs[] = $file_path;
  9861. $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
  9862. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  9863. $my_dep->setAttribute('xml:base', '');
  9864. }
  9865. } else {
  9866. $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
  9867. $my_dep_file->setAttribute('href', $doc_info[0]);
  9868. $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
  9869. }
  9870. break;
  9871. default:
  9872. $my_dep_file->setAttribute('href', $doc_info[0]);
  9873. $my_dep->setAttribute('xml:base', '');
  9874. break;
  9875. }
  9876. }
  9877. $my_dep->appendChild($my_dep_file);
  9878. $resources->appendChild($my_dep);
  9879. $dependency = $xmldoc->createElement('dependency');
  9880. $dependency->setAttribute('identifierref', $res_id);
  9881. $my_resource->appendChild($dependency);
  9882. $i++;
  9883. }
  9884. }
  9885. $resources->appendChild($my_resource);
  9886. $zip_files[] = $my_file_path;
  9887. } else {
  9888. // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
  9889. switch ($item->type) {
  9890. case TOOL_LINK:
  9891. $my_item = $xmldoc->createElement('item');
  9892. $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
  9893. $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
  9894. $my_item->setAttribute('isvisible', 'true');
  9895. // Give a child element <title> to the <item> element.
  9896. $my_title = $xmldoc->createElement(
  9897. 'title',
  9898. htmlspecialchars(
  9899. api_utf8_encode($item->get_title()),
  9900. ENT_QUOTES,
  9901. 'UTF-8'
  9902. )
  9903. );
  9904. $my_item->appendChild($my_title);
  9905. // Give a child element <adlcp:prerequisites> to the <item> element.
  9906. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
  9907. $my_prereqs->setAttribute('type', 'aicc_script');
  9908. $my_item->appendChild($my_prereqs);
  9909. // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
  9910. //$xmldoc->createElement('adlcp:maxtimeallowed', '');
  9911. // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
  9912. //$xmldoc->createElement('adlcp:timelimitaction', '');
  9913. // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
  9914. //$xmldoc->createElement('adlcp:datafromlms', '');
  9915. // Give a child element <adlcp:masteryscore> to the <item> element.
  9916. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
  9917. $my_item->appendChild($my_masteryscore);
  9918. // Attach this item to the organization element or its parent if there is one.
  9919. if (!empty($item->parent) && $item->parent != 0) {
  9920. $children = $organization->childNodes;
  9921. for ($i = 0; $i < $children->length; $i++) {
  9922. $item_temp = $children->item($i);
  9923. if ($item_temp->nodeName == 'item') {
  9924. if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
  9925. $item_temp->appendChild($my_item);
  9926. }
  9927. }
  9928. }
  9929. } else {
  9930. $organization->appendChild($my_item);
  9931. }
  9932. $my_file_path = 'link_'.$item->get_id().'.html';
  9933. $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
  9934. WHERE c_id = '.$course_id.' AND id = '.$item->path;
  9935. $rs = Database::query($sql);
  9936. if ($link = Database::fetch_array($rs)) {
  9937. $url = $link['url'];
  9938. $title = stripslashes($link['title']);
  9939. $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
  9940. $my_xml_file_path = $my_file_path;
  9941. $my_sub_dir = dirname($my_file_path);
  9942. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  9943. $my_xml_sub_dir = $my_sub_dir;
  9944. // Give a <resource> child to the <resources> element.
  9945. $my_resource = $xmldoc->createElement('resource');
  9946. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  9947. $my_resource->setAttribute('type', 'webcontent');
  9948. $my_resource->setAttribute('href', $my_xml_file_path);
  9949. // adlcp:scormtype can be either 'sco' or 'asset'.
  9950. $my_resource->setAttribute('adlcp:scormtype', 'asset');
  9951. // xml:base is the base directory to find the files declared in this resource.
  9952. $my_resource->setAttribute('xml:base', '');
  9953. // give a <file> child to the <resource> element.
  9954. $my_file = $xmldoc->createElement('file');
  9955. $my_file->setAttribute('href', $my_xml_file_path);
  9956. $my_resource->appendChild($my_file);
  9957. $resources->appendChild($my_resource);
  9958. }
  9959. break;
  9960. case TOOL_QUIZ:
  9961. $exe_id = $item->path;
  9962. // Should be using ref when everything will be cleaned up in this regard.
  9963. $exe = new Exercise();
  9964. $exe->read($exe_id);
  9965. $my_item = $xmldoc->createElement('item');
  9966. $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
  9967. $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
  9968. $my_item->setAttribute('isvisible', 'true');
  9969. // Give a child element <title> to the <item> element.
  9970. $my_title = $xmldoc->createElement(
  9971. 'title',
  9972. htmlspecialchars(
  9973. api_utf8_encode($item->get_title()),
  9974. ENT_QUOTES,
  9975. 'UTF-8'
  9976. )
  9977. );
  9978. $my_item->appendChild($my_title);
  9979. $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
  9980. $my_item->appendChild($my_max_score);
  9981. // Give a child element <adlcp:prerequisites> to the <item> element.
  9982. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
  9983. $my_prereqs->setAttribute('type', 'aicc_script');
  9984. $my_item->appendChild($my_prereqs);
  9985. // Give a child element <adlcp:masteryscore> to the <item> element.
  9986. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
  9987. $my_item->appendChild($my_masteryscore);
  9988. // Attach this item to the organization element or hits parent if there is one.
  9989. if (!empty($item->parent) && $item->parent != 0) {
  9990. $children = $organization->childNodes;
  9991. $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
  9992. if ($possible_parent) {
  9993. if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
  9994. $possible_parent->appendChild($my_item);
  9995. }
  9996. }
  9997. } else {
  9998. $organization->appendChild($my_item);
  9999. }
  10000. // Get the path of the file(s) from the course directory root
  10001. //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
  10002. $my_file_path = 'quiz_'.$item->get_id().'.html';
  10003. // Write the contents of the exported exercise into a (big) html file
  10004. // to later pack it into the exported SCORM. The file will be removed afterwards.
  10005. $scormExercise = new ScormExercise($exe, true);
  10006. $contents = $scormExercise->export();
  10007. $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
  10008. $res = file_put_contents($tmp_file_path, $contents);
  10009. if ($res === false) {
  10010. error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
  10011. }
  10012. $files_cleanup[] = $tmp_file_path;
  10013. $my_xml_file_path = $my_file_path;
  10014. $my_sub_dir = dirname($my_file_path);
  10015. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  10016. $my_xml_sub_dir = $my_sub_dir;
  10017. // Give a <resource> child to the <resources> element.
  10018. $my_resource = $xmldoc->createElement('resource');
  10019. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  10020. $my_resource->setAttribute('type', 'webcontent');
  10021. $my_resource->setAttribute('href', $my_xml_file_path);
  10022. // adlcp:scormtype can be either 'sco' or 'asset'.
  10023. $my_resource->setAttribute('adlcp:scormtype', 'sco');
  10024. // xml:base is the base directory to find the files declared in this resource.
  10025. $my_resource->setAttribute('xml:base', '');
  10026. // Give a <file> child to the <resource> element.
  10027. $my_file = $xmldoc->createElement('file');
  10028. $my_file->setAttribute('href', $my_xml_file_path);
  10029. $my_resource->appendChild($my_file);
  10030. // Get included docs.
  10031. $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
  10032. // Dependency to other files - not yet supported.
  10033. $i = 1;
  10034. foreach ($inc_docs as $doc_info) {
  10035. if (count($doc_info) < 1 || empty($doc_info[0])) {
  10036. continue;
  10037. }
  10038. $my_dep = $xmldoc->createElement('resource');
  10039. $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
  10040. $my_dep->setAttribute('identifier', $res_id);
  10041. $my_dep->setAttribute('type', 'webcontent');
  10042. $my_dep->setAttribute('adlcp:scormtype', 'asset');
  10043. $my_dep_file = $xmldoc->createElement('file');
  10044. // Check type of URL.
  10045. if ($doc_info[1] == 'remote') {
  10046. // Remote file. Save url as is.
  10047. $my_dep_file->setAttribute('href', $doc_info[0]);
  10048. $my_dep->setAttribute('xml:base', '');
  10049. } elseif ($doc_info[1] == 'local') {
  10050. switch ($doc_info[2]) {
  10051. case 'url': // Local URL - save path as url for now, don't zip file.
  10052. // Save file but as local file (retrieve from URL).
  10053. $abs_path = api_get_path(SYS_PATH).
  10054. str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
  10055. $current_dir = dirname($abs_path);
  10056. $current_dir = str_replace('\\', '/', $current_dir);
  10057. $file_path = realpath($abs_path);
  10058. $file_path = str_replace('\\', '/', $file_path);
  10059. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10060. $my_dep->setAttribute('xml:base', '');
  10061. if (strstr($file_path, $main_path) !== false) {
  10062. // The calculated real path is really inside the chamilo root path.
  10063. // Reduce file path to what's under the DocumentRoot.
  10064. $file_path = substr($file_path, strlen($root_path));
  10065. $zip_files_abs[] = $file_path;
  10066. $link_updates[$my_file_path][] = [
  10067. 'orig' => $doc_info[0],
  10068. 'dest' => 'document/'.$file_path,
  10069. ];
  10070. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10071. $my_dep->setAttribute('xml:base', '');
  10072. } elseif (empty($file_path)) {
  10073. $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
  10074. $file_path = str_replace('//', '/', $file_path);
  10075. if (file_exists($file_path)) {
  10076. $file_path = substr($file_path, strlen($current_dir));
  10077. // We get the relative path.
  10078. $zip_files[] = $my_sub_dir.'/'.$file_path;
  10079. $link_updates[$my_file_path][] = [
  10080. 'orig' => $doc_info[0],
  10081. 'dest' => 'document/'.$file_path,
  10082. ];
  10083. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10084. $my_dep->setAttribute('xml:base', '');
  10085. }
  10086. }
  10087. break;
  10088. case 'abs':
  10089. // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
  10090. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
  10091. $current_dir = str_replace('\\', '/', $current_dir);
  10092. $file_path = realpath($doc_info[0]);
  10093. $file_path = str_replace('\\', '/', $file_path);
  10094. $my_dep_file->setAttribute('href', $file_path);
  10095. $my_dep->setAttribute('xml:base', '');
  10096. if (strstr($file_path, $main_path) !== false) {
  10097. // The calculated real path is really inside the chamilo root path.
  10098. // Reduce file path to what's under the DocumentRoot.
  10099. $file_path = substr($file_path, strlen($root_path));
  10100. $zip_files_abs[] = $file_path;
  10101. $link_updates[$my_file_path][] = [
  10102. 'orig' => $doc_info[0],
  10103. 'dest' => $file_path,
  10104. ];
  10105. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10106. $my_dep->setAttribute('xml:base', '');
  10107. } elseif (empty($file_path)) {
  10108. $docSysPartPath = str_replace(
  10109. api_get_path(REL_COURSE_PATH),
  10110. '',
  10111. $doc_info[0]
  10112. );
  10113. $docSysPartPathNoCourseCode = str_replace(
  10114. $_course['directory'].'/',
  10115. '',
  10116. $docSysPartPath
  10117. );
  10118. $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
  10119. if (file_exists($docSysPath)) {
  10120. $file_path = $docSysPartPathNoCourseCode;
  10121. $zip_files[] = $my_sub_dir.'/'.$file_path;
  10122. $link_updates[$my_file_path][] = [
  10123. 'orig' => $doc_info[0],
  10124. 'dest' => $file_path,
  10125. ];
  10126. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10127. $my_dep->setAttribute('xml:base', '');
  10128. }
  10129. }
  10130. break;
  10131. case 'rel':
  10132. // Path relative to the current document. Save xml:base as current document's
  10133. // directory and save file in zip as subdir.file_path
  10134. if (substr($doc_info[0], 0, 2) === '..') {
  10135. // Relative path going up.
  10136. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
  10137. $current_dir = str_replace('\\', '/', $current_dir);
  10138. $file_path = realpath($current_dir.$doc_info[0]);
  10139. $file_path = str_replace('\\', '/', $file_path);
  10140. if (strstr($file_path, $main_path) !== false) {
  10141. // The calculated real path is really inside Chamilo's root path.
  10142. // Reduce file path to what's under the DocumentRoot.
  10143. $file_path = substr($file_path, strlen($root_path));
  10144. $file_path_dest = $file_path;
  10145. // File path is courses/CHAMILO/document/....
  10146. $info_file_path = explode('/', $file_path);
  10147. if ($info_file_path[0] == 'courses') {
  10148. // Add character "/" in file path.
  10149. $file_path_dest = 'document/'.$file_path;
  10150. }
  10151. $zip_files_abs[] = $file_path;
  10152. $link_updates[$my_file_path][] = [
  10153. 'orig' => $doc_info[0],
  10154. 'dest' => $file_path_dest,
  10155. ];
  10156. $my_dep_file->setAttribute('href', 'document/'.$file_path);
  10157. $my_dep->setAttribute('xml:base', '');
  10158. }
  10159. } else {
  10160. $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
  10161. $my_dep_file->setAttribute('href', $doc_info[0]);
  10162. $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
  10163. }
  10164. break;
  10165. default:
  10166. $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
  10167. $my_dep->setAttribute('xml:base', '');
  10168. break;
  10169. }
  10170. }
  10171. $my_dep->appendChild($my_dep_file);
  10172. $resources->appendChild($my_dep);
  10173. $dependency = $xmldoc->createElement('dependency');
  10174. $dependency->setAttribute('identifierref', $res_id);
  10175. $my_resource->appendChild($dependency);
  10176. $i++;
  10177. }
  10178. $resources->appendChild($my_resource);
  10179. $zip_files[] = $my_file_path;
  10180. break;
  10181. default:
  10182. // Get the path of the file(s) from the course directory root
  10183. $my_file_path = 'non_exportable.html';
  10184. //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
  10185. $my_xml_file_path = $my_file_path;
  10186. $my_sub_dir = dirname($my_file_path);
  10187. $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
  10188. //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
  10189. $my_xml_sub_dir = $my_sub_dir;
  10190. // Give a <resource> child to the <resources> element.
  10191. $my_resource = $xmldoc->createElement('resource');
  10192. $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
  10193. $my_resource->setAttribute('type', 'webcontent');
  10194. $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
  10195. // adlcp:scormtype can be either 'sco' or 'asset'.
  10196. $my_resource->setAttribute('adlcp:scormtype', 'asset');
  10197. // xml:base is the base directory to find the files declared in this resource.
  10198. $my_resource->setAttribute('xml:base', '');
  10199. // Give a <file> child to the <resource> element.
  10200. $my_file = $xmldoc->createElement('file');
  10201. $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
  10202. $my_resource->appendChild($my_file);
  10203. $resources->appendChild($my_resource);
  10204. break;
  10205. }
  10206. }
  10207. }
  10208. $organizations->appendChild($organization);
  10209. $root->appendChild($organizations);
  10210. $root->appendChild($resources);
  10211. $xmldoc->appendChild($root);
  10212. $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
  10213. // then add the file to the zip, then destroy the file (this is done automatically).
  10214. // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
  10215. foreach ($zip_files as $file_path) {
  10216. if (empty($file_path)) {
  10217. continue;
  10218. }
  10219. $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
  10220. $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
  10221. if (!empty($path_to_remove) && !empty($path_to_replace)) {
  10222. $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
  10223. }
  10224. $this->create_path($dest_file);
  10225. @copy($filePath, $dest_file);
  10226. // Check if the file needs a link update.
  10227. if (in_array($file_path, array_keys($link_updates))) {
  10228. $string = file_get_contents($dest_file);
  10229. unlink($dest_file);
  10230. foreach ($link_updates[$file_path] as $old_new) {
  10231. // This is an ugly hack that allows .flv files to be found by the flv player that
  10232. // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
  10233. // to find the flv to play in document/main/, so we replace main/ in the flv path by
  10234. // ../../.. to return from inc/lib/flv_player to the document/main path.
  10235. if (substr($old_new['dest'], -3) === 'flv' &&
  10236. substr($old_new['dest'], 0, 5) === 'main/'
  10237. ) {
  10238. $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
  10239. } elseif (substr($old_new['dest'], -3) === 'flv' &&
  10240. substr($old_new['dest'], 0, 6) === 'video/'
  10241. ) {
  10242. $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
  10243. }
  10244. // Fix to avoid problems with default_course_document
  10245. if (strpos('main/default_course_document', $old_new['dest']) === false) {
  10246. $newDestination = $old_new['dest'];
  10247. if (isset($old_new['replace']) && !empty($old_new['replace'])) {
  10248. $newDestination = $old_new['replace'];
  10249. }
  10250. } else {
  10251. $newDestination = str_replace('document/', '', $old_new['dest']);
  10252. }
  10253. $string = str_replace($old_new['orig'], $newDestination, $string);
  10254. // Add files inside the HTMLs
  10255. $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
  10256. $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
  10257. if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
  10258. copy(
  10259. $sys_course_path.$new_path,
  10260. $destinationFile
  10261. );
  10262. }
  10263. }
  10264. file_put_contents($dest_file, $string);
  10265. }
  10266. if (file_exists($filePath) && $copyAll) {
  10267. $extension = $this->get_extension($filePath);
  10268. if (in_array($extension, ['html', 'html'])) {
  10269. $containerOrigin = dirname($filePath);
  10270. $containerDestination = dirname($dest_file);
  10271. $finder = new Finder();
  10272. $finder->files()->in($containerOrigin)
  10273. ->notName('*_DELETED_*')
  10274. ->exclude('share_folder')
  10275. ->exclude('chat_files')
  10276. ->exclude('certificates')
  10277. ;
  10278. if (is_dir($containerOrigin) &&
  10279. is_dir($containerDestination)
  10280. ) {
  10281. $fs = new Filesystem();
  10282. $fs->mirror(
  10283. $containerOrigin,
  10284. $containerDestination,
  10285. $finder
  10286. );
  10287. }
  10288. }
  10289. }
  10290. }
  10291. foreach ($zip_files_abs as $file_path) {
  10292. if (empty($file_path)) {
  10293. continue;
  10294. }
  10295. if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
  10296. continue;
  10297. }
  10298. $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
  10299. if (strstr($file_path, 'upload/users') !== false) {
  10300. $pos = strpos($file_path, 'my_files/');
  10301. if ($pos !== false) {
  10302. $onlyDirectory = str_replace(
  10303. 'upload/users/',
  10304. '',
  10305. substr($file_path, $pos, strlen($file_path))
  10306. );
  10307. $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
  10308. }
  10309. }
  10310. if (strstr($file_path, 'default_course_document/') !== false) {
  10311. $replace = str_replace('/main', '', $file_path);
  10312. $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
  10313. }
  10314. if (empty($dest_file)) {
  10315. continue;
  10316. }
  10317. $this->create_path($dest_file);
  10318. copy($main_path.$file_path, $dest_file);
  10319. // Check if the file needs a link update.
  10320. if (in_array($file_path, array_keys($link_updates))) {
  10321. $string = file_get_contents($dest_file);
  10322. unlink($dest_file);
  10323. foreach ($link_updates[$file_path] as $old_new) {
  10324. // This is an ugly hack that allows .flv files to be found by the flv player that
  10325. // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
  10326. // to find the flv to play in document/main/, so we replace main/ in the flv path by
  10327. // ../../.. to return from inc/lib/flv_player to the document/main path.
  10328. if (substr($old_new['dest'], -3) == 'flv' &&
  10329. substr($old_new['dest'], 0, 5) == 'main/'
  10330. ) {
  10331. $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
  10332. }
  10333. $string = str_replace($old_new['orig'], $old_new['dest'], $string);
  10334. }
  10335. file_put_contents($dest_file, $string);
  10336. }
  10337. }
  10338. if (is_array($links_to_create)) {
  10339. foreach ($links_to_create as $file => $link) {
  10340. $content = '<!DOCTYPE html><head>
  10341. <meta charset="'.api_get_language_isocode().'" />
  10342. <title>'.$link['title'].'</title>
  10343. </head>
  10344. <body dir="'.api_get_text_direction().'">
  10345. <div style="text-align:center">
  10346. <a href="'.$link['url'].'">'.$link['title'].'</a></div>
  10347. </body>
  10348. </html>';
  10349. file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
  10350. }
  10351. }
  10352. // Add non exportable message explanation.
  10353. $lang_not_exportable = get_lang('ThisItemIsNotExportable');
  10354. $file_content = '<!DOCTYPE html><head>
  10355. <meta charset="'.api_get_language_isocode().'" />
  10356. <title>'.$lang_not_exportable.'</title>
  10357. <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
  10358. </head>
  10359. <body dir="'.api_get_text_direction().'">';
  10360. $file_content .=
  10361. <<<EOD
  10362. <style>
  10363. .error-message {
  10364. font-family: arial, verdana, helvetica, sans-serif;
  10365. border-width: 1px;
  10366. border-style: solid;
  10367. left: 50%;
  10368. margin: 10px auto;
  10369. min-height: 30px;
  10370. padding: 5px;
  10371. right: 50%;
  10372. width: 500px;
  10373. background-color: #FFD1D1;
  10374. border-color: #FF0000;
  10375. color: #000;
  10376. }
  10377. </style>
  10378. <body>
  10379. <div class="error-message">
  10380. $lang_not_exportable
  10381. </div>
  10382. </body>
  10383. </html>
  10384. EOD;
  10385. if (!is_dir($archivePath.$temp_dir_short.'/document')) {
  10386. @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
  10387. }
  10388. file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
  10389. // Add the extra files that go along with a SCORM package.
  10390. $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
  10391. $fs = new Filesystem();
  10392. $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
  10393. // Finalize the imsmanifest structure, add to the zip, then return the zip.
  10394. $manifest = @$xmldoc->saveXML();
  10395. $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
  10396. file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
  10397. $zip_folder->add(
  10398. $archivePath.'/'.$temp_dir_short,
  10399. PCLZIP_OPT_REMOVE_PATH,
  10400. $archivePath.'/'.$temp_dir_short.'/'
  10401. );
  10402. // Clean possible temporary files.
  10403. foreach ($files_cleanup as $file) {
  10404. $res = unlink($file);
  10405. if ($res === false) {
  10406. error_log(
  10407. 'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
  10408. 0
  10409. );
  10410. }
  10411. }
  10412. $name = api_replace_dangerous_char($this->get_name()).'.zip';
  10413. DocumentManager::file_send_for_download($temp_zip_file, true, $name);
  10414. }
  10415. /**
  10416. * @param int $lp_id
  10417. *
  10418. * @return bool
  10419. */
  10420. public function scorm_export_to_pdf($lp_id)
  10421. {
  10422. $lp_id = (int) $lp_id;
  10423. $files_to_export = [];
  10424. $sessionId = api_get_session_id();
  10425. $course_data = api_get_course_info($this->cc);
  10426. if (!empty($course_data)) {
  10427. $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
  10428. $list = self::get_flat_ordered_items_list($lp_id);
  10429. if (!empty($list)) {
  10430. foreach ($list as $item_id) {
  10431. $item = $this->items[$item_id];
  10432. switch ($item->type) {
  10433. case 'document':
  10434. // Getting documents from a LP with chamilo documents
  10435. $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
  10436. // Try loading document from the base course.
  10437. if (empty($file_data) && !empty($sessionId)) {
  10438. $file_data = DocumentManager::get_document_data_by_id(
  10439. $item->path,
  10440. $this->cc,
  10441. false,
  10442. 0
  10443. );
  10444. }
  10445. $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
  10446. if (file_exists($file_path)) {
  10447. $files_to_export[] = [
  10448. 'title' => $item->get_title(),
  10449. 'path' => $file_path,
  10450. ];
  10451. }
  10452. break;
  10453. case 'asset': //commes from a scorm package generated by chamilo
  10454. case 'sco':
  10455. $file_path = $scorm_path.'/'.$item->path;
  10456. if (file_exists($file_path)) {
  10457. $files_to_export[] = [
  10458. 'title' => $item->get_title(),
  10459. 'path' => $file_path,
  10460. ];
  10461. }
  10462. break;
  10463. case 'dir':
  10464. $files_to_export[] = [
  10465. 'title' => $item->get_title(),
  10466. 'path' => null,
  10467. ];
  10468. break;
  10469. }
  10470. }
  10471. }
  10472. $pdf = new PDF();
  10473. $result = $pdf->html_to_pdf(
  10474. $files_to_export,
  10475. $this->name,
  10476. $this->cc,
  10477. true,
  10478. true,
  10479. true,
  10480. $this->get_name()
  10481. );
  10482. return $result;
  10483. }
  10484. return false;
  10485. }
  10486. /**
  10487. * Temp function to be moved in main_api or the best place around for this.
  10488. * Creates a file path if it doesn't exist.
  10489. *
  10490. * @param string $path
  10491. */
  10492. public function create_path($path)
  10493. {
  10494. $path_bits = explode('/', dirname($path));
  10495. // IS_WINDOWS_OS has been defined in main_api.lib.php
  10496. $path_built = IS_WINDOWS_OS ? '' : '/';
  10497. foreach ($path_bits as $bit) {
  10498. if (!empty($bit)) {
  10499. $new_path = $path_built.$bit;
  10500. if (is_dir($new_path)) {
  10501. $path_built = $new_path.'/';
  10502. } else {
  10503. mkdir($new_path, api_get_permissions_for_new_directories());
  10504. $path_built = $new_path.'/';
  10505. }
  10506. }
  10507. }
  10508. }
  10509. /**
  10510. * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
  10511. *
  10512. * @return bool The results of the unlink function, or false if there was no image to start with
  10513. */
  10514. public function delete_lp_image()
  10515. {
  10516. $img = $this->get_preview_image();
  10517. if ($img != '') {
  10518. $del_file = $this->get_preview_image_path(null, 'sys');
  10519. if (isset($del_file) && file_exists($del_file)) {
  10520. $del_file_2 = $this->get_preview_image_path(64, 'sys');
  10521. if (file_exists($del_file_2)) {
  10522. unlink($del_file_2);
  10523. }
  10524. $this->set_preview_image('');
  10525. return @unlink($del_file);
  10526. }
  10527. }
  10528. return false;
  10529. }
  10530. /**
  10531. * Uploads an author image to the upload/learning_path/images directory.
  10532. *
  10533. * @param array The image array, coming from the $_FILES superglobal
  10534. *
  10535. * @return bool True on success, false on error
  10536. */
  10537. public function upload_image($image_array)
  10538. {
  10539. if (!empty($image_array['name'])) {
  10540. $upload_ok = process_uploaded_file($image_array);
  10541. $has_attachment = true;
  10542. }
  10543. if ($upload_ok && $has_attachment) {
  10544. $courseDir = api_get_course_path().'/upload/learning_path/images';
  10545. $sys_course_path = api_get_path(SYS_COURSE_PATH);
  10546. $updir = $sys_course_path.$courseDir;
  10547. // Try to add an extension to the file if it hasn't one.
  10548. $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
  10549. if (filter_extension($new_file_name)) {
  10550. $file_extension = explode('.', $image_array['name']);
  10551. $file_extension = strtolower($file_extension[count($file_extension) - 1]);
  10552. $filename = uniqid('');
  10553. $new_file_name = $filename.'.'.$file_extension;
  10554. $new_path = $updir.'/'.$new_file_name;
  10555. // Resize the image.
  10556. $temp = new Image($image_array['tmp_name']);
  10557. $temp->resize(104);
  10558. $result = $temp->send_image($new_path);
  10559. // Storing the image filename.
  10560. if ($result) {
  10561. $this->set_preview_image($new_file_name);
  10562. //Resize to 64px to use on course homepage
  10563. $temp->resize(64);
  10564. $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
  10565. return true;
  10566. }
  10567. }
  10568. }
  10569. return false;
  10570. }
  10571. /**
  10572. * @param int $lp_id
  10573. * @param string $status
  10574. */
  10575. public function set_autolaunch($lp_id, $status)
  10576. {
  10577. $course_id = api_get_course_int_id();
  10578. $lp_id = (int) $lp_id;
  10579. $status = (int) $status;
  10580. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  10581. // Setting everything to autolaunch = 0
  10582. $attributes['autolaunch'] = 0;
  10583. $where = [
  10584. 'session_id = ? AND c_id = ? ' => [
  10585. api_get_session_id(),
  10586. $course_id,
  10587. ],
  10588. ];
  10589. Database::update($lp_table, $attributes, $where);
  10590. if ($status == 1) {
  10591. //Setting my lp_id to autolaunch = 1
  10592. $attributes['autolaunch'] = 1;
  10593. $where = [
  10594. 'iid = ? AND session_id = ? AND c_id = ?' => [
  10595. $lp_id,
  10596. api_get_session_id(),
  10597. $course_id,
  10598. ],
  10599. ];
  10600. Database::update($lp_table, $attributes, $where);
  10601. }
  10602. }
  10603. /**
  10604. * Gets previous_item_id for the next element of the lp_item table.
  10605. *
  10606. * @author Isaac flores paz
  10607. *
  10608. * @return int Previous item ID
  10609. */
  10610. public function select_previous_item_id()
  10611. {
  10612. $course_id = api_get_course_int_id();
  10613. $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  10614. // Get the max order of the items
  10615. $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
  10616. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  10617. $rs_max_order = Database::query($sql);
  10618. $row_max_order = Database::fetch_object($rs_max_order);
  10619. $max_order = $row_max_order->display_order;
  10620. // Get the previous item ID
  10621. $sql = "SELECT iid as previous FROM $table_lp_item
  10622. WHERE
  10623. c_id = $course_id AND
  10624. lp_id = ".$this->lp_id." AND
  10625. display_order = '$max_order' ";
  10626. $rs_max = Database::query($sql);
  10627. $row_max = Database::fetch_object($rs_max);
  10628. // Return the previous item ID
  10629. return $row_max->previous;
  10630. }
  10631. /**
  10632. * Copies an LP.
  10633. */
  10634. public function copy()
  10635. {
  10636. // Course builder
  10637. $cb = new CourseBuilder();
  10638. //Setting tools that will be copied
  10639. $cb->set_tools_to_build(['learnpaths']);
  10640. //Setting elements that will be copied
  10641. $cb->set_tools_specific_id_list(
  10642. ['learnpaths' => [$this->lp_id]]
  10643. );
  10644. $course = $cb->build();
  10645. //Course restorer
  10646. $course_restorer = new CourseRestorer($course);
  10647. $course_restorer->set_add_text_in_items(true);
  10648. $course_restorer->set_tool_copy_settings(
  10649. ['learnpaths' => ['reset_dates' => true]]
  10650. );
  10651. $course_restorer->restore(
  10652. api_get_course_id(),
  10653. api_get_session_id(),
  10654. false,
  10655. false
  10656. );
  10657. }
  10658. /**
  10659. * Verify document size.
  10660. *
  10661. * @param string $s
  10662. *
  10663. * @return bool
  10664. */
  10665. public static function verify_document_size($s)
  10666. {
  10667. $post_max = ini_get('post_max_size');
  10668. if (substr($post_max, -1, 1) == 'M') {
  10669. $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
  10670. } elseif (substr($post_max, -1, 1) == 'G') {
  10671. $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
  10672. }
  10673. $upl_max = ini_get('upload_max_filesize');
  10674. if (substr($upl_max, -1, 1) == 'M') {
  10675. $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
  10676. } elseif (substr($upl_max, -1, 1) == 'G') {
  10677. $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
  10678. }
  10679. $documents_total_space = DocumentManager::documents_total_space();
  10680. $course_max_space = DocumentManager::get_course_quota();
  10681. $total_size = filesize($s) + $documents_total_space;
  10682. if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
  10683. return true;
  10684. }
  10685. return false;
  10686. }
  10687. /**
  10688. * Clear LP prerequisites.
  10689. */
  10690. public function clear_prerequisites()
  10691. {
  10692. $course_id = $this->get_course_int_id();
  10693. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  10694. $lp_id = $this->get_id();
  10695. // Cleaning prerequisites
  10696. $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
  10697. WHERE c_id = $course_id AND lp_id = $lp_id";
  10698. Database::query($sql);
  10699. // Cleaning mastery score for exercises
  10700. $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
  10701. WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
  10702. Database::query($sql);
  10703. }
  10704. public function set_previous_step_as_prerequisite_for_all_items()
  10705. {
  10706. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  10707. $course_id = $this->get_course_int_id();
  10708. $lp_id = $this->get_id();
  10709. if (!empty($this->items)) {
  10710. $previous_item_id = null;
  10711. $previous_item_max = 0;
  10712. $previous_item_type = null;
  10713. $last_item_not_dir = null;
  10714. $last_item_not_dir_type = null;
  10715. $last_item_not_dir_max = null;
  10716. foreach ($this->ordered_items as $itemId) {
  10717. $item = $this->getItem($itemId);
  10718. // if there was a previous item... (otherwise jump to set it)
  10719. if (!empty($previous_item_id)) {
  10720. $current_item_id = $item->get_id(); //save current id
  10721. if ($item->get_type() != 'dir') {
  10722. // Current item is not a folder, so it qualifies to get a prerequisites
  10723. if ($last_item_not_dir_type == 'quiz') {
  10724. // if previous is quiz, mark its max score as default score to be achieved
  10725. $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
  10726. WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
  10727. Database::query($sql);
  10728. }
  10729. // now simply update the prerequisite to set it to the last non-chapter item
  10730. $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
  10731. WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
  10732. Database::query($sql);
  10733. // record item as 'non-chapter' reference
  10734. $last_item_not_dir = $item->get_id();
  10735. $last_item_not_dir_type = $item->get_type();
  10736. $last_item_not_dir_max = $item->get_max();
  10737. }
  10738. } else {
  10739. if ($item->get_type() != 'dir') {
  10740. // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
  10741. $last_item_not_dir = $item->get_id();
  10742. $last_item_not_dir_type = $item->get_type();
  10743. $last_item_not_dir_max = $item->get_max();
  10744. }
  10745. }
  10746. // Saving the item as "previous item" for the next loop
  10747. $previous_item_id = $item->get_id();
  10748. $previous_item_max = $item->get_max();
  10749. $previous_item_type = $item->get_type();
  10750. }
  10751. }
  10752. }
  10753. /**
  10754. * @param array $params
  10755. *
  10756. * @throws \Doctrine\ORM\OptimisticLockException
  10757. *
  10758. * @return int
  10759. */
  10760. public static function createCategory($params)
  10761. {
  10762. $em = Database::getManager();
  10763. $item = new CLpCategory();
  10764. $item->setName($params['name']);
  10765. $item->setCId($params['c_id']);
  10766. $em->persist($item);
  10767. $em->flush();
  10768. api_item_property_update(
  10769. api_get_course_info(),
  10770. TOOL_LEARNPATH_CATEGORY,
  10771. $item->getId(),
  10772. 'visible',
  10773. api_get_user_id()
  10774. );
  10775. return $item->getId();
  10776. }
  10777. /**
  10778. * @param array $params
  10779. *
  10780. * @throws \Doctrine\ORM\ORMException
  10781. * @throws \Doctrine\ORM\OptimisticLockException
  10782. * @throws \Doctrine\ORM\TransactionRequiredException
  10783. */
  10784. public static function updateCategory($params)
  10785. {
  10786. $em = Database::getManager();
  10787. /** @var CLpCategory $item */
  10788. $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
  10789. if ($item) {
  10790. $item->setName($params['name']);
  10791. $em->merge($item);
  10792. $em->flush();
  10793. }
  10794. }
  10795. /**
  10796. * @param int $id
  10797. *
  10798. * @throws \Doctrine\ORM\ORMException
  10799. * @throws \Doctrine\ORM\OptimisticLockException
  10800. * @throws \Doctrine\ORM\TransactionRequiredException
  10801. */
  10802. public static function moveUpCategory($id)
  10803. {
  10804. $id = (int) $id;
  10805. $em = Database::getManager();
  10806. /** @var CLpCategory $item */
  10807. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  10808. if ($item) {
  10809. $position = $item->getPosition() - 1;
  10810. $item->setPosition($position);
  10811. $em->persist($item);
  10812. $em->flush();
  10813. }
  10814. }
  10815. /**
  10816. * @param int $id
  10817. *
  10818. * @throws \Doctrine\ORM\ORMException
  10819. * @throws \Doctrine\ORM\OptimisticLockException
  10820. * @throws \Doctrine\ORM\TransactionRequiredException
  10821. */
  10822. public static function moveDownCategory($id)
  10823. {
  10824. $id = (int) $id;
  10825. $em = Database::getManager();
  10826. /** @var CLpCategory $item */
  10827. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  10828. if ($item) {
  10829. $position = $item->getPosition() + 1;
  10830. $item->setPosition($position);
  10831. $em->persist($item);
  10832. $em->flush();
  10833. }
  10834. }
  10835. /**
  10836. * @param int $courseId
  10837. *
  10838. * @throws \Doctrine\ORM\Query\QueryException
  10839. *
  10840. * @return int|mixed
  10841. */
  10842. public static function getCountCategories($courseId)
  10843. {
  10844. if (empty($courseId)) {
  10845. return 0;
  10846. }
  10847. $em = Database::getManager();
  10848. $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
  10849. $query->setParameter('id', $courseId);
  10850. return $query->getSingleScalarResult();
  10851. }
  10852. /**
  10853. * @param int $courseId
  10854. *
  10855. * @return mixed
  10856. */
  10857. public static function getCategories($courseId)
  10858. {
  10859. $em = Database::getManager();
  10860. // Using doctrine extensions
  10861. /** @var SortableRepository $repo */
  10862. $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
  10863. $items = $repo
  10864. ->getBySortableGroupsQuery(['cId' => $courseId])
  10865. ->getResult();
  10866. return $items;
  10867. }
  10868. /**
  10869. * @param int $id
  10870. *
  10871. * @throws \Doctrine\ORM\ORMException
  10872. * @throws \Doctrine\ORM\OptimisticLockException
  10873. * @throws \Doctrine\ORM\TransactionRequiredException
  10874. *
  10875. * @return CLpCategory
  10876. */
  10877. public static function getCategory($id)
  10878. {
  10879. $id = (int) $id;
  10880. $em = Database::getManager();
  10881. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  10882. return $item;
  10883. }
  10884. /**
  10885. * @param int $courseId
  10886. *
  10887. * @return array
  10888. */
  10889. public static function getCategoryByCourse($courseId)
  10890. {
  10891. $em = Database::getManager();
  10892. $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
  10893. ['cId' => $courseId]
  10894. );
  10895. return $items;
  10896. }
  10897. /**
  10898. * @param int $id
  10899. *
  10900. * @throws \Doctrine\ORM\ORMException
  10901. * @throws \Doctrine\ORM\OptimisticLockException
  10902. * @throws \Doctrine\ORM\TransactionRequiredException
  10903. *
  10904. * @return mixed
  10905. */
  10906. public static function deleteCategory($id)
  10907. {
  10908. $em = Database::getManager();
  10909. $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
  10910. if ($item) {
  10911. $courseId = $item->getCId();
  10912. $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
  10913. $query->setParameter('id', $courseId);
  10914. $query->setParameter('catId', $item->getId());
  10915. $lps = $query->getResult();
  10916. // Setting category = 0.
  10917. if ($lps) {
  10918. foreach ($lps as $lpItem) {
  10919. $lpItem->setCategoryId(0);
  10920. }
  10921. }
  10922. // Removing category.
  10923. $em->remove($item);
  10924. $em->flush();
  10925. $courseInfo = api_get_course_info_by_id($courseId);
  10926. $sessionId = api_get_session_id();
  10927. // Delete link tool
  10928. $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
  10929. $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
  10930. // Delete tools
  10931. $sql = "DELETE FROM $tbl_tool
  10932. WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
  10933. Database::query($sql);
  10934. return true;
  10935. }
  10936. return false;
  10937. }
  10938. /**
  10939. * @param int $courseId
  10940. * @param bool $addSelectOption
  10941. *
  10942. * @return mixed
  10943. */
  10944. public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
  10945. {
  10946. $items = self::getCategoryByCourse($courseId);
  10947. $cats = [];
  10948. if ($addSelectOption) {
  10949. $cats = [get_lang('SelectACategory')];
  10950. }
  10951. if (!empty($items)) {
  10952. foreach ($items as $cat) {
  10953. $cats[$cat->getId()] = $cat->getName();
  10954. }
  10955. }
  10956. return $cats;
  10957. }
  10958. /**
  10959. * @param string $courseCode
  10960. * @param int $lpId
  10961. * @param int $user_id
  10962. *
  10963. * @return learnpath
  10964. */
  10965. public static function getLpFromSession($courseCode, $lpId, $user_id)
  10966. {
  10967. $debug = 0;
  10968. $learnPath = null;
  10969. $lpObject = Session::read('lpobject');
  10970. if ($lpObject !== null) {
  10971. $learnPath = UnserializeApi::unserialize('lp', $lpObject);
  10972. if ($debug) {
  10973. error_log('getLpFromSession: unserialize');
  10974. error_log('------getLpFromSession------');
  10975. error_log('------unserialize------');
  10976. error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
  10977. error_log("api_get_sessionid: ".api_get_session_id());
  10978. }
  10979. }
  10980. if (!is_object($learnPath)) {
  10981. $learnPath = new learnpath($courseCode, $lpId, $user_id);
  10982. if ($debug) {
  10983. error_log('------getLpFromSession------');
  10984. error_log('getLpFromSession: create new learnpath');
  10985. error_log("create new LP with $courseCode - $lpId - $user_id");
  10986. error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
  10987. error_log("api_get_sessionid: ".api_get_session_id());
  10988. }
  10989. }
  10990. return $learnPath;
  10991. }
  10992. /**
  10993. * @param int $itemId
  10994. *
  10995. * @return learnpathItem|false
  10996. */
  10997. public function getItem($itemId)
  10998. {
  10999. if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
  11000. return $this->items[$itemId];
  11001. }
  11002. return false;
  11003. }
  11004. /**
  11005. * @return int
  11006. */
  11007. public function getCurrentAttempt()
  11008. {
  11009. $attempt = $this->getItem($this->get_current_item_id());
  11010. if ($attempt) {
  11011. $attemptId = $attempt->get_attempt_id();
  11012. return $attemptId;
  11013. }
  11014. return 0;
  11015. }
  11016. /**
  11017. * @return int
  11018. */
  11019. public function getCategoryId()
  11020. {
  11021. return (int) $this->categoryId;
  11022. }
  11023. /**
  11024. * @param int $categoryId
  11025. *
  11026. * @return bool
  11027. */
  11028. public function setCategoryId($categoryId)
  11029. {
  11030. $this->categoryId = (int) $categoryId;
  11031. $table = Database::get_course_table(TABLE_LP_MAIN);
  11032. $lp_id = $this->get_id();
  11033. $sql = "UPDATE $table SET category_id = ".$this->categoryId."
  11034. WHERE iid = $lp_id";
  11035. Database::query($sql);
  11036. return true;
  11037. }
  11038. /**
  11039. * Get whether this is a learning path with the possibility to subscribe
  11040. * users or not.
  11041. *
  11042. * @return int
  11043. */
  11044. public function getSubscribeUsers()
  11045. {
  11046. return $this->subscribeUsers;
  11047. }
  11048. /**
  11049. * Set whether this is a learning path with the possibility to subscribe
  11050. * users or not.
  11051. *
  11052. * @param int $value (0 = false, 1 = true)
  11053. *
  11054. * @return bool
  11055. */
  11056. public function setSubscribeUsers($value)
  11057. {
  11058. $this->subscribeUsers = (int) $value;
  11059. $table = Database::get_course_table(TABLE_LP_MAIN);
  11060. $lp_id = $this->get_id();
  11061. $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
  11062. WHERE iid = $lp_id";
  11063. Database::query($sql);
  11064. return true;
  11065. }
  11066. /**
  11067. * Calculate the count of stars for a user in this LP
  11068. * This calculation is based on the following rules:
  11069. * - the student gets one star when he gets to 50% of the learning path
  11070. * - the student gets a second star when the average score of all tests inside the learning path >= 50%
  11071. * - the student gets a third star when the average score of all tests inside the learning path >= 80%
  11072. * - the student gets the final star when the score for the *last* test is >= 80%.
  11073. *
  11074. * @param int $sessionId Optional. The session ID
  11075. *
  11076. * @return int The count of stars
  11077. */
  11078. public function getCalculateStars($sessionId = 0)
  11079. {
  11080. $stars = 0;
  11081. $progress = self::getProgress(
  11082. $this->lp_id,
  11083. $this->user_id,
  11084. $this->course_int_id,
  11085. $sessionId
  11086. );
  11087. if ($progress >= 50) {
  11088. $stars++;
  11089. }
  11090. // Calculate stars chapters evaluation
  11091. $exercisesItems = $this->getExercisesItems();
  11092. if (!empty($exercisesItems)) {
  11093. $totalResult = 0;
  11094. foreach ($exercisesItems as $exerciseItem) {
  11095. $exerciseResultInfo = Event::getExerciseResultsByUser(
  11096. $this->user_id,
  11097. $exerciseItem->path,
  11098. $this->course_int_id,
  11099. $sessionId,
  11100. $this->lp_id,
  11101. $exerciseItem->db_id
  11102. );
  11103. $exerciseResultInfo = end($exerciseResultInfo);
  11104. if (!$exerciseResultInfo) {
  11105. continue;
  11106. }
  11107. if (!empty($exerciseResultInfo['exe_weighting'])) {
  11108. $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
  11109. } else {
  11110. $exerciseResult = 0;
  11111. }
  11112. $totalResult += $exerciseResult;
  11113. }
  11114. $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
  11115. if ($totalExerciseAverage >= 50) {
  11116. $stars++;
  11117. }
  11118. if ($totalExerciseAverage >= 80) {
  11119. $stars++;
  11120. }
  11121. }
  11122. // Calculate star for final evaluation
  11123. $finalEvaluationItem = $this->getFinalEvaluationItem();
  11124. if (!empty($finalEvaluationItem)) {
  11125. $evaluationResultInfo = Event::getExerciseResultsByUser(
  11126. $this->user_id,
  11127. $finalEvaluationItem->path,
  11128. $this->course_int_id,
  11129. $sessionId,
  11130. $this->lp_id,
  11131. $finalEvaluationItem->db_id
  11132. );
  11133. $evaluationResultInfo = end($evaluationResultInfo);
  11134. if ($evaluationResultInfo) {
  11135. $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
  11136. if ($evaluationResult >= 80) {
  11137. $stars++;
  11138. }
  11139. }
  11140. }
  11141. return $stars;
  11142. }
  11143. /**
  11144. * Get the items of exercise type.
  11145. *
  11146. * @return array The items. Otherwise return false
  11147. */
  11148. public function getExercisesItems()
  11149. {
  11150. $exercises = [];
  11151. foreach ($this->items as $item) {
  11152. if ($item->type != 'quiz') {
  11153. continue;
  11154. }
  11155. $exercises[] = $item;
  11156. }
  11157. array_pop($exercises);
  11158. return $exercises;
  11159. }
  11160. /**
  11161. * Get the item of exercise type (evaluation type).
  11162. *
  11163. * @return array The final evaluation. Otherwise return false
  11164. */
  11165. public function getFinalEvaluationItem()
  11166. {
  11167. $exercises = [];
  11168. foreach ($this->items as $item) {
  11169. if ($item->type != 'quiz') {
  11170. continue;
  11171. }
  11172. $exercises[] = $item;
  11173. }
  11174. return array_pop($exercises);
  11175. }
  11176. /**
  11177. * Calculate the total points achieved for the current user in this learning path.
  11178. *
  11179. * @param int $sessionId Optional. The session Id
  11180. *
  11181. * @return int
  11182. */
  11183. public function getCalculateScore($sessionId = 0)
  11184. {
  11185. // Calculate stars chapters evaluation
  11186. $exercisesItems = $this->getExercisesItems();
  11187. $finalEvaluationItem = $this->getFinalEvaluationItem();
  11188. $totalExercisesResult = 0;
  11189. $totalEvaluationResult = 0;
  11190. if ($exercisesItems !== false) {
  11191. foreach ($exercisesItems as $exerciseItem) {
  11192. $exerciseResultInfo = Event::getExerciseResultsByUser(
  11193. $this->user_id,
  11194. $exerciseItem->path,
  11195. $this->course_int_id,
  11196. $sessionId,
  11197. $this->lp_id,
  11198. $exerciseItem->db_id
  11199. );
  11200. $exerciseResultInfo = end($exerciseResultInfo);
  11201. if (!$exerciseResultInfo) {
  11202. continue;
  11203. }
  11204. $totalExercisesResult += $exerciseResultInfo['exe_result'];
  11205. }
  11206. }
  11207. if (!empty($finalEvaluationItem)) {
  11208. $evaluationResultInfo = Event::getExerciseResultsByUser(
  11209. $this->user_id,
  11210. $finalEvaluationItem->path,
  11211. $this->course_int_id,
  11212. $sessionId,
  11213. $this->lp_id,
  11214. $finalEvaluationItem->db_id
  11215. );
  11216. $evaluationResultInfo = end($evaluationResultInfo);
  11217. if ($evaluationResultInfo) {
  11218. $totalEvaluationResult += $evaluationResultInfo['exe_result'];
  11219. }
  11220. }
  11221. return $totalExercisesResult + $totalEvaluationResult;
  11222. }
  11223. /**
  11224. * Check if URL is not allowed to be show in a iframe.
  11225. *
  11226. * @param string $src
  11227. *
  11228. * @return string
  11229. */
  11230. public function fixBlockedLinks($src)
  11231. {
  11232. $urlInfo = parse_url($src);
  11233. $platformProtocol = 'https';
  11234. if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
  11235. $platformProtocol = 'http';
  11236. }
  11237. $protocolFixApplied = false;
  11238. //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
  11239. $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
  11240. $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
  11241. if ($platformProtocol != $scheme) {
  11242. Session::write('x_frame_source', $src);
  11243. $src = 'blank.php?error=x_frames_options';
  11244. $protocolFixApplied = true;
  11245. }
  11246. if ($protocolFixApplied == false) {
  11247. if (strpos(api_get_path(WEB_PATH), $host) === false) {
  11248. // Check X-Frame-Options
  11249. $ch = curl_init();
  11250. $options = [
  11251. CURLOPT_URL => $src,
  11252. CURLOPT_RETURNTRANSFER => true,
  11253. CURLOPT_HEADER => true,
  11254. CURLOPT_FOLLOWLOCATION => true,
  11255. CURLOPT_ENCODING => "",
  11256. CURLOPT_AUTOREFERER => true,
  11257. CURLOPT_CONNECTTIMEOUT => 120,
  11258. CURLOPT_TIMEOUT => 120,
  11259. CURLOPT_MAXREDIRS => 10,
  11260. ];
  11261. $proxySettings = api_get_configuration_value('proxy_settings');
  11262. if (!empty($proxySettings) &&
  11263. isset($proxySettings['curl_setopt_array'])
  11264. ) {
  11265. $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
  11266. $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
  11267. }
  11268. curl_setopt_array($ch, $options);
  11269. $response = curl_exec($ch);
  11270. $httpCode = curl_getinfo($ch);
  11271. $headers = substr($response, 0, $httpCode['header_size']);
  11272. $error = false;
  11273. if (stripos($headers, 'X-Frame-Options: DENY') > -1
  11274. //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
  11275. ) {
  11276. $error = true;
  11277. }
  11278. if ($error) {
  11279. Session::write('x_frame_source', $src);
  11280. $src = 'blank.php?error=x_frames_options';
  11281. }
  11282. }
  11283. }
  11284. return $src;
  11285. }
  11286. /**
  11287. * Check if this LP has a created forum in the basis course.
  11288. *
  11289. * @return bool
  11290. */
  11291. public function lpHasForum()
  11292. {
  11293. $forumTable = Database::get_course_table(TABLE_FORUM);
  11294. $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
  11295. $fakeFrom = "
  11296. $forumTable f
  11297. INNER JOIN $itemProperty ip
  11298. ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
  11299. ";
  11300. $resultData = Database::select(
  11301. 'COUNT(f.iid) AS qty',
  11302. $fakeFrom,
  11303. [
  11304. 'where' => [
  11305. 'ip.visibility != ? AND ' => 2,
  11306. 'ip.tool = ? AND ' => TOOL_FORUM,
  11307. 'f.c_id = ? AND ' => intval($this->course_int_id),
  11308. 'f.lp_id = ?' => intval($this->lp_id),
  11309. ],
  11310. ],
  11311. 'first'
  11312. );
  11313. return $resultData['qty'] > 0;
  11314. }
  11315. /**
  11316. * Get the forum for this learning path.
  11317. *
  11318. * @param int $sessionId
  11319. *
  11320. * @return bool
  11321. */
  11322. public function getForum($sessionId = 0)
  11323. {
  11324. $forumTable = Database::get_course_table(TABLE_FORUM);
  11325. $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
  11326. $fakeFrom = "$forumTable f
  11327. INNER JOIN $itemProperty ip ";
  11328. if ($this->lp_session_id == 0) {
  11329. $fakeFrom .= "
  11330. ON (
  11331. f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
  11332. f.session_id = ip.session_id OR ip.session_id IS NULL
  11333. )
  11334. )
  11335. ";
  11336. } else {
  11337. $fakeFrom .= "
  11338. ON (
  11339. f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
  11340. )
  11341. ";
  11342. }
  11343. $resultData = Database::select(
  11344. 'f.*',
  11345. $fakeFrom,
  11346. [
  11347. 'where' => [
  11348. 'ip.visibility != ? AND ' => 2,
  11349. 'ip.tool = ? AND ' => TOOL_FORUM,
  11350. 'f.session_id = ? AND ' => $sessionId,
  11351. 'f.c_id = ? AND ' => intval($this->course_int_id),
  11352. 'f.lp_id = ?' => intval($this->lp_id),
  11353. ],
  11354. ],
  11355. 'first'
  11356. );
  11357. if (empty($resultData)) {
  11358. return false;
  11359. }
  11360. return $resultData;
  11361. }
  11362. /**
  11363. * Create a forum for this learning path.
  11364. *
  11365. * @param int $forumCategoryId
  11366. *
  11367. * @return int The forum ID if was created. Otherwise return false
  11368. */
  11369. public function createForum($forumCategoryId)
  11370. {
  11371. require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
  11372. $forumId = store_forum(
  11373. [
  11374. 'lp_id' => $this->lp_id,
  11375. 'forum_title' => $this->name,
  11376. 'forum_comment' => null,
  11377. 'forum_category' => (int) $forumCategoryId,
  11378. 'students_can_edit_group' => ['students_can_edit' => 0],
  11379. 'allow_new_threads_group' => ['allow_new_threads' => 0],
  11380. 'default_view_type_group' => ['default_view_type' => 'flat'],
  11381. 'group_forum' => 0,
  11382. 'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
  11383. ],
  11384. [],
  11385. true
  11386. );
  11387. return $forumId;
  11388. }
  11389. /**
  11390. * Get the LP Final Item form.
  11391. *
  11392. * @throws Exception
  11393. * @throws HTML_QuickForm_Error
  11394. *
  11395. * @return string
  11396. */
  11397. public function getFinalItemForm()
  11398. {
  11399. $finalItem = $this->getFinalItem();
  11400. $title = '';
  11401. if ($finalItem) {
  11402. $title = $finalItem->get_title();
  11403. $buttonText = get_lang('Save');
  11404. $content = $this->getSavedFinalItem();
  11405. } else {
  11406. $buttonText = get_lang('LPCreateDocument');
  11407. $content = $this->getFinalItemTemplate();
  11408. }
  11409. $courseInfo = api_get_course_info();
  11410. $result = $this->generate_lp_folder($courseInfo);
  11411. $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
  11412. $relative_prefix = '../../';
  11413. $editorConfig = [
  11414. 'ToolbarSet' => 'LearningPathDocuments',
  11415. 'Width' => '100%',
  11416. 'Height' => '500',
  11417. 'FullPage' => true,
  11418. 'CreateDocumentDir' => $relative_prefix,
  11419. 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
  11420. 'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
  11421. ];
  11422. $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
  11423. 'type' => 'document',
  11424. 'lp_id' => $this->lp_id,
  11425. ]);
  11426. $form = new FormValidator('final_item', 'POST', $url);
  11427. $form->addText('title', get_lang('Title'));
  11428. $form->addButtonSave($buttonText);
  11429. $form->addHtml(
  11430. Display::return_message(
  11431. 'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
  11432. 'normal',
  11433. false
  11434. )
  11435. );
  11436. $renderer = $form->defaultRenderer();
  11437. $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
  11438. $form->addHtmlEditor(
  11439. 'content_lp_certificate',
  11440. null,
  11441. true,
  11442. false,
  11443. $editorConfig,
  11444. true
  11445. );
  11446. $form->addHidden('action', 'add_final_item');
  11447. $form->addHidden('path', Session::read('pathItem'));
  11448. $form->addHidden('previous', $this->get_last());
  11449. $form->setDefaults(
  11450. ['title' => $title, 'content_lp_certificate' => $content]
  11451. );
  11452. if ($form->validate()) {
  11453. $values = $form->exportValues();
  11454. $lastItemId = $this->getLastInFirstLevel();
  11455. if (!$finalItem) {
  11456. $documentId = $this->create_document(
  11457. $this->course_info,
  11458. $values['content_lp_certificate'],
  11459. $values['title']
  11460. );
  11461. $this->add_item(
  11462. 0,
  11463. $lastItemId,
  11464. 'final_item',
  11465. $documentId,
  11466. $values['title'],
  11467. ''
  11468. );
  11469. Display::addFlash(
  11470. Display::return_message(get_lang('Added'))
  11471. );
  11472. } else {
  11473. $this->edit_document($this->course_info);
  11474. }
  11475. }
  11476. return $form->returnForm();
  11477. }
  11478. /**
  11479. * Check if the current lp item is first, both, last or none from lp list.
  11480. *
  11481. * @param int $currentItemId
  11482. *
  11483. * @return string
  11484. */
  11485. public function isFirstOrLastItem($currentItemId)
  11486. {
  11487. $lpItemId = [];
  11488. $typeListNotToVerify = self::getChapterTypes();
  11489. // Using get_toc() function instead $this->items because returns the correct order of the items
  11490. foreach ($this->get_toc() as $item) {
  11491. if (!in_array($item['type'], $typeListNotToVerify)) {
  11492. $lpItemId[] = $item['id'];
  11493. }
  11494. }
  11495. $lastLpItemIndex = count($lpItemId) - 1;
  11496. $position = array_search($currentItemId, $lpItemId);
  11497. switch ($position) {
  11498. case 0:
  11499. if (!$lastLpItemIndex) {
  11500. $answer = 'both';
  11501. break;
  11502. }
  11503. $answer = 'first';
  11504. break;
  11505. case $lastLpItemIndex:
  11506. $answer = 'last';
  11507. break;
  11508. default:
  11509. $answer = 'none';
  11510. }
  11511. return $answer;
  11512. }
  11513. /**
  11514. * Get whether this is a learning path with the accumulated SCORM time or not.
  11515. *
  11516. * @return int
  11517. */
  11518. public function getAccumulateScormTime()
  11519. {
  11520. return $this->accumulateScormTime;
  11521. }
  11522. /**
  11523. * Set whether this is a learning path with the accumulated SCORM time or not.
  11524. *
  11525. * @param int $value (0 = false, 1 = true)
  11526. *
  11527. * @return bool Always returns true
  11528. */
  11529. public function setAccumulateScormTime($value)
  11530. {
  11531. $this->accumulateScormTime = (int) $value;
  11532. $lp_table = Database::get_course_table(TABLE_LP_MAIN);
  11533. $lp_id = $this->get_id();
  11534. $sql = "UPDATE $lp_table
  11535. SET accumulate_scorm_time = ".$this->accumulateScormTime."
  11536. WHERE iid = $lp_id";
  11537. Database::query($sql);
  11538. return true;
  11539. }
  11540. /**
  11541. * Returns an HTML-formatted link to a resource, to incorporate directly into
  11542. * the new learning path tool.
  11543. *
  11544. * The function is a big switch on tool type.
  11545. * In each case, we query the corresponding table for information and build the link
  11546. * with that information.
  11547. *
  11548. * @author Yannick Warnier <ywarnier@beeznest.org> - rebranding based on
  11549. * previous work (display_addedresource_link_in_learnpath())
  11550. *
  11551. * @param int $course_id Course code
  11552. * @param int $learningPathId The learning path ID (in lp table)
  11553. * @param int $id_in_path the unique index in the items table
  11554. * @param int $lpViewId
  11555. *
  11556. * @return string
  11557. */
  11558. public static function rl_get_resource_link_for_learnpath(
  11559. $course_id,
  11560. $learningPathId,
  11561. $id_in_path,
  11562. $lpViewId
  11563. ) {
  11564. $session_id = api_get_session_id();
  11565. $course_info = api_get_course_info_by_id($course_id);
  11566. $learningPathId = (int) $learningPathId;
  11567. $id_in_path = (int) $id_in_path;
  11568. $lpViewId = (int) $lpViewId;
  11569. $em = Database::getManager();
  11570. $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
  11571. /** @var CLpItem $rowItem */
  11572. $rowItem = $lpItemRepo->findOneBy([
  11573. 'cId' => $course_id,
  11574. 'lpId' => $learningPathId,
  11575. 'iid' => $id_in_path,
  11576. ]);
  11577. if (!$rowItem) {
  11578. // Try one more time with "id"
  11579. /** @var CLpItem $rowItem */
  11580. $rowItem = $lpItemRepo->findOneBy([
  11581. 'cId' => $course_id,
  11582. 'lpId' => $learningPathId,
  11583. 'id' => $id_in_path,
  11584. ]);
  11585. if (!$rowItem) {
  11586. return -1;
  11587. }
  11588. }
  11589. $course_code = $course_info['code'];
  11590. $type = $rowItem->getItemType();
  11591. $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
  11592. $main_dir_path = api_get_path(WEB_CODE_PATH);
  11593. $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
  11594. $link = '';
  11595. $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
  11596. switch ($type) {
  11597. case 'dir':
  11598. return $main_dir_path.'lp/blank.php';
  11599. case TOOL_CALENDAR_EVENT:
  11600. return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
  11601. case TOOL_ANNOUNCEMENT:
  11602. return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
  11603. case TOOL_LINK:
  11604. $linkInfo = Link::getLinkInfo($id);
  11605. if (isset($linkInfo['url'])) {
  11606. return $linkInfo['url'];
  11607. }
  11608. return '';
  11609. case TOOL_QUIZ:
  11610. if (empty($id)) {
  11611. return '';
  11612. }
  11613. // Get the lp_item_view with the highest view_count.
  11614. $learnpathItemViewResult = $em
  11615. ->getRepository('ChamiloCourseBundle:CLpItemView')
  11616. ->findBy(
  11617. ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
  11618. ['viewCount' => 'DESC'],
  11619. 1
  11620. );
  11621. /** @var CLpItemView $learnpathItemViewData */
  11622. $learnpathItemViewData = current($learnpathItemViewResult);
  11623. $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
  11624. return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
  11625. .http_build_query([
  11626. 'lp_init' => 1,
  11627. 'learnpath_item_view_id' => $learnpathItemViewId,
  11628. 'learnpath_id' => $learningPathId,
  11629. 'learnpath_item_id' => $id_in_path,
  11630. 'exerciseId' => $id,
  11631. ]);
  11632. case TOOL_HOTPOTATOES: //lowercase because of strtolower above
  11633. $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
  11634. $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
  11635. $myrow = Database::fetch_array($result);
  11636. $path = $myrow['path'];
  11637. return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
  11638. .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
  11639. .'&lp_view_id='.$lpViewId.'&'.$extraParams;
  11640. case TOOL_FORUM:
  11641. return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
  11642. case TOOL_THREAD:
  11643. // forum post
  11644. $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
  11645. if (empty($id)) {
  11646. return '';
  11647. }
  11648. $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
  11649. $result = Database::query($sql);
  11650. $myrow = Database::fetch_array($result);
  11651. return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
  11652. .$extraParams;
  11653. case TOOL_POST:
  11654. $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
  11655. $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
  11656. $myrow = Database::fetch_array($result);
  11657. return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
  11658. .$myrow['forum_id'].'&lp=true&'.$extraParams;
  11659. case TOOL_READOUT_TEXT:
  11660. return api_get_path(WEB_CODE_PATH).
  11661. 'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
  11662. case TOOL_DOCUMENT:
  11663. $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
  11664. $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
  11665. if (empty($document)) {
  11666. // Try with normal id
  11667. $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
  11668. if (empty($document)) {
  11669. return '';
  11670. }
  11671. }
  11672. $documentPathInfo = pathinfo($document->getPath());
  11673. $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
  11674. $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
  11675. $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
  11676. $openmethod = 2;
  11677. $officedoc = false;
  11678. Session::write('openmethod', $openmethod);
  11679. Session::write('officedoc', $officedoc);
  11680. if ($showDirectUrl) {
  11681. $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
  11682. if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
  11683. if (Link::isPdfLink($file)) {
  11684. $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
  11685. return $pdfUrl;
  11686. }
  11687. }
  11688. return $file;
  11689. }
  11690. return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
  11691. case TOOL_LP_FINAL_ITEM:
  11692. return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
  11693. .$extraParams;
  11694. case 'assignments':
  11695. return $main_dir_path.'work/work.php?'.$extraParams;
  11696. case TOOL_DROPBOX:
  11697. return $main_dir_path.'dropbox/index.php?'.$extraParams;
  11698. case 'introduction_text': //DEPRECATED
  11699. return '';
  11700. case TOOL_COURSE_DESCRIPTION:
  11701. return $main_dir_path.'course_description?'.$extraParams;
  11702. case TOOL_GROUP:
  11703. return $main_dir_path.'group/group.php?'.$extraParams;
  11704. case TOOL_USER:
  11705. return $main_dir_path.'user/user.php?'.$extraParams;
  11706. case TOOL_STUDENTPUBLICATION:
  11707. if (!empty($rowItem->getPath())) {
  11708. return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
  11709. }
  11710. return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
  11711. }
  11712. return $link;
  11713. }
  11714. /**
  11715. * Gets the name of a resource (generally used in learnpath when no name is provided).
  11716. *
  11717. * @author Yannick Warnier <ywarnier@beeznest.org>
  11718. *
  11719. * @param string $course_code Course code
  11720. * @param int $learningPathId
  11721. * @param int $id_in_path The resource ID
  11722. *
  11723. * @return string
  11724. */
  11725. public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
  11726. {
  11727. $_course = api_get_course_info($course_code);
  11728. if (empty($_course)) {
  11729. return '';
  11730. }
  11731. $course_id = $_course['real_id'];
  11732. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  11733. $learningPathId = (int) $learningPathId;
  11734. $id_in_path = (int) $id_in_path;
  11735. $sql = "SELECT item_type, title, ref
  11736. FROM $tbl_lp_item
  11737. WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
  11738. $res_item = Database::query($sql);
  11739. if (Database::num_rows($res_item) < 1) {
  11740. return '';
  11741. }
  11742. $row_item = Database::fetch_array($res_item);
  11743. $type = strtolower($row_item['item_type']);
  11744. $id = $row_item['ref'];
  11745. $output = '';
  11746. switch ($type) {
  11747. case TOOL_CALENDAR_EVENT:
  11748. $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
  11749. $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
  11750. $myrow = Database::fetch_array($result);
  11751. $output = $myrow['title'];
  11752. break;
  11753. case TOOL_ANNOUNCEMENT:
  11754. $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
  11755. $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
  11756. $myrow = Database::fetch_array($result);
  11757. $output = $myrow['title'];
  11758. break;
  11759. case TOOL_LINK:
  11760. // Doesn't take $target into account.
  11761. $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
  11762. $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
  11763. $myrow = Database::fetch_array($result);
  11764. $output = $myrow['title'];
  11765. break;
  11766. case TOOL_QUIZ:
  11767. $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
  11768. $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
  11769. $myrow = Database::fetch_array($result);
  11770. $output = $myrow['title'];
  11771. break;
  11772. case TOOL_FORUM:
  11773. $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
  11774. $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
  11775. $myrow = Database::fetch_array($result);
  11776. $output = $myrow['forum_name'];
  11777. break;
  11778. case TOOL_THREAD:
  11779. $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
  11780. // Grabbing the title of the post.
  11781. $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
  11782. $result_title = Database::query($sql_title);
  11783. $myrow_title = Database::fetch_array($result_title);
  11784. $output = $myrow_title['post_title'];
  11785. break;
  11786. case TOOL_POST:
  11787. $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
  11788. $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
  11789. $result = Database::query($sql);
  11790. $post = Database::fetch_array($result);
  11791. $output = $post['post_title'];
  11792. break;
  11793. case 'dir':
  11794. case TOOL_DOCUMENT:
  11795. $title = $row_item['title'];
  11796. $output = '-';
  11797. if (!empty($title)) {
  11798. $output = $title;
  11799. }
  11800. break;
  11801. case 'hotpotatoes':
  11802. $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
  11803. $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
  11804. $myrow = Database::fetch_array($result);
  11805. $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
  11806. $last = count($pathname) - 1; // Making a correct name for the link.
  11807. $filename = $pathname[$last]; // Making a correct name for the link.
  11808. $myrow['path'] = rawurlencode($myrow['path']);
  11809. $output = $filename;
  11810. break;
  11811. }
  11812. return stripslashes($output);
  11813. }
  11814. /**
  11815. * Get the parent names for the current item.
  11816. *
  11817. * @param int $newItemId Optional. The item ID
  11818. *
  11819. * @return array
  11820. */
  11821. public function getCurrentItemParentNames($newItemId = 0)
  11822. {
  11823. $newItemId = $newItemId ?: $this->get_current_item_id();
  11824. $return = [];
  11825. $item = $this->getItem($newItemId);
  11826. $parent = $this->getItem($item->get_parent());
  11827. while ($parent) {
  11828. $return[] = $parent->get_title();
  11829. $parent = $this->getItem($parent->get_parent());
  11830. }
  11831. return array_reverse($return);
  11832. }
  11833. /**
  11834. * Reads and process "lp_subscription_settings" setting.
  11835. *
  11836. * @return array
  11837. */
  11838. public static function getSubscriptionSettings()
  11839. {
  11840. $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
  11841. if (empty($subscriptionSettings)) {
  11842. // By default allow both settings
  11843. $subscriptionSettings = [
  11844. 'allow_add_users_to_lp' => true,
  11845. 'allow_add_users_to_lp_category' => true,
  11846. ];
  11847. } else {
  11848. $subscriptionSettings = $subscriptionSettings['options'];
  11849. }
  11850. return $subscriptionSettings;
  11851. }
  11852. /**
  11853. * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
  11854. */
  11855. public function exportToCourseBuildFormat()
  11856. {
  11857. if (!api_is_allowed_to_edit()) {
  11858. return false;
  11859. }
  11860. $courseBuilder = new CourseBuilder();
  11861. $itemList = [];
  11862. /** @var learnpathItem $item */
  11863. foreach ($this->items as $item) {
  11864. $itemList[$item->get_type()][] = $item->get_path();
  11865. }
  11866. if (empty($itemList)) {
  11867. return false;
  11868. }
  11869. if (isset($itemList['document'])) {
  11870. // Get parents
  11871. foreach ($itemList['document'] as $documentId) {
  11872. $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
  11873. if (!empty($documentInfo['parents'])) {
  11874. foreach ($documentInfo['parents'] as $parentInfo) {
  11875. if (in_array($parentInfo['iid'], $itemList['document'])) {
  11876. continue;
  11877. }
  11878. $itemList['document'][] = $parentInfo['iid'];
  11879. }
  11880. }
  11881. }
  11882. $courseInfo = api_get_course_info();
  11883. foreach ($itemList['document'] as $documentId) {
  11884. $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
  11885. $items = DocumentManager::get_resources_from_source_html(
  11886. $documentInfo['absolute_path'],
  11887. true,
  11888. TOOL_DOCUMENT
  11889. );
  11890. if (!empty($items)) {
  11891. foreach ($items as $item) {
  11892. // Get information about source url
  11893. $url = $item[0]; // url
  11894. $scope = $item[1]; // scope (local, remote)
  11895. $type = $item[2]; // type (rel, abs, url)
  11896. $origParseUrl = parse_url($url);
  11897. $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
  11898. if ($scope == 'local') {
  11899. if ($type == 'abs' || $type == 'rel') {
  11900. $documentFile = strstr($realOrigPath, 'document');
  11901. if (strpos($realOrigPath, $documentFile) !== false) {
  11902. $documentFile = str_replace('document', '', $documentFile);
  11903. $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
  11904. // Document found! Add it to the list
  11905. if ($itemDocumentId) {
  11906. $itemList['document'][] = $itemDocumentId;
  11907. }
  11908. }
  11909. }
  11910. }
  11911. }
  11912. }
  11913. }
  11914. $courseBuilder->build_documents(
  11915. api_get_session_id(),
  11916. $this->get_course_int_id(),
  11917. true,
  11918. $itemList['document']
  11919. );
  11920. }
  11921. if (isset($itemList['quiz'])) {
  11922. $courseBuilder->build_quizzes(
  11923. api_get_session_id(),
  11924. $this->get_course_int_id(),
  11925. true,
  11926. $itemList['quiz']
  11927. );
  11928. }
  11929. require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
  11930. /*if (!empty($itemList['thread'])) {
  11931. $postList = [];
  11932. foreach ($itemList['thread'] as $postId) {
  11933. $post = get_post_information($postId);
  11934. if ($post) {
  11935. if (!isset($itemList['forum'])) {
  11936. $itemList['forum'] = [];
  11937. }
  11938. $itemList['forum'][] = $post['forum_id'];
  11939. $postList[] = $postId;
  11940. }
  11941. }
  11942. if (!empty($postList)) {
  11943. $courseBuilder->build_forum_posts(
  11944. $this->get_course_int_id(),
  11945. null,
  11946. null,
  11947. $postList
  11948. );
  11949. }
  11950. }*/
  11951. if (!empty($itemList['thread'])) {
  11952. $threadList = [];
  11953. $em = Database::getManager();
  11954. $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
  11955. foreach ($itemList['thread'] as $threadId) {
  11956. /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
  11957. $thread = $repo->find($threadId);
  11958. if ($thread) {
  11959. $itemList['forum'][] = $thread->getForumId();
  11960. $threadList[] = $thread->getIid();
  11961. }
  11962. }
  11963. if (!empty($threadList)) {
  11964. $courseBuilder->build_forum_topics(
  11965. api_get_session_id(),
  11966. $this->get_course_int_id(),
  11967. null,
  11968. $threadList
  11969. );
  11970. }
  11971. }
  11972. $forumCategoryList = [];
  11973. if (isset($itemList['forum'])) {
  11974. foreach ($itemList['forum'] as $forumId) {
  11975. $forumInfo = get_forums($forumId);
  11976. $forumCategoryList[] = $forumInfo['forum_category'];
  11977. }
  11978. }
  11979. if (!empty($forumCategoryList)) {
  11980. $courseBuilder->build_forum_category(
  11981. api_get_session_id(),
  11982. $this->get_course_int_id(),
  11983. true,
  11984. $forumCategoryList
  11985. );
  11986. }
  11987. if (!empty($itemList['forum'])) {
  11988. $courseBuilder->build_forums(
  11989. api_get_session_id(),
  11990. $this->get_course_int_id(),
  11991. true,
  11992. $itemList['forum']
  11993. );
  11994. }
  11995. if (isset($itemList['link'])) {
  11996. $courseBuilder->build_links(
  11997. api_get_session_id(),
  11998. $this->get_course_int_id(),
  11999. true,
  12000. $itemList['link']
  12001. );
  12002. }
  12003. if (!empty($itemList['student_publication'])) {
  12004. $courseBuilder->build_works(
  12005. api_get_session_id(),
  12006. $this->get_course_int_id(),
  12007. true,
  12008. $itemList['student_publication']
  12009. );
  12010. }
  12011. $courseBuilder->build_learnpaths(
  12012. api_get_session_id(),
  12013. $this->get_course_int_id(),
  12014. true,
  12015. [$this->get_id()],
  12016. false
  12017. );
  12018. $courseBuilder->restoreDocumentsFromList();
  12019. $zipFile = CourseArchiver::createBackup($courseBuilder->course);
  12020. $zipPath = CourseArchiver::getBackupDir().$zipFile;
  12021. $result = DocumentManager::file_send_for_download(
  12022. $zipPath,
  12023. true,
  12024. $this->get_name().'.zip'
  12025. );
  12026. if ($result) {
  12027. api_not_allowed();
  12028. }
  12029. return true;
  12030. }
  12031. /**
  12032. * Get whether this is a learning path with the accumulated work time or not.
  12033. *
  12034. * @return int
  12035. */
  12036. public function getAccumulateWorkTime()
  12037. {
  12038. return (int) $this->accumulateWorkTime;
  12039. }
  12040. /**
  12041. * Get whether this is a learning path with the accumulated work time or not.
  12042. *
  12043. * @return int
  12044. */
  12045. public function getAccumulateWorkTimeTotalCourse()
  12046. {
  12047. $table = Database::get_course_table(TABLE_LP_MAIN);
  12048. $sql = "SELECT SUM(accumulate_work_time) AS total
  12049. FROM $table
  12050. WHERE c_id = ".$this->course_int_id;
  12051. $result = Database::query($sql);
  12052. $row = Database::fetch_array($result);
  12053. return (int) $row['total'];
  12054. }
  12055. /**
  12056. * Set whether this is a learning path with the accumulated work time or not.
  12057. *
  12058. * @param int $value (0 = false, 1 = true)
  12059. *
  12060. * @return bool
  12061. */
  12062. public function setAccumulateWorkTime($value)
  12063. {
  12064. if (!api_get_configuration_value('lp_minimum_time')) {
  12065. return false;
  12066. }
  12067. $this->accumulateWorkTime = (int) $value;
  12068. $table = Database::get_course_table(TABLE_LP_MAIN);
  12069. $lp_id = $this->get_id();
  12070. $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
  12071. WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
  12072. Database::query($sql);
  12073. return true;
  12074. }
  12075. /**
  12076. * @param int $lpId
  12077. * @param int $courseId
  12078. *
  12079. * @return mixed
  12080. */
  12081. public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
  12082. {
  12083. $lpId = (int) $lpId;
  12084. $courseId = (int) $courseId;
  12085. $table = Database::get_course_table(TABLE_LP_MAIN);
  12086. $sql = "SELECT accumulate_work_time
  12087. FROM $table
  12088. WHERE c_id = $courseId AND id = $lpId";
  12089. $result = Database::query($sql);
  12090. $row = Database::fetch_array($result);
  12091. return $row['accumulate_work_time'];
  12092. }
  12093. /**
  12094. * @param int $courseId
  12095. *
  12096. * @return int
  12097. */
  12098. public static function getAccumulateWorkTimeTotal($courseId)
  12099. {
  12100. $table = Database::get_course_table(TABLE_LP_MAIN);
  12101. $courseId = (int) $courseId;
  12102. $sql = "SELECT SUM(accumulate_work_time) AS total
  12103. FROM $table
  12104. WHERE c_id = $courseId";
  12105. $result = Database::query($sql);
  12106. $row = Database::fetch_array($result);
  12107. return (int) $row['total'];
  12108. }
  12109. /**
  12110. * In order to use the lp icon option you need to create the "lp_icon" LP extra field
  12111. * and put the images in.
  12112. *
  12113. * @return array
  12114. */
  12115. public static function getIconSelect()
  12116. {
  12117. $theme = api_get_visual_theme();
  12118. $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
  12119. $icons = ['' => get_lang('SelectAnOption')];
  12120. if (is_dir($path)) {
  12121. $finder = new Finder();
  12122. $finder->files()->in($path);
  12123. $allowedExtensions = ['jpeg', 'jpg', 'png'];
  12124. /** @var SplFileInfo $file */
  12125. foreach ($finder as $file) {
  12126. if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
  12127. $icons[$file->getFilename()] = $file->getFilename();
  12128. }
  12129. }
  12130. }
  12131. return $icons;
  12132. }
  12133. /**
  12134. * @param int $lpId
  12135. *
  12136. * @return string
  12137. */
  12138. public static function getSelectedIcon($lpId)
  12139. {
  12140. $extraFieldValue = new ExtraFieldValue('lp');
  12141. $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
  12142. $icon = '';
  12143. if (!empty($lpIcon) && isset($lpIcon['value'])) {
  12144. $icon = $lpIcon['value'];
  12145. }
  12146. return $icon;
  12147. }
  12148. /**
  12149. * @param int $lpId
  12150. *
  12151. * @return string
  12152. */
  12153. public static function getSelectedIconHtml($lpId)
  12154. {
  12155. $icon = self::getSelectedIcon($lpId);
  12156. if (empty($icon)) {
  12157. return '';
  12158. }
  12159. $theme = api_get_visual_theme();
  12160. $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
  12161. return Display::img($path);
  12162. }
  12163. /**
  12164. * @param string $value
  12165. *
  12166. * @return string
  12167. */
  12168. public function cleanItemTitle($value)
  12169. {
  12170. $value = Security::remove_XSS(strip_tags($value));
  12171. return $value;
  12172. }
  12173. /**
  12174. * @param FormValidator $form
  12175. */
  12176. public function setItemTitle(FormValidator $form)
  12177. {
  12178. if (api_get_configuration_value('save_titles_as_html')) {
  12179. $form->addHtmlEditor(
  12180. 'title',
  12181. get_lang('Title'),
  12182. true,
  12183. false,
  12184. ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
  12185. );
  12186. } else {
  12187. $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
  12188. $form->applyFilter('title', 'trim');
  12189. $form->applyFilter('title', 'html_filter');
  12190. }
  12191. }
  12192. /**
  12193. * @return array
  12194. */
  12195. public function getItemsForForm($addParentCondition = false)
  12196. {
  12197. $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  12198. $course_id = api_get_course_int_id();
  12199. $sql = "SELECT * FROM $tbl_lp_item
  12200. WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
  12201. if ($addParentCondition) {
  12202. $sql .= ' AND parent_item_id = 0 ';
  12203. }
  12204. $sql .= ' ORDER BY display_order ASC';
  12205. $result = Database::query($sql);
  12206. $arrLP = [];
  12207. while ($row = Database::fetch_array($result)) {
  12208. $arrLP[] = [
  12209. 'iid' => $row['iid'],
  12210. 'id' => $row['iid'],
  12211. 'item_type' => $row['item_type'],
  12212. 'title' => $this->cleanItemTitle($row['title']),
  12213. 'title_raw' => $row['title'],
  12214. 'path' => $row['path'],
  12215. 'description' => Security::remove_XSS($row['description']),
  12216. 'parent_item_id' => $row['parent_item_id'],
  12217. 'previous_item_id' => $row['previous_item_id'],
  12218. 'next_item_id' => $row['next_item_id'],
  12219. 'display_order' => $row['display_order'],
  12220. 'max_score' => $row['max_score'],
  12221. 'min_score' => $row['min_score'],
  12222. 'mastery_score' => $row['mastery_score'],
  12223. 'prerequisite' => $row['prerequisite'],
  12224. 'max_time_allowed' => $row['max_time_allowed'],
  12225. 'prerequisite_min_score' => $row['prerequisite_min_score'],
  12226. 'prerequisite_max_score' => $row['prerequisite_max_score'],
  12227. ];
  12228. }
  12229. return $arrLP;
  12230. }
  12231. /**
  12232. * Get the depth level of LP item.
  12233. *
  12234. * @param array $items
  12235. * @param int $currentItemId
  12236. *
  12237. * @return int
  12238. */
  12239. private static function get_level_for_item($items, $currentItemId)
  12240. {
  12241. $parentItemId = 0;
  12242. if (isset($items[$currentItemId])) {
  12243. $parentItemId = $items[$currentItemId]->parent;
  12244. }
  12245. if ($parentItemId == 0) {
  12246. return 0;
  12247. } else {
  12248. return self::get_level_for_item($items, $parentItemId) + 1;
  12249. }
  12250. }
  12251. /**
  12252. * Generate the link for a learnpath category as course tool.
  12253. *
  12254. * @param int $categoryId
  12255. *
  12256. * @return string
  12257. */
  12258. private static function getCategoryLinkForTool($categoryId)
  12259. {
  12260. $categoryId = (int) $categoryId;
  12261. $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
  12262. .http_build_query(
  12263. [
  12264. 'action' => 'view_category',
  12265. 'id' => $categoryId,
  12266. ]
  12267. );
  12268. return $link;
  12269. }
  12270. /**
  12271. * Return the scorm item type object with spaces replaced with _
  12272. * The return result is use to build a css classname like scorm_type_$return.
  12273. *
  12274. * @param $in_type
  12275. *
  12276. * @return mixed
  12277. */
  12278. private static function format_scorm_type_item($in_type)
  12279. {
  12280. return str_replace(' ', '_', $in_type);
  12281. }
  12282. /**
  12283. * Check and obtain the lp final item if exist.
  12284. *
  12285. * @return learnpathItem
  12286. */
  12287. private function getFinalItem()
  12288. {
  12289. if (empty($this->items)) {
  12290. return null;
  12291. }
  12292. foreach ($this->items as $item) {
  12293. if ($item->type !== 'final_item') {
  12294. continue;
  12295. }
  12296. return $item;
  12297. }
  12298. }
  12299. /**
  12300. * Get the LP Final Item Template.
  12301. *
  12302. * @return string
  12303. */
  12304. private function getFinalItemTemplate()
  12305. {
  12306. return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
  12307. }
  12308. /**
  12309. * Get the LP Final Item Url.
  12310. *
  12311. * @return string
  12312. */
  12313. private function getSavedFinalItem()
  12314. {
  12315. $finalItem = $this->getFinalItem();
  12316. $doc = DocumentManager::get_document_data_by_id(
  12317. $finalItem->path,
  12318. $this->cc
  12319. );
  12320. if ($doc && file_exists($doc['absolute_path'])) {
  12321. return file_get_contents($doc['absolute_path']);
  12322. }
  12323. return '';
  12324. }
  12325. }