exercise.class.php 347 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use ChamiloSession as Session;
  4. /**
  5. * Class Exercise
  6. *
  7. * Allows to instantiate an object of type Exercise
  8. * @package chamilo.exercise
  9. * @author Olivier Brouckaert
  10. * @author Julio Montoya Cleaning exercises
  11. * Modified by Hubert Borderiou #294
  12. */
  13. class Exercise
  14. {
  15. public $id;
  16. public $name;
  17. public $title;
  18. public $exercise;
  19. public $description;
  20. public $sound;
  21. public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
  22. public $random;
  23. public $random_answers;
  24. public $active;
  25. public $timeLimit;
  26. public $attempts;
  27. public $feedback_type;
  28. public $end_time;
  29. public $start_time;
  30. public $questionList; // array with the list of this exercise's questions
  31. /* including question list of the media */
  32. public $questionListUncompressed;
  33. public $results_disabled;
  34. public $expired_time;
  35. public $course;
  36. public $course_id;
  37. public $propagate_neg;
  38. public $saveCorrectAnswers;
  39. public $review_answers;
  40. public $randomByCat;
  41. public $text_when_finished;
  42. public $display_category_name;
  43. public $pass_percentage;
  44. public $edit_exercise_in_lp = false;
  45. public $is_gradebook_locked = false;
  46. public $exercise_was_added_in_lp = false;
  47. public $lpList = array();
  48. public $force_edit_exercise_in_lp = false;
  49. public $categories;
  50. public $categories_grouping = true;
  51. public $endButton = 0;
  52. public $categoryWithQuestionList;
  53. public $mediaList;
  54. public $loadQuestionAJAX = false;
  55. // Notification send to the teacher.
  56. public $emailNotificationTemplate = null;
  57. // Notification send to the student.
  58. public $emailNotificationTemplateToUser = null;
  59. public $countQuestions = 0;
  60. public $fastEdition = false;
  61. public $modelType = 1;
  62. public $questionSelectionType = EX_Q_SELECTION_ORDERED;
  63. public $hideQuestionTitle = 0;
  64. public $scoreTypeModel = 0;
  65. public $categoryMinusOne = true; // Shows the category -1: See BT#6540
  66. public $globalCategoryId = null;
  67. public $onSuccessMessage = null;
  68. public $onFailedMessage = null;
  69. public $emailAlert;
  70. public $notifyUserByEmail = '';
  71. public $sessionId = 0;
  72. /**
  73. * Constructor of the class
  74. *
  75. * @author Olivier Brouckaert
  76. */
  77. public function __construct($course_id = null)
  78. {
  79. $this->id = 0;
  80. $this->exercise = '';
  81. $this->description = '';
  82. $this->sound = '';
  83. $this->type = ALL_ON_ONE_PAGE;
  84. $this->random = 0;
  85. $this->random_answers = 0;
  86. $this->active = 1;
  87. $this->questionList = array();
  88. $this->timeLimit = 0;
  89. $this->end_time = '';
  90. $this->start_time = '';
  91. $this->results_disabled = 1;
  92. $this->expired_time = 0;
  93. $this->propagate_neg = 0;
  94. $this->saveCorrectAnswers = 0;
  95. $this->review_answers = false;
  96. $this->randomByCat = 0;
  97. $this->text_when_finished = '';
  98. $this->display_category_name = 0;
  99. $this->pass_percentage = '';
  100. $this->modelType = 1;
  101. $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
  102. $this->endButton = 0;
  103. $this->scoreTypeModel = 0;
  104. $this->globalCategoryId = null;
  105. if (!empty($course_id)) {
  106. $course_info = api_get_course_info_by_id($course_id);
  107. } else {
  108. $course_info = api_get_course_info();
  109. }
  110. $this->course_id = $course_info['real_id'];
  111. $this->course = $course_info;
  112. }
  113. /**
  114. * Reads exercise information from the data base
  115. *
  116. * @author Olivier Brouckaert
  117. * @param integer $id - exercise Id
  118. *
  119. * @return boolean - true if exercise exists, otherwise false
  120. */
  121. public function read($id, $parseQuestionList = true)
  122. {
  123. $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
  124. $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  125. $id = intval($id);
  126. if (empty($this->course_id)) {
  127. return false;
  128. }
  129. $sql = "SELECT * FROM $TBL_EXERCISES WHERE c_id = ".$this->course_id." AND id = ".$id;
  130. $result = Database::query($sql);
  131. // if the exercise has been found
  132. if ($object = Database::fetch_object($result)) {
  133. $this->id = $id;
  134. $this->exercise = $object->title;
  135. $this->name = $object->title;
  136. $this->title = $object->title;
  137. $this->description = $object->description;
  138. $this->sound = $object->sound;
  139. $this->type = $object->type;
  140. if (empty($this->type)) {
  141. $this->type = ONE_PER_PAGE;
  142. }
  143. $this->random = $object->random;
  144. $this->random_answers = $object->random_answers;
  145. $this->active = $object->active;
  146. $this->results_disabled = $object->results_disabled;
  147. $this->attempts = $object->max_attempt;
  148. $this->feedback_type = $object->feedback_type;
  149. $this->propagate_neg = $object->propagate_neg;
  150. $this->saveCorrectAnswers = $object->save_correct_answers;
  151. $this->randomByCat = $object->random_by_category;
  152. $this->text_when_finished = $object->text_when_finished;
  153. $this->display_category_name = $object->display_category_name;
  154. $this->pass_percentage = $object->pass_percentage;
  155. $this->sessionId = $object->session_id;
  156. $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
  157. $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
  158. $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
  159. $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
  160. $sql = "SELECT lp_id, max_score
  161. FROM $table_lp_item
  162. WHERE c_id = {$this->course_id} AND
  163. item_type = '".TOOL_QUIZ."' AND
  164. path = '".$id."'";
  165. $result = Database::query($sql);
  166. if (Database::num_rows($result) > 0) {
  167. $this->exercise_was_added_in_lp = true;
  168. $this->lpList = Database::store_result($result, 'ASSOC');
  169. }
  170. $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
  171. if ($this->exercise_was_added_in_lp) {
  172. $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
  173. } else {
  174. $this->edit_exercise_in_lp = true;
  175. }
  176. if (!empty($object->end_time)) {
  177. $this->end_time = $object->end_time;
  178. }
  179. if (!empty($object->start_time)) {
  180. $this->start_time = $object->start_time;
  181. }
  182. //control time
  183. $this->expired_time = $object->expired_time;
  184. //Checking if question_order is correctly set
  185. //$this->questionList = $this->selectQuestionList(true);
  186. if ($parseQuestionList) {
  187. $this->setQuestionList();
  188. }
  189. //overload questions list with recorded questions list
  190. //load questions only for exercises of type 'one question per page'
  191. //this is needed only is there is no questions
  192. /*
  193. // @todo not sure were in the code this is used somebody mess with the exercise tool
  194. // @todo don't know who add that config and why $_configuration['live_exercise_tracking']
  195. global $_configuration, $questionList;
  196. if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
  197. isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
  198. $this->questionList = $questionList;
  199. }*/
  200. return true;
  201. }
  202. return false;
  203. }
  204. /**
  205. * @return string
  206. */
  207. public function getCutTitle()
  208. {
  209. return cut($this->exercise, EXERCISE_MAX_NAME_SIZE);
  210. }
  211. /**
  212. * returns the exercise ID
  213. *
  214. * @author Olivier Brouckaert
  215. * @return int - exercise ID
  216. */
  217. public function selectId()
  218. {
  219. return $this->id;
  220. }
  221. /**
  222. * returns the exercise title
  223. *
  224. * @author Olivier Brouckaert
  225. * @return string - exercise title
  226. */
  227. public function selectTitle()
  228. {
  229. return $this->exercise;
  230. }
  231. /**
  232. * returns the number of attempts setted
  233. *
  234. * @return int - exercise attempts
  235. */
  236. public function selectAttempts()
  237. {
  238. return $this->attempts;
  239. }
  240. /** returns the number of FeedbackType *
  241. * 0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback
  242. * @return int - exercise attempts
  243. */
  244. public function selectFeedbackType()
  245. {
  246. return $this->feedback_type;
  247. }
  248. /**
  249. * returns the time limit
  250. */
  251. public function selectTimeLimit()
  252. {
  253. return $this->timeLimit;
  254. }
  255. /**
  256. * returns the exercise description
  257. *
  258. * @author Olivier Brouckaert
  259. * @return string - exercise description
  260. */
  261. public function selectDescription()
  262. {
  263. return $this->description;
  264. }
  265. /**
  266. * returns the exercise sound file
  267. *
  268. * @author Olivier Brouckaert
  269. * @return string - exercise description
  270. */
  271. public function selectSound()
  272. {
  273. return $this->sound;
  274. }
  275. /**
  276. * returns the exercise type
  277. *
  278. * @author Olivier Brouckaert
  279. * @return integer - exercise type
  280. */
  281. public function selectType()
  282. {
  283. return $this->type;
  284. }
  285. /**
  286. * @return int
  287. */
  288. public function getModelType()
  289. {
  290. return $this->modelType;
  291. }
  292. /**
  293. * @return int
  294. */
  295. public function selectEndButton()
  296. {
  297. return $this->endButton;
  298. }
  299. /**
  300. * @return string
  301. */
  302. public function getOnSuccessMessage()
  303. {
  304. return $this->onSuccessMessage;
  305. }
  306. /**
  307. * @return string
  308. */
  309. public function getOnFailedMessage()
  310. {
  311. return $this->onFailedMessage;
  312. }
  313. /**
  314. * @author hubert borderiou 30-11-11
  315. * @return integer : do we display the question category name for students
  316. */
  317. public function selectDisplayCategoryName()
  318. {
  319. return $this->display_category_name;
  320. }
  321. /**
  322. * @return int
  323. */
  324. public function selectPassPercentage()
  325. {
  326. return $this->pass_percentage;
  327. }
  328. /**
  329. *
  330. * Modify object to update the switch display_category_name
  331. * @author hubert borderiou 30-11-11
  332. * @param int $in_txt is an integer 0 or 1
  333. */
  334. public function updateDisplayCategoryName($in_txt)
  335. {
  336. $this->display_category_name = $in_txt;
  337. }
  338. /**
  339. * @author hubert borderiou 28-11-11
  340. * @return string html text : the text to display ay the end of the test.
  341. */
  342. public function selectTextWhenFinished()
  343. {
  344. return $this->text_when_finished;
  345. }
  346. /**
  347. * @author hubert borderiou 28-11-11
  348. * @return string html text : update the text to display ay the end of the test.
  349. */
  350. public function updateTextWhenFinished($in_txt)
  351. {
  352. $this->text_when_finished = $in_txt;
  353. }
  354. /**
  355. * return 1 or 2 if randomByCat
  356. * @author hubert borderiou
  357. * @return integer - quiz random by category
  358. */
  359. public function selectRandomByCat()
  360. {
  361. return $this->randomByCat;
  362. }
  363. /**
  364. * return 0 if no random by cat
  365. * return 1 if random by cat, categories shuffled
  366. * return 2 if random by cat, categories sorted by alphabetic order
  367. * @author hubert borderiou
  368. * @return integer - quiz random by category
  369. */
  370. public function isRandomByCat()
  371. {
  372. /*$res = 0;
  373. if ($this->randomByCat == 1) {
  374. $res = 1;
  375. } else if ($this->randomByCat == 2) {
  376. $res = 2;
  377. }
  378. */
  379. $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
  380. if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
  381. $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
  382. } else if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
  383. $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
  384. }
  385. return $res;
  386. }
  387. /**
  388. * return nothing
  389. * update randomByCat value for object
  390. * @param int $random
  391. *
  392. * @author hubert borderiou
  393. */
  394. public function updateRandomByCat($random)
  395. {
  396. if (in_array($random, array(
  397. EXERCISE_CATEGORY_RANDOM_SHUFFLED,
  398. EXERCISE_CATEGORY_RANDOM_ORDERED,
  399. EXERCISE_CATEGORY_RANDOM_DISABLED
  400. )
  401. )) {
  402. $this->randomByCat = $random;
  403. } else {
  404. $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
  405. }
  406. }
  407. /**
  408. * Tells if questions are selected randomly, and if so returns the draws
  409. *
  410. * @author Carlos Vargas
  411. * @return integer - results disabled exercise
  412. */
  413. public function selectResultsDisabled()
  414. {
  415. return $this->results_disabled;
  416. }
  417. /**
  418. * tells if questions are selected randomly, and if so returns the draws
  419. *
  420. * @author Olivier Brouckaert
  421. * @return integer - 0 if not random, otherwise the draws
  422. */
  423. public function isRandom()
  424. {
  425. if($this->random > 0 || $this->random == -1) {
  426. return true;
  427. } else {
  428. return false;
  429. }
  430. }
  431. /**
  432. * returns random answers status.
  433. *
  434. * @author Juan Carlos Rana
  435. */
  436. public function selectRandomAnswers()
  437. {
  438. return $this->random_answers;
  439. }
  440. /**
  441. * Same as isRandom() but has a name applied to values different than 0 or 1
  442. */
  443. public function getShuffle()
  444. {
  445. return $this->random;
  446. }
  447. /**
  448. * returns the exercise status (1 = enabled ; 0 = disabled)
  449. *
  450. * @author Olivier Brouckaert
  451. * @return boolean - true if enabled, otherwise false
  452. */
  453. public function selectStatus()
  454. {
  455. return $this->active;
  456. }
  457. /**
  458. * If false the question list will be managed as always if true the question will be filtered
  459. * depending of the exercise settings (table c_quiz_rel_category)
  460. * @param bool active or inactive grouping
  461. **/
  462. public function setCategoriesGrouping($status)
  463. {
  464. $this->categories_grouping = (bool) $status;
  465. }
  466. /**
  467. * @return int
  468. */
  469. public function getHideQuestionTitle()
  470. {
  471. return $this->hideQuestionTitle;
  472. }
  473. /**
  474. * @param $value
  475. */
  476. public function setHideQuestionTitle($value)
  477. {
  478. $this->hideQuestionTitle = intval($value);
  479. }
  480. /**
  481. * @return int
  482. */
  483. public function getScoreTypeModel()
  484. {
  485. return $this->scoreTypeModel;
  486. }
  487. /**
  488. * @param int $value
  489. */
  490. public function setScoreTypeModel($value)
  491. {
  492. $this->scoreTypeModel = intval($value);
  493. }
  494. /**
  495. * @return int
  496. */
  497. public function getGlobalCategoryId()
  498. {
  499. return $this->globalCategoryId;
  500. }
  501. /**
  502. * @param int $value
  503. */
  504. public function setGlobalCategoryId($value)
  505. {
  506. if (is_array($value) && isset($value[0])) {
  507. $value = $value[0];
  508. }
  509. $this->globalCategoryId = intval($value);
  510. }
  511. /**
  512. *
  513. * @param int $start
  514. * @param int $limit
  515. * @param int $sidx
  516. * @param string $sord
  517. * @param array $where_condition
  518. * @param array $extraFields
  519. */
  520. public function getQuestionListPagination($start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array())
  521. {
  522. if (!empty($this->id)) {
  523. $category_list = TestCategory::getListOfCategoriesNameForTest($this->id, false);
  524. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  525. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  526. $sql = "SELECT q.iid
  527. FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
  528. ON (e.question_id = q.iid AND e.c_id = ".$this->course_id." )
  529. WHERE e.exercice_id = '".Database::escape_string($this->id)."'
  530. ";
  531. $orderCondition = "ORDER BY question_order";
  532. if (!empty($sidx) && !empty($sord)) {
  533. if ($sidx == 'question') {
  534. if (in_array(strtolower($sord), array('desc', 'asc'))) {
  535. $orderCondition = " ORDER BY q.$sidx $sord";
  536. }
  537. }
  538. }
  539. $sql .= $orderCondition;
  540. $limitCondition = null;
  541. if (isset($start) && isset($limit)) {
  542. $start = intval($start);
  543. $limit = intval($limit);
  544. $limitCondition = " LIMIT $start, $limit";
  545. }
  546. $sql .= $limitCondition;
  547. $result = Database::query($sql);
  548. $questions = array();
  549. if (Database::num_rows($result)) {
  550. if (!empty($extraFields)) {
  551. $extraFieldValue = new ExtraFieldValue('question');
  552. }
  553. while ($question = Database::fetch_array($result, 'ASSOC')) {
  554. /** @var Question $objQuestionTmp */
  555. $objQuestionTmp = Question::read($question['iid']);
  556. $category_labels = TestCategory::return_category_labels($objQuestionTmp->category_list, $category_list);
  557. if (empty($category_labels)) {
  558. $category_labels = "-";
  559. }
  560. // Question type
  561. list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
  562. $question_media = null;
  563. if (!empty($objQuestionTmp->parent_id)) {
  564. $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
  565. $question_media = Question::getMediaLabel($objQuestionMedia->question);
  566. }
  567. $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media);
  568. $question = array(
  569. 'id' => $question['iid'],
  570. 'question' => $objQuestionTmp->selectTitle(),
  571. 'type' => $questionType,
  572. 'category' => Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'),
  573. 'score' => $objQuestionTmp->selectWeighting(),
  574. 'level' => $objQuestionTmp->level
  575. );
  576. if (!empty($extraFields)) {
  577. foreach ($extraFields as $extraField) {
  578. $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']);
  579. $stringValue = null;
  580. if ($value) {
  581. $stringValue = $value['field_value'];
  582. }
  583. $question[$extraField['field_variable']] = $stringValue;
  584. }
  585. }
  586. $questions[] = $question;
  587. }
  588. }
  589. return $questions;
  590. }
  591. }
  592. /**
  593. * Get question count per exercise from DB (any special treatment)
  594. * @return int
  595. */
  596. public function getQuestionCount()
  597. {
  598. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  599. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  600. $sql = "SELECT count(q.id) as count
  601. FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
  602. ON (e.question_id = q.id)
  603. WHERE e.c_id = {$this->course_id} AND e.exercice_id = ".Database::escape_string($this->id);
  604. $result = Database::query($sql);
  605. $count = 0;
  606. if (Database::num_rows($result)) {
  607. $row = Database::fetch_array($result);
  608. $count = $row['count'];
  609. }
  610. return $count;
  611. }
  612. /**
  613. * @return array
  614. */
  615. public function getQuestionOrderedListByName()
  616. {
  617. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  618. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  619. // Getting question list from the order (question list drag n drop interface ).
  620. $sql = "SELECT e.question_id
  621. FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
  622. ON (e.question_id= q.id)
  623. WHERE e.c_id = {$this->course_id} AND e.exercice_id = '".Database::escape_string($this->id)."'
  624. ORDER BY q.question";
  625. $result = Database::query($sql);
  626. $list = array();
  627. if (Database::num_rows($result)) {
  628. $list = Database::store_result($result, 'ASSOC');
  629. }
  630. return $list;
  631. }
  632. /**
  633. * Gets the question list ordered by the question_order setting (drag and drop)
  634. * @return array
  635. */
  636. private function getQuestionOrderedList()
  637. {
  638. $questionList = array();
  639. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  640. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  641. // Getting question_order to verify that the question
  642. // list is correct and all question_order's were set
  643. $sql = "SELECT DISTINCT e.question_order
  644. FROM $TBL_EXERCICE_QUESTION e
  645. INNER JOIN $TBL_QUESTIONS q
  646. ON (e.question_id = q.id)
  647. WHERE
  648. e.c_id = {$this->course_id} AND
  649. e.exercice_id = ".Database::escape_string($this->id);
  650. $result = Database::query($sql);
  651. $count_question_orders = Database::num_rows($result);
  652. // Getting question list from the order (question list drag n drop interface ).
  653. $sql = "SELECT DISTINCT e.question_id, e.question_order
  654. FROM $TBL_EXERCICE_QUESTION e
  655. INNER JOIN $TBL_QUESTIONS q
  656. ON (e.question_id= q.id)
  657. WHERE
  658. e.c_id = {$this->course_id} AND
  659. e.exercice_id = '".Database::escape_string($this->id)."'
  660. ORDER BY question_order";
  661. $result = Database::query($sql);
  662. // Fills the array with the question ID for this exercise
  663. // the key of the array is the question position
  664. $temp_question_list = array();
  665. $counter = 1;
  666. while ($new_object = Database::fetch_object($result)) {
  667. // Correct order.
  668. $questionList[$new_object->question_order] = $new_object->question_id;
  669. // Just in case we save the order in other array
  670. $temp_question_list[$counter] = $new_object->question_id;
  671. $counter++;
  672. }
  673. if (!empty($temp_question_list)) {
  674. /* If both array don't match it means that question_order was not correctly set
  675. for all questions using the default mysql order */
  676. if (count($temp_question_list) != $count_question_orders) {
  677. $questionList = $temp_question_list;
  678. }
  679. }
  680. return $questionList;
  681. }
  682. /**
  683. * Select N values from the questions per category array
  684. *
  685. * @param array $categoriesAddedInExercise
  686. * @param array $question_list
  687. * @param array $questions_by_category per category
  688. * @param bool $flatResult
  689. * @param bool $randomizeQuestions
  690. *
  691. * @return array
  692. */
  693. private function pickQuestionsPerCategory(
  694. $categoriesAddedInExercise,
  695. $question_list,
  696. & $questions_by_category,
  697. $flatResult = true,
  698. $randomizeQuestions = false
  699. ) {
  700. $addAll = true;
  701. $categoryCountArray = array();
  702. // Getting how many questions will be selected per category.
  703. if (!empty($categoriesAddedInExercise)) {
  704. $addAll = false;
  705. // Parsing question according the category rel exercise settings
  706. foreach ($categoriesAddedInExercise as $category_info) {
  707. $category_id = $category_info['category_id'];
  708. if (isset($questions_by_category[$category_id])) {
  709. // How many question will be picked from this category.
  710. $count = $category_info['count_questions'];
  711. // -1 means all questions
  712. if ($count == -1) {
  713. $categoryCountArray[$category_id] = 999;
  714. } else {
  715. $categoryCountArray[$category_id] = $count;
  716. }
  717. }
  718. }
  719. }
  720. if (!empty($questions_by_category)) {
  721. $temp_question_list = array();
  722. foreach ($questions_by_category as $category_id => & $categoryQuestionList) {
  723. if (isset($categoryCountArray) && !empty($categoryCountArray)) {
  724. if (isset($categoryCountArray[$category_id])) {
  725. $numberOfQuestions = $categoryCountArray[$category_id];
  726. } else {
  727. $numberOfQuestions = 0;
  728. }
  729. }
  730. if ($addAll) {
  731. $numberOfQuestions = 999;
  732. }
  733. if (!empty($numberOfQuestions)) {
  734. $elements = TestCategory::getNElementsFromArray(
  735. $categoryQuestionList,
  736. $numberOfQuestions,
  737. $randomizeQuestions
  738. );
  739. if (!empty($elements)) {
  740. $temp_question_list[$category_id] = $elements;
  741. $categoryQuestionList = $elements;
  742. }
  743. }
  744. }
  745. if (!empty($temp_question_list)) {
  746. if ($flatResult) {
  747. $temp_question_list = array_flatten($temp_question_list);
  748. }
  749. $question_list = $temp_question_list;
  750. }
  751. }
  752. return $question_list;
  753. }
  754. /**
  755. * Selecting question list depending in the exercise-category
  756. * relationship (category table in exercise settings)
  757. *
  758. * @param array $question_list
  759. * @param int $questionSelectionType
  760. * @return array
  761. */
  762. public function getQuestionListWithCategoryListFilteredByCategorySettings($question_list, $questionSelectionType)
  763. {
  764. $result = array(
  765. 'question_list' => array(),
  766. 'category_with_questions_list' => array()
  767. );
  768. // Order/random categories
  769. $cat = new TestCategory();
  770. // Setting category order.
  771. switch ($questionSelectionType) {
  772. case EX_Q_SELECTION_ORDERED: // 1
  773. case EX_Q_SELECTION_RANDOM: // 2
  774. // This options are not allowed here.
  775. break;
  776. case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
  777. $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
  778. $this,
  779. $this->course['real_id'],
  780. 'title ASC',
  781. false,
  782. true
  783. );
  784. $questions_by_category = TestCategory::getQuestionsByCat(
  785. $this->id,
  786. $question_list,
  787. $categoriesAddedInExercise
  788. );
  789. $question_list = $this->pickQuestionsPerCategory(
  790. $categoriesAddedInExercise,
  791. $question_list,
  792. $questions_by_category,
  793. true,
  794. false
  795. );
  796. break;
  797. case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
  798. case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
  799. $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
  800. $this,
  801. $this->course['real_id'],
  802. null,
  803. true,
  804. true
  805. );
  806. $questions_by_category = TestCategory::getQuestionsByCat(
  807. $this->id,
  808. $question_list,
  809. $categoriesAddedInExercise
  810. );
  811. $question_list = $this->pickQuestionsPerCategory(
  812. $categoriesAddedInExercise,
  813. $question_list,
  814. $questions_by_category,
  815. true,
  816. false
  817. );
  818. break;
  819. case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
  820. $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
  821. $this,
  822. $this->course['real_id'],
  823. 'title DESC',
  824. false,
  825. true
  826. );
  827. $questions_by_category = TestCategory::getQuestionsByCat(
  828. $this->id,
  829. $question_list,
  830. $categoriesAddedInExercise
  831. );
  832. $question_list = $this->pickQuestionsPerCategory(
  833. $categoriesAddedInExercise,
  834. $question_list,
  835. $questions_by_category,
  836. true,
  837. true
  838. );
  839. break;
  840. case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
  841. case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
  842. $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
  843. $this,
  844. $this->course['real_id'],
  845. null,
  846. true,
  847. true
  848. );
  849. $questions_by_category = TestCategory::getQuestionsByCat(
  850. $this->id,
  851. $question_list,
  852. $categoriesAddedInExercise
  853. );
  854. $question_list = $this->pickQuestionsPerCategory(
  855. $categoriesAddedInExercise,
  856. $question_list,
  857. $questions_by_category,
  858. true,
  859. true
  860. );
  861. break;
  862. case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
  863. break;
  864. case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
  865. break;
  866. case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
  867. $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
  868. $this,
  869. $this->course['real_id'],
  870. 'root ASC, lft ASC',
  871. false,
  872. true
  873. );
  874. $questions_by_category = TestCategory::getQuestionsByCat(
  875. $this->id,
  876. $question_list,
  877. $categoriesAddedInExercise
  878. );
  879. $question_list = $this->pickQuestionsPerCategory(
  880. $categoriesAddedInExercise,
  881. $question_list,
  882. $questions_by_category,
  883. true,
  884. false
  885. );
  886. break;
  887. case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
  888. $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
  889. $this,
  890. $this->course['real_id'],
  891. 'root, lft ASC',
  892. false,
  893. true
  894. );
  895. $questions_by_category = TestCategory::getQuestionsByCat(
  896. $this->id,
  897. $question_list,
  898. $categoriesAddedInExercise
  899. );
  900. $question_list = $this->pickQuestionsPerCategory(
  901. $categoriesAddedInExercise,
  902. $question_list,
  903. $questions_by_category,
  904. true,
  905. true
  906. );
  907. break;
  908. }
  909. $result['question_list'] = isset($question_list) ? $question_list : array();
  910. $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
  911. // Adding category info in the category list with question list:
  912. if (!empty($questions_by_category)) {
  913. /*$em = Database::getManager();
  914. $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');*/
  915. $newCategoryList = array();
  916. foreach ($questions_by_category as $categoryId => $questionList) {
  917. $cat = new TestCategory();
  918. $cat = $cat->getCategory($categoryId);
  919. $cat = (array)$cat;
  920. $cat['iid'] = $cat['id'];
  921. //*$cat['name'] = $cat['name'];
  922. $categoryParentInfo = null;
  923. // Parent is not set no loop here
  924. if (!empty($cat['parent_id'])) {
  925. if (!isset($parentsLoaded[$cat['parent_id']])) {
  926. $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']);
  927. $parentsLoaded[$cat['parent_id']] = $categoryEntity;
  928. } else {
  929. $categoryEntity = $parentsLoaded[$cat['parent_id']];
  930. }
  931. $path = $repo->getPath($categoryEntity);
  932. $index = 0;
  933. if ($this->categoryMinusOne) {
  934. //$index = 1;
  935. }
  936. /** @var \Chamilo\Entity\CQuizCategory $categoryParent*/
  937. foreach ($path as $categoryParent) {
  938. $visibility = $categoryParent->getVisibility();
  939. if ($visibility == 0) {
  940. $categoryParentId = $categoryId;
  941. $categoryTitle = $cat['title'];
  942. if (count($path) > 1) {
  943. continue;
  944. }
  945. } else {
  946. $categoryParentId = $categoryParent->getIid();
  947. $categoryTitle = $categoryParent->getTitle();
  948. }
  949. $categoryParentInfo['id'] = $categoryParentId;
  950. $categoryParentInfo['iid'] = $categoryParentId;
  951. $categoryParentInfo['parent_path'] = null;
  952. $categoryParentInfo['title'] = $categoryTitle;
  953. $categoryParentInfo['name'] = $categoryTitle;
  954. $categoryParentInfo['parent_id'] = null;
  955. break;
  956. }
  957. }
  958. $cat['parent_info'] = $categoryParentInfo;
  959. $newCategoryList[$categoryId] = array(
  960. 'category' => $cat,
  961. 'question_list' => $questionList
  962. );
  963. }
  964. $result['category_with_questions_list'] = $newCategoryList;
  965. }
  966. return $result;
  967. }
  968. /**
  969. * returns the array with the question ID list
  970. *
  971. * @author Olivier Brouckaert
  972. * @return array - question ID list
  973. */
  974. public function selectQuestionList($from_db = false)
  975. {
  976. if ($from_db && !empty($this->id)) {
  977. $nbQuestions = $this->getQuestionCount();
  978. $questionSelectionType = $this->getQuestionSelectionType();
  979. switch ($questionSelectionType) {
  980. case EX_Q_SELECTION_ORDERED:
  981. $questionList = $this->getQuestionOrderedList();
  982. break;
  983. case EX_Q_SELECTION_RANDOM:
  984. // Not a random exercise, or if there are not at least 2 questions
  985. if ($this->random == 0 || $nbQuestions < 2) {
  986. $questionList = $this->getQuestionOrderedList();
  987. } else {
  988. $questionList = $this->selectRandomList();
  989. }
  990. break;
  991. default:
  992. $questionList = $this->getQuestionOrderedList();
  993. $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
  994. $questionList,
  995. $questionSelectionType
  996. );
  997. $this->categoryWithQuestionList = $result['category_with_questions_list'];
  998. $questionList = $result['question_list'];
  999. break;
  1000. }
  1001. return $questionList;
  1002. }
  1003. return $this->questionList;
  1004. }
  1005. /**
  1006. * returns the number of questions in this exercise
  1007. *
  1008. * @author Olivier Brouckaert
  1009. * @return integer - number of questions
  1010. */
  1011. public function selectNbrQuestions()
  1012. {
  1013. return sizeof($this->questionList);
  1014. }
  1015. /**
  1016. * @return int
  1017. */
  1018. public function selectPropagateNeg()
  1019. {
  1020. return $this->propagate_neg;
  1021. }
  1022. /**
  1023. * @return int
  1024. */
  1025. public function selectSaveCorrectAnswers()
  1026. {
  1027. return $this->saveCorrectAnswers;
  1028. }
  1029. /**
  1030. * Selects questions randomly in the question list
  1031. *
  1032. * @author Olivier Brouckaert
  1033. * @author Hubert Borderiou 15 nov 2011
  1034. * @return array - if the exercise is not set to take questions randomly, returns the question list
  1035. * without randomizing, otherwise, returns the list with questions selected randomly
  1036. */
  1037. public function selectRandomList()
  1038. {
  1039. /*$nbQuestions = $this->selectNbrQuestions();
  1040. $temp_list = $this->questionList;
  1041. //Not a random exercise, or if there are not at least 2 questions
  1042. if($this->random == 0 || $nbQuestions < 2) {
  1043. return $this->questionList;
  1044. }
  1045. if ($nbQuestions != 0) {
  1046. shuffle($temp_list);
  1047. $my_random_list = array_combine(range(1,$nbQuestions),$temp_list);
  1048. $my_question_list = array();
  1049. // $this->random == -1 if random with all questions
  1050. if ($this->random > 0) {
  1051. $i = 0;
  1052. foreach ($my_random_list as $item) {
  1053. if ($i < $this->random) {
  1054. $my_question_list[$i] = $item;
  1055. } else {
  1056. break;
  1057. }
  1058. $i++;
  1059. }
  1060. } else {
  1061. $my_question_list = $my_random_list;
  1062. }
  1063. return $my_question_list;
  1064. }*/
  1065. $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1066. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1067. $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
  1068. $randomLimit = "LIMIT $random";
  1069. // Random all questions so no limit
  1070. if ($random == -1) {
  1071. $randomLimit = null;
  1072. }
  1073. // @todo improve this query
  1074. $sql = "SELECT e.question_id
  1075. FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q
  1076. ON (e.question_id= q.iid)
  1077. WHERE e.c_id = {$this->course_id} AND e.exercice_id = '".Database::escape_string($this->id)."'
  1078. ORDER BY RAND()
  1079. $randomLimit ";
  1080. $result = Database::query($sql);
  1081. $questionList = array();
  1082. while ($row = Database::fetch_object($result)) {
  1083. $questionList[] = $row->question_id;
  1084. }
  1085. return $questionList;
  1086. }
  1087. /**
  1088. * returns 'true' if the question ID is in the question list
  1089. *
  1090. * @author Olivier Brouckaert
  1091. * @param integer $questionId - question ID
  1092. * @return boolean - true if in the list, otherwise false
  1093. */
  1094. public function isInList($questionId)
  1095. {
  1096. if (is_array($this->questionList)) {
  1097. return in_array($questionId, $this->questionList);
  1098. } else {
  1099. return false;
  1100. }
  1101. }
  1102. /**
  1103. * changes the exercise title
  1104. *
  1105. * @author Olivier Brouckaert
  1106. * @param string $title - exercise title
  1107. */
  1108. public function updateTitle($title)
  1109. {
  1110. $this->exercise=$title;
  1111. }
  1112. /**
  1113. * changes the exercise max attempts
  1114. *
  1115. * @param int $attempts - exercise max attempts
  1116. */
  1117. public function updateAttempts($attempts)
  1118. {
  1119. $this->attempts=$attempts;
  1120. }
  1121. /**
  1122. * changes the exercise feedback type
  1123. *
  1124. * @param int $feedback_type
  1125. */
  1126. public function updateFeedbackType($feedback_type)
  1127. {
  1128. $this->feedback_type=$feedback_type;
  1129. }
  1130. /**
  1131. * changes the exercise description
  1132. *
  1133. * @author Olivier Brouckaert
  1134. * @param string $description - exercise description
  1135. */
  1136. public function updateDescription($description)
  1137. {
  1138. $this->description=$description;
  1139. }
  1140. /**
  1141. * changes the exercise expired_time
  1142. *
  1143. * @author Isaac flores
  1144. * @param int $expired_time The expired time of the quiz
  1145. */
  1146. public function updateExpiredTime($expired_time)
  1147. {
  1148. $this->expired_time = $expired_time;
  1149. }
  1150. /**
  1151. * @param $value
  1152. */
  1153. public function updatePropagateNegative($value)
  1154. {
  1155. $this->propagate_neg = $value;
  1156. }
  1157. /**
  1158. * @param $value int
  1159. */
  1160. public function updateSaveCorrectAnswers($value)
  1161. {
  1162. $this->saveCorrectAnswers = $value;
  1163. }
  1164. /**
  1165. * @param $value
  1166. */
  1167. public function updateReviewAnswers($value)
  1168. {
  1169. $this->review_answers = isset($value) && $value ? true : false;
  1170. }
  1171. /**
  1172. * @param $value
  1173. */
  1174. public function updatePassPercentage($value)
  1175. {
  1176. $this->pass_percentage = $value;
  1177. }
  1178. /**
  1179. * @param string $text
  1180. */
  1181. public function updateEmailNotificationTemplate($text)
  1182. {
  1183. $this->emailNotificationTemplate = $text;
  1184. }
  1185. /**
  1186. * @param string $text
  1187. */
  1188. public function updateEmailNotificationTemplateToUser($text)
  1189. {
  1190. $this->emailNotificationTemplateToUser = $text;
  1191. }
  1192. /**
  1193. * @param string $value
  1194. */
  1195. public function setNotifyUserByEmail($value)
  1196. {
  1197. $this->notifyUserByEmail = $value;
  1198. }
  1199. /**
  1200. * @param int $value
  1201. */
  1202. public function updateEndButton($value)
  1203. {
  1204. $this->endButton = intval($value);
  1205. }
  1206. /**
  1207. * @param string $value
  1208. */
  1209. public function setOnSuccessMessage($value)
  1210. {
  1211. $this->onSuccessMessage = $value;
  1212. }
  1213. /**
  1214. * @param string $value
  1215. */
  1216. public function setOnFailedMessage($value)
  1217. {
  1218. $this->onFailedMessage = $value;
  1219. }
  1220. /**
  1221. * @param $value
  1222. */
  1223. public function setModelType($value)
  1224. {
  1225. $this->modelType = intval($value);
  1226. }
  1227. /**
  1228. * @param int $value
  1229. */
  1230. public function setQuestionSelectionType($value)
  1231. {
  1232. $this->questionSelectionType = intval($value);
  1233. }
  1234. /**
  1235. * @return int
  1236. */
  1237. public function getQuestionSelectionType()
  1238. {
  1239. return $this->questionSelectionType;
  1240. }
  1241. /**
  1242. * @param array $categories
  1243. */
  1244. public function updateCategories($categories)
  1245. {
  1246. if (!empty($categories)) {
  1247. $categories = array_map('intval', $categories);
  1248. $this->categories = $categories;
  1249. }
  1250. }
  1251. /**
  1252. * changes the exercise sound file
  1253. *
  1254. * @author Olivier Brouckaert
  1255. * @param string $sound - exercise sound file
  1256. * @param string $delete - ask to delete the file
  1257. */
  1258. public function updateSound($sound,$delete)
  1259. {
  1260. global $audioPath, $documentPath;
  1261. $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
  1262. if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
  1263. $this->sound=$sound['name'];
  1264. if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
  1265. $sql = "SELECT 1 FROM $TBL_DOCUMENT
  1266. WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
  1267. $result = Database::query($sql);
  1268. if (!Database::num_rows($result)) {
  1269. $id = add_document(
  1270. $this->course,
  1271. str_replace($documentPath,'',$audioPath).'/'.$this->sound,
  1272. 'file',
  1273. $sound['size'],
  1274. $sound['name']
  1275. );
  1276. api_item_property_update(
  1277. $this->course,
  1278. TOOL_DOCUMENT,
  1279. $id,
  1280. 'DocumentAdded',
  1281. api_get_user_id()
  1282. );
  1283. item_property_update_on_folder(
  1284. $this->course,
  1285. str_replace($documentPath, '', $audioPath),
  1286. api_get_user_id()
  1287. );
  1288. }
  1289. }
  1290. } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
  1291. $this->sound='';
  1292. }
  1293. }
  1294. /**
  1295. * changes the exercise type
  1296. *
  1297. * @author Olivier Brouckaert
  1298. * @param integer $type - exercise type
  1299. */
  1300. public function updateType($type)
  1301. {
  1302. $this->type = $type;
  1303. }
  1304. /**
  1305. * sets to 0 if questions are not selected randomly
  1306. * if questions are selected randomly, sets the draws
  1307. *
  1308. * @author Olivier Brouckaert
  1309. * @param integer $random - 0 if not random, otherwise the draws
  1310. */
  1311. public function setRandom($random)
  1312. {
  1313. /*if ($random == 'all') {
  1314. $random = $this->selectNbrQuestions();
  1315. }*/
  1316. $this->random = $random;
  1317. }
  1318. /**
  1319. * sets to 0 if answers are not selected randomly
  1320. * if answers are selected randomly
  1321. * @author Juan Carlos Rana
  1322. * @param integer $random_answers - random answers
  1323. */
  1324. public function updateRandomAnswers($random_answers)
  1325. {
  1326. $this->random_answers = $random_answers;
  1327. }
  1328. /**
  1329. * enables the exercise
  1330. *
  1331. * @author Olivier Brouckaert
  1332. */
  1333. public function enable()
  1334. {
  1335. $this->active=1;
  1336. }
  1337. /**
  1338. * disables the exercise
  1339. *
  1340. * @author Olivier Brouckaert
  1341. */
  1342. public function disable()
  1343. {
  1344. $this->active=0;
  1345. }
  1346. /**
  1347. * Set disable results
  1348. */
  1349. public function disable_results()
  1350. {
  1351. $this->results_disabled = true;
  1352. }
  1353. /**
  1354. * Enable results
  1355. */
  1356. public function enable_results()
  1357. {
  1358. $this->results_disabled = false;
  1359. }
  1360. /**
  1361. * @param int $results_disabled
  1362. */
  1363. public function updateResultsDisabled($results_disabled)
  1364. {
  1365. $this->results_disabled = intval($results_disabled);
  1366. }
  1367. /**
  1368. * updates the exercise in the data base
  1369. *
  1370. * @author Olivier Brouckaert
  1371. */
  1372. public function save($type_e = '')
  1373. {
  1374. $_course = $this->course;
  1375. $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
  1376. $id = $this->id;
  1377. $exercise = $this->exercise;
  1378. $description = $this->description;
  1379. $sound = $this->sound;
  1380. $type = $this->type;
  1381. $attempts = isset($this->attempts) ? $this->attempts : 0;
  1382. $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
  1383. $random = $this->random;
  1384. $random_answers = $this->random_answers;
  1385. $active = $this->active;
  1386. $propagate_neg = (int) $this->propagate_neg;
  1387. $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
  1388. $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
  1389. $randomByCat = intval($this->randomByCat);
  1390. $text_when_finished = $this->text_when_finished;
  1391. $display_category_name = intval($this->display_category_name);
  1392. $pass_percentage = intval($this->pass_percentage);
  1393. $session_id = $this->sessionId;
  1394. //If direct we do not show results
  1395. if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
  1396. $results_disabled = 0;
  1397. } else {
  1398. $results_disabled = intval($this->results_disabled);
  1399. }
  1400. $expired_time = intval($this->expired_time);
  1401. // Exercise already exists
  1402. if ($id) {
  1403. // we prepare date in the database using the api_get_utc_datetime() function
  1404. if (!empty($this->start_time)) {
  1405. $start_time = $this->start_time;
  1406. } else {
  1407. $start_time = null;
  1408. }
  1409. if (!empty($this->end_time)) {
  1410. $end_time = $this->end_time;
  1411. } else {
  1412. $end_time = null;
  1413. }
  1414. $params = [
  1415. 'title' => $exercise,
  1416. 'description' => $description,
  1417. ];
  1418. $paramsExtra = [];
  1419. if ($type_e != 'simple') {
  1420. $paramsExtra = [
  1421. 'sound' => $sound,
  1422. 'type' => $type,
  1423. 'random' => $random,
  1424. 'random_answers' => $random_answers,
  1425. 'active' => $active,
  1426. 'feedback_type' => $feedback_type,
  1427. 'start_time' => $start_time,
  1428. 'end_time' => $end_time,
  1429. 'max_attempt' => $attempts,
  1430. 'expired_time' => $expired_time,
  1431. 'propagate_neg' => $propagate_neg,
  1432. 'save_correct_answers' => $saveCorrectAnswers,
  1433. 'review_answers' => $review_answers,
  1434. 'random_by_category' => $randomByCat,
  1435. 'text_when_finished' => $text_when_finished,
  1436. 'display_category_name' => $display_category_name,
  1437. 'pass_percentage' => $pass_percentage,
  1438. 'results_disabled' => $results_disabled,
  1439. 'question_selection_type' => $this->getQuestionSelectionType()
  1440. ];
  1441. }
  1442. $params = array_merge($params, $paramsExtra);
  1443. Database::update(
  1444. $TBL_EXERCISES,
  1445. $params,
  1446. ['c_id = ? AND id = ?' => [$this->course_id, $id]]
  1447. );
  1448. // update into the item_property table
  1449. api_item_property_update(
  1450. $_course,
  1451. TOOL_QUIZ,
  1452. $id,
  1453. 'QuizUpdated',
  1454. api_get_user_id()
  1455. );
  1456. if (api_get_setting('search_enabled')=='true') {
  1457. $this->search_engine_edit();
  1458. }
  1459. } else {
  1460. // Creates a new exercise
  1461. // In this case of new exercise, we don't do the api_get_utc_datetime()
  1462. // for date because, bellow, we call function api_set_default_visibility()
  1463. // In this function, api_set_default_visibility,
  1464. // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
  1465. // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
  1466. if (!empty($this->start_time)) {
  1467. $start_time = $this->start_time;
  1468. } else {
  1469. $start_time = null;
  1470. }
  1471. if (!empty($this->end_time)) {
  1472. $end_time = $this->end_time;
  1473. } else {
  1474. $end_time = null;
  1475. }
  1476. $params = [
  1477. 'c_id' => $this->course_id,
  1478. 'start_time' => $start_time,
  1479. 'end_time' => $end_time,
  1480. 'title' => $exercise,
  1481. 'description' => $description,
  1482. 'sound' => $sound,
  1483. 'type' => $type,
  1484. 'random' => $random,
  1485. 'random_answers' => $random_answers,
  1486. 'active' => $active,
  1487. 'results_disabled' => $results_disabled,
  1488. 'max_attempt' => $attempts,
  1489. 'feedback_type' => $feedback_type,
  1490. 'expired_time' => $expired_time,
  1491. 'session_id' => $session_id,
  1492. 'review_answers' => $review_answers,
  1493. 'random_by_category' => $randomByCat,
  1494. 'text_when_finished' => $text_when_finished,
  1495. 'display_category_name' => $display_category_name,
  1496. 'pass_percentage' => $pass_percentage,
  1497. 'save_correct_answers' => (int) $saveCorrectAnswers,
  1498. 'propagate_neg' => $propagate_neg
  1499. ];
  1500. $this->id = Database::insert($TBL_EXERCISES, $params);
  1501. if ($this->id) {
  1502. $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
  1503. Database::query($sql);
  1504. $sql = "UPDATE $TBL_EXERCISES
  1505. SET question_selection_type= ".intval($this->getQuestionSelectionType())."
  1506. WHERE id = ".$this->id." AND c_id = ".$this->course_id;
  1507. Database::query($sql);
  1508. // insert into the item_property table
  1509. api_item_property_update(
  1510. $this->course,
  1511. TOOL_QUIZ,
  1512. $this->id,
  1513. 'QuizAdded',
  1514. api_get_user_id()
  1515. );
  1516. // This function save the quiz again, carefull about start_time
  1517. // and end_time if you remove this line (see above)
  1518. api_set_default_visibility(
  1519. $this->id,
  1520. TOOL_QUIZ,
  1521. null,
  1522. $this->course
  1523. );
  1524. if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
  1525. $this->search_engine_save();
  1526. }
  1527. }
  1528. }
  1529. $this->save_categories_in_exercise($this->categories);
  1530. // Updates the question position
  1531. $this->update_question_positions();
  1532. }
  1533. /**
  1534. * Updates question position
  1535. */
  1536. public function update_question_positions()
  1537. {
  1538. $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1539. //Fixes #3483 when updating order
  1540. $question_list = $this->selectQuestionList(true);
  1541. if (!empty($question_list)) {
  1542. foreach ($question_list as $position => $questionId) {
  1543. $sql = "UPDATE $quiz_question_table SET
  1544. question_order ='".intval($position)."'
  1545. WHERE
  1546. c_id = ".$this->course_id." AND
  1547. question_id = ".intval($questionId)." AND
  1548. exercice_id=".intval($this->id);
  1549. Database::query($sql);
  1550. }
  1551. }
  1552. }
  1553. /**
  1554. * Adds a question into the question list
  1555. *
  1556. * @author Olivier Brouckaert
  1557. * @param integer $questionId - question ID
  1558. * @return boolean - true if the question has been added, otherwise false
  1559. */
  1560. public function addToList($questionId)
  1561. {
  1562. // checks if the question ID is not in the list
  1563. if (!$this->isInList($questionId)) {
  1564. // selects the max position
  1565. if (!$this->selectNbrQuestions()) {
  1566. $pos = 1;
  1567. } else {
  1568. if (is_array($this->questionList)) {
  1569. $pos = max(array_keys($this->questionList)) + 1;
  1570. }
  1571. }
  1572. $this->questionList[$pos] = $questionId;
  1573. return true;
  1574. }
  1575. return false;
  1576. }
  1577. /**
  1578. * removes a question from the question list
  1579. *
  1580. * @author Olivier Brouckaert
  1581. * @param integer $questionId - question ID
  1582. * @return boolean - true if the question has been removed, otherwise false
  1583. */
  1584. public function removeFromList($questionId)
  1585. {
  1586. // searches the position of the question ID in the list
  1587. $pos = array_search($questionId,$this->questionList);
  1588. // question not found
  1589. if ($pos === false) {
  1590. return false;
  1591. } else {
  1592. // dont reduce the number of random question if we use random by category option, or if
  1593. // random all questions
  1594. if ($this->isRandom() && $this->isRandomByCat() == 0) {
  1595. if (count($this->questionList) >= $this->random && $this->random > 0) {
  1596. $this->random -= 1;
  1597. $this->save();
  1598. }
  1599. }
  1600. // deletes the position from the array containing the wanted question ID
  1601. unset($this->questionList[$pos]);
  1602. return true;
  1603. }
  1604. }
  1605. /**
  1606. * deletes the exercise from the database
  1607. * Notice : leaves the question in the data base
  1608. *
  1609. * @author Olivier Brouckaert
  1610. */
  1611. public function delete()
  1612. {
  1613. $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
  1614. $sql = "UPDATE $TBL_EXERCISES SET active='-1'
  1615. WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
  1616. Database::query($sql);
  1617. api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id());
  1618. api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id());
  1619. if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian') ) {
  1620. $this->search_engine_delete();
  1621. }
  1622. }
  1623. /**
  1624. * Creates the form to create / edit an exercise
  1625. * @param FormValidator $form
  1626. */
  1627. public function createForm($form, $type='full')
  1628. {
  1629. if (empty($type)) {
  1630. $type = 'full';
  1631. }
  1632. // form title
  1633. if (!empty($_GET['exerciseId'])) {
  1634. $form_title = get_lang('ModifyExercise');
  1635. } else {
  1636. $form_title = get_lang('NewEx');
  1637. }
  1638. $form->addElement('header', $form_title);
  1639. // Title.
  1640. $form->addElement(
  1641. 'text',
  1642. 'exerciseTitle',
  1643. get_lang('ExerciseName'),
  1644. array('id' => 'exercise_title')
  1645. );
  1646. $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
  1647. $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
  1648. $editor_config = array(
  1649. 'ToolbarSet' => 'TestQuestionDescription',
  1650. 'Width' => '100%',
  1651. 'Height' => '150',
  1652. );
  1653. if (is_array($type)){
  1654. $editor_config = array_merge($editor_config, $type);
  1655. }
  1656. $form->addHtmlEditor(
  1657. 'exerciseDescription',
  1658. get_lang('ExerciseDescription'),
  1659. false,
  1660. false,
  1661. $editor_config
  1662. );
  1663. if ($type == 'full') {
  1664. //Can't modify a DirectFeedback question
  1665. if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
  1666. // feedback type
  1667. $radios_feedback = array();
  1668. $radios_feedback[] = $form->createElement(
  1669. 'radio',
  1670. 'exerciseFeedbackType',
  1671. null,
  1672. get_lang('ExerciseAtTheEndOfTheTest'),
  1673. '0',
  1674. array(
  1675. 'id' => 'exerciseType_0',
  1676. 'onclick' => 'check_feedback()',
  1677. )
  1678. );
  1679. if (api_get_setting('enable_quiz_scenario') == 'true') {
  1680. //Can't convert a question from one feedback to another if there is more than 1 question already added
  1681. if ($this->selectNbrQuestions() == 0) {
  1682. $radios_feedback[] = $form->createElement(
  1683. 'radio',
  1684. 'exerciseFeedbackType',
  1685. null,
  1686. get_lang('DirectFeedback'),
  1687. '1',
  1688. array(
  1689. 'id' => 'exerciseType_1',
  1690. 'onclick' => 'check_direct_feedback()',
  1691. )
  1692. );
  1693. }
  1694. }
  1695. $radios_feedback[] = $form->createElement(
  1696. 'radio',
  1697. 'exerciseFeedbackType',
  1698. null,
  1699. get_lang('NoFeedback'),
  1700. '2',
  1701. array('id' => 'exerciseType_2')
  1702. );
  1703. $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
  1704. // Type of results display on the final page
  1705. $radios_results_disabled = array();
  1706. $radios_results_disabled[] = $form->createElement(
  1707. 'radio',
  1708. 'results_disabled',
  1709. null,
  1710. get_lang('ShowScoreAndRightAnswer'),
  1711. '0',
  1712. array('id' => 'result_disabled_0')
  1713. );
  1714. $radios_results_disabled[] = $form->createElement(
  1715. 'radio',
  1716. 'results_disabled',
  1717. null,
  1718. get_lang('DoNotShowScoreNorRightAnswer'),
  1719. '1',
  1720. array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
  1721. );
  1722. $radios_results_disabled[] = $form->createElement(
  1723. 'radio',
  1724. 'results_disabled',
  1725. null,
  1726. get_lang('OnlyShowScore'),
  1727. '2',
  1728. array('id' => 'result_disabled_2')
  1729. );
  1730. $radios_results_disabled[] = $form->createElement(
  1731. 'radio',
  1732. 'results_disabled',
  1733. null,
  1734. get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
  1735. '4',
  1736. array('id' => 'result_disabled_4')
  1737. );
  1738. $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
  1739. // Type of questions disposition on page
  1740. $radios = array();
  1741. $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
  1742. $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
  1743. $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
  1744. } else {
  1745. // if is Directfeedback but has not questions we can allow to modify the question type
  1746. if ($this->selectNbrQuestions() == 0) {
  1747. // feedback type
  1748. $radios_feedback = array();
  1749. $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
  1750. if (api_get_setting('enable_quiz_scenario') == 'true') {
  1751. $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
  1752. }
  1753. $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
  1754. $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
  1755. //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
  1756. $radios_results_disabled = array();
  1757. $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
  1758. $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'), '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
  1759. $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'), '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
  1760. $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
  1761. // Type of questions disposition on page
  1762. $radios = array();
  1763. $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1');
  1764. $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
  1765. $form->addGroup($radios, null, get_lang('ExerciseType'));
  1766. } else {
  1767. //Show options freeze
  1768. $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
  1769. $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'), '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
  1770. $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'), '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
  1771. $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
  1772. $result_disable_group->freeze();
  1773. //we force the options to the DirectFeedback exercisetype
  1774. $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
  1775. $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
  1776. // Type of questions disposition on page
  1777. $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
  1778. $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
  1779. $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
  1780. $type_group->freeze();
  1781. }
  1782. }
  1783. if (true) {
  1784. $option = array(
  1785. EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
  1786. // defined by user
  1787. EX_Q_SELECTION_RANDOM => get_lang('Random'),
  1788. // 1-10, All
  1789. 'per_categories' => '--------'.get_lang(
  1790. 'UsingCategories'
  1791. ).'----------',
  1792. // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
  1793. EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang(
  1794. 'OrderedCategoriesAlphabeticallyWithQuestionsOrdered'
  1795. ),
  1796. // A 123 B 456 C 78 (0, 1, all)
  1797. EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang(
  1798. 'RandomCategoriesWithQuestionsOrdered'
  1799. ),
  1800. // C 78 B 456 A 123
  1801. EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang(
  1802. 'OrderedCategoriesAlphabeticallyWithRandomQuestions'
  1803. ),
  1804. // A 321 B 654 C 87
  1805. EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang(
  1806. 'RandomCategoriesWithRandomQuestions'
  1807. ),
  1808. //C 87 B 654 A 321
  1809. //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
  1810. /* B 456 C 78 A 123
  1811. 456 78 123
  1812. 123 456 78
  1813. */
  1814. //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
  1815. /*
  1816. A 123 B 456 C 78
  1817. B 456 C 78 A 123
  1818. B 654 C 87 A 321
  1819. 654 87 321
  1820. 165 842 73
  1821. */
  1822. //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
  1823. //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
  1824. );
  1825. $form->addElement(
  1826. 'select',
  1827. 'question_selection_type',
  1828. array(get_lang('QuestionSelection')),
  1829. $option,
  1830. array(
  1831. 'id' => 'questionSelection',
  1832. 'onclick' => 'checkQuestionSelection()'
  1833. )
  1834. );
  1835. $displayMatrix = 'none';
  1836. $displayRandom = 'none';
  1837. $selectionType = $this->getQuestionSelectionType();
  1838. switch ($selectionType) {
  1839. case EX_Q_SELECTION_RANDOM:
  1840. $displayRandom = 'block';
  1841. break;
  1842. case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
  1843. $displayMatrix = 'block';
  1844. break;
  1845. }
  1846. $form->addElement(
  1847. 'html',
  1848. '<div id="hidden_random" style="display:'.$displayRandom.'">'
  1849. );
  1850. // Number of random question.
  1851. $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
  1852. $option = range(0, $max);
  1853. $option[0] = get_lang('No');
  1854. $option[-1] = get_lang('AllQuestionsShort');
  1855. $form->addElement(
  1856. 'select',
  1857. 'randomQuestions',
  1858. array(
  1859. get_lang('RandomQuestions'),
  1860. get_lang('RandomQuestionsHelp')
  1861. ),
  1862. $option,
  1863. array('id' => 'randomQuestions')
  1864. );
  1865. $form->addElement('html', '</div>');
  1866. $form->addElement(
  1867. 'html',
  1868. '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
  1869. );
  1870. // Category selection.
  1871. $cat = new TestCategory();
  1872. $cat_form = $cat->returnCategoryForm($this);
  1873. $form->addElement('label', null, $cat_form);
  1874. $form->addElement('html', '</div>');
  1875. // Category name.
  1876. $radio_display_cat_name = array(
  1877. $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
  1878. $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
  1879. );
  1880. $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
  1881. // Random answers.
  1882. $radios_random_answers = array(
  1883. $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
  1884. $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
  1885. );
  1886. $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
  1887. // Hide question title.
  1888. $group = array(
  1889. $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
  1890. $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
  1891. );
  1892. $form->addGroup($group, null, get_lang('HideQuestionTitle'));
  1893. } else {
  1894. // number of random question
  1895. /*
  1896. $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10 ;
  1897. $option = range(0, $max);
  1898. $option[0] = get_lang('No');
  1899. $option[-1] = get_lang('AllQuestionsShort');
  1900. $form->addElement('select', 'randomQuestions',array(get_lang('RandomQuestions'), get_lang('RandomQuestionsHelp')), $option, array('id'=>'randomQuestions'));
  1901. // Random answers
  1902. $radios_random_answers = array();
  1903. $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'),'1');
  1904. $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('No'),'0');
  1905. $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
  1906. // Random by category
  1907. $form->addElement('html','<div class="clear">&nbsp;</div>');
  1908. $radiocat = array();
  1909. $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesShuffled'),'1');
  1910. $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesSorted'),'2');
  1911. $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('No'),'0');
  1912. $radioCatGroup = $form->addGroup($radiocat, null, get_lang('RandomQuestionByCategory'), '');
  1913. $form->addElement('html','<div class="clear">&nbsp;</div>');
  1914. // add the radio display the category name for student
  1915. $radio_display_cat_name = array();
  1916. $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1');
  1917. $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0');
  1918. $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');*/
  1919. }
  1920. // Attempts
  1921. $attempt_option = range(0, 10);
  1922. $attempt_option[0] = get_lang('Infinite');
  1923. $form->addElement(
  1924. 'select',
  1925. 'exerciseAttempts',
  1926. get_lang('ExerciseAttempts'),
  1927. $attempt_option,
  1928. ['id' => 'exerciseAttempts']
  1929. );
  1930. // Exercise time limit
  1931. $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
  1932. $var = Exercise::selectTimeLimit();
  1933. if (!empty($this->start_time)) {
  1934. $form->addElement('html', '<div id="start_date_div" style="display:block;">');
  1935. } else {
  1936. $form->addElement('html', '<div id="start_date_div" style="display:none;">');
  1937. }
  1938. $form->addElement('date_time_picker', 'start_time');
  1939. $form->addElement('html','</div>');
  1940. $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
  1941. if (!empty($this->end_time)) {
  1942. $form->addElement('html', '<div id="end_date_div" style="display:block;">');
  1943. } else {
  1944. $form->addElement('html', '<div id="end_date_div" style="display:none;">');
  1945. }
  1946. $form->addElement('date_time_picker', 'end_time');
  1947. $form->addElement('html','</div>');
  1948. //$check_option=$this->selectType();
  1949. $diplay = 'block';
  1950. $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
  1951. $form->addCheckBox(
  1952. 'save_correct_answers',
  1953. null,
  1954. get_lang('SaveTheCorrectAnswersForTheNextAttempt')
  1955. );
  1956. $form->addElement('html','<div class="clear">&nbsp;</div>');
  1957. $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
  1958. $form->addElement('html','<div id="divtimecontrol" style="display:'.$diplay.';">');
  1959. //Timer control
  1960. //$time_hours_option = range(0,12);
  1961. //$time_minutes_option = range(0,59);
  1962. $form->addElement(
  1963. 'checkbox',
  1964. 'enabletimercontrol',
  1965. null,
  1966. get_lang('EnableTimerControl'),
  1967. array(
  1968. 'onclick' => 'option_time_expired()',
  1969. 'id' => 'enabletimercontrol',
  1970. 'onload' => 'check_load_time()',
  1971. )
  1972. );
  1973. $expired_date = (int)$this->selectExpiredTime();
  1974. if (($expired_date!='0')) {
  1975. $form->addElement('html','<div id="timercontrol" style="display:block;">');
  1976. } else {
  1977. $form->addElement('html','<div id="timercontrol" style="display:none;">');
  1978. }
  1979. $form->addText(
  1980. 'enabletimercontroltotalminutes',
  1981. get_lang('ExerciseTotalDurationInMinutes'),
  1982. false,
  1983. [
  1984. 'id' => 'enabletimercontroltotalminutes',
  1985. 'cols-size' => [2, 2, 8]
  1986. ]
  1987. );
  1988. $form->addElement('html','</div>');
  1989. $form->addElement(
  1990. 'text',
  1991. 'pass_percentage',
  1992. array(get_lang('PassPercentage'), null, '%'),
  1993. array('id' => 'pass_percentage')
  1994. );
  1995. $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
  1996. $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
  1997. $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
  1998. // add the text_when_finished textbox
  1999. $form->addHtmlEditor(
  2000. 'text_when_finished',
  2001. get_lang('TextWhenFinished'),
  2002. false,
  2003. false,
  2004. $editor_config
  2005. );
  2006. $defaults = array();
  2007. if (api_get_setting('search_enabled') === 'true') {
  2008. require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
  2009. $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
  2010. $form->addElement('select_language', 'language', get_lang('SearchFeatureDocumentLanguage'));
  2011. $specific_fields = get_specific_field_list();
  2012. foreach ($specific_fields as $specific_field) {
  2013. $form->addElement ('text', $specific_field['code'], $specific_field['name']);
  2014. $filter = array(
  2015. 'c_id' => api_get_course_int_id(),
  2016. 'field_id' => $specific_field['id'],
  2017. 'ref_id' => $this->id,
  2018. 'tool_id' => "'" . TOOL_QUIZ . "'"
  2019. );
  2020. $values = get_specific_field_values_list($filter, array('value'));
  2021. if ( !empty($values) ) {
  2022. $arr_str_values = array();
  2023. foreach ($values as $value) {
  2024. $arr_str_values[] = $value['value'];
  2025. }
  2026. $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
  2027. }
  2028. }
  2029. }
  2030. $form->addElement('html','</div>'); //End advanced setting
  2031. $form->addElement('html','</div>');
  2032. }
  2033. // submit
  2034. if (isset($_GET['exerciseId'])) {
  2035. $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
  2036. } else {
  2037. $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
  2038. }
  2039. $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
  2040. if ($type == 'full') {
  2041. // rules
  2042. $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
  2043. $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
  2044. $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
  2045. }
  2046. // defaults
  2047. if ($type=='full') {
  2048. if ($this->id > 0) {
  2049. if ($this->random > $this->selectNbrQuestions()) {
  2050. $defaults['randomQuestions'] = $this->selectNbrQuestions();
  2051. } else {
  2052. $defaults['randomQuestions'] = $this->random;
  2053. }
  2054. $defaults['randomAnswers'] = $this->selectRandomAnswers();
  2055. $defaults['exerciseType'] = $this->selectType();
  2056. $defaults['exerciseTitle'] = $this->get_formated_title();
  2057. $defaults['exerciseDescription'] = $this->selectDescription();
  2058. $defaults['exerciseAttempts'] = $this->selectAttempts();
  2059. $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
  2060. $defaults['results_disabled'] = $this->selectResultsDisabled();
  2061. $defaults['propagate_neg'] = $this->selectPropagateNeg();
  2062. $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
  2063. $defaults['review_answers'] = $this->review_answers;
  2064. $defaults['randomByCat'] = $this->selectRandomByCat();
  2065. $defaults['text_when_finished'] = $this->selectTextWhenFinished();
  2066. $defaults['display_category_name'] = $this->selectDisplayCategoryName();
  2067. $defaults['pass_percentage'] = $this->selectPassPercentage();
  2068. $defaults['question_selection_type'] = $this->getQuestionSelectionType();
  2069. if (!empty($this->start_time)) {
  2070. $defaults['activate_start_date_check'] = 1;
  2071. }
  2072. if (!empty($this->end_time)) {
  2073. $defaults['activate_end_date_check'] = 1;
  2074. }
  2075. $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
  2076. $defaults['end_time'] = empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
  2077. // Get expired time
  2078. if ($this->expired_time != '0') {
  2079. $defaults['enabletimercontrol'] = 1;
  2080. $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
  2081. } else {
  2082. $defaults['enabletimercontroltotalminutes'] = 0;
  2083. }
  2084. } else {
  2085. $defaults['exerciseType'] = 2;
  2086. $defaults['exerciseAttempts'] = 0;
  2087. $defaults['randomQuestions'] = 0;
  2088. $defaults['randomAnswers'] = 0;
  2089. $defaults['exerciseDescription'] = '';
  2090. $defaults['exerciseFeedbackType'] = 0;
  2091. $defaults['results_disabled'] = 0;
  2092. $defaults['randomByCat'] = 0;
  2093. $defaults['text_when_finished'] = '';
  2094. $defaults['start_time'] = date('Y-m-d 12:00:00');
  2095. $defaults['display_category_name'] = 1;
  2096. $defaults['end_time'] = date('Y-m-d 12:00:00', time()+84600);
  2097. $defaults['pass_percentage'] = '';
  2098. $defaults['end_button'] = $this->selectEndButton();
  2099. $defaults['question_selection_type'] = 1;
  2100. $defaults['hide_question_title'] = 0;
  2101. $defaults['on_success_message'] = null;
  2102. $defaults['on_failed_message'] = null;
  2103. }
  2104. } else {
  2105. $defaults['exerciseTitle'] = $this->selectTitle();
  2106. $defaults['exerciseDescription'] = $this->selectDescription();
  2107. }
  2108. if (api_get_setting('search_enabled') === 'true') {
  2109. $defaults['index_document'] = 'checked="checked"';
  2110. }
  2111. $form->setDefaults($defaults);
  2112. // Freeze some elements.
  2113. if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
  2114. $elementsToFreeze = array(
  2115. 'randomQuestions',
  2116. //'randomByCat',
  2117. 'exerciseAttempts',
  2118. 'propagate_neg',
  2119. 'enabletimercontrol',
  2120. 'review_answers'
  2121. );
  2122. foreach ($elementsToFreeze as $elementName) {
  2123. /** @var HTML_QuickForm_element $element */
  2124. $element = $form->getElement($elementName);
  2125. $element->freeze();
  2126. }
  2127. //$radioCatGroup->freeze();
  2128. }
  2129. }
  2130. /**
  2131. * function which process the creation of exercises
  2132. * @param FormValidator $form
  2133. * @param string
  2134. */
  2135. public function processCreation($form, $type = '')
  2136. {
  2137. $this->updateTitle(Exercise::format_title_variable($form->getSubmitValue('exerciseTitle')));
  2138. $this->updateDescription($form->getSubmitValue('exerciseDescription'));
  2139. $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
  2140. $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
  2141. $this->updateType($form->getSubmitValue('exerciseType'));
  2142. $this->setRandom($form->getSubmitValue('randomQuestions'));
  2143. $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
  2144. $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
  2145. $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
  2146. $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
  2147. $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
  2148. $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
  2149. $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
  2150. $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
  2151. $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
  2152. $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
  2153. $this->updateCategories($form->getSubmitValue('category'));
  2154. $this->updateEndButton($form->getSubmitValue('end_button'));
  2155. $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
  2156. $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
  2157. $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
  2158. $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
  2159. $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
  2160. $this->setModelType($form->getSubmitValue('model_type'));
  2161. $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
  2162. $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
  2163. $this->sessionId = api_get_session_id();
  2164. $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
  2165. $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
  2166. $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
  2167. if ($form->getSubmitValue('activate_start_date_check') == 1) {
  2168. $start_time = $form->getSubmitValue('start_time');
  2169. $this->start_time = api_get_utc_datetime($start_time);
  2170. } else {
  2171. $this->start_time = null;
  2172. }
  2173. if ($form->getSubmitValue('activate_end_date_check') == 1) {
  2174. $end_time = $form->getSubmitValue('end_time');
  2175. $this->end_time = api_get_utc_datetime($end_time);
  2176. } else {
  2177. $this->end_time = null;
  2178. }
  2179. if ($form->getSubmitValue('enabletimercontrol') == 1) {
  2180. $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
  2181. if ($this->expired_time == 0) {
  2182. $this->expired_time = $expired_total_time;
  2183. }
  2184. } else {
  2185. $this->expired_time = 0;
  2186. }
  2187. if ($form->getSubmitValue('randomAnswers') == 1) {
  2188. $this->random_answers=1;
  2189. } else {
  2190. $this->random_answers=0;
  2191. }
  2192. $this->save($type);
  2193. }
  2194. function search_engine_save()
  2195. {
  2196. if ($_POST['index_document'] != 1) {
  2197. return;
  2198. }
  2199. $course_id = api_get_course_id();
  2200. require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
  2201. require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
  2202. require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
  2203. $specific_fields = get_specific_field_list();
  2204. $ic_slide = new IndexableChunk();
  2205. $all_specific_terms = '';
  2206. foreach ($specific_fields as $specific_field) {
  2207. if (isset($_REQUEST[$specific_field['code']])) {
  2208. $sterms = trim($_REQUEST[$specific_field['code']]);
  2209. if (!empty($sterms)) {
  2210. $all_specific_terms .= ' '. $sterms;
  2211. $sterms = explode(',', $sterms);
  2212. foreach ($sterms as $sterm) {
  2213. $ic_slide->addTerm(trim($sterm), $specific_field['code']);
  2214. add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
  2215. }
  2216. }
  2217. }
  2218. }
  2219. // build the chunk to index
  2220. $ic_slide->addValue("title", $this->exercise);
  2221. $ic_slide->addCourseId($course_id);
  2222. $ic_slide->addToolId(TOOL_QUIZ);
  2223. $xapian_data = array(
  2224. SE_COURSE_ID => $course_id,
  2225. SE_TOOL_ID => TOOL_QUIZ,
  2226. SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
  2227. SE_USER => (int)api_get_user_id(),
  2228. );
  2229. $ic_slide->xapian_data = serialize($xapian_data);
  2230. $exercise_description = $all_specific_terms .' '. $this->description;
  2231. $ic_slide->addValue("content", $exercise_description);
  2232. $di = new ChamiloIndexer();
  2233. isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
  2234. $di->connectDb(NULL, NULL, $lang);
  2235. $di->addChunk($ic_slide);
  2236. //index and return search engine document id
  2237. $did = $di->index();
  2238. if ($did) {
  2239. // save it to db
  2240. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  2241. $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
  2242. VALUES (NULL , \'%s\', \'%s\', %s, %s)';
  2243. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
  2244. Database::query($sql);
  2245. }
  2246. }
  2247. function search_engine_edit()
  2248. {
  2249. // update search enchine and its values table if enabled
  2250. if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
  2251. $course_id = api_get_course_id();
  2252. // actually, it consists on delete terms from db,
  2253. // insert new ones, create a new search engine document, and remove the old one
  2254. // get search_did
  2255. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  2256. $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
  2257. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  2258. $res = Database::query($sql);
  2259. if (Database::num_rows($res) > 0) {
  2260. require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
  2261. require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
  2262. require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
  2263. $se_ref = Database::fetch_array($res);
  2264. $specific_fields = get_specific_field_list();
  2265. $ic_slide = new IndexableChunk();
  2266. $all_specific_terms = '';
  2267. foreach ($specific_fields as $specific_field) {
  2268. delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
  2269. if (isset($_REQUEST[$specific_field['code']])) {
  2270. $sterms = trim($_REQUEST[$specific_field['code']]);
  2271. $all_specific_terms .= ' '. $sterms;
  2272. $sterms = explode(',', $sterms);
  2273. foreach ($sterms as $sterm) {
  2274. $ic_slide->addTerm(trim($sterm), $specific_field['code']);
  2275. add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
  2276. }
  2277. }
  2278. }
  2279. // build the chunk to index
  2280. $ic_slide->addValue("title", $this->exercise);
  2281. $ic_slide->addCourseId($course_id);
  2282. $ic_slide->addToolId(TOOL_QUIZ);
  2283. $xapian_data = array(
  2284. SE_COURSE_ID => $course_id,
  2285. SE_TOOL_ID => TOOL_QUIZ,
  2286. SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
  2287. SE_USER => (int)api_get_user_id(),
  2288. );
  2289. $ic_slide->xapian_data = serialize($xapian_data);
  2290. $exercise_description = $all_specific_terms .' '. $this->description;
  2291. $ic_slide->addValue("content", $exercise_description);
  2292. $di = new ChamiloIndexer();
  2293. isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
  2294. $di->connectDb(NULL, NULL, $lang);
  2295. $di->remove_document((int)$se_ref['search_did']);
  2296. $di->addChunk($ic_slide);
  2297. //index and return search engine document id
  2298. $did = $di->index();
  2299. if ($did) {
  2300. // save it to db
  2301. $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
  2302. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  2303. Database::query($sql);
  2304. $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
  2305. VALUES (NULL , \'%s\', \'%s\', %s, %s)';
  2306. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
  2307. Database::query($sql);
  2308. }
  2309. } else {
  2310. $this->search_engine_save();
  2311. }
  2312. }
  2313. }
  2314. function search_engine_delete()
  2315. {
  2316. // remove from search engine if enabled
  2317. if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) {
  2318. $course_id = api_get_course_id();
  2319. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  2320. $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL LIMIT 1';
  2321. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  2322. $res = Database::query($sql);
  2323. if (Database::num_rows($res) > 0) {
  2324. $row = Database::fetch_array($res);
  2325. require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
  2326. $di = new ChamiloIndexer();
  2327. $di->remove_document((int)$row['search_did']);
  2328. unset($di);
  2329. $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  2330. foreach ( $this->questionList as $question_i) {
  2331. $sql = 'SELECT type FROM %s WHERE id=%s';
  2332. $sql = sprintf($sql, $tbl_quiz_question, $question_i);
  2333. $qres = Database::query($sql);
  2334. if (Database::num_rows($qres) > 0) {
  2335. $qrow = Database::fetch_array($qres);
  2336. $objQuestion = Question::getInstance($qrow['type']);
  2337. $objQuestion = Question::read((int)$question_i);
  2338. $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
  2339. unset($objQuestion);
  2340. }
  2341. }
  2342. }
  2343. $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL LIMIT 1';
  2344. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  2345. Database::query($sql);
  2346. // remove terms from db
  2347. require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
  2348. delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
  2349. }
  2350. }
  2351. public function selectExpiredTime()
  2352. {
  2353. return $this->expired_time;
  2354. }
  2355. /**
  2356. * Cleans the student's results only for the Exercise tool (Not from the LP)
  2357. * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
  2358. * Works with exercises in sessions
  2359. * @param bool $cleanLpTests
  2360. * @param string $cleanResultBeforeDate
  2361. *
  2362. * @return int quantity of user's exercises deleted
  2363. */
  2364. public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
  2365. {
  2366. $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  2367. $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
  2368. $sql_where = ' AND
  2369. orig_lp_id = 0 AND
  2370. orig_lp_item_id = 0';
  2371. // if we want to delete results from LP too
  2372. if ($cleanLpTests) {
  2373. $sql_where = "";
  2374. }
  2375. // if we want to delete attempts before date $cleanResultBeforeDate
  2376. // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
  2377. if (!empty($cleanResultBeforeDate)) {
  2378. $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
  2379. if (api_is_valid_date($cleanResultBeforeDate)) {
  2380. $sql_where .= " AND exe_date <= '$cleanResultBeforeDate' ";
  2381. } else {
  2382. return 0;
  2383. }
  2384. }
  2385. $sql = "SELECT exe_id
  2386. FROM $table_track_e_exercises
  2387. WHERE
  2388. c_id = ".api_get_course_int_id()." AND
  2389. exe_exo_id = ".$this->id." AND
  2390. session_id = ".api_get_session_id()." ".
  2391. $sql_where;
  2392. $result = Database::query($sql);
  2393. $exe_list = Database::store_result($result);
  2394. // deleting TRACK_E_ATTEMPT table
  2395. // check if exe in learning path or not
  2396. $i = 0;
  2397. if (is_array($exe_list) && count($exe_list) > 0) {
  2398. foreach ($exe_list as $item) {
  2399. $sql = "DELETE FROM $table_track_e_attempt
  2400. WHERE exe_id = '".$item['exe_id']."'";
  2401. Database::query($sql);
  2402. $i++;
  2403. }
  2404. }
  2405. $session_id = api_get_session_id();
  2406. // delete TRACK_E_EXERCISES table
  2407. $sql = "DELETE FROM $table_track_e_exercises
  2408. WHERE c_id = ".api_get_course_int_id()."
  2409. AND exe_exo_id = ".$this->id."
  2410. $sql_where
  2411. AND session_id = ".$session_id."";
  2412. Database::query($sql);
  2413. Event::addEvent(
  2414. LOG_EXERCISE_RESULT_DELETE,
  2415. LOG_EXERCISE_ID,
  2416. $this->id,
  2417. null,
  2418. null,
  2419. api_get_course_int_id(),
  2420. $session_id
  2421. );
  2422. return $i;
  2423. }
  2424. /**
  2425. * Copies an exercise (duplicate all questions and answers)
  2426. */
  2427. public function copy_exercise()
  2428. {
  2429. $exercise_obj = $this;
  2430. // force the creation of a new exercise
  2431. $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
  2432. //Hides the new exercise
  2433. $exercise_obj->updateStatus(false);
  2434. $exercise_obj->updateId(0);
  2435. $exercise_obj->save();
  2436. $new_exercise_id = $exercise_obj->selectId();
  2437. $question_list = $exercise_obj->selectQuestionList();
  2438. if (!empty($question_list)) {
  2439. //Question creation
  2440. foreach ($question_list as $old_question_id) {
  2441. $old_question_obj = Question::read($old_question_id);
  2442. $new_id = $old_question_obj->duplicate();
  2443. if ($new_id) {
  2444. $new_question_obj = Question::read($new_id);
  2445. if (isset($new_question_obj) && $new_question_obj) {
  2446. $new_question_obj->addToList($new_exercise_id);
  2447. // This should be moved to the duplicate function
  2448. $new_answer_obj = new Answer($old_question_id);
  2449. $new_answer_obj->read();
  2450. $new_answer_obj->duplicate($new_id);
  2451. }
  2452. }
  2453. }
  2454. }
  2455. }
  2456. /**
  2457. * Changes the exercise id
  2458. *
  2459. * @param int $id - exercise id
  2460. */
  2461. private function updateId($id)
  2462. {
  2463. $this->id = $id;
  2464. }
  2465. /**
  2466. * Changes the exercise status
  2467. *
  2468. * @param string $status - exercise status
  2469. */
  2470. function updateStatus($status)
  2471. {
  2472. $this->active = $status;
  2473. }
  2474. /**
  2475. * @param int $lp_id
  2476. * @param int $lp_item_id
  2477. * @param int $lp_item_view_id
  2478. * @param string $status
  2479. * @return array
  2480. */
  2481. public function get_stat_track_exercise_info(
  2482. $lp_id = 0,
  2483. $lp_item_id = 0,
  2484. $lp_item_view_id = 0,
  2485. $status = 'incomplete'
  2486. ) {
  2487. $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  2488. if (empty($lp_id)) {
  2489. $lp_id = 0;
  2490. }
  2491. if (empty($lp_item_id)) {
  2492. $lp_item_id = 0;
  2493. }
  2494. if (empty($lp_item_view_id)) {
  2495. $lp_item_view_id = 0;
  2496. }
  2497. $condition = ' WHERE exe_exo_id = ' . "'" . $this->id . "'" .' AND
  2498. exe_user_id = ' . "'" . api_get_user_id() . "'" . ' AND
  2499. c_id = ' . api_get_course_int_id() . ' AND
  2500. status = ' . "'" . Database::escape_string($status). "'" . ' AND
  2501. orig_lp_id = ' . "'" . $lp_id . "'" . ' AND
  2502. orig_lp_item_id = ' . "'" . $lp_item_id . "'" . ' AND
  2503. orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
  2504. session_id = ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
  2505. $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
  2506. $result = Database::query($sql_track);
  2507. $new_array = array();
  2508. if (Database::num_rows($result) > 0 ) {
  2509. $new_array = Database::fetch_array($result, 'ASSOC');
  2510. $new_array['num_exe'] = Database::num_rows($result);
  2511. }
  2512. return $new_array;
  2513. }
  2514. /**
  2515. * Saves a test attempt
  2516. *
  2517. * @param int clock_expired_time
  2518. * @param int int lp id
  2519. * @param int int lp item id
  2520. * @param int int lp item_view id
  2521. * @param float $weight
  2522. * @param array question list
  2523. */
  2524. public function save_stat_track_exercise_info(
  2525. $clock_expired_time = 0,
  2526. $safe_lp_id = 0,
  2527. $safe_lp_item_id = 0,
  2528. $safe_lp_item_view_id = 0,
  2529. $questionList = array(),
  2530. $weight = 0
  2531. ) {
  2532. $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  2533. $safe_lp_id = intval($safe_lp_id);
  2534. $safe_lp_item_id = intval($safe_lp_item_id);
  2535. $safe_lp_item_view_id = intval($safe_lp_item_view_id);
  2536. if (empty($safe_lp_id)) {
  2537. $safe_lp_id = 0;
  2538. }
  2539. if (empty($safe_lp_item_id)) {
  2540. $safe_lp_item_id = 0;
  2541. }
  2542. if (empty($clock_expired_time)) {
  2543. $clock_expired_time = null;
  2544. }
  2545. $questionList = array_map('intval', $questionList);
  2546. $params = array(
  2547. 'exe_exo_id' => $this->id ,
  2548. 'exe_user_id' => api_get_user_id(),
  2549. 'c_id' => api_get_course_int_id(),
  2550. 'status' => 'incomplete',
  2551. 'session_id' => api_get_session_id(),
  2552. 'data_tracking' => implode(',', $questionList) ,
  2553. 'start_date' => api_get_utc_datetime(),
  2554. 'orig_lp_id' => $safe_lp_id,
  2555. 'orig_lp_item_id' => $safe_lp_item_id,
  2556. 'orig_lp_item_view_id' => $safe_lp_item_view_id,
  2557. 'exe_weighting'=> $weight,
  2558. 'user_ip' => api_get_real_ip(),
  2559. 'exe_date' => api_get_utc_datetime(),
  2560. 'exe_result' => 0,
  2561. 'steps_counter' => 0,
  2562. 'exe_duration' => 0,
  2563. 'expired_time_control' => $clock_expired_time,
  2564. 'questions_to_check' => ''
  2565. );
  2566. $id = Database::insert($track_exercises, $params);
  2567. return $id;
  2568. }
  2569. /**
  2570. * @param int $question_id
  2571. * @param int $questionNum
  2572. * @param array $questions_in_media
  2573. * @param string $currentAnswer
  2574. * @return string
  2575. */
  2576. public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
  2577. {
  2578. global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
  2579. $nbrQuestions = $this->get_count_question_list();
  2580. $all_button = $html = $label = '';
  2581. $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
  2582. if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
  2583. $urlTitle = get_lang('ContinueTest');
  2584. if ($questionNum == count($this->questionList)) {
  2585. $urlTitle = get_lang('EndTest');
  2586. }
  2587. $html .= Display::url(
  2588. $urlTitle,
  2589. 'exercise_submit_modal.php?' . http_build_query([
  2590. 'learnpath_id' => $safe_lp_id,
  2591. 'learnpath_item_id' => $safe_lp_item_id,
  2592. 'learnpath_item_view_id' => $safe_lp_item_view_id,
  2593. 'origin' => $origin,
  2594. 'hotspot' => $hotspot_get,
  2595. 'nbrQuestions' => $nbrQuestions,
  2596. 'num' => $questionNum,
  2597. 'exerciseType' => $this->type,
  2598. 'exerciseId' => $this->id
  2599. ]),
  2600. [
  2601. 'class' => 'ajax btn btn-default',
  2602. 'data-title' => $urlTitle,
  2603. 'data-size' => 'md'
  2604. ]
  2605. );
  2606. $html .='<br />';
  2607. } else {
  2608. // User
  2609. if (api_is_allowed_to_session_edit()) {
  2610. if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
  2611. if ($this->review_answers) {
  2612. $label = get_lang('ReviewQuestions');
  2613. $class = 'btn btn-success';
  2614. } else {
  2615. $label = get_lang('EndTest');
  2616. $class = 'btn btn-warning';
  2617. }
  2618. } else {
  2619. $label = get_lang('NextQuestion');
  2620. $class = 'btn btn-primary';
  2621. }
  2622. $class .= ' question-validate-btn'; // used to select it with jquery
  2623. if ($this->type == ONE_PER_PAGE) {
  2624. if ($questionNum != 1) {
  2625. $prev_question = $questionNum - 2;
  2626. $all_button .= '<a href="javascript://" class="btn btn-default" onclick="previous_question_and_save('.$prev_question.', '.$question_id.' ); ">'.get_lang('PreviousQuestion').'</a>';
  2627. }
  2628. //Next question
  2629. if (!empty($questions_in_media)) {
  2630. $questions_in_media = "['".implode("','",$questions_in_media)."']";
  2631. $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
  2632. } else {
  2633. $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
  2634. }
  2635. $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
  2636. $html .= $all_button;
  2637. } else {
  2638. if ($this->review_answers) {
  2639. $all_label = get_lang('ReviewQuestions');
  2640. $class = 'btn btn-success';
  2641. } else {
  2642. $all_label = get_lang('EndTest');
  2643. $class = 'btn btn-warning';
  2644. }
  2645. $class .= ' question-validate-btn'; // used to select it with jquery
  2646. $all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
  2647. $all_button .= '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
  2648. $html .= $all_button;
  2649. }
  2650. }
  2651. }
  2652. return $html;
  2653. }
  2654. /**
  2655. * So the time control will work
  2656. *
  2657. * @param string $time_left
  2658. * @return string
  2659. */
  2660. public function show_time_control_js($time_left)
  2661. {
  2662. $time_left = intval($time_left);
  2663. return "<script>
  2664. function get_expired_date_string(expired_time) {
  2665. var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  2666. var day, month, year, hours, minutes, seconds, date_string;
  2667. var obj_date = new Date(expired_time);
  2668. day = obj_date.getDate();
  2669. if (day < 10) day = '0' + day;
  2670. month = obj_date.getMonth();
  2671. year = obj_date.getFullYear();
  2672. hours = obj_date.getHours();
  2673. if (hours < 10) hours = '0' + hours;
  2674. minutes = obj_date.getMinutes();
  2675. if (minutes < 10) minutes = '0' + minutes;
  2676. seconds = obj_date.getSeconds();
  2677. if (seconds < 10) seconds = '0' + seconds;
  2678. date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
  2679. return date_string;
  2680. }
  2681. function open_clock_warning() {
  2682. $('#clock_warning').dialog({
  2683. modal:true,
  2684. height:250,
  2685. closeOnEscape: false,
  2686. resizable: false,
  2687. buttons: {
  2688. '".addslashes(get_lang("EndTest"))."': function() {
  2689. $('#clock_warning').dialog('close');
  2690. }
  2691. },
  2692. close: function() {
  2693. send_form();
  2694. }
  2695. });
  2696. $('#clock_warning').dialog('open');
  2697. $('#counter_to_redirect').epiclock({
  2698. mode: $.epiclock.modes.countdown,
  2699. offset: {seconds: 5},
  2700. format: 's'
  2701. }).bind('timer', function () {
  2702. send_form();
  2703. });
  2704. }
  2705. function send_form() {
  2706. if ($('#exercise_form').length) {
  2707. $('#exercise_form').submit();
  2708. } else {
  2709. //In reminder
  2710. final_submit();
  2711. }
  2712. }
  2713. function onExpiredTimeExercise() {
  2714. $('#wrapper-clock').hide();
  2715. $('#exercise_form').hide();
  2716. $('#expired-message-id').show();
  2717. //Fixes bug #5263
  2718. $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
  2719. open_clock_warning();
  2720. }
  2721. $(document).ready(function() {
  2722. var current_time = new Date().getTime();
  2723. var time_left = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
  2724. var expired_time = current_time + (time_left*1000);
  2725. var expired_date = get_expired_date_string(expired_time);
  2726. $('#exercise_clock_warning').epiclock({
  2727. mode: $.epiclock.modes.countdown,
  2728. offset: {seconds: time_left},
  2729. format: 'x:i:s',
  2730. renderer: 'minute'
  2731. }).bind('timer', function () {
  2732. onExpiredTimeExercise();
  2733. });
  2734. $('#submit_save').click(function () {});
  2735. });
  2736. </script>";
  2737. }
  2738. /**
  2739. * Lp javascript for hotspots
  2740. */
  2741. public function show_lp_javascript()
  2742. {
  2743. return '';
  2744. }
  2745. /**
  2746. * This function was originally found in the exercise_show.php
  2747. * @param int $exeId
  2748. * @param int $questionId
  2749. * @param int $choice the user selected
  2750. * @param string $from function is called from 'exercise_show' or 'exercise_result'
  2751. * @param array $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
  2752. * @param bool $saved_results save results in the DB or just show the reponse
  2753. * @param bool $from_database gets information from DB or from the current selection
  2754. * @param bool $show_result show results or not
  2755. * @param int $propagate_neg
  2756. * @param array $hotspot_delineation_result
  2757. * @param boolean $showTotalScoreAndUserChoices
  2758. * @todo reduce parameters of this function
  2759. * @return string html code
  2760. */
  2761. public function manage_answer(
  2762. $exeId,
  2763. $questionId,
  2764. $choice,
  2765. $from = 'exercise_show',
  2766. $exerciseResultCoordinates = array(),
  2767. $saved_results = true,
  2768. $from_database = false,
  2769. $show_result = true,
  2770. $propagate_neg = 0,
  2771. $hotspot_delineation_result = array(),
  2772. $showTotalScoreAndUserChoices = false
  2773. ) {
  2774. global $debug;
  2775. //needed in order to use in the exercise_attempt() for the time
  2776. global $learnpath_id, $learnpath_item_id;
  2777. require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
  2778. $em = Database::getManager();
  2779. $feedback_type = $this->selectFeedbackType();
  2780. $results_disabled = $this->selectResultsDisabled();
  2781. if ($debug) {
  2782. error_log("<------ manage_answer ------> ");
  2783. error_log('exe_id: '.$exeId);
  2784. error_log('$from: '.$from);
  2785. error_log('$saved_results: '.intval($saved_results));
  2786. error_log('$from_database: '.intval($from_database));
  2787. error_log('$show_result: '.$show_result);
  2788. error_log('$propagate_neg: '.$propagate_neg);
  2789. error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
  2790. error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
  2791. error_log('$learnpath_id: '.$learnpath_id);
  2792. error_log('$learnpath_item_id: '.$learnpath_item_id);
  2793. error_log('$choice: '.print_r($choice, 1));
  2794. }
  2795. $extra_data = array();
  2796. $final_overlap = 0;
  2797. $final_missing = 0;
  2798. $final_excess = 0;
  2799. $overlap_color = 0;
  2800. $missing_color = 0;
  2801. $excess_color = 0;
  2802. $threadhold1 = 0;
  2803. $threadhold2 = 0;
  2804. $threadhold3 = 0;
  2805. $arrques = null;
  2806. $arrans = null;
  2807. $questionId = intval($questionId);
  2808. $exeId = intval($exeId);
  2809. $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
  2810. $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
  2811. // Creates a temporary Question object
  2812. $course_id = $this->course_id;
  2813. $objQuestionTmp = Question::read($questionId, $course_id);
  2814. if ($objQuestionTmp === false) {
  2815. return false;
  2816. }
  2817. $questionName = $objQuestionTmp->selectTitle();
  2818. $questionWeighting = $objQuestionTmp->selectWeighting();
  2819. $answerType = $objQuestionTmp->selectType();
  2820. $quesId = $objQuestionTmp->selectId();
  2821. $extra = $objQuestionTmp->extra;
  2822. $next = 1; //not for now
  2823. // Extra information of the question
  2824. if (!empty($extra)) {
  2825. $extra = explode(':', $extra);
  2826. if ($debug) {
  2827. error_log(print_r($extra, 1));
  2828. }
  2829. // Fixes problems with negatives values using intval
  2830. $true_score = floatval(trim($extra[0]));
  2831. $false_score = floatval(trim($extra[1]));
  2832. $doubt_score = floatval(trim($extra[2]));
  2833. }
  2834. $totalWeighting = 0;
  2835. $totalScore = 0;
  2836. // Destruction of the Question object
  2837. //unset($objQuestionTmp);
  2838. // Construction of the Answer object
  2839. $objAnswerTmp = new Answer($questionId);
  2840. $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
  2841. if ($debug) {
  2842. error_log('Count of answers: '.$nbrAnswers);
  2843. error_log('$answerType: '.$answerType);
  2844. }
  2845. if ($answerType == FREE_ANSWER ||
  2846. $answerType == ORAL_EXPRESSION ||
  2847. $answerType == CALCULATED_ANSWER
  2848. ) {
  2849. $nbrAnswers = 1;
  2850. }
  2851. if ($answerType == ORAL_EXPRESSION) {
  2852. $exe_info = Event::get_exercise_results_by_attempt($exeId);
  2853. $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
  2854. $objQuestionTmp->initFile(
  2855. api_get_session_id(),
  2856. isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
  2857. isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
  2858. isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
  2859. );
  2860. //probably this attempt came in an exercise all question by page
  2861. if ($feedback_type == 0) {
  2862. $objQuestionTmp->replaceWithRealExe($exeId);
  2863. }
  2864. }
  2865. $user_answer = '';
  2866. // Get answer list for matching
  2867. $sql = "SELECT id_auto, id, answer
  2868. FROM $table_ans
  2869. WHERE c_id = $course_id AND question_id = $questionId";
  2870. $res_answer = Database::query($sql);
  2871. $answerMatching = array();
  2872. while ($real_answer = Database::fetch_array($res_answer)) {
  2873. $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
  2874. }
  2875. $real_answers = array();
  2876. $quiz_question_options = Question::readQuestionOption(
  2877. $questionId,
  2878. $course_id
  2879. );
  2880. $organs_at_risk_hit = 0;
  2881. $questionScore = 0;
  2882. if ($debug) error_log('Start answer loop ');
  2883. $answer_correct_array = array();
  2884. $orderedHotspots = [];
  2885. if ($answerType == HOT_SPOT) {
  2886. $orderedHotspots = $em
  2887. ->getRepository('ChamiloCoreBundle:TrackEHotspot')
  2888. ->findBy([
  2889. 'hotspotQuestionId' => $questionId,
  2890. 'cId' => $course_id,
  2891. 'hotspotExeId' => $exeId
  2892. ],
  2893. ['hotspotId' => 'ASC']
  2894. );
  2895. }
  2896. for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
  2897. $answer = $objAnswerTmp->selectAnswer($answerId);
  2898. $answerComment = $objAnswerTmp->selectComment($answerId);
  2899. $answerCorrect = $objAnswerTmp->isCorrect($answerId);
  2900. $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
  2901. $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
  2902. $answer_correct_array[$answerId] = (bool)$answerCorrect;
  2903. if ($debug) {
  2904. error_log("answer auto id: $answerAutoId ");
  2905. error_log("answer correct: $answerCorrect ");
  2906. }
  2907. // Delineation
  2908. $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
  2909. $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
  2910. switch ($answerType) {
  2911. // for unique answer
  2912. case UNIQUE_ANSWER:
  2913. case UNIQUE_ANSWER_IMAGE:
  2914. case UNIQUE_ANSWER_NO_OPTION:
  2915. if ($from_database) {
  2916. $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
  2917. WHERE
  2918. exe_id = '".$exeId."' AND
  2919. question_id= '".$questionId."'";
  2920. $result = Database::query($sql);
  2921. $choice = Database::result($result,0,"answer");
  2922. $studentChoice = $choice == $answerAutoId ? 1 : 0;
  2923. if ($studentChoice) {
  2924. $questionScore += $answerWeighting;
  2925. $totalScore += $answerWeighting;
  2926. }
  2927. } else {
  2928. $studentChoice = $choice == $answerAutoId ? 1 : 0;
  2929. if ($studentChoice) {
  2930. $questionScore += $answerWeighting;
  2931. $totalScore += $answerWeighting;
  2932. }
  2933. }
  2934. break;
  2935. // for multiple answers
  2936. case MULTIPLE_ANSWER_TRUE_FALSE:
  2937. if ($from_database) {
  2938. $choice = array();
  2939. $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
  2940. WHERE
  2941. exe_id = $exeId AND
  2942. question_id = ".$questionId;
  2943. $result = Database::query($sql);
  2944. while ($row = Database::fetch_array($result)) {
  2945. $ind = $row['answer'];
  2946. $values = explode(':', $ind);
  2947. $my_answer_id = isset($values[0]) ? $values[0] : '';
  2948. $option = isset($values[1]) ? $values[1] : '';
  2949. $choice[$my_answer_id] = $option;
  2950. }
  2951. }
  2952. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  2953. if (!empty($studentChoice)) {
  2954. if ($studentChoice == $answerCorrect) {
  2955. $questionScore += $true_score;
  2956. } else {
  2957. if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
  2958. $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
  2959. ) {
  2960. $questionScore += $doubt_score;
  2961. } else {
  2962. $questionScore += $false_score;
  2963. }
  2964. }
  2965. } else {
  2966. // If no result then the user just hit don't know
  2967. $studentChoice = 3;
  2968. $questionScore += $doubt_score;
  2969. }
  2970. $totalScore = $questionScore;
  2971. break;
  2972. case MULTIPLE_ANSWER: //2
  2973. if ($from_database) {
  2974. $choice = array();
  2975. $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
  2976. WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
  2977. $resultans = Database::query($sql);
  2978. while ($row = Database::fetch_array($resultans)) {
  2979. $ind = $row['answer'];
  2980. $choice[$ind] = 1;
  2981. }
  2982. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  2983. $real_answers[$answerId] = (bool)$studentChoice;
  2984. if ($studentChoice) {
  2985. $questionScore +=$answerWeighting;
  2986. }
  2987. } else {
  2988. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  2989. $real_answers[$answerId] = (bool)$studentChoice;
  2990. if (isset($studentChoice)) {
  2991. $questionScore += $answerWeighting;
  2992. }
  2993. }
  2994. $totalScore += $answerWeighting;
  2995. if ($debug) error_log("studentChoice: $studentChoice");
  2996. break;
  2997. case GLOBAL_MULTIPLE_ANSWER:
  2998. if ($from_database) {
  2999. $choice = array();
  3000. $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
  3001. WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
  3002. $resultans = Database::query($sql);
  3003. while ($row = Database::fetch_array($resultans)) {
  3004. $ind = $row['answer'];
  3005. $choice[$ind] = 1;
  3006. }
  3007. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  3008. $real_answers[$answerId] = (bool)$studentChoice;
  3009. if ($studentChoice) {
  3010. $questionScore +=$answerWeighting;
  3011. }
  3012. } else {
  3013. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  3014. if (isset($studentChoice)) {
  3015. $questionScore += $answerWeighting;
  3016. }
  3017. $real_answers[$answerId] = (bool)$studentChoice;
  3018. }
  3019. $totalScore += $answerWeighting;
  3020. if ($debug) error_log("studentChoice: $studentChoice");
  3021. break;
  3022. case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
  3023. if ($from_database) {
  3024. $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
  3025. WHERE exe_id = $exeId AND question_id= ".$questionId;
  3026. $resultans = Database::query($sql);
  3027. while ($row = Database::fetch_array($resultans)) {
  3028. $ind = $row['answer'];
  3029. $result = explode(':',$ind);
  3030. if (isset($result[0])) {
  3031. $my_answer_id = isset($result[0]) ? $result[0] : '';
  3032. $option = isset($result[1]) ? $result[1] : '';
  3033. $choice[$my_answer_id] = $option;
  3034. }
  3035. }
  3036. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
  3037. if ($answerCorrect == $studentChoice) {
  3038. //$answerCorrect = 1;
  3039. $real_answers[$answerId] = true;
  3040. } else {
  3041. //$answerCorrect = 0;
  3042. $real_answers[$answerId] = false;
  3043. }
  3044. } else {
  3045. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
  3046. if ($answerCorrect == $studentChoice) {
  3047. //$answerCorrect = 1;
  3048. $real_answers[$answerId] = true;
  3049. } else {
  3050. //$answerCorrect = 0;
  3051. $real_answers[$answerId] = false;
  3052. }
  3053. }
  3054. break;
  3055. case MULTIPLE_ANSWER_COMBINATION:
  3056. if ($from_database) {
  3057. $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
  3058. WHERE exe_id = $exeId AND question_id= $questionId";
  3059. $resultans = Database::query($sql);
  3060. while ($row = Database::fetch_array($resultans)) {
  3061. $ind = $row['answer'];
  3062. $choice[$ind] = 1;
  3063. }
  3064. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  3065. if ($answerCorrect == 1) {
  3066. if ($studentChoice) {
  3067. $real_answers[$answerId] = true;
  3068. } else {
  3069. $real_answers[$answerId] = false;
  3070. }
  3071. } else {
  3072. if ($studentChoice) {
  3073. $real_answers[$answerId] = false;
  3074. } else {
  3075. $real_answers[$answerId] = true;
  3076. }
  3077. }
  3078. } else {
  3079. $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
  3080. if ($answerCorrect == 1) {
  3081. if ($studentChoice) {
  3082. $real_answers[$answerId] = true;
  3083. } else {
  3084. $real_answers[$answerId] = false;
  3085. }
  3086. } else {
  3087. if ($studentChoice) {
  3088. $real_answers[$answerId] = false;
  3089. } else {
  3090. $real_answers[$answerId] = true;
  3091. }
  3092. }
  3093. }
  3094. break;
  3095. case FILL_IN_BLANKS:
  3096. $str = '';
  3097. if ($from_database) {
  3098. $sql = "SELECT answer
  3099. FROM $TBL_TRACK_ATTEMPT
  3100. WHERE
  3101. exe_id = $exeId AND
  3102. question_id= ".intval($questionId);
  3103. $result = Database::query($sql);
  3104. $str = Database::result($result, 0, 'answer');
  3105. }
  3106. if ($saved_results == false && strpos($str, 'font color') !== false) {
  3107. // the question is encoded like this
  3108. // [A] B [C] D [E] F::10,10,10@1
  3109. // number 1 before the "@" means that is a switchable fill in blank question
  3110. // [A] B [C] D [E] F::10,10,10@ or [A] B [C] D [E] F::10,10,10
  3111. // means that is a normal fill blank question
  3112. // first we explode the "::"
  3113. $pre_array = explode('::', $answer);
  3114. // is switchable fill blank or not
  3115. $last = count($pre_array) - 1;
  3116. $is_set_switchable = explode('@', $pre_array[$last]);
  3117. $switchable_answer_set = false;
  3118. if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
  3119. $switchable_answer_set = true;
  3120. }
  3121. $answer = '';
  3122. for ($k = 0; $k < $last; $k++) {
  3123. $answer .= $pre_array[$k];
  3124. }
  3125. // splits weightings that are joined with a comma
  3126. $answerWeighting = explode(',', $is_set_switchable[0]);
  3127. // we save the answer because it will be modified
  3128. $temp = $answer;
  3129. $answer = '';
  3130. $j = 0;
  3131. //initialise answer tags
  3132. $user_tags = $correct_tags = $real_text = array();
  3133. // the loop will stop at the end of the text
  3134. while (1) {
  3135. // quits the loop if there are no more blanks (detect '[')
  3136. if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
  3137. // adds the end of the text
  3138. $answer = $temp;
  3139. $real_text[] = $answer;
  3140. break; //no more "blanks", quit the loop
  3141. }
  3142. // adds the piece of text that is before the blank
  3143. //and ends with '[' into a general storage array
  3144. $real_text[] = api_substr($temp, 0, $pos +1);
  3145. $answer .= api_substr($temp, 0, $pos +1);
  3146. //take the string remaining (after the last "[" we found)
  3147. $temp = api_substr($temp, $pos +1);
  3148. // quit the loop if there are no more blanks, and update $pos to the position of next ']'
  3149. if (($pos = api_strpos($temp, ']')) === false) {
  3150. // adds the end of the text
  3151. $answer .= $temp;
  3152. break;
  3153. }
  3154. if ($from_database) {
  3155. $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
  3156. WHERE
  3157. exe_id = '".$exeId."' AND
  3158. question_id= ".intval($questionId)."";
  3159. $resfill = Database::query($queryfill);
  3160. $str = Database::result($resfill, 0, 'answer');
  3161. api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
  3162. $str = str_replace('\r\n', '', $str);
  3163. $choice = $arr[1];
  3164. if (isset($choice[$j])) {
  3165. $tmp = api_strrpos($choice[$j], ' / ');
  3166. $choice[$j] = api_substr($choice[$j], 0, $tmp);
  3167. $choice[$j] = trim($choice[$j]);
  3168. // Needed to let characters ' and " to work as part of an answer
  3169. $choice[$j] = stripslashes($choice[$j]);
  3170. } else {
  3171. $choice[$j] = null;
  3172. }
  3173. } else {
  3174. // This value is the user input, not escaped while correct answer is escaped by fckeditor
  3175. $choice[$j] = api_htmlentities(trim($choice[$j]));
  3176. }
  3177. $user_tags[] = $choice[$j];
  3178. //put the contents of the [] answer tag into correct_tags[]
  3179. $correct_tags[] = api_substr($temp, 0, $pos);
  3180. $j++;
  3181. $temp = api_substr($temp, $pos +1);
  3182. }
  3183. $answer = '';
  3184. $real_correct_tags = $correct_tags;
  3185. $chosen_list = array();
  3186. for ($i = 0; $i < count($real_correct_tags); $i++) {
  3187. if ($i == 0) {
  3188. $answer .= $real_text[0];
  3189. }
  3190. if (!$switchable_answer_set) {
  3191. // Needed to parse ' and " characters
  3192. $user_tags[$i] = stripslashes($user_tags[$i]);
  3193. if ($correct_tags[$i] == $user_tags[$i]) {
  3194. // gives the related weighting to the student
  3195. $questionScore += $answerWeighting[$i];
  3196. // increments total score
  3197. $totalScore += $answerWeighting[$i];
  3198. // adds the word in green at the end of the string
  3199. $answer .= $correct_tags[$i];
  3200. } elseif (!empty($user_tags[$i])) {
  3201. // else if the word entered by the student IS NOT the same as the one defined by the professor
  3202. // adds the word in red at the end of the string, and strikes it
  3203. $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
  3204. } else {
  3205. // adds a tabulation if no word has been typed by the student
  3206. $answer .= ''; // remove &nbsp; that causes issue
  3207. }
  3208. } else {
  3209. // switchable fill in the blanks
  3210. if (in_array($user_tags[$i], $correct_tags)) {
  3211. $chosen_list[] = $user_tags[$i];
  3212. $correct_tags = array_diff($correct_tags, $chosen_list);
  3213. // gives the related weighting to the student
  3214. $questionScore += $answerWeighting[$i];
  3215. // increments total score
  3216. $totalScore += $answerWeighting[$i];
  3217. // adds the word in green at the end of the string
  3218. $answer .= $user_tags[$i];
  3219. } elseif (!empty ($user_tags[$i])) {
  3220. // else if the word entered by the student IS NOT the same as the one defined by the professor
  3221. // adds the word in red at the end of the string, and strikes it
  3222. $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
  3223. } else {
  3224. // adds a tabulation if no word has been typed by the student
  3225. $answer .= ''; // remove &nbsp; that causes issue
  3226. }
  3227. }
  3228. // adds the correct word, followed by ] to close the blank
  3229. $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
  3230. if (isset($real_text[$i +1])) {
  3231. $answer .= $real_text[$i +1];
  3232. }
  3233. }
  3234. } else {
  3235. // insert the student result in the track_e_attempt table, field answer
  3236. // $answer is the answer like in the c_quiz_answer table for the question
  3237. // student data are choice[]
  3238. $listCorrectAnswers = FillBlanks::getAnswerInfo(
  3239. $answer
  3240. );
  3241. $switchableAnswerSet = $listCorrectAnswers["switchable"];
  3242. $answerWeighting = $listCorrectAnswers["tabweighting"];
  3243. // user choices is an array $choice
  3244. // get existing user data in n the BDD
  3245. if ($from_database) {
  3246. $sql = "SELECT answer
  3247. FROM $TBL_TRACK_ATTEMPT
  3248. WHERE
  3249. exe_id = $exeId AND
  3250. question_id= ".intval($questionId);
  3251. $result = Database::query($sql);
  3252. $str = Database::result($result, 0, 'answer');
  3253. $listStudentResults = FillBlanks::getAnswerInfo(
  3254. $str,
  3255. true
  3256. );
  3257. $choice = $listStudentResults['studentanswer'];
  3258. }
  3259. // loop other all blanks words
  3260. if (!$switchableAnswerSet) {
  3261. // not switchable answer, must be in the same place than teacher order
  3262. for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
  3263. $studentAnswer = isset($choice[$i]) ? trim($choice[$i]) : '';
  3264. // This value is the user input, not escaped while correct answer is escaped by fckeditor
  3265. // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
  3266. if (!$from_database) {
  3267. $studentAnswer = htmlentities(
  3268. api_utf8_encode($studentAnswer)
  3269. );
  3270. }
  3271. $correctAnswer = $listCorrectAnswers['tabwords'][$i];
  3272. $isAnswerCorrect = 0;
  3273. if (FillBlanks::isGoodStudentAnswer(
  3274. $studentAnswer,
  3275. $correctAnswer
  3276. )
  3277. ) {
  3278. // gives the related weighting to the student
  3279. $questionScore += $answerWeighting[$i];
  3280. // increments total score
  3281. $totalScore += $answerWeighting[$i];
  3282. $isAnswerCorrect = 1;
  3283. }
  3284. $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
  3285. $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
  3286. }
  3287. } else {
  3288. // switchable answer
  3289. $listStudentAnswerTemp = $choice;
  3290. $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
  3291. // for every teacher answer, check if there is a student answer
  3292. for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
  3293. $studentAnswer = trim(
  3294. $listStudentAnswerTemp[$i]
  3295. );
  3296. $found = false;
  3297. for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
  3298. $correctAnswer = $listTeacherAnswerTemp[$j];
  3299. if (!$found) {
  3300. if (FillBlanks::isGoodStudentAnswer(
  3301. $studentAnswer,
  3302. $correctAnswer
  3303. )
  3304. ) {
  3305. $questionScore += $answerWeighting[$i];
  3306. $totalScore += $answerWeighting[$i];
  3307. $listTeacherAnswerTemp[$j] = "";
  3308. $found = true;
  3309. }
  3310. }
  3311. }
  3312. $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
  3313. if (!$found) {
  3314. $listCorrectAnswers['studentscore'][$i] = 0;
  3315. } else {
  3316. $listCorrectAnswers['studentscore'][$i] = 1;
  3317. }
  3318. }
  3319. }
  3320. $answer = FillBlanks::getAnswerInStudentAttempt(
  3321. $listCorrectAnswers
  3322. );
  3323. }
  3324. break;
  3325. case CALCULATED_ANSWER:
  3326. $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
  3327. $preArray = explode('@@', $answer);
  3328. $last = count($preArray) - 1;
  3329. $answer = '';
  3330. for ($k = 0; $k < $last; $k++) {
  3331. $answer .= $preArray[$k];
  3332. }
  3333. $answerWeighting = array($answerWeighting);
  3334. // we save the answer because it will be modified
  3335. $temp = $answer;
  3336. $answer = '';
  3337. $j = 0;
  3338. //initialise answer tags
  3339. $userTags = $correctTags = $realText = array();
  3340. // the loop will stop at the end of the text
  3341. while (1) {
  3342. // quits the loop if there are no more blanks (detect '[')
  3343. if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
  3344. // adds the end of the text
  3345. $answer = $temp;
  3346. $realText[] = $answer;
  3347. break; //no more "blanks", quit the loop
  3348. }
  3349. // adds the piece of text that is before the blank
  3350. //and ends with '[' into a general storage array
  3351. $realText[] = api_substr($temp, 0, $pos +1);
  3352. $answer .= api_substr($temp, 0, $pos +1);
  3353. //take the string remaining (after the last "[" we found)
  3354. $temp = api_substr($temp, $pos +1);
  3355. // quit the loop if there are no more blanks, and update $pos to the position of next ']'
  3356. if (($pos = api_strpos($temp, ']')) === false) {
  3357. // adds the end of the text
  3358. $answer .= $temp;
  3359. break;
  3360. }
  3361. if ($from_database) {
  3362. $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
  3363. WHERE
  3364. exe_id = '".$exeId."' AND
  3365. question_id= ".intval($questionId);
  3366. $resfill = Database::query($queryfill);
  3367. $str = Database::result($resfill, 0, 'answer');
  3368. api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
  3369. $str = str_replace('\r\n', '', $str);
  3370. $choice = $arr[1];
  3371. if (isset($choice[$j])) {
  3372. $tmp = api_strrpos($choice[$j], ' / ');
  3373. if ($tmp) {
  3374. $choice[$j] = api_substr($choice[$j], 0, $tmp);
  3375. } else {
  3376. $tmp = ltrim($tmp, '[');
  3377. $tmp = rtrim($tmp, ']');
  3378. }
  3379. $choice[$j] = trim($choice[$j]);
  3380. // Needed to let characters ' and " to work as part of an answer
  3381. $choice[$j] = stripslashes($choice[$j]);
  3382. } else {
  3383. $choice[$j] = null;
  3384. }
  3385. } else {
  3386. // This value is the user input, not escaped while correct answer is escaped by fckeditor
  3387. $choice[$j] = api_htmlentities(trim($choice[$j]));
  3388. }
  3389. $userTags[] = $choice[$j];
  3390. //put the contents of the [] answer tag into correct_tags[]
  3391. $correctTags[] = api_substr($temp, 0, $pos);
  3392. $j++;
  3393. $temp = api_substr($temp, $pos +1);
  3394. }
  3395. $answer = '';
  3396. $realCorrectTags = $correctTags;
  3397. for ($i = 0; $i < count($realCorrectTags); $i++) {
  3398. if ($i == 0) {
  3399. $answer .= $realText[0];
  3400. }
  3401. // Needed to parse ' and " characters
  3402. $userTags[$i] = stripslashes($userTags[$i]);
  3403. if ($correctTags[$i] == $userTags[$i]) {
  3404. // gives the related weighting to the student
  3405. $questionScore += $answerWeighting[$i];
  3406. // increments total score
  3407. $totalScore += $answerWeighting[$i];
  3408. // adds the word in green at the end of the string
  3409. $answer .= $correctTags[$i];
  3410. } elseif (!empty($userTags[$i])) {
  3411. // else if the word entered by the student IS NOT the same as the one defined by the professor
  3412. // adds the word in red at the end of the string, and strikes it
  3413. $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
  3414. } else {
  3415. // adds a tabulation if no word has been typed by the student
  3416. $answer .= ''; // remove &nbsp; that causes issue
  3417. }
  3418. // adds the correct word, followed by ] to close the blank
  3419. if (
  3420. $this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM
  3421. ) {
  3422. $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>';
  3423. }
  3424. $answer .= ']';
  3425. if (isset($realText[$i +1])) {
  3426. $answer .= $realText[$i +1];
  3427. }
  3428. }
  3429. break;
  3430. case FREE_ANSWER:
  3431. if ($from_database) {
  3432. $query = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
  3433. WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
  3434. $resq = Database::query($query);
  3435. $data = Database::fetch_array($resq);
  3436. $choice = $data['answer'];
  3437. $choice = str_replace('\r\n', '', $choice);
  3438. $choice = stripslashes($choice);
  3439. $questionScore = $data['marks'];
  3440. if ($questionScore == -1) {
  3441. $totalScore+= 0;
  3442. } else {
  3443. $totalScore+= $questionScore;
  3444. }
  3445. if ($questionScore == '') {
  3446. $questionScore = 0;
  3447. }
  3448. $arrques = $questionName;
  3449. $arrans = $choice;
  3450. } else {
  3451. $studentChoice = $choice;
  3452. if ($studentChoice) {
  3453. //Fixing negative puntation see #2193
  3454. $questionScore = 0;
  3455. $totalScore += 0;
  3456. }
  3457. }
  3458. break;
  3459. case ORAL_EXPRESSION:
  3460. if ($from_database) {
  3461. $query = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
  3462. WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
  3463. $resq = Database::query($query);
  3464. $row = Database::fetch_assoc($resq);
  3465. $choice = $row['answer'];
  3466. $choice = str_replace('\r\n', '', $choice);
  3467. $choice = stripslashes($choice);
  3468. $questionScore = $row['marks'];
  3469. if ($questionScore==-1) {
  3470. $totalScore+=0;
  3471. } else {
  3472. $totalScore+=$questionScore;
  3473. }
  3474. $arrques = $questionName;
  3475. $arrans = $choice;
  3476. } else {
  3477. $studentChoice = $choice;
  3478. if ($studentChoice) {
  3479. //Fixing negative puntation see #2193
  3480. $questionScore = 0;
  3481. $totalScore += 0;
  3482. }
  3483. }
  3484. break;
  3485. case DRAGGABLE:
  3486. //no break
  3487. case MATCHING_DRAGGABLE:
  3488. //no break
  3489. case MATCHING:
  3490. if ($from_database) {
  3491. $sql = 'SELECT id, answer, id_auto
  3492. FROM '.$table_ans.'
  3493. WHERE
  3494. c_id = '.$course_id.' AND
  3495. question_id = "'.$questionId.'" AND
  3496. correct = 0';
  3497. $res_answer = Database::query($sql);
  3498. // Getting the real answer
  3499. $real_list = array();
  3500. while ($real_answer = Database::fetch_array($res_answer)) {
  3501. $real_list[$real_answer['id_auto']] = $real_answer['answer'];
  3502. }
  3503. $sql = 'SELECT id, answer, correct, id_auto, ponderation
  3504. FROM '.$table_ans.'
  3505. WHERE
  3506. c_id = '.$course_id.' AND
  3507. question_id="'.$questionId.'" AND
  3508. correct <> 0
  3509. ORDER BY id_auto';
  3510. $res_answers = Database::query($sql);
  3511. $questionScore = 0;
  3512. while ($a_answers = Database::fetch_array($res_answers)) {
  3513. $i_answer_id = $a_answers['id']; //3
  3514. $s_answer_label = $a_answers['answer']; // your daddy - your mother
  3515. $i_answer_correct_answer = $a_answers['correct']; //1 - 2
  3516. $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
  3517. $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
  3518. WHERE
  3519. exe_id = '$exeId' AND
  3520. question_id = '$questionId' AND
  3521. position = '$i_answer_id_auto'";
  3522. $res_user_answer = Database::query($sql);
  3523. if (Database::num_rows($res_user_answer) > 0) {
  3524. // rich - good looking
  3525. $s_user_answer = Database::result($res_user_answer, 0, 0);
  3526. } else {
  3527. $s_user_answer = 0;
  3528. }
  3529. $i_answerWeighting = $a_answers['ponderation'];
  3530. $user_answer = '';
  3531. if (!empty($s_user_answer)) {
  3532. if ($answerType == DRAGGABLE) {
  3533. if ($s_user_answer == $i_answer_correct_answer) {
  3534. $questionScore += $i_answerWeighting;
  3535. $totalScore += $i_answerWeighting;
  3536. $user_answer = Display::label(get_lang('Correct'), 'success');
  3537. } else {
  3538. $user_answer = Display::label(get_lang('Incorrect'), 'danger');
  3539. }
  3540. } else {
  3541. if ($s_user_answer == $i_answer_correct_answer) {
  3542. $questionScore += $i_answerWeighting;
  3543. $totalScore += $i_answerWeighting;
  3544. if (isset($real_list[$i_answer_id])) {
  3545. $user_answer = Display::span($real_list[$i_answer_id]);
  3546. }
  3547. } else {
  3548. $user_answer = Display::span(
  3549. $real_list[$s_user_answer],
  3550. ['style' => 'color: #FF0000; text-decoration: line-through;']
  3551. );
  3552. }
  3553. }
  3554. } elseif ($answerType == DRAGGABLE) {
  3555. $user_answer = Display::label(get_lang('Incorrect'), 'danger');
  3556. }
  3557. if ($show_result) {
  3558. if ($showTotalScoreAndUserChoices == true) {
  3559. $user_answer = '';
  3560. }
  3561. echo '<tr>';
  3562. echo '<td>' . $s_answer_label . '</td>';
  3563. echo '<td>' . $user_answer;
  3564. if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
  3565. if (isset($real_list[$i_answer_correct_answer]) && $showTotalScoreAndUserChoices == false) {
  3566. echo Display::span(
  3567. $real_list[$i_answer_correct_answer],
  3568. ['style' => 'color: #008000; font-weight: bold;']
  3569. );
  3570. }
  3571. }
  3572. echo '</td>';
  3573. echo '</tr>';
  3574. }
  3575. }
  3576. break(2); // break the switch and the "for" condition
  3577. } else {
  3578. if ($answerCorrect) {
  3579. if (isset($choice[$answerAutoId]) &&
  3580. $answerCorrect == $choice[$answerAutoId]
  3581. ) {
  3582. $questionScore += $answerWeighting;
  3583. $totalScore += $answerWeighting;
  3584. $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
  3585. } else {
  3586. if (isset($answerMatching[$choice[$answerAutoId]])) {
  3587. $user_answer = Display::span(
  3588. $answerMatching[$choice[$answerAutoId]],
  3589. ['style' => 'color: #FF0000; text-decoration: line-through;']
  3590. );
  3591. }
  3592. }
  3593. $matching[$answerAutoId] = $choice[$answerAutoId];
  3594. }
  3595. break;
  3596. }
  3597. case HOT_SPOT:
  3598. if ($from_database) {
  3599. $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
  3600. $sql = "SELECT hotspot_correct
  3601. FROM $TBL_TRACK_HOTSPOT
  3602. WHERE
  3603. hotspot_exe_id = '".$exeId."' AND
  3604. hotspot_question_id= '".$questionId."' AND
  3605. hotspot_answer_id = ".intval($answerAutoId)."";
  3606. $result = Database::query($sql);
  3607. $studentChoice = Database::result($result, 0, "hotspot_correct");
  3608. if ($studentChoice) {
  3609. $questionScore += $answerWeighting;
  3610. $totalScore += $answerWeighting;
  3611. }
  3612. } else {
  3613. if (!isset($choice[$answerAutoId])) {
  3614. $choice[$answerAutoId] = 0;
  3615. } else {
  3616. $studentChoice = $choice[$answerAutoId];
  3617. $choiceIsValid = false;
  3618. if (!empty($studentChoice)) {
  3619. $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
  3620. $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
  3621. $choicePoint = Geometry::decodePoint($studentChoice);
  3622. switch ($hotspotType) {
  3623. case 'square':
  3624. $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
  3625. $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
  3626. break;
  3627. case 'circle':
  3628. $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
  3629. $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
  3630. break;
  3631. case 'poly':
  3632. $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
  3633. $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
  3634. break;
  3635. }
  3636. }
  3637. $choice[$answerAutoId] = 0;
  3638. if ($choiceIsValid) {
  3639. $questionScore += $answerWeighting;
  3640. $totalScore += $answerWeighting;
  3641. $choice[$answerAutoId] = 1;
  3642. }
  3643. }
  3644. }
  3645. break;
  3646. // @todo never added to chamilo
  3647. //for hotspot with fixed order
  3648. case HOT_SPOT_ORDER :
  3649. $studentChoice = $choice['order'][$answerId];
  3650. if ($studentChoice == $answerId) {
  3651. $questionScore += $answerWeighting;
  3652. $totalScore += $answerWeighting;
  3653. $studentChoice = true;
  3654. } else {
  3655. $studentChoice = false;
  3656. }
  3657. break;
  3658. // for hotspot with delineation
  3659. case HOT_SPOT_DELINEATION :
  3660. if ($from_database) {
  3661. // getting the user answer
  3662. $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
  3663. $query = "SELECT hotspot_correct, hotspot_coordinate
  3664. FROM $TBL_TRACK_HOTSPOT
  3665. WHERE
  3666. hotspot_exe_id = '".$exeId."' AND
  3667. hotspot_question_id= '".$questionId."' AND
  3668. hotspot_answer_id='1'";
  3669. //by default we take 1 because it's a delineation
  3670. $resq = Database::query($query);
  3671. $row = Database::fetch_array($resq,'ASSOC');
  3672. $choice = $row['hotspot_correct'];
  3673. $user_answer = $row['hotspot_coordinate'];
  3674. // THIS is very important otherwise the poly_compile will throw an error!!
  3675. // round-up the coordinates
  3676. $coords = explode('/',$user_answer);
  3677. $user_array = '';
  3678. foreach ($coords as $coord) {
  3679. list($x,$y) = explode(';',$coord);
  3680. $user_array .= round($x).';'.round($y).'/';
  3681. }
  3682. $user_array = substr($user_array,0,-1);
  3683. } else {
  3684. if (!empty($studentChoice)) {
  3685. $newquestionList[] = $questionId;
  3686. }
  3687. if ($answerId === 1) {
  3688. $studentChoice = $choice[$answerId];
  3689. $questionScore += $answerWeighting;
  3690. if ($hotspot_delineation_result[1]==1) {
  3691. $totalScore += $answerWeighting; //adding the total
  3692. }
  3693. }
  3694. }
  3695. $_SESSION['hotspot_coord'][1] = $delineation_cord;
  3696. $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
  3697. break;
  3698. } // end switch Answertype
  3699. if ($show_result) {
  3700. if ($debug) error_log('Showing questions $from '.$from);
  3701. if ($from == 'exercise_result') {
  3702. //display answers (if not matching type, or if the answer is correct)
  3703. if (
  3704. !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
  3705. $answerCorrect
  3706. ) {
  3707. if (
  3708. in_array(
  3709. $answerType,
  3710. array(
  3711. UNIQUE_ANSWER,
  3712. UNIQUE_ANSWER_IMAGE,
  3713. UNIQUE_ANSWER_NO_OPTION,
  3714. MULTIPLE_ANSWER,
  3715. MULTIPLE_ANSWER_COMBINATION,
  3716. GLOBAL_MULTIPLE_ANSWER
  3717. )
  3718. )
  3719. ) {
  3720. ExerciseShowFunctions::display_unique_or_multiple_answer(
  3721. $feedback_type,
  3722. $answerType,
  3723. $studentChoice,
  3724. $answer,
  3725. $answerComment,
  3726. $answerCorrect,
  3727. 0,
  3728. 0,
  3729. 0,
  3730. $results_disabled,
  3731. $showTotalScoreAndUserChoices
  3732. );
  3733. } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
  3734. ExerciseShowFunctions::display_multiple_answer_true_false(
  3735. $feedback_type,
  3736. $answerType,
  3737. $studentChoice,
  3738. $answer,
  3739. $answerComment,
  3740. $answerCorrect,
  3741. 0,
  3742. $questionId,
  3743. 0,
  3744. $results_disabled,
  3745. $showTotalScoreAndUserChoices
  3746. );
  3747. } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
  3748. ExerciseShowFunctions::display_multiple_answer_combination_true_false(
  3749. $feedback_type,
  3750. $answerType,
  3751. $studentChoice,
  3752. $answer,
  3753. $answerComment,
  3754. $answerCorrect,
  3755. 0,
  3756. 0,
  3757. 0,
  3758. $results_disabled,
  3759. $showTotalScoreAndUserChoices
  3760. );
  3761. } elseif ($answerType == FILL_IN_BLANKS) {
  3762. ExerciseShowFunctions::display_fill_in_blanks_answer(
  3763. $feedback_type,
  3764. $answer,
  3765. 0,
  3766. 0,
  3767. $results_disabled,
  3768. '',
  3769. $showTotalScoreAndUserChoices
  3770. );
  3771. } elseif ($answerType == CALCULATED_ANSWER) {
  3772. ExerciseShowFunctions::display_calculated_answer(
  3773. $feedback_type,
  3774. $answer,
  3775. 0,
  3776. 0,
  3777. $results_disabled,
  3778. $showTotalScoreAndUserChoices
  3779. );
  3780. } elseif ($answerType == FREE_ANSWER) {
  3781. ExerciseShowFunctions::display_free_answer(
  3782. $feedback_type,
  3783. $choice,
  3784. $exeId,
  3785. $questionId,
  3786. $questionScore,
  3787. $results_disabled
  3788. );
  3789. } elseif ($answerType == ORAL_EXPRESSION) {
  3790. // to store the details of open questions in an array to be used in mail
  3791. ExerciseShowFunctions::display_oral_expression_answer(
  3792. $feedback_type,
  3793. $choice,
  3794. 0,
  3795. 0,
  3796. $nano,
  3797. $results_disabled
  3798. );
  3799. } elseif ($answerType == HOT_SPOT) {
  3800. foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
  3801. if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
  3802. break;
  3803. }
  3804. }
  3805. ExerciseShowFunctions::display_hotspot_answer(
  3806. $feedback_type,
  3807. ++$correctAnswerId,
  3808. $answer,
  3809. $studentChoice,
  3810. $answerComment,
  3811. $results_disabled,
  3812. $answerId,
  3813. $showTotalScoreAndUserChoices
  3814. );
  3815. } elseif ($answerType == HOT_SPOT_ORDER) {
  3816. ExerciseShowFunctions::display_hotspot_order_answer(
  3817. $feedback_type,
  3818. $answerId,
  3819. $answer,
  3820. $studentChoice,
  3821. $answerComment
  3822. );
  3823. } elseif ($answerType == HOT_SPOT_DELINEATION) {
  3824. $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
  3825. //round-up the coordinates
  3826. $coords = explode('/',$user_answer);
  3827. $user_array = '';
  3828. foreach ($coords as $coord) {
  3829. list($x,$y) = explode(';',$coord);
  3830. $user_array .= round($x).';'.round($y).'/';
  3831. }
  3832. $user_array = substr($user_array,0,-1);
  3833. if ($next) {
  3834. $user_answer = $user_array;
  3835. // we compare only the delineation not the other points
  3836. $answer_question = $_SESSION['hotspot_coord'][1];
  3837. $answerDestination = $_SESSION['hotspot_dest'][1];
  3838. //calculating the area
  3839. $poly_user = convert_coordinates($user_answer, '/');
  3840. $poly_answer = convert_coordinates($answer_question, '|');
  3841. $max_coord = poly_get_max($poly_user, $poly_answer);
  3842. $poly_user_compiled = poly_compile($poly_user, $max_coord);
  3843. $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
  3844. $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
  3845. $overlap = $poly_results['both'];
  3846. $poly_answer_area = $poly_results['s1'];
  3847. $poly_user_area = $poly_results['s2'];
  3848. $missing = $poly_results['s1Only'];
  3849. $excess = $poly_results['s2Only'];
  3850. //$overlap = round(polygons_overlap($poly_answer,$poly_user));
  3851. // //this is an area in pixels
  3852. if ($debug > 0) {
  3853. error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
  3854. }
  3855. if ($overlap < 1) {
  3856. //shortcut to avoid complicated calculations
  3857. $final_overlap = 0;
  3858. $final_missing = 100;
  3859. $final_excess = 100;
  3860. } else {
  3861. // the final overlap is the percentage of the initial polygon
  3862. // that is overlapped by the user's polygon
  3863. $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
  3864. if ($debug > 1) {
  3865. error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
  3866. }
  3867. // the final missing area is the percentage of the initial polygon
  3868. // that is not overlapped by the user's polygon
  3869. $final_missing = 100 - $final_overlap;
  3870. if ($debug > 1) {
  3871. error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
  3872. }
  3873. // the final excess area is the percentage of the initial polygon's size
  3874. // that is covered by the user's polygon outside of the initial polygon
  3875. $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
  3876. if ($debug > 1) {
  3877. error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
  3878. }
  3879. }
  3880. //checking the destination parameters parsing the "@@"
  3881. $destination_items= explode('@@', $answerDestination);
  3882. $threadhold_total = $destination_items[0];
  3883. $threadhold_items=explode(';',$threadhold_total);
  3884. $threadhold1 = $threadhold_items[0]; // overlap
  3885. $threadhold2 = $threadhold_items[1]; // excess
  3886. $threadhold3 = $threadhold_items[2]; //missing
  3887. // if is delineation
  3888. if ($answerId===1) {
  3889. //setting colors
  3890. if ($final_overlap>=$threadhold1) {
  3891. $overlap_color=true; //echo 'a';
  3892. }
  3893. //echo $excess.'-'.$threadhold2;
  3894. if ($final_excess<=$threadhold2) {
  3895. $excess_color=true; //echo 'b';
  3896. }
  3897. //echo '--------'.$missing.'-'.$threadhold3;
  3898. if ($final_missing<=$threadhold3) {
  3899. $missing_color=true; //echo 'c';
  3900. }
  3901. // if pass
  3902. if (
  3903. $final_overlap >= $threadhold1 &&
  3904. $final_missing <= $threadhold3 &&
  3905. $final_excess <= $threadhold2
  3906. ) {
  3907. $next=1; //go to the oars
  3908. $result_comment=get_lang('Acceptable');
  3909. $final_answer = 1; // do not update with update_exercise_attempt
  3910. } else {
  3911. $next=0;
  3912. $result_comment=get_lang('Unacceptable');
  3913. $comment=$answerDestination=$objAnswerTmp->selectComment(1);
  3914. $answerDestination=$objAnswerTmp->selectDestination(1);
  3915. //checking the destination parameters parsing the "@@"
  3916. $destination_items= explode('@@', $answerDestination);
  3917. }
  3918. } elseif($answerId>1) {
  3919. if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
  3920. if ($debug>0) {
  3921. error_log(__LINE__.' - answerId is of type noerror',0);
  3922. }
  3923. //type no error shouldn't be treated
  3924. $next = 1;
  3925. continue;
  3926. }
  3927. if ($debug>0) {
  3928. error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
  3929. }
  3930. //check the intersection between the oar and the user
  3931. //echo 'user'; print_r($x_user_list); print_r($y_user_list);
  3932. //echo 'official';print_r($x_list);print_r($y_list);
  3933. //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
  3934. $inter= $result['success'];
  3935. //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
  3936. $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
  3937. $poly_answer = convert_coordinates($delineation_cord,'|');
  3938. $max_coord = poly_get_max($poly_user,$poly_answer);
  3939. $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
  3940. $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
  3941. if ($overlap == false) {
  3942. //all good, no overlap
  3943. $next = 1;
  3944. continue;
  3945. } else {
  3946. if ($debug>0) {
  3947. error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
  3948. }
  3949. $organs_at_risk_hit++;
  3950. //show the feedback
  3951. $next=0;
  3952. $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
  3953. $answerDestination=$objAnswerTmp->selectDestination($answerId);
  3954. $destination_items= explode('@@', $answerDestination);
  3955. $try_hotspot=$destination_items[1];
  3956. $lp_hotspot=$destination_items[2];
  3957. $select_question_hotspot=$destination_items[3];
  3958. $url_hotspot=$destination_items[4];
  3959. }
  3960. }
  3961. } else { // the first delineation feedback
  3962. if ($debug>0) {
  3963. error_log(__LINE__.' first',0);
  3964. }
  3965. }
  3966. } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
  3967. echo '<tr>';
  3968. echo Display::tag('td', $answerMatching[$answerId]);
  3969. echo Display::tag(
  3970. 'td',
  3971. "$user_answer / " . Display::tag(
  3972. 'strong',
  3973. $answerMatching[$answerCorrect],
  3974. ['style' => 'color: #008000; font-weight: bold;']
  3975. )
  3976. );
  3977. echo '</tr>';
  3978. }
  3979. }
  3980. } else {
  3981. if ($debug) error_log('Showing questions $from '.$from);
  3982. switch ($answerType) {
  3983. case UNIQUE_ANSWER:
  3984. case UNIQUE_ANSWER_IMAGE:
  3985. case UNIQUE_ANSWER_NO_OPTION:
  3986. case MULTIPLE_ANSWER:
  3987. case GLOBAL_MULTIPLE_ANSWER :
  3988. case MULTIPLE_ANSWER_COMBINATION:
  3989. if ($answerId == 1) {
  3990. ExerciseShowFunctions::display_unique_or_multiple_answer(
  3991. $feedback_type,
  3992. $answerType,
  3993. $studentChoice,
  3994. $answer,
  3995. $answerComment,
  3996. $answerCorrect,
  3997. $exeId,
  3998. $questionId,
  3999. $answerId,
  4000. $results_disabled,
  4001. $showTotalScoreAndUserChoices
  4002. );
  4003. } else {
  4004. ExerciseShowFunctions::display_unique_or_multiple_answer(
  4005. $feedback_type,
  4006. $answerType,
  4007. $studentChoice,
  4008. $answer,
  4009. $answerComment,
  4010. $answerCorrect,
  4011. $exeId,
  4012. $questionId,
  4013. '',
  4014. $results_disabled,
  4015. $showTotalScoreAndUserChoices
  4016. );
  4017. }
  4018. break;
  4019. case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
  4020. if ($answerId == 1) {
  4021. ExerciseShowFunctions::display_multiple_answer_combination_true_false(
  4022. $feedback_type,
  4023. $answerType,
  4024. $studentChoice,
  4025. $answer,
  4026. $answerComment,
  4027. $answerCorrect,
  4028. $exeId,
  4029. $questionId,
  4030. $answerId,
  4031. $results_disabled,
  4032. $showTotalScoreAndUserChoices
  4033. );
  4034. } else {
  4035. ExerciseShowFunctions::display_multiple_answer_combination_true_false(
  4036. $feedback_type,
  4037. $answerType,
  4038. $studentChoice,
  4039. $answer,
  4040. $answerComment,
  4041. $answerCorrect,
  4042. $exeId,
  4043. $questionId,
  4044. '',
  4045. $results_disabled,
  4046. $showTotalScoreAndUserChoices
  4047. );
  4048. }
  4049. break;
  4050. case MULTIPLE_ANSWER_TRUE_FALSE:
  4051. if ($answerId == 1) {
  4052. ExerciseShowFunctions::display_multiple_answer_true_false(
  4053. $feedback_type,
  4054. $answerType,
  4055. $studentChoice,
  4056. $answer,
  4057. $answerComment,
  4058. $answerCorrect,
  4059. $exeId,
  4060. $questionId,
  4061. $answerId,
  4062. $results_disabled,
  4063. $showTotalScoreAndUserChoices
  4064. );
  4065. } else {
  4066. ExerciseShowFunctions::display_multiple_answer_true_false(
  4067. $feedback_type,
  4068. $answerType,
  4069. $studentChoice,
  4070. $answer,
  4071. $answerComment,
  4072. $answerCorrect,
  4073. $exeId,
  4074. $questionId,
  4075. '',
  4076. $results_disabled,
  4077. $showTotalScoreAndUserChoices
  4078. );
  4079. }
  4080. break;
  4081. case FILL_IN_BLANKS:
  4082. ExerciseShowFunctions::display_fill_in_blanks_answer(
  4083. $feedback_type,
  4084. $answer,
  4085. $exeId,
  4086. $questionId,
  4087. $results_disabled,
  4088. $str,
  4089. $showTotalScoreAndUserChoices
  4090. );
  4091. break;
  4092. case CALCULATED_ANSWER:
  4093. ExerciseShowFunctions::display_calculated_answer(
  4094. $feedback_type,
  4095. $answer,
  4096. $exeId,
  4097. $questionId,
  4098. $results_disabled,
  4099. '',
  4100. $showTotalScoreAndUserChoices
  4101. );
  4102. break;
  4103. case FREE_ANSWER:
  4104. echo ExerciseShowFunctions::display_free_answer(
  4105. $feedback_type,
  4106. $choice,
  4107. $exeId,
  4108. $questionId,
  4109. $questionScore,
  4110. $results_disabled
  4111. );
  4112. break;
  4113. case ORAL_EXPRESSION:
  4114. echo '<tr>
  4115. <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
  4116. $feedback_type,
  4117. $choice,
  4118. $exeId,
  4119. $questionId,
  4120. $objQuestionTmp->getFileUrl(),
  4121. $results_disabled
  4122. ) . '</td>
  4123. </tr>
  4124. </table>';
  4125. break;
  4126. case HOT_SPOT:
  4127. ExerciseShowFunctions::display_hotspot_answer(
  4128. $feedback_type,
  4129. $answerId,
  4130. $answer,
  4131. $studentChoice,
  4132. $answerComment,
  4133. $results_disabled,
  4134. $answerId,
  4135. $showTotalScoreAndUserChoices
  4136. );
  4137. break;
  4138. case HOT_SPOT_DELINEATION:
  4139. $user_answer = $user_array;
  4140. if ($next) {
  4141. //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
  4142. // Save into db
  4143. /* $sql = "INSERT INTO $tbl_track_e_hotspot (
  4144. * hotspot_user_id,
  4145. * hotspot_course_code,
  4146. * hotspot_exe_id,
  4147. * hotspot_question_id,
  4148. * hotspot_answer_id,
  4149. * hotspot_correct,
  4150. * hotspot_coordinate
  4151. * )
  4152. VALUES (
  4153. * '".Database::escape_string($_user['user_id'])."',
  4154. * '".Database::escape_string($_course['id'])."',
  4155. * '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
  4156. * '".Database::escape_string($answerId)."',
  4157. * '".Database::escape_string($studentChoice)."',
  4158. * '".Database::escape_string($user_array)."')";
  4159. $result = Database::query($sql,__FILE__,__LINE__);
  4160. */
  4161. $user_answer = $user_array;
  4162. // we compare only the delineation not the other points
  4163. $answer_question = $_SESSION['hotspot_coord'][1];
  4164. $answerDestination = $_SESSION['hotspot_dest'][1];
  4165. //calculating the area
  4166. $poly_user = convert_coordinates($user_answer, '/');
  4167. $poly_answer = convert_coordinates($answer_question, '|');
  4168. $max_coord = poly_get_max($poly_user, $poly_answer);
  4169. $poly_user_compiled = poly_compile($poly_user, $max_coord);
  4170. $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
  4171. $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
  4172. $overlap = $poly_results['both'];
  4173. $poly_answer_area = $poly_results['s1'];
  4174. $poly_user_area = $poly_results['s2'];
  4175. $missing = $poly_results['s1Only'];
  4176. $excess = $poly_results['s2Only'];
  4177. //$overlap = round(polygons_overlap($poly_answer,$poly_user)); //this is an area in pixels
  4178. if ($debug > 0) {
  4179. error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
  4180. }
  4181. if ($overlap < 1) {
  4182. //shortcut to avoid complicated calculations
  4183. $final_overlap = 0;
  4184. $final_missing = 100;
  4185. $final_excess = 100;
  4186. } else {
  4187. // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
  4188. $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
  4189. if ($debug > 1) {
  4190. error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
  4191. }
  4192. // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
  4193. $final_missing = 100 - $final_overlap;
  4194. if ($debug > 1) {
  4195. error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
  4196. }
  4197. // the final excess area is the percentage of the initial polygon's size that is covered by the user's polygon outside of the initial polygon
  4198. $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
  4199. if ($debug > 1) {
  4200. error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
  4201. }
  4202. }
  4203. //checking the destination parameters parsing the "@@"
  4204. $destination_items = explode('@@', $answerDestination);
  4205. $threadhold_total = $destination_items[0];
  4206. $threadhold_items = explode(';', $threadhold_total);
  4207. $threadhold1 = $threadhold_items[0]; // overlap
  4208. $threadhold2 = $threadhold_items[1]; // excess
  4209. $threadhold3 = $threadhold_items[2]; //missing
  4210. // if is delineation
  4211. if ($answerId === 1) {
  4212. //setting colors
  4213. if ($final_overlap >= $threadhold1) {
  4214. $overlap_color = true; //echo 'a';
  4215. }
  4216. //echo $excess.'-'.$threadhold2;
  4217. if ($final_excess <= $threadhold2) {
  4218. $excess_color = true; //echo 'b';
  4219. }
  4220. //echo '--------'.$missing.'-'.$threadhold3;
  4221. if ($final_missing <= $threadhold3) {
  4222. $missing_color = true; //echo 'c';
  4223. }
  4224. // if pass
  4225. if ($final_overlap >= $threadhold1 && $final_missing <= $threadhold3 && $final_excess <= $threadhold2) {
  4226. $next = 1; //go to the oars
  4227. $result_comment = get_lang('Acceptable');
  4228. $final_answer = 1; // do not update with update_exercise_attempt
  4229. } else {
  4230. $next = 0;
  4231. $result_comment = get_lang('Unacceptable');
  4232. $comment = $answerDestination = $objAnswerTmp->selectComment(1);
  4233. $answerDestination = $objAnswerTmp->selectDestination(1);
  4234. //checking the destination parameters parsing the "@@"
  4235. $destination_items = explode('@@', $answerDestination);
  4236. }
  4237. } elseif ($answerId > 1) {
  4238. if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
  4239. if ($debug > 0) {
  4240. error_log(__LINE__ . ' - answerId is of type noerror', 0);
  4241. }
  4242. //type no error shouldn't be treated
  4243. $next = 1;
  4244. continue;
  4245. }
  4246. if ($debug > 0) {
  4247. error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
  4248. }
  4249. //check the intersection between the oar and the user
  4250. //echo 'user'; print_r($x_user_list); print_r($y_user_list);
  4251. //echo 'official';print_r($x_list);print_r($y_list);
  4252. //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
  4253. $inter = $result['success'];
  4254. //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
  4255. $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
  4256. $poly_answer = convert_coordinates($delineation_cord, '|');
  4257. $max_coord = poly_get_max($poly_user, $poly_answer);
  4258. $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
  4259. $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
  4260. if ($overlap == false) {
  4261. //all good, no overlap
  4262. $next = 1;
  4263. continue;
  4264. } else {
  4265. if ($debug > 0) {
  4266. error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
  4267. }
  4268. $organs_at_risk_hit++;
  4269. //show the feedback
  4270. $next = 0;
  4271. $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
  4272. $answerDestination = $objAnswerTmp->selectDestination($answerId);
  4273. $destination_items = explode('@@', $answerDestination);
  4274. $try_hotspot = $destination_items[1];
  4275. $lp_hotspot = $destination_items[2];
  4276. $select_question_hotspot = $destination_items[3];
  4277. $url_hotspot=$destination_items[4];
  4278. }
  4279. }
  4280. } else { // the first delineation feedback
  4281. if ($debug > 0) {
  4282. error_log(__LINE__ . ' first', 0);
  4283. }
  4284. }
  4285. break;
  4286. case HOT_SPOT_ORDER:
  4287. ExerciseShowFunctions::display_hotspot_order_answer(
  4288. $feedback_type,
  4289. $answerId,
  4290. $answer,
  4291. $studentChoice,
  4292. $answerComment
  4293. );
  4294. break;
  4295. case DRAGGABLE:
  4296. //no break
  4297. case MATCHING_DRAGGABLE:
  4298. //no break
  4299. case MATCHING:
  4300. echo '<tr>';
  4301. echo Display::tag('td', $answerMatching[$answerId]);
  4302. echo Display::tag(
  4303. 'td',
  4304. "$user_answer / " . Display::tag(
  4305. 'strong',
  4306. $answerMatching[$answerCorrect],
  4307. ['style' => 'color: #008000; font-weight: bold;']
  4308. )
  4309. );
  4310. echo '</tr>';
  4311. break;
  4312. }
  4313. }
  4314. }
  4315. if ($debug) error_log(' ------ ');
  4316. } // end for that loops over all answers of the current question
  4317. if ($debug) error_log('-- end answer loop --');
  4318. $final_answer = true;
  4319. foreach ($real_answers as $my_answer) {
  4320. if (!$my_answer) {
  4321. $final_answer = false;
  4322. }
  4323. }
  4324. //we add the total score after dealing with the answers
  4325. if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
  4326. $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
  4327. ) {
  4328. if ($final_answer) {
  4329. //getting only the first score where we save the weight of all the question
  4330. $answerWeighting = $objAnswerTmp->selectWeighting(1);
  4331. $questionScore += $answerWeighting;
  4332. $totalScore += $answerWeighting;
  4333. }
  4334. }
  4335. //Fixes multiple answer question in order to be exact
  4336. //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
  4337. /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
  4338. $diff = @array_diff($answer_correct_array, $real_answers);
  4339. // All good answers or nothing works like exact
  4340. $counter = 1;
  4341. $correct_answer = true;
  4342. foreach ($real_answers as $my_answer) {
  4343. if ($debug)
  4344. error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
  4345. if ($my_answer != $answer_correct_array[$counter]) {
  4346. $correct_answer = false;
  4347. break;
  4348. }
  4349. $counter++;
  4350. }
  4351. if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
  4352. if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
  4353. if ($debug) error_log(" correct_answer: ".$correct_answer);
  4354. if ($correct_answer == false) {
  4355. $questionScore = 0;
  4356. }
  4357. // This makes the result non exact
  4358. if (!empty($diff)) {
  4359. $questionScore = 0;
  4360. }
  4361. }*/
  4362. $extra_data = array(
  4363. 'final_overlap' => $final_overlap,
  4364. 'final_missing'=>$final_missing,
  4365. 'final_excess'=> $final_excess,
  4366. 'overlap_color' => $overlap_color,
  4367. 'missing_color'=>$missing_color,
  4368. 'excess_color'=> $excess_color,
  4369. 'threadhold1' => $threadhold1,
  4370. 'threadhold2'=>$threadhold2,
  4371. 'threadhold3'=> $threadhold3,
  4372. );
  4373. if ($from == 'exercise_result') {
  4374. // if answer is hotspot. To the difference of exercise_show.php,
  4375. // we use the results from the session (from_db=0)
  4376. // TODO Change this, because it is wrong to show the user
  4377. // some results that haven't been stored in the database yet
  4378. if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
  4379. if ($debug) error_log('$from AND this is a hotspot kind of question ');
  4380. $my_exe_id = 0;
  4381. $from_database = 0;
  4382. if ($answerType == HOT_SPOT_DELINEATION) {
  4383. if (0) {
  4384. if ($overlap_color) {
  4385. $overlap_color='green';
  4386. } else {
  4387. $overlap_color='red';
  4388. }
  4389. if ($missing_color) {
  4390. $missing_color='green';
  4391. } else {
  4392. $missing_color='red';
  4393. }
  4394. if ($excess_color) {
  4395. $excess_color='green';
  4396. } else {
  4397. $excess_color='red';
  4398. }
  4399. if (!is_numeric($final_overlap)) {
  4400. $final_overlap = 0;
  4401. }
  4402. if (!is_numeric($final_missing)) {
  4403. $final_missing = 0;
  4404. }
  4405. if (!is_numeric($final_excess)) {
  4406. $final_excess = 0;
  4407. }
  4408. if ($final_overlap>100) {
  4409. $final_overlap = 100;
  4410. }
  4411. $table_resume='<table class="data_table">
  4412. <tr class="row_odd" >
  4413. <td></td>
  4414. <td ><b>' . get_lang('Requirements') . '</b></td>
  4415. <td><b>' . get_lang('YourAnswer') . '</b></td>
  4416. </tr>
  4417. <tr class="row_even">
  4418. <td><b>' . get_lang('Overlap') . '</b></td>
  4419. <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
  4420. <td><div style="color:' . $overlap_color . '">'
  4421. . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
  4422. </tr>
  4423. <tr>
  4424. <td><b>' . get_lang('Excess') . '</b></td>
  4425. <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
  4426. <td><div style="color:' . $excess_color . '">'
  4427. . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
  4428. </tr>
  4429. <tr class="row_even">
  4430. <td><b>' . get_lang('Missing') . '</b></td>
  4431. <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
  4432. <td><div style="color:' . $missing_color . '">'
  4433. . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
  4434. </tr>
  4435. </table>';
  4436. if ($next == 0) {
  4437. $try = $try_hotspot;
  4438. $lp = $lp_hotspot;
  4439. $destinationid = $select_question_hotspot;
  4440. $url = $url_hotspot;
  4441. } else {
  4442. //show if no error
  4443. //echo 'no error';
  4444. $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
  4445. $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
  4446. }
  4447. echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
  4448. <p style="text-align:center">';
  4449. $message = '<p>' . get_lang('YourDelineation') . '</p>';
  4450. $message .= $table_resume;
  4451. $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
  4452. if ($organs_at_risk_hit > 0) {
  4453. $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
  4454. }
  4455. $message .='<p>' . $comment . '</p>';
  4456. echo $message;
  4457. } else {
  4458. echo $hotspot_delineation_result[0]; //prints message
  4459. $from_database = 1; // the hotspot_solution.swf needs this variable
  4460. }
  4461. //save the score attempts
  4462. if (1) {
  4463. //getting the answer 1 or 0 comes from exercise_submit_modal.php
  4464. $final_answer = $hotspot_delineation_result[1];
  4465. if ($final_answer == 0) {
  4466. $questionScore = 0;
  4467. }
  4468. // we always insert the answer_id 1 = delineation
  4469. Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
  4470. //in delineation mode, get the answer from $hotspot_delineation_result[1]
  4471. Event::saveExerciseAttemptHotspot(
  4472. $exeId,
  4473. $quesId,
  4474. 1,
  4475. $hotspot_delineation_result[1],
  4476. $exerciseResultCoordinates[$quesId]
  4477. );
  4478. } else {
  4479. if ($final_answer==0) {
  4480. $questionScore = 0;
  4481. $answer=0;
  4482. Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
  4483. if (is_array($exerciseResultCoordinates[$quesId])) {
  4484. foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
  4485. Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,0,$val);
  4486. }
  4487. }
  4488. } else {
  4489. Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
  4490. if (is_array($exerciseResultCoordinates[$quesId])) {
  4491. foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
  4492. Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,$choice[$idx],$val);
  4493. }
  4494. }
  4495. }
  4496. }
  4497. $my_exe_id = $exeId;
  4498. }
  4499. }
  4500. if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
  4501. // We made an extra table for the answers
  4502. if ($show_result) {
  4503. $relPath = api_get_path(REL_PATH);
  4504. // if ($origin != 'learnpath') {
  4505. echo '</table></td></tr>';
  4506. echo "
  4507. <tr>
  4508. <td colspan=\"2\">
  4509. <p><em>" . get_lang('HotSpot') . "</em></p>
  4510. <div id=\"hotspot-solution-$questionId\"></div>
  4511. <script>
  4512. $(document).on('ready', function () {
  4513. new HotspotQuestion({
  4514. questionId: $questionId,
  4515. exerciseId: $exeId,
  4516. selector: '#hotspot-solution-$questionId',
  4517. for: 'solution',
  4518. relPath: '$relPath'
  4519. });
  4520. });
  4521. </script>
  4522. </td>
  4523. </tr>
  4524. ";
  4525. // }
  4526. }
  4527. }
  4528. //if ($origin != 'learnpath') {
  4529. if ($show_result) {
  4530. echo '</table>';
  4531. }
  4532. // }
  4533. }
  4534. unset ($objAnswerTmp);
  4535. $totalWeighting += $questionWeighting;
  4536. // Store results directly in the database
  4537. // For all in one page exercises, the results will be
  4538. // stored by exercise_results.php (using the session)
  4539. if ($saved_results) {
  4540. if ($debug) error_log("Save question results $saved_results");
  4541. if ($debug) error_log(print_r($choice ,1 ));
  4542. if (empty($choice)) {
  4543. $choice = 0;
  4544. }
  4545. if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
  4546. if ($choice != 0) {
  4547. $reply = array_keys($choice);
  4548. for ($i = 0; $i < sizeof($reply); $i++) {
  4549. $ans = $reply[$i];
  4550. Event::saveQuestionAttempt(
  4551. $questionScore,
  4552. $ans . ':' . $choice[$ans],
  4553. $quesId,
  4554. $exeId,
  4555. $i,
  4556. $this->id
  4557. );
  4558. if ($debug) {
  4559. error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
  4560. }
  4561. }
  4562. } else {
  4563. Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
  4564. }
  4565. } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
  4566. if ($choice != 0) {
  4567. $reply = array_keys($choice);
  4568. if ($debug) {
  4569. error_log("reply " . print_r($reply, 1) . "");
  4570. }
  4571. for ($i = 0; $i < sizeof($reply); $i++) {
  4572. $ans = $reply[$i];
  4573. Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
  4574. }
  4575. } else {
  4576. Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
  4577. }
  4578. } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
  4579. if ($choice != 0) {
  4580. $reply = array_keys($choice);
  4581. for ($i = 0; $i < sizeof($reply); $i++) {
  4582. $ans = $reply[$i];
  4583. Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
  4584. }
  4585. } else {
  4586. Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
  4587. }
  4588. } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
  4589. if (isset($matching)) {
  4590. foreach ($matching as $j => $val) {
  4591. Event::saveQuestionAttempt($questionScore, $val, $quesId, $exeId, $j, $this->id);
  4592. }
  4593. }
  4594. } elseif ($answerType == FREE_ANSWER) {
  4595. $answer = $choice;
  4596. Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
  4597. } elseif ($answerType == ORAL_EXPRESSION) {
  4598. $answer = $choice;
  4599. Event::saveQuestionAttempt(
  4600. $questionScore,
  4601. $answer,
  4602. $quesId,
  4603. $exeId,
  4604. 0,
  4605. $this->id,
  4606. false,
  4607. $objQuestionTmp->getAbsoluteFilePath()
  4608. );
  4609. } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
  4610. $answer = $choice;
  4611. Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
  4612. // } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
  4613. } elseif ($answerType == HOT_SPOT) {
  4614. $answer = [];
  4615. if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
  4616. Database::delete(
  4617. Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
  4618. [
  4619. 'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
  4620. $exeId,
  4621. $questionId,
  4622. api_get_course_int_id()
  4623. ]
  4624. ]
  4625. );
  4626. foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
  4627. $answer[] = $val;
  4628. Event::saveExerciseAttemptHotspot($exeId, $quesId, $idx, $choice[$idx], $val, false, $this->id);
  4629. }
  4630. }
  4631. Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
  4632. } else {
  4633. Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
  4634. }
  4635. }
  4636. if ($propagate_neg == 0 && $questionScore < 0) {
  4637. $questionScore = 0;
  4638. }
  4639. if ($saved_results) {
  4640. $stat_table = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  4641. $sql = 'UPDATE ' . $stat_table . ' SET
  4642. exe_result = exe_result + ' . floatval($questionScore) . '
  4643. WHERE exe_id = ' . $exeId;
  4644. Database::query($sql);
  4645. }
  4646. $return_array = array(
  4647. 'score' => $questionScore,
  4648. 'weight' => $questionWeighting,
  4649. 'extra' => $extra_data,
  4650. 'open_question' => $arrques,
  4651. 'open_answer' => $arrans,
  4652. 'answer_type' => $answerType
  4653. );
  4654. return $return_array;
  4655. }
  4656. /**
  4657. * Sends a notification when a user ends an examn
  4658. *
  4659. * @param integer $exe_id
  4660. */
  4661. public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id)
  4662. {
  4663. if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
  4664. return null;
  4665. }
  4666. // Email configuration settings
  4667. $courseCode = api_get_course_id();
  4668. $courseInfo = api_get_course_info($courseCode);
  4669. $sessionId = api_get_session_id();
  4670. if (empty($courseInfo)) {
  4671. return false;
  4672. }
  4673. $url_email = api_get_path(WEB_CODE_PATH)
  4674. . 'exercise/exercise_show.php?'
  4675. . api_get_cidreq()
  4676. . '&id_session='
  4677. . $sessionId
  4678. . '&id='
  4679. . $exe_id
  4680. . '&action=qualify';
  4681. $user_info = api_get_user_info(api_get_user_id());
  4682. $msg = get_lang('ExerciseAttempted').'<br /><br />'
  4683. .get_lang('AttemptDetails').' : <br /><br />'.
  4684. '<table>'
  4685. .'<tr>'
  4686. .'<td><em>'.get_lang('CourseName').'</em></td>'
  4687. .'<td>&nbsp;<b>#course#</b></td>'
  4688. .'</tr>'
  4689. .'<tr>'
  4690. .'<td>'.get_lang('TestAttempted').'</td>'
  4691. .'<td>&nbsp;#exercise#</td>'
  4692. .'</tr>'
  4693. .'<tr>'
  4694. .'<td>'.get_lang('StudentName').'</td>'
  4695. .'<td>&nbsp;#firstName# #lastName#</td>'
  4696. .'</tr>'
  4697. .'<tr>'
  4698. .'<td>'.get_lang('StudentEmail').'</td>'
  4699. .'<td>&nbsp;#email#</td>'
  4700. .'</tr>'
  4701. .'</table>';
  4702. $open_question_list = null;
  4703. $msg = str_replace("#email#", $user_info['email'], $msg);
  4704. $msg1 = str_replace("#exercise#", $this->exercise, $msg);
  4705. $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
  4706. $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
  4707. $msg = str_replace("#course#", $courseInfo['name'], $msg1);
  4708. if ($origin != 'learnpath') {
  4709. $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
  4710. }
  4711. $msg1 = str_replace("#url#", $url_email, $msg);
  4712. $mail_content = $msg1;
  4713. $subject = get_lang('ExerciseAttempted');
  4714. if (!empty($sessionId)) {
  4715. $teachers = CourseManager::get_coach_list_from_course_code($courseCode, $sessionId);
  4716. } else {
  4717. $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
  4718. }
  4719. if (!empty($teachers)) {
  4720. foreach ($teachers as $user_id => $teacher_data) {
  4721. MessageManager::send_message_simple(
  4722. $user_id,
  4723. $subject,
  4724. $mail_content
  4725. );
  4726. }
  4727. }
  4728. }
  4729. /**
  4730. * Sends a notification when a user ends an examn
  4731. *
  4732. * @param integer $exe_id
  4733. */
  4734. function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
  4735. {
  4736. if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
  4737. return null;
  4738. }
  4739. // Email configuration settings
  4740. $courseCode = api_get_course_id();
  4741. $course_info = api_get_course_info($courseCode);
  4742. $url_email = api_get_path(WEB_CODE_PATH)
  4743. . 'exercise/exercise_show.php?'
  4744. . api_get_cidreq()
  4745. . '&id_session='
  4746. . api_get_session_id()
  4747. . '&id='
  4748. . $exe_id
  4749. . '&action=qualify';
  4750. $user_info = api_get_user_info(api_get_user_id());
  4751. $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
  4752. .get_lang('AttemptDetails').' : <br /><br />'
  4753. .'<table>'
  4754. .'<tr>'
  4755. .'<td><em>'.get_lang('CourseName').'</em></td>'
  4756. .'<td>&nbsp;<b>#course#</b></td>'
  4757. .'</tr>'
  4758. .'<tr>'
  4759. .'<td>'.get_lang('TestAttempted').'</td>'
  4760. .'<td>&nbsp;#exercise#</td>'
  4761. .'</tr>'
  4762. .'<tr>'
  4763. .'<td>'.get_lang('StudentName').'</td>'
  4764. .'<td>&nbsp;#firstName# #lastName#</td>'
  4765. .'</tr>'
  4766. .'<tr>'
  4767. .'<td>'.get_lang('StudentEmail').'</td>'
  4768. .'<td>&nbsp;#mail#</td>'
  4769. .'</tr>'
  4770. .'</table>';
  4771. $open_question_list = null;
  4772. foreach ($question_list_answers as $item) {
  4773. $question = $item['question'];
  4774. $answer = $item['answer'];
  4775. $answer_type = $item['answer_type'];
  4776. if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
  4777. $open_question_list .=
  4778. '<tr>'
  4779. .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
  4780. .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
  4781. .'</tr>'
  4782. .'<tr>'
  4783. .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
  4784. .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
  4785. .'</tr>';
  4786. }
  4787. }
  4788. if (!empty($open_question_list)) {
  4789. $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
  4790. '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
  4791. $msg .= $open_question_list;
  4792. $msg .= '</table><br />';
  4793. $msg1 = str_replace("#exercise#", $this->exercise, $msg);
  4794. $msg = str_replace("#firstName#", $user_info['firstname'],$msg1);
  4795. $msg1 = str_replace("#lastName#", $user_info['lastname'],$msg);
  4796. $msg = str_replace("#mail#", $user_info['email'],$msg1);
  4797. $msg = str_replace("#course#", $course_info['name'],$msg1);
  4798. if ($origin != 'learnpath') {
  4799. $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
  4800. }
  4801. $msg1 = str_replace("#url#", $url_email, $msg);
  4802. $mail_content = $msg1;
  4803. $subject = get_lang('OpenQuestionsAttempted');
  4804. if (api_get_session_id()) {
  4805. $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
  4806. } else {
  4807. $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
  4808. }
  4809. if (!empty($teachers)) {
  4810. foreach ($teachers as $user_id => $teacher_data) {
  4811. MessageManager::send_message_simple(
  4812. $user_id,
  4813. $subject,
  4814. $mail_content
  4815. );
  4816. }
  4817. }
  4818. }
  4819. }
  4820. function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
  4821. {
  4822. if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
  4823. return null;
  4824. }
  4825. // Email configuration settings
  4826. $courseCode = api_get_course_id();
  4827. $course_info = api_get_course_info($courseCode);
  4828. $url_email = api_get_path(WEB_CODE_PATH)
  4829. . 'exercise/exercise_show.php?'
  4830. . api_get_cidreq()
  4831. . '&id_session='
  4832. . api_get_session_id()
  4833. . '&id='
  4834. . $exe_id
  4835. . '&action=qualify';
  4836. $user_info = api_get_user_info(api_get_user_id());
  4837. $oral_question_list = null;
  4838. foreach ($question_list_answers as $item) {
  4839. $question = $item['question'];
  4840. $answer = $item['answer'];
  4841. $answer_type = $item['answer_type'];
  4842. if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
  4843. $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
  4844. .'<tr>'
  4845. .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
  4846. .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
  4847. .'</tr>'
  4848. .'<tr>'
  4849. .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
  4850. .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
  4851. .'</tr></table>';
  4852. }
  4853. }
  4854. if (!empty($oral_question_list)) {
  4855. $msg = get_lang('OralQuestionsAttempted').'<br /><br />
  4856. '.get_lang('AttemptDetails').' : <br /><br />'
  4857. .'<table>'
  4858. .'<tr>'
  4859. .'<td><em>'.get_lang('CourseName').'</em></td>'
  4860. .'<td>&nbsp;<b>#course#</b></td>'
  4861. .'</tr>'
  4862. .'<tr>'
  4863. .'<td>'.get_lang('TestAttempted').'</td>'
  4864. .'<td>&nbsp;#exercise#</td>'
  4865. .'</tr>'
  4866. .'<tr>'
  4867. .'<td>'.get_lang('StudentName').'</td>'
  4868. .'<td>&nbsp;#firstName# #lastName#</td>'
  4869. .'</tr>'
  4870. .'<tr>'
  4871. .'<td>'.get_lang('StudentEmail').'</td>'
  4872. .'<td>&nbsp;#mail#</td>'
  4873. .'</tr>'
  4874. .'</table>';
  4875. $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
  4876. $msg1 = str_replace("#exercise#", $this->exercise, $msg);
  4877. $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
  4878. $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
  4879. $msg = str_replace("#mail#", $user_info['email'], $msg1);
  4880. $msg = str_replace("#course#", $course_info['name'], $msg1);
  4881. if ($origin != 'learnpath') {
  4882. $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
  4883. }
  4884. $msg1 = str_replace("#url#", $url_email, $msg);
  4885. $mail_content = $msg1;
  4886. $subject = get_lang('OralQuestionsAttempted');
  4887. if (api_get_session_id()) {
  4888. $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
  4889. } else {
  4890. $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
  4891. }
  4892. if (!empty($teachers)) {
  4893. foreach ($teachers as $user_id => $teacher_data) {
  4894. MessageManager::send_message_simple(
  4895. $user_id,
  4896. $subject,
  4897. $mail_content
  4898. );
  4899. }
  4900. }
  4901. }
  4902. }
  4903. /**
  4904. * @param array $user_data result of api_get_user_info()
  4905. * @param string $start_date
  4906. * @param null $duration
  4907. * @param string $ip Optional. The user IP
  4908. * @return string
  4909. */
  4910. public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
  4911. {
  4912. $array = array();
  4913. if (!empty($user_data)) {
  4914. $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
  4915. $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
  4916. if (!empty($user_data['official_code'])) {
  4917. $array[] = array(
  4918. 'title' => get_lang('OfficialCode'),
  4919. 'content' => $user_data['official_code']
  4920. );
  4921. }
  4922. }
  4923. // Description can be very long and is generally meant to explain
  4924. // rules *before* the exam. Leaving here to make display easier if
  4925. // necessary
  4926. /*
  4927. if (!empty($this->description)) {
  4928. $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
  4929. }
  4930. */
  4931. if (!empty($start_date)) {
  4932. $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
  4933. }
  4934. if (!empty($duration)) {
  4935. $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
  4936. }
  4937. if (!empty($ip)) {
  4938. $array[] = array('title' => get_lang('IP'), 'content' => $ip);
  4939. }
  4940. $html = '<div class="question-result">';
  4941. $html .= Display::page_header(
  4942. Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
  4943. );
  4944. $html .= Display::description($array);
  4945. $html .="</div>";
  4946. return $html;
  4947. }
  4948. /**
  4949. * Create a quiz from quiz data
  4950. * @param string Title
  4951. * @param int Time before it expires (in minutes)
  4952. * @param int Type of exercise
  4953. * @param int Whether it's randomly picked questions (1) or not (0)
  4954. * @param int Whether the exercise is visible to the user (1) or not (0)
  4955. * @param int Whether the results are show to the user (0) or not (1)
  4956. * @param int Maximum number of attempts (0 if no limit)
  4957. * @param int Feedback type
  4958. * @todo this was function was added due the import exercise via CSV
  4959. * @return int New exercise ID
  4960. */
  4961. public function createExercise(
  4962. $title,
  4963. $expired_time = 0,
  4964. $type = 2,
  4965. $random = 0,
  4966. $active = 1,
  4967. $results_disabled = 0,
  4968. $max_attempt = 0,
  4969. $feedback = 3,
  4970. $propagateNegative = 0
  4971. ) {
  4972. $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
  4973. $type = intval($type);
  4974. $random = intval($random);
  4975. $active = intval($active);
  4976. $results_disabled = intval($results_disabled);
  4977. $max_attempt = intval($max_attempt);
  4978. $feedback = intval($feedback);
  4979. $expired_time = intval($expired_time);
  4980. $title = Database::escape_string($title);
  4981. $propagateNegative = intval($propagateNegative);
  4982. $sessionId = api_get_session_id();
  4983. $course_id = api_get_course_int_id();
  4984. // Save a new quiz
  4985. $sql = "INSERT INTO $tbl_quiz (
  4986. c_id,
  4987. title,
  4988. type,
  4989. random,
  4990. active,
  4991. results_disabled,
  4992. max_attempt,
  4993. start_time,
  4994. end_time,
  4995. feedback_type,
  4996. expired_time,
  4997. session_id,
  4998. propagate_neg
  4999. )
  5000. VALUES (
  5001. '$course_id',
  5002. '$title',
  5003. $type,
  5004. $random,
  5005. $active,
  5006. $results_disabled,
  5007. $max_attempt,
  5008. '',
  5009. '',
  5010. $feedback,
  5011. $expired_time,
  5012. $sessionId,
  5013. $propagateNegative
  5014. )";
  5015. Database::query($sql);
  5016. $quiz_id = Database::insert_id();
  5017. if ($quiz_id) {
  5018. $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
  5019. Database::query($sql);
  5020. }
  5021. return $quiz_id;
  5022. }
  5023. function process_geometry()
  5024. {
  5025. }
  5026. /**
  5027. * Returns the exercise result
  5028. * @param int attempt id
  5029. * @return float exercise result
  5030. */
  5031. public function get_exercise_result($exe_id)
  5032. {
  5033. $result = array();
  5034. $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
  5035. if (!empty($track_exercise_info)) {
  5036. $totalScore = 0;
  5037. $objExercise = new Exercise();
  5038. $objExercise->read($track_exercise_info['exe_exo_id']);
  5039. if (!empty($track_exercise_info['data_tracking'])) {
  5040. $question_list = explode(',', $track_exercise_info['data_tracking']);
  5041. }
  5042. foreach ($question_list as $questionId) {
  5043. $question_result = $objExercise->manage_answer(
  5044. $exe_id,
  5045. $questionId,
  5046. '',
  5047. 'exercise_show',
  5048. array(),
  5049. false,
  5050. true,
  5051. false,
  5052. $objExercise->selectPropagateNeg()
  5053. );
  5054. $totalScore += $question_result['score'];
  5055. }
  5056. if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
  5057. $totalScore = 0;
  5058. }
  5059. $result = array(
  5060. 'score' => $totalScore,
  5061. 'weight' => $track_exercise_info['exe_weighting']
  5062. );
  5063. }
  5064. return $result;
  5065. }
  5066. /**
  5067. * Checks if the exercise is visible due a lot of conditions
  5068. * visibility, time limits, student attempts
  5069. * Return associative array
  5070. * value : true if execise visible
  5071. * message : HTML formated message
  5072. * rawMessage : text message
  5073. * @param int $lpId
  5074. * @param int $lpItemId
  5075. * @param int $lpItemViewId
  5076. * @param bool $filterByAdmin
  5077. * @return array
  5078. */
  5079. public function is_visible(
  5080. $lpId = 0,
  5081. $lpItemId = 0,
  5082. $lpItemViewId = 0,
  5083. $filterByAdmin = true
  5084. ) {
  5085. // 1. By default the exercise is visible
  5086. $isVisible = true;
  5087. $message = null;
  5088. // 1.1 Admins and teachers can access to the exercise
  5089. if ($filterByAdmin) {
  5090. if (api_is_platform_admin() || api_is_course_admin()) {
  5091. return array('value' => true, 'message' => '');
  5092. }
  5093. }
  5094. // Deleted exercise.
  5095. if ($this->active == -1) {
  5096. return array(
  5097. 'value' => false,
  5098. 'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
  5099. 'rawMessage' => get_lang('ExerciseNotFound')
  5100. );
  5101. }
  5102. // Checking visibility in the item_property table.
  5103. $visibility = api_get_item_visibility(
  5104. api_get_course_info(),
  5105. TOOL_QUIZ,
  5106. $this->id,
  5107. api_get_session_id()
  5108. );
  5109. if ($visibility == 0 || $visibility == 2) {
  5110. $this->active = 0;
  5111. }
  5112. // 2. If the exercise is not active.
  5113. if (empty($lpId)) {
  5114. // 2.1 LP is OFF
  5115. if ($this->active == 0) {
  5116. return array(
  5117. 'value' => false,
  5118. 'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
  5119. 'rawMessage' => get_lang('ExerciseNotFound')
  5120. );
  5121. }
  5122. } else {
  5123. // 2.1 LP is loaded
  5124. if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
  5125. return array(
  5126. 'value' => false,
  5127. 'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
  5128. 'rawMessage' => get_lang('ExerciseNotFound')
  5129. );
  5130. }
  5131. }
  5132. //3. We check if the time limits are on
  5133. if (!empty($this->start_time) || !empty($this->end_time)) {
  5134. $limitTimeExists = true;
  5135. } else {
  5136. $limitTimeExists = false;
  5137. }
  5138. if ($limitTimeExists) {
  5139. $timeNow = time();
  5140. $existsStartDate = false;
  5141. $nowIsAfterStartDate = true;
  5142. $existsEndDate = false;
  5143. $nowIsBeforeEndDate = true;
  5144. if (!empty($this->start_time)) {
  5145. $existsStartDate = true;
  5146. }
  5147. if (!empty($this->end_time)) {
  5148. $existsEndDate = true;
  5149. }
  5150. // check if we are before-or-after end-or-start date
  5151. if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
  5152. $nowIsAfterStartDate = false;
  5153. }
  5154. if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
  5155. $nowIsBeforeEndDate = false;
  5156. }
  5157. // lets check all cases
  5158. if ($existsStartDate && !$existsEndDate) {
  5159. // exists start date and dont exists end date
  5160. if ($nowIsAfterStartDate) {
  5161. // after start date, no end date
  5162. $isVisible = true;
  5163. $message = sprintf(get_lang('ExerciseAvailableSinceX'),
  5164. api_convert_and_format_date($this->start_time));
  5165. } else {
  5166. // before start date, no end date
  5167. $isVisible = false;
  5168. $message = sprintf(get_lang('ExerciseAvailableFromX'),
  5169. api_convert_and_format_date($this->start_time));
  5170. }
  5171. } else if (!$existsStartDate && $existsEndDate) {
  5172. // doesnt exist start date, exists end date
  5173. if ($nowIsBeforeEndDate) {
  5174. // before end date, no start date
  5175. $isVisible = true;
  5176. $message = sprintf(get_lang('ExerciseAvailableUntilX'),
  5177. api_convert_and_format_date($this->end_time));
  5178. } else {
  5179. // after end date, no start date
  5180. $isVisible = false;
  5181. $message = sprintf(get_lang('ExerciseAvailableUntilX'),
  5182. api_convert_and_format_date($this->end_time));
  5183. }
  5184. } elseif ($existsStartDate && $existsEndDate) {
  5185. // exists start date and end date
  5186. if ($nowIsAfterStartDate) {
  5187. if ($nowIsBeforeEndDate) {
  5188. // after start date and before end date
  5189. $isVisible = true;
  5190. $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
  5191. api_convert_and_format_date($this->start_time),
  5192. api_convert_and_format_date($this->end_time));
  5193. } else {
  5194. // after start date and after end date
  5195. $isVisible = false;
  5196. $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
  5197. api_convert_and_format_date($this->start_time),
  5198. api_convert_and_format_date($this->end_time));
  5199. }
  5200. } else {
  5201. if ($nowIsBeforeEndDate) {
  5202. // before start date and before end date
  5203. $isVisible = false;
  5204. $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
  5205. api_convert_and_format_date($this->start_time),
  5206. api_convert_and_format_date($this->end_time));
  5207. }
  5208. // case before start date and after end date is impossible
  5209. }
  5210. } elseif (!$existsStartDate && !$existsEndDate) {
  5211. // doesnt exist start date nor end date
  5212. $isVisible = true;
  5213. $message = "";
  5214. }
  5215. }
  5216. // 4. We check if the student have attempts
  5217. $exerciseAttempts = $this->selectAttempts();
  5218. if ($isVisible) {
  5219. if ($exerciseAttempts > 0) {
  5220. $attemptCount = Event::get_attempt_count_not_finished(
  5221. api_get_user_id(),
  5222. $this->id,
  5223. $lpId,
  5224. $lpItemId,
  5225. $lpItemViewId
  5226. );
  5227. if ($attemptCount >= $exerciseAttempts) {
  5228. $message = sprintf(
  5229. get_lang('ReachedMaxAttempts'),
  5230. $this->name,
  5231. $exerciseAttempts
  5232. );
  5233. $isVisible = false;
  5234. }
  5235. }
  5236. }
  5237. $rawMessage = "";
  5238. if (!empty($message)){
  5239. $rawMessage = $message;
  5240. $message = Display::return_message($message, 'warning', false);
  5241. }
  5242. return array(
  5243. 'value' => $isVisible,
  5244. 'message' => $message,
  5245. 'rawMessage' => $rawMessage
  5246. );
  5247. }
  5248. public function added_in_lp()
  5249. {
  5250. $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
  5251. $sql = "SELECT max_score FROM $TBL_LP_ITEM
  5252. WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
  5253. $result = Database::query($sql);
  5254. if (Database::num_rows($result) > 0) {
  5255. return true;
  5256. }
  5257. return false;
  5258. }
  5259. /**
  5260. * Returns an array with the media list
  5261. * @param array question list
  5262. * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
  5263. * <code>
  5264. * array (size=2)
  5265. * 999 =>
  5266. * array (size=3)
  5267. * 0 => int 7
  5268. * 1 => int 6
  5269. * 2 => int 3254
  5270. * 100 =>
  5271. * array (size=1)
  5272. * 0 => int 5
  5273. * </code>
  5274. * @return array
  5275. */
  5276. private function setMediaList($questionList)
  5277. {
  5278. $mediaList = array();
  5279. if (!empty($questionList)) {
  5280. foreach ($questionList as $questionId) {
  5281. $objQuestionTmp = Question::read($questionId, $this->course_id);
  5282. // If a media question exists
  5283. if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
  5284. $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
  5285. } else {
  5286. //Always the last item
  5287. $mediaList[999][] = $objQuestionTmp->id;
  5288. }
  5289. }
  5290. }
  5291. $this->mediaList = $mediaList;
  5292. }
  5293. /**
  5294. * Returns an array with this form
  5295. * @example
  5296. * <code>
  5297. * array (size=3)
  5298. 999 =>
  5299. array (size=3)
  5300. 0 => int 3422
  5301. 1 => int 3423
  5302. 2 => int 3424
  5303. 100 =>
  5304. array (size=2)
  5305. 0 => int 3469
  5306. 1 => int 3470
  5307. 101 =>
  5308. array (size=1)
  5309. 0 => int 3482
  5310. * </code>
  5311. * The array inside the key 999 means the question list that belongs to the media id = 999,
  5312. * this case is special because 999 means "no media".
  5313. * @return array
  5314. */
  5315. public function getMediaList()
  5316. {
  5317. return $this->mediaList;
  5318. }
  5319. /**
  5320. * Is media question activated?
  5321. * @return bool
  5322. */
  5323. public function mediaIsActivated()
  5324. {
  5325. $mediaQuestions = $this->getMediaList();
  5326. $active = false;
  5327. if (isset($mediaQuestions) && !empty($mediaQuestions)) {
  5328. $media_count = count($mediaQuestions);
  5329. if ($media_count > 1) {
  5330. return true;
  5331. } elseif ($media_count == 1) {
  5332. if (isset($mediaQuestions[999])) {
  5333. return false;
  5334. } else {
  5335. return true;
  5336. }
  5337. }
  5338. }
  5339. return $active;
  5340. }
  5341. /**
  5342. * Gets question list from the exercise
  5343. *
  5344. * @return array
  5345. */
  5346. public function getQuestionList()
  5347. {
  5348. return $this->questionList;
  5349. }
  5350. /**
  5351. * Question list with medias compressed like this
  5352. * @example
  5353. * <code>
  5354. * array(
  5355. * question_id_1,
  5356. * question_id_2,
  5357. * media_id, <- this media id contains question ids
  5358. * question_id_3,
  5359. * )
  5360. * </code>
  5361. * @return array
  5362. */
  5363. public function getQuestionListWithMediasCompressed()
  5364. {
  5365. return $this->questionList;
  5366. }
  5367. /**
  5368. * Question list with medias uncompressed like this
  5369. * @example
  5370. * <code>
  5371. * array(
  5372. * question_id,
  5373. * question_id,
  5374. * question_id, <- belongs to a media id
  5375. * question_id, <- belongs to a media id
  5376. * question_id,
  5377. * )
  5378. * </code>
  5379. * @return array
  5380. */
  5381. public function getQuestionListWithMediasUncompressed()
  5382. {
  5383. return $this->questionListUncompressed;
  5384. }
  5385. /**
  5386. * Sets the question list when the exercise->read() is executed
  5387. */
  5388. public function setQuestionList()
  5389. {
  5390. // Getting question list.
  5391. $questionList = $this->selectQuestionList(true);
  5392. $this->setMediaList($questionList);
  5393. $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
  5394. $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
  5395. }
  5396. /**
  5397. *
  5398. * @params array question list
  5399. * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
  5400. *
  5401. **/
  5402. public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
  5403. {
  5404. $new_question_list = array();
  5405. if (!empty($question_list)) {
  5406. $media_questions = $this->getMediaList();
  5407. $media_active = $this->mediaIsActivated($media_questions);
  5408. if ($media_active) {
  5409. $counter = 1;
  5410. foreach ($question_list as $question_id) {
  5411. $add_question = true;
  5412. foreach ($media_questions as $media_id => $question_list_in_media) {
  5413. if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
  5414. $add_question = false;
  5415. if (!in_array($media_id, $new_question_list)) {
  5416. $new_question_list[$counter] = $media_id;
  5417. $counter++;
  5418. }
  5419. break;
  5420. }
  5421. }
  5422. if ($add_question) {
  5423. $new_question_list[$counter] = $question_id;
  5424. $counter++;
  5425. }
  5426. }
  5427. if ($expand_media_questions) {
  5428. $media_key_list = array_keys($media_questions);
  5429. foreach ($new_question_list as &$question_id) {
  5430. if (in_array($question_id, $media_key_list)) {
  5431. $question_id = $media_questions[$question_id];
  5432. }
  5433. }
  5434. $new_question_list = array_flatten($new_question_list);
  5435. }
  5436. } else {
  5437. $new_question_list = $question_list;
  5438. }
  5439. }
  5440. return $new_question_list;
  5441. }
  5442. function get_validated_question_list()
  5443. {
  5444. $tabres = array();
  5445. $isRandomByCategory = $this->isRandomByCat();
  5446. if ($isRandomByCategory == 0) {
  5447. if ($this->isRandom()) {
  5448. $tabres = $this->selectRandomList();
  5449. } else {
  5450. $tabres = $this->selectQuestionList();
  5451. }
  5452. } else {
  5453. if ($this->isRandom()) {
  5454. // USE question categories
  5455. // get questions by category for this exercise
  5456. // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
  5457. // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
  5458. // value is the array of question id of this category
  5459. $questionList = array();
  5460. $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
  5461. $isRandomByCategory = $this->selectRandomByCat();
  5462. // We sort categories based on the term between [] in the head
  5463. // of the category's description
  5464. /* examples of categories :
  5465. * [biologie] Maitriser les mecanismes de base de la genetique
  5466. * [biologie] Relier les moyens de depenses et les agents infectieux
  5467. * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
  5468. * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
  5469. * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
  5470. * [chimie] Connaître les charges des particules
  5471. * We want that in the order of the groups defined by the term
  5472. * between brackets at the beginning of the category title
  5473. */
  5474. // If test option is Grouped By Categories
  5475. if ($isRandomByCategory == 2) {
  5476. $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
  5477. }
  5478. while (list($cat_id, $tabquestion) = each($tabCategoryQuestions)) {
  5479. $number_of_random_question = $this->random;
  5480. if ($this->random == -1) {
  5481. $number_of_random_question = count($this->questionList);
  5482. }
  5483. $questionList = array_merge(
  5484. $questionList,
  5485. TestCategory::getNElementsFromArray(
  5486. $tabquestion,
  5487. $number_of_random_question
  5488. )
  5489. );
  5490. }
  5491. // shuffle the question list if test is not grouped by categories
  5492. if ($isRandomByCategory == 1) {
  5493. shuffle($questionList); // or not
  5494. }
  5495. $tabres = $questionList;
  5496. } else {
  5497. // Problem, random by category has been selected and
  5498. // we have no $this->isRandom number of question selected
  5499. // Should not happened
  5500. }
  5501. }
  5502. return $tabres;
  5503. }
  5504. function get_question_list($expand_media_questions = false)
  5505. {
  5506. $question_list = $this->get_validated_question_list();
  5507. $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
  5508. return $question_list;
  5509. }
  5510. function transform_question_list_with_medias($question_list, $expand_media_questions = false)
  5511. {
  5512. $new_question_list = array();
  5513. if (!empty($question_list)) {
  5514. $media_questions = $this->getMediaList();
  5515. $media_active = $this->mediaIsActivated($media_questions);
  5516. if ($media_active) {
  5517. $counter = 1;
  5518. foreach ($question_list as $question_id) {
  5519. $add_question = true;
  5520. foreach ($media_questions as $media_id => $question_list_in_media) {
  5521. if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
  5522. $add_question = false;
  5523. if (!in_array($media_id, $new_question_list)) {
  5524. $new_question_list[$counter] = $media_id;
  5525. $counter++;
  5526. }
  5527. break;
  5528. }
  5529. }
  5530. if ($add_question) {
  5531. $new_question_list[$counter] = $question_id;
  5532. $counter++;
  5533. }
  5534. }
  5535. if ($expand_media_questions) {
  5536. $media_key_list = array_keys($media_questions);
  5537. foreach ($new_question_list as &$question_id) {
  5538. if (in_array($question_id, $media_key_list)) {
  5539. $question_id = $media_questions[$question_id];
  5540. }
  5541. }
  5542. $new_question_list = array_flatten($new_question_list);
  5543. }
  5544. } else {
  5545. $new_question_list = $question_list;
  5546. }
  5547. }
  5548. return $new_question_list;
  5549. }
  5550. /**
  5551. * @param int $exe_id
  5552. * @return array|mixed
  5553. */
  5554. public function get_stat_track_exercise_info_by_exe_id($exe_id)
  5555. {
  5556. $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  5557. $exe_id = intval($exe_id);
  5558. $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
  5559. $result = Database::query($sql_track);
  5560. $new_array = array();
  5561. if (Database::num_rows($result) > 0 ) {
  5562. $new_array = Database::fetch_array($result, 'ASSOC');
  5563. $new_array['duration'] = null;
  5564. $start_date = api_get_utc_datetime($new_array['start_date'], true);
  5565. $end_date = api_get_utc_datetime($new_array['exe_date'], true);
  5566. if (!empty($start_date) && !empty($end_date)) {
  5567. $start_date = api_strtotime($start_date, 'UTC');
  5568. $end_date = api_strtotime($end_date, 'UTC');
  5569. if ($start_date && $end_date) {
  5570. $mytime = $end_date- $start_date;
  5571. $new_learnpath_item = new learnpathItem(null);
  5572. $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
  5573. $h = get_lang('h');
  5574. $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
  5575. $new_array['duration'] = $time_attemp;
  5576. }
  5577. }
  5578. }
  5579. return $new_array;
  5580. }
  5581. public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
  5582. {
  5583. $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
  5584. $question_id = intval($question_id);
  5585. $exe_id = intval($exe_id);
  5586. $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  5587. if ($exercise_info) {
  5588. if (empty($exercise_info['questions_to_check'])) {
  5589. if ($action == 'add') {
  5590. $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
  5591. Database::query($sql);
  5592. }
  5593. } else {
  5594. $remind_list = explode(',',$exercise_info['questions_to_check']);
  5595. $remind_list_string = '';
  5596. if ($action == 'add') {
  5597. if (!in_array($question_id, $remind_list)) {
  5598. $remind_list[] = $question_id;
  5599. if (!empty($remind_list)) {
  5600. sort($remind_list);
  5601. array_filter($remind_list);
  5602. }
  5603. $remind_list_string = implode(',', $remind_list);
  5604. }
  5605. } elseif ($action == 'delete') {
  5606. if (!empty($remind_list)) {
  5607. if (in_array($question_id, $remind_list)) {
  5608. $remind_list = array_flip($remind_list);
  5609. unset($remind_list[$question_id]);
  5610. $remind_list = array_flip($remind_list);
  5611. if (!empty($remind_list)) {
  5612. sort($remind_list);
  5613. array_filter($remind_list);
  5614. $remind_list_string = implode(',', $remind_list);
  5615. }
  5616. }
  5617. }
  5618. }
  5619. $remind_list_string = Database::escape_string($remind_list_string);
  5620. $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
  5621. Database::query($sql);
  5622. }
  5623. }
  5624. }
  5625. public function fill_in_blank_answer_to_array($answer)
  5626. {
  5627. api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
  5628. $teacher_answer_list = $teacher_answer_list[0];
  5629. return $teacher_answer_list;
  5630. }
  5631. public function fill_in_blank_answer_to_string($answer)
  5632. {
  5633. $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
  5634. $result = '';
  5635. if (!empty($teacher_answer_list)) {
  5636. $i = 0;
  5637. foreach ($teacher_answer_list as $teacher_item) {
  5638. $value = null;
  5639. //Cleaning student answer list
  5640. $value = strip_tags($teacher_item);
  5641. $value = api_substr($value, 1, api_strlen($value) - 2);
  5642. $value = explode('/', $value);
  5643. if (!empty($value[0])) {
  5644. $value = trim($value[0]);
  5645. $value = str_replace('&nbsp;', '', $value);
  5646. $result .= $value;
  5647. }
  5648. }
  5649. }
  5650. return $result;
  5651. }
  5652. function return_time_left_div()
  5653. {
  5654. $html = '<div id="clock_warning" style="display:none">';
  5655. $html .= Display::return_message(
  5656. get_lang('ReachedTimeLimit'),
  5657. 'warning'
  5658. );
  5659. $html .= ' ';
  5660. $html .= sprintf(
  5661. get_lang('YouWillBeRedirectedInXSeconds'),
  5662. '<span id="counter_to_redirect" class="red_alert"></span>'
  5663. );
  5664. $html .= '</div>';
  5665. $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
  5666. return $html;
  5667. }
  5668. function get_count_question_list()
  5669. {
  5670. //Real question count
  5671. $question_count = 0;
  5672. $question_list = $this->get_question_list();
  5673. if (!empty($question_list)) {
  5674. $question_count = count($question_list);
  5675. }
  5676. return $question_count;
  5677. }
  5678. function get_exercise_list_ordered()
  5679. {
  5680. $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
  5681. $course_id = api_get_course_int_id();
  5682. $session_id = api_get_session_id();
  5683. $sql = "SELECT exercise_id, exercise_order
  5684. FROM $table_exercise_order
  5685. WHERE c_id = $course_id AND session_id = $session_id
  5686. ORDER BY exercise_order";
  5687. $result = Database::query($sql);
  5688. $list = array();
  5689. if (Database::num_rows($result)) {
  5690. while ($row = Database::fetch_array($result, 'ASSOC')) {
  5691. $list[$row['exercise_order']] = $row['exercise_id'];
  5692. }
  5693. }
  5694. return $list;
  5695. }
  5696. /**
  5697. * Get categories added in the exercise--category matrix
  5698. * @return bool
  5699. */
  5700. public function get_categories_in_exercise()
  5701. {
  5702. $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
  5703. if (!empty($this->id)) {
  5704. $sql = "SELECT * FROM $table
  5705. WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
  5706. $result = Database::query($sql);
  5707. $list = array();
  5708. if (Database::num_rows($result)) {
  5709. while ($row = Database::fetch_array($result, 'ASSOC')) {
  5710. $list[$row['category_id']] = $row;
  5711. }
  5712. return $list;
  5713. }
  5714. }
  5715. return false;
  5716. }
  5717. /**
  5718. * @param null $order
  5719. * @return bool
  5720. */
  5721. public function get_categories_with_name_in_exercise($order = null)
  5722. {
  5723. $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
  5724. $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  5725. $sql = "SELECT * FROM $table qc
  5726. INNER JOIN $table_category c
  5727. ON (category_id = c.iid)
  5728. WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
  5729. if (!empty($order)) {
  5730. $sql .= "ORDER BY $order ";
  5731. }
  5732. $result = Database::query($sql);
  5733. if (Database::num_rows($result)) {
  5734. while ($row = Database::fetch_array($result, 'ASSOC')) {
  5735. $list[$row['category_id']] = $row;
  5736. }
  5737. return $list;
  5738. }
  5739. return false;
  5740. }
  5741. /**
  5742. * Get total number of question that will be parsed when using the category/exercise
  5743. */
  5744. public function getNumberQuestionExerciseCategory()
  5745. {
  5746. $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
  5747. if (!empty($this->id)) {
  5748. $sql = "SELECT SUM(count_questions) count_questions
  5749. FROM $table
  5750. WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
  5751. $result = Database::query($sql);
  5752. if (Database::num_rows($result)) {
  5753. $row = Database::fetch_array($result);
  5754. return $row['count_questions'];
  5755. }
  5756. }
  5757. return 0;
  5758. }
  5759. /**
  5760. * Save categories in the TABLE_QUIZ_REL_CATEGORY table
  5761. * @param array $categories
  5762. */
  5763. public function save_categories_in_exercise($categories)
  5764. {
  5765. if (!empty($categories) && !empty($this->id)) {
  5766. $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
  5767. $sql = "DELETE FROM $table
  5768. WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
  5769. Database::query($sql);
  5770. if (!empty($categories)) {
  5771. foreach ($categories as $category_id => $count_questions) {
  5772. $params = array(
  5773. 'c_id' => $this->course_id,
  5774. 'exercise_id' => $this->id,
  5775. 'category_id' => $category_id,
  5776. 'count_questions' => $count_questions
  5777. );
  5778. Database::insert($table, $params);
  5779. }
  5780. }
  5781. }
  5782. }
  5783. /**
  5784. * @param array $questionList
  5785. * @param int $currentQuestion
  5786. * @param array $conditions
  5787. * @param string $link
  5788. * @return string
  5789. */
  5790. public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
  5791. {
  5792. $mediaQuestions = $this->getMediaList();
  5793. $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
  5794. $counter = 0;
  5795. $nextValue = 0;
  5796. $wasMedia = false;
  5797. $before = 0;
  5798. $counterNoMedias = 0;
  5799. foreach ($questionList as $questionId) {
  5800. $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
  5801. if (!empty($nextValue)) {
  5802. if ($wasMedia) {
  5803. $nextValue = $nextValue - $before + 1;
  5804. }
  5805. }
  5806. if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
  5807. $fixedValue = $counterNoMedias;
  5808. $html .= Display::progressPaginationBar(
  5809. $nextValue,
  5810. $mediaQuestions[$questionId],
  5811. $currentQuestion,
  5812. $fixedValue,
  5813. $conditions,
  5814. $link,
  5815. true,
  5816. true
  5817. );
  5818. $counter += count($mediaQuestions[$questionId]) - 1 ;
  5819. $before = count($questionList);
  5820. $wasMedia = true;
  5821. $nextValue += count($questionList);
  5822. } else {
  5823. $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
  5824. $counter++;
  5825. $nextValue++;
  5826. $wasMedia = false;
  5827. }
  5828. $counterNoMedias++;
  5829. }
  5830. $html .= '</ul></div>';
  5831. return $html;
  5832. }
  5833. /**
  5834. * Shows a list of numbers that represents the question to answer in a exercise
  5835. *
  5836. * @param array $categories
  5837. * @param int $current
  5838. * @param array $conditions
  5839. * @param string $link
  5840. * @return string
  5841. */
  5842. public function progressExercisePaginationBarWithCategories(
  5843. $categories,
  5844. $current,
  5845. $conditions = array(),
  5846. $link = null
  5847. ) {
  5848. $html = null;
  5849. $counterNoMedias = 0;
  5850. $nextValue = 0;
  5851. $wasMedia = false;
  5852. $before = 0;
  5853. if (!empty($categories)) {
  5854. $selectionType = $this->getQuestionSelectionType();
  5855. $useRootAsCategoryTitle = false;
  5856. // Grouping questions per parent category see BT#6540
  5857. if (in_array(
  5858. $selectionType,
  5859. array(
  5860. EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
  5861. EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
  5862. )
  5863. )) {
  5864. $useRootAsCategoryTitle = true;
  5865. }
  5866. // If the exercise is set to only show the titles of the categories
  5867. // at the root of the tree, then pre-order the categories tree by
  5868. // removing children and summing their questions into the parent
  5869. // categories
  5870. if ($useRootAsCategoryTitle) {
  5871. // The new categories list starts empty
  5872. $newCategoryList = array();
  5873. foreach ($categories as $category) {
  5874. $rootElement = $category['root'];
  5875. if (isset($category['parent_info'])) {
  5876. $rootElement = $category['parent_info']['id'];
  5877. }
  5878. //$rootElement = $category['id'];
  5879. // If the current category's ancestor was never seen
  5880. // before, then declare it and assign the current
  5881. // category to it.
  5882. if (!isset($newCategoryList[$rootElement])) {
  5883. $newCategoryList[$rootElement] = $category;
  5884. } else {
  5885. // If it was already seen, then merge the previous with
  5886. // the current category
  5887. $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
  5888. $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
  5889. $newCategoryList[$rootElement] = $category;
  5890. }
  5891. }
  5892. // Now use the newly built categories list, with only parents
  5893. $categories = $newCategoryList;
  5894. }
  5895. foreach ($categories as $category) {
  5896. $questionList = $category['question_list'];
  5897. // Check if in this category there questions added in a media
  5898. $mediaQuestionId = $category['media_question'];
  5899. $isMedia = false;
  5900. $fixedValue = null;
  5901. // Media exists!
  5902. if ($mediaQuestionId != 999) {
  5903. $isMedia = true;
  5904. $fixedValue = $counterNoMedias;
  5905. }
  5906. //$categoryName = $category['path']; << show the path
  5907. $categoryName = $category['name'];
  5908. if ($useRootAsCategoryTitle) {
  5909. if (isset($category['parent_info'])) {
  5910. $categoryName = $category['parent_info']['title'];
  5911. }
  5912. }
  5913. $html .= '<div class="row">';
  5914. $html .= '<div class="span2">'.$categoryName.'</div>';
  5915. $html .= '<div class="span8">';
  5916. if (!empty($nextValue)) {
  5917. if ($wasMedia) {
  5918. $nextValue = $nextValue - $before + 1;
  5919. }
  5920. }
  5921. $html .= Display::progressPaginationBar(
  5922. $nextValue,
  5923. $questionList,
  5924. $current,
  5925. $fixedValue,
  5926. $conditions,
  5927. $link,
  5928. $isMedia,
  5929. true
  5930. );
  5931. $html .= '</div>';
  5932. $html .= '</div>';
  5933. if ($mediaQuestionId == 999) {
  5934. $counterNoMedias += count($questionList);
  5935. } else {
  5936. $counterNoMedias++;
  5937. }
  5938. $nextValue += count($questionList);
  5939. $before = count($questionList);
  5940. if ($mediaQuestionId != 999) {
  5941. $wasMedia = true;
  5942. } else {
  5943. $wasMedia = false;
  5944. }
  5945. }
  5946. }
  5947. return $html;
  5948. }
  5949. /**
  5950. * Renders a question list
  5951. *
  5952. * @param array $questionList (with media questions compressed)
  5953. * @param int $currentQuestion
  5954. * @param array $exerciseResult
  5955. * @param array $attemptList
  5956. * @param array $remindList
  5957. */
  5958. public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
  5959. {
  5960. $mediaQuestions = $this->getMediaList();
  5961. $i = 0;
  5962. // Normal question list render (medias compressed)
  5963. foreach ($questionList as $questionId) {
  5964. $i++;
  5965. // For sequential exercises
  5966. if ($this->type == ONE_PER_PAGE) {
  5967. // If it is not the right question, goes to the next loop iteration
  5968. if ($currentQuestion != $i) {
  5969. continue;
  5970. } else {
  5971. if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
  5972. // if the user has already answered this question
  5973. if (isset($exerciseResult[$questionId])) {
  5974. Display::display_normal_message(get_lang('AlreadyAnswered'));
  5975. break;
  5976. }
  5977. }
  5978. }
  5979. }
  5980. // The $questionList contains the media id we check if this questionId is a media question type
  5981. if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
  5982. // The question belongs to a media
  5983. $mediaQuestionList = $mediaQuestions[$questionId];
  5984. $objQuestionTmp = Question::read($questionId);
  5985. $counter = 1;
  5986. if ($objQuestionTmp->type == MEDIA_QUESTION) {
  5987. echo $objQuestionTmp->show_media_content();
  5988. $countQuestionsInsideMedia = count($mediaQuestionList);
  5989. // Show questions that belongs to a media
  5990. if (!empty($mediaQuestionList)) {
  5991. // In order to parse media questions we use letters a, b, c, etc.
  5992. $letterCounter = 97;
  5993. foreach ($mediaQuestionList as $questionIdInsideMedia) {
  5994. $isLastQuestionInMedia = false;
  5995. if ($counter == $countQuestionsInsideMedia) {
  5996. $isLastQuestionInMedia = true;
  5997. }
  5998. $this->renderQuestion(
  5999. $questionIdInsideMedia,
  6000. $attemptList,
  6001. $remindList,
  6002. chr($letterCounter),
  6003. $currentQuestion,
  6004. $mediaQuestionList,
  6005. $isLastQuestionInMedia,
  6006. $questionList
  6007. );
  6008. $letterCounter++;
  6009. $counter++;
  6010. }
  6011. }
  6012. } else {
  6013. $this->renderQuestion(
  6014. $questionId,
  6015. $attemptList,
  6016. $remindList,
  6017. $i,
  6018. $currentQuestion,
  6019. null,
  6020. null,
  6021. $questionList
  6022. );
  6023. $i++;
  6024. }
  6025. } else {
  6026. // Normal question render.
  6027. $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
  6028. }
  6029. // For sequential exercises.
  6030. if ($this->type == ONE_PER_PAGE) {
  6031. // quits the loop
  6032. break;
  6033. }
  6034. }
  6035. // end foreach()
  6036. if ($this->type == ALL_ON_ONE_PAGE) {
  6037. $exercise_actions = $this->show_button($questionId, $currentQuestion);
  6038. echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
  6039. }
  6040. }
  6041. /**
  6042. * @param int $questionId
  6043. * @param array $attemptList
  6044. * @param array $remindList
  6045. * @param int $i
  6046. * @param int $current_question
  6047. * @param array $questions_in_media
  6048. * @param bool $last_question_in_media
  6049. * @param array $realQuestionList
  6050. * @param bool $generateJS
  6051. * @return null
  6052. */
  6053. public function renderQuestion(
  6054. $questionId,
  6055. $attemptList,
  6056. $remindList,
  6057. $i,
  6058. $current_question,
  6059. $questions_in_media = array(),
  6060. $last_question_in_media = false,
  6061. $realQuestionList,
  6062. $generateJS = true
  6063. ) {
  6064. // With this option on the question is loaded via AJAX
  6065. //$generateJS = true;
  6066. //$this->loadQuestionAJAX = true;
  6067. if ($generateJS && $this->loadQuestionAJAX) {
  6068. $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId;
  6069. $params = array(
  6070. 'questionId' => $questionId,
  6071. 'attemptList'=> $attemptList,
  6072. 'remindList' => $remindList,
  6073. 'i' => $i,
  6074. 'current_question' => $current_question,
  6075. 'questions_in_media' => $questions_in_media,
  6076. 'last_question_in_media' => $last_question_in_media
  6077. );
  6078. $params = json_encode($params);
  6079. $script = '<script>
  6080. $(function(){
  6081. var params = '.$params.';
  6082. $.ajax({
  6083. type: "GET",
  6084. async: false,
  6085. data: params,
  6086. url: "'.$url.'",
  6087. success: function(return_value) {
  6088. $("#ajaxquestiondiv'.$questionId.'").html(return_value);
  6089. }
  6090. });
  6091. });
  6092. </script>
  6093. <div id="ajaxquestiondiv'.$questionId.'"></div>';
  6094. echo $script;
  6095. } else {
  6096. global $origin;
  6097. $question_obj = Question::read($questionId);
  6098. $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
  6099. $remind_highlight = null;
  6100. //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
  6101. if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
  6102. $remind_highlight = 'no_remind_highlight';
  6103. if (in_array($question_obj->type, Question::question_type_no_review())) {
  6104. return null;
  6105. }
  6106. }
  6107. $attributes = array('id' =>'remind_list['.$questionId.']');
  6108. if (is_array($remindList) && in_array($questionId, $remindList)) {
  6109. //$attributes['checked'] = 1;
  6110. //$remind_highlight = ' remind_highlight ';
  6111. }
  6112. // Showing the question
  6113. $exercise_actions = null;
  6114. echo '<a id="questionanchor'.$questionId.'"></a><br />';
  6115. echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
  6116. // Shows the question + possible answers
  6117. $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
  6118. echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
  6119. // Button save and continue
  6120. switch ($this->type) {
  6121. case ONE_PER_PAGE:
  6122. $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
  6123. break;
  6124. case ALL_ON_ONE_PAGE:
  6125. $button = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', null, true, 1); ">'.get_lang('SaveForNow').'</a>';
  6126. $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
  6127. $exercise_actions .= Display::div($button, array('class'=>'exercise_save_now_button'));
  6128. break;
  6129. }
  6130. if (!empty($questions_in_media)) {
  6131. $count_of_questions_inside_media = count($questions_in_media);
  6132. if ($count_of_questions_inside_media > 1) {
  6133. $button = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false, 0); ">'.get_lang('SaveForNow').'</a>';
  6134. $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
  6135. $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button'));
  6136. }
  6137. if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
  6138. $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
  6139. }
  6140. }
  6141. // Checkbox review answers
  6142. if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
  6143. $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
  6144. $exercise_actions .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
  6145. }
  6146. echo Display::div(' ', array('class'=>'clear'));
  6147. $paginationCounter = null;
  6148. if ($this->type == ONE_PER_PAGE) {
  6149. if (empty($questions_in_media)) {
  6150. $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
  6151. } else {
  6152. if ($last_question_in_media) {
  6153. $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
  6154. }
  6155. }
  6156. }
  6157. echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
  6158. echo Display::div($exercise_actions, array('class'=>'form-actions'));
  6159. echo '</div>';
  6160. }
  6161. }
  6162. /**
  6163. * Shows a question
  6164. * @param Question $objQuestionTmp
  6165. * @param bool $only_questions if true only show the questions, no exercise title
  6166. * @param bool $origin origin i.e = learnpath
  6167. * @param string $current_item current item from the list of questions
  6168. * @param bool $show_title
  6169. * @param bool $freeze
  6170. * @param array $user_choice
  6171. * @param bool $show_comment
  6172. * @param null $exercise_feedback
  6173. * @param bool $show_answers
  6174. * @param null $modelType
  6175. * @param bool $categoryMinusOne
  6176. * @return bool|null|string
  6177. */
  6178. public function showQuestion(
  6179. Question $objQuestionTmp,
  6180. $only_questions = false,
  6181. $origin = false,
  6182. $current_item = '',
  6183. $show_title = true,
  6184. $freeze = false,
  6185. $user_choice = array(),
  6186. $show_comment = false,
  6187. $exercise_feedback = null,
  6188. $show_answers = false,
  6189. $modelType = null,
  6190. $categoryMinusOne = true
  6191. ) {
  6192. // Text direction for the current language
  6193. //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
  6194. // Change false to true in the following line to enable answer hinting
  6195. $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
  6196. // Reads question information
  6197. if (!$objQuestionTmp) {
  6198. // Question not found
  6199. return false;
  6200. }
  6201. $html = null;
  6202. $questionId = $objQuestionTmp->id;
  6203. if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
  6204. $show_comment = false;
  6205. }
  6206. $answerType = $objQuestionTmp->selectType();
  6207. $pictureName = $objQuestionTmp->selectPicture();
  6208. $s = null;
  6209. $form = new FormValidator('question');
  6210. $renderer = $form->defaultRenderer();
  6211. $form_template = '{content}';
  6212. $renderer->setFormTemplate($form_template);
  6213. if ($answerType != HOT_SPOT && $answerType != HOT_SPOT_DELINEATION) {
  6214. // Question is not a hotspot
  6215. if (!$only_questions) {
  6216. $questionDescription = $objQuestionTmp->selectDescription();
  6217. if ($show_title) {
  6218. $categoryName = TestCategory::getCategoryNamesForQuestion($objQuestionTmp->id, null, true, $categoryMinusOne);
  6219. $html .= $categoryName;
  6220. $html .= Display::div($current_item.'. '.$objQuestionTmp->selectTitle(), array('class' => 'question_title'));
  6221. if (!empty($questionDescription)) {
  6222. $html .= Display::div($questionDescription, array('class' => 'question_description'));
  6223. }
  6224. } else {
  6225. $html .= '<div class="media">';
  6226. $html .= '<div class="pull-left">';
  6227. $html .= '<div class="media-object">';
  6228. $html .= Display::div($current_item, array('class' => 'question_no_title'));
  6229. $html .= '</div>';
  6230. $html .= '</div>';
  6231. $html .= '<div class="media-body">';
  6232. if (!empty($questionDescription)) {
  6233. $html .= Display::div($questionDescription, array('class' => 'question_description'));
  6234. }
  6235. $html .= '</div>';
  6236. $html .= '</div>';
  6237. }
  6238. }
  6239. if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) && $freeze) {
  6240. return null;
  6241. }
  6242. $html .= '<div class="question_options">';
  6243. // construction of the Answer object (also gets all answers details)
  6244. $objAnswerTmp = new Answer($questionId, null, $this);
  6245. $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
  6246. $course_id = api_get_course_int_id();
  6247. $sessionId = api_get_session_id();
  6248. $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
  6249. // For "matching" type here, we need something a little bit special
  6250. // because the match between the suggestions and the answers cannot be
  6251. // done easily (suggestions and answers are in the same table), so we
  6252. // have to go through answers first (elems with "correct" value to 0).
  6253. $select_items = array();
  6254. //This will contain the number of answers on the left side. We call them
  6255. // suggestions here, for the sake of comprehensions, while the ones
  6256. // on the right side are called answers
  6257. $num_suggestions = 0;
  6258. if ($answerType == MATCHING || $answerType == DRAGGABLE) {
  6259. if ($answerType == DRAGGABLE) {
  6260. $s .= '<div class="ui-widget ui-helper-clearfix">
  6261. <ul class="drag_question ui-helper-reset ui-helper-clearfix">';
  6262. } else {
  6263. $s .= '<div id="drag'.$questionId.'_question" class="drag_question">';
  6264. $s .= '<table class="data_table">';
  6265. }
  6266. $j = 1; //iterate through answers
  6267. $letter = 'A'; //mark letters for each answer
  6268. $answer_matching = array();
  6269. $capital_letter = array();
  6270. //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
  6271. foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
  6272. $answerCorrect = $objAnswerTmp->isCorrect($answerId);
  6273. $answer = $objAnswerTmp->selectAnswer($answerId);
  6274. if ($answerCorrect == 0) {
  6275. // options (A, B, C, ...) that will be put into the list-box
  6276. // have the "correct" field set to 0 because they are answer
  6277. $capital_letter[$j] = $letter;
  6278. //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
  6279. $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
  6280. $j++;
  6281. $letter++;
  6282. }
  6283. }
  6284. $i = 1;
  6285. $select_items[0]['id'] = 0;
  6286. $select_items[0]['letter'] = '--';
  6287. $select_items[0]['answer'] = '';
  6288. foreach ($answer_matching as $id => $value) {
  6289. $select_items[$i]['id'] = $value['id'];
  6290. $select_items[$i]['letter'] = $capital_letter[$id];
  6291. $select_items[$i]['answer'] = $value['answer'];
  6292. $i++;
  6293. }
  6294. $num_suggestions = ($nbrAnswers - $j) + 1;
  6295. } elseif ($answerType == FREE_ANSWER) {
  6296. $content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
  6297. $toolBar = 'TestFreeAnswer';
  6298. if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
  6299. $toolBar = 'TestFreeAnswerStrict';
  6300. }
  6301. $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => $toolBar));
  6302. $form->setDefaults(array("choice[".$questionId."]" => $content));
  6303. $s .= $form->return_form();
  6304. } elseif ($answerType == ORAL_EXPRESSION) {
  6305. // Add nanogong
  6306. if (api_get_setting('enable_record_audio') === 'true') {
  6307. //@todo pass this as a parameter
  6308. global $exercise_stat_info, $exerciseId;
  6309. if (!empty($exercise_stat_info)) {
  6310. $objQuestionTmp->initFile(
  6311. api_get_session_id(),
  6312. api_get_user_id(),
  6313. $exercise_stat_info['exe_exo_id'],
  6314. $exercise_stat_info['exe_id']
  6315. );
  6316. } else {
  6317. $objQuestionTmp->initFile(
  6318. api_get_session_id(),
  6319. api_get_user_id(),
  6320. $exerciseId,
  6321. 'temp_exe'
  6322. );
  6323. }
  6324. $s .= $objQuestionTmp->returnRecorder();
  6325. }
  6326. $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => 'TestFreeAnswer'));
  6327. //$form->setDefaults(array("choice[".$questionId."]" => $content));
  6328. $s .= $form->return_form();
  6329. }
  6330. // Now navigate through the possible answers, using the max number of
  6331. // answers for the question as a limiter
  6332. $lines_count = 1; // a counter for matching-type answers
  6333. if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
  6334. $header = Display::tag('th', get_lang('Options'));
  6335. foreach ($objQuestionTmp->options as $item) {
  6336. $header .= Display::tag('th', $item);
  6337. }
  6338. if ($show_comment) {
  6339. $header .= Display::tag('th', get_lang('Feedback'));
  6340. }
  6341. $s .= '<table class="data_table">';
  6342. $s .= Display::tag('tr', $header, array('style' => 'text-align:left;'));
  6343. }
  6344. if ($show_comment) {
  6345. if (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_COMBINATION, UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, GLOBAL_MULTIPLE_ANSWER))) {
  6346. $header = Display::tag('th', get_lang('Options'));
  6347. if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
  6348. $header .= Display::tag('th', get_lang('Feedback'));
  6349. }
  6350. $s .= '<table class="data_table">';
  6351. $s.= Display::tag('tr', $header, array('style' => 'text-align:left;'));
  6352. }
  6353. }
  6354. $matching_correct_answer = 0;
  6355. $user_choice_array = array();
  6356. if (!empty($user_choice)) {
  6357. foreach ($user_choice as $item) {
  6358. $user_choice_array[] = $item['answer'];
  6359. }
  6360. }
  6361. foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
  6362. $answer = $objAnswerTmp->selectAnswer($answerId);
  6363. $answerCorrect = $objAnswerTmp->isCorrect($answerId);
  6364. $comment = $objAnswerTmp->selectComment($answerId);
  6365. //$numAnswer = $objAnswerTmp->selectAutoId($answerId);
  6366. $numAnswer = $answerId;
  6367. $attributes = array();
  6368. // Unique answer
  6369. if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
  6370. $input_id = 'choice-'.$questionId.'-'.$answerId;
  6371. if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
  6372. $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
  6373. } else {
  6374. $attributes = array('id' => $input_id);
  6375. }
  6376. if ($debug_mark_answer) {
  6377. if ($answerCorrect) {
  6378. $attributes['checked'] = 1;
  6379. $attributes['selected'] = 1;
  6380. }
  6381. }
  6382. $answer = Security::remove_XSS($answer);
  6383. $s .= Display::input('hidden', 'choice2['.$questionId.']', '0');
  6384. $answer_input = null;
  6385. if ($answerType == UNIQUE_ANSWER_IMAGE) {
  6386. $attributes['style'] = 'display:none';
  6387. $answer_input .= '<div id="answer'.$questionId.$numAnswer.'" style="float:left" class="highlight_image_default highlight_image">';
  6388. }
  6389. $answer_input .= '<label class="radio">';
  6390. $answer_input .= Display::input('radio', 'choice['.$questionId.']', $numAnswer, $attributes);
  6391. $answer_input .= $answer;
  6392. $answer_input .= '</label>';
  6393. if ($answerType == UNIQUE_ANSWER_IMAGE) {
  6394. $answer_input .= "</div>";
  6395. }
  6396. if ($show_comment) {
  6397. $s .= '<tr><td>';
  6398. $s .= $answer_input;
  6399. $s .= '</td>';
  6400. $s .= '<td>';
  6401. $s .= $comment;
  6402. $s .= '</td>';
  6403. $s .= '</tr>';
  6404. } else {
  6405. $s .= $answer_input;
  6406. }
  6407. } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
  6408. $input_id = 'choice-'.$questionId.'-'.$answerId;
  6409. $answer = Security::remove_XSS($answer);
  6410. if (in_array($numAnswer, $user_choice_array)) {
  6411. $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
  6412. } else {
  6413. $attributes = array('id' => $input_id);
  6414. }
  6415. if ($debug_mark_answer) {
  6416. if ($answerCorrect) {
  6417. $attributes['checked'] = 1;
  6418. $attributes['selected'] = 1;
  6419. }
  6420. }
  6421. if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
  6422. $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
  6423. $answer_input = '<label class="checkbox">';
  6424. $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', $numAnswer, $attributes);
  6425. $answer_input .= $answer;
  6426. $answer_input .= '</label>';
  6427. if ($show_comment) {
  6428. $s .= '<tr><td>';
  6429. $s .= $answer_input;
  6430. $s .= '</td>';
  6431. $s .= '<td>';
  6432. $s .= $comment;
  6433. $s .= '</td>';
  6434. $s .='</tr>';
  6435. } else {
  6436. $s .= $answer_input;
  6437. }
  6438. } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
  6439. $my_choice = array();
  6440. if (!empty($user_choice_array)) {
  6441. foreach ($user_choice_array as $item) {
  6442. $item = explode(':', $item);
  6443. $my_choice[$item[0]] = $item[1];
  6444. }
  6445. }
  6446. $s .='<tr>';
  6447. $s .= Display::tag('td', $answer);
  6448. if (!empty($quiz_question_options)) {
  6449. foreach ($quiz_question_options as $id => $item) {
  6450. $id = $item['iid'];
  6451. if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
  6452. $attributes = array('checked' => 1, 'selected' => 1);
  6453. } else {
  6454. $attributes = array();
  6455. }
  6456. if ($debug_mark_answer) {
  6457. if ($id == $answerCorrect) {
  6458. $attributes['checked'] = 1;
  6459. $attributes['selected'] = 1;
  6460. }
  6461. }
  6462. $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $id, $attributes), array('style' => ''));
  6463. }
  6464. }
  6465. if ($show_comment) {
  6466. $s .= '<td>';
  6467. $s .= $comment;
  6468. $s .= '</td>';
  6469. }
  6470. $s.='</tr>';
  6471. }
  6472. } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
  6473. // multiple answers
  6474. $input_id = 'choice-'.$questionId.'-'.$answerId;
  6475. if (in_array($numAnswer, $user_choice_array)) {
  6476. $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
  6477. } else {
  6478. $attributes = array('id' => $input_id);
  6479. }
  6480. if ($debug_mark_answer) {
  6481. if ($answerCorrect) {
  6482. $attributes['checked'] = 1;
  6483. $attributes['selected'] = 1;
  6484. }
  6485. }
  6486. $answer = Security::remove_XSS($answer);
  6487. $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
  6488. $answer_input .= '<label class="checkbox">';
  6489. $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', 1, $attributes);
  6490. $answer_input .= $answer;
  6491. $answer_input .= '</label>';
  6492. if ($show_comment) {
  6493. $s.= '<tr>';
  6494. $s .= '<td>';
  6495. $s.= $answer_input;
  6496. $s .= '</td>';
  6497. $s .= '<td>';
  6498. $s .= $comment;
  6499. $s .= '</td>';
  6500. $s.= '</tr>';
  6501. } else {
  6502. $s.= $answer_input;
  6503. }
  6504. } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
  6505. $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
  6506. $my_choice = array();
  6507. if (!empty($user_choice_array)) {
  6508. foreach ($user_choice_array as $item) {
  6509. $item = explode(':', $item);
  6510. $my_choice[$item[0]] = $item[1];
  6511. }
  6512. }
  6513. $answer = Security::remove_XSS($answer);
  6514. $s .='<tr>';
  6515. $s .= Display::tag('td', $answer);
  6516. foreach ($objQuestionTmp->options as $key => $item) {
  6517. if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
  6518. $attributes = array('checked' => 1, 'selected' => 1);
  6519. } else {
  6520. $attributes = array();
  6521. }
  6522. if ($debug_mark_answer) {
  6523. if ($key == $answerCorrect) {
  6524. $attributes['checked'] = 1;
  6525. $attributes['selected'] = 1;
  6526. }
  6527. }
  6528. $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $key, $attributes));
  6529. }
  6530. if ($show_comment) {
  6531. $s .= '<td>';
  6532. $s .= $comment;
  6533. $s .= '</td>';
  6534. }
  6535. $s.='</tr>';
  6536. } elseif ($answerType == FILL_IN_BLANKS) {
  6537. list($answer) = explode('::', $answer);
  6538. //Correct answer
  6539. api_preg_match_all('/\[[^]]+\]/', $answer, $correct_answer_list);
  6540. //Student's answezr
  6541. if (isset($user_choice[0]['answer'])) {
  6542. api_preg_match_all('/\[[^]]+\]/', $user_choice[0]['answer'], $student_answer_list);
  6543. $student_answer_list = $student_answer_list[0];
  6544. }
  6545. //If debug
  6546. if ($debug_mark_answer) {
  6547. $student_answer_list = $correct_answer_list[0];
  6548. }
  6549. if (!empty($correct_answer_list) && !empty($student_answer_list)) {
  6550. $correct_answer_list = $correct_answer_list[0];
  6551. $i = 0;
  6552. foreach ($correct_answer_list as $correct_item) {
  6553. $value = null;
  6554. if (isset($student_answer_list[$i]) && !empty($student_answer_list[$i])) {
  6555. //Cleaning student answer list
  6556. $value = strip_tags($student_answer_list[$i]);
  6557. $value = api_substr($value, 1, api_strlen($value) - 2);
  6558. $value = explode('/', $value);
  6559. if (!empty($value[0])) {
  6560. $value = str_replace('&nbsp;', '', trim($value[0]));
  6561. }
  6562. $correct_item = preg_quote($correct_item);
  6563. $correct_item = api_preg_replace('|/|', '\/', $correct_item); // to prevent error if there is a / in the text to find
  6564. $answer = api_preg_replace('/'.$correct_item.'/', Display::input('text', "choice[$questionId][]", $value), $answer, 1);
  6565. }
  6566. $i++;
  6567. }
  6568. } else {
  6569. $answer = api_preg_replace('/\[[^]]+\]/', Display::input('text', "choice[$questionId][]", '', $attributes), $answer);
  6570. }
  6571. $s .= $answer;
  6572. } elseif ($answerType == MATCHING) {
  6573. // matching type, showing suggestions and answers
  6574. // TODO: replace $answerId by $numAnswer
  6575. if ($lines_count == 1) {
  6576. $s .= $objAnswerTmp->getJs();
  6577. }
  6578. if ($answerCorrect != 0) {
  6579. // only show elements to be answered (not the contents of
  6580. // the select boxes, who are correct = 0)
  6581. $s .= '<tr><td width="45%">';
  6582. $parsed_answer = $answer;
  6583. $windowId = $questionId.'_'.$lines_count;
  6584. //left part questions
  6585. $s .= ' <div id="window_'.$windowId.'" class="window window_left_question window'.$questionId.'_question">
  6586. <b>'.$lines_count.'</b>.&nbsp'.$parsed_answer.'
  6587. </div>
  6588. </td>';
  6589. // middle part (matches selects)
  6590. $s .= '<td width="10%" align="center">&nbsp;&nbsp;';
  6591. $s .= '<div style="display:block">';
  6592. $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']">';
  6593. $selectedValue = 0;
  6594. // fills the list-box
  6595. $item = 0;
  6596. foreach ($select_items as $val) {
  6597. // set $debug_mark_answer to true at public static function start to
  6598. // show the correct answer with a suffix '-x'
  6599. $selected = '';
  6600. if ($debug_mark_answer) {
  6601. if ($val['id'] == $answerCorrect) {
  6602. $selected = 'selected="selected"';
  6603. $selectedValue = $val['id'];
  6604. }
  6605. }
  6606. if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
  6607. $selected = 'selected="selected"';
  6608. $selectedValue = $val['id'];
  6609. }
  6610. //$s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
  6611. $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
  6612. $item++;
  6613. }
  6614. if (!empty($answerCorrect) && !empty($selectedValue)) {
  6615. $s.= '<script>
  6616. jsPlumb.ready(function() {
  6617. jsPlumb.connect({
  6618. source: "window_'.$windowId.'",
  6619. target: "window_'.$questionId.'_'.$selectedValue.'_answer",
  6620. endpoint:["Blank", { radius:15 }],
  6621. anchor:["RightMiddle","LeftMiddle"],
  6622. paintStyle:{ strokeStyle:"#8a8888" , lineWidth:8 },
  6623. connector: [connectorType, { curviness: curvinessValue } ],
  6624. })
  6625. });
  6626. </script>';
  6627. }
  6628. $s .= '</select></div></td>';
  6629. $s.='<td width="45%" valign="top" >';
  6630. if (isset($select_items[$lines_count])) {
  6631. $s.= '<div id="window_'.$windowId.'_answer" class="window window_right_question">
  6632. <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
  6633. </div>';
  6634. } else {
  6635. $s.='&nbsp;';
  6636. }
  6637. $s .= '</td>';
  6638. $s .= '</tr>';
  6639. $lines_count++;
  6640. //if the left side of the "matching" has been completely
  6641. // shown but the right side still has values to show...
  6642. if (($lines_count - 1) == $num_suggestions) {
  6643. // if it remains answers to shown at the right side
  6644. while (isset($select_items[$lines_count])) {
  6645. $s .= '<tr>
  6646. <td colspan="2"></td>
  6647. <td valign="top">';
  6648. $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
  6649. $s .= $select_items[$lines_count]['answer'];
  6650. $s.="</td>
  6651. </tr>";
  6652. $lines_count++;
  6653. } // end while()
  6654. } // end if()
  6655. $matching_correct_answer++;
  6656. }
  6657. } elseif ($answerType == DRAGGABLE) {
  6658. // matching type, showing suggestions and answers
  6659. // TODO: replace $answerId by $numAnswer
  6660. if ($answerCorrect != 0) {
  6661. // only show elements to be answered (not the contents of
  6662. // the select boxes, who are correct = 0)
  6663. $s .= '<td>';
  6664. $parsed_answer = $answer;
  6665. $windowId = $questionId.'_'.$numAnswer; //67_293 - 67_294
  6666. //left part questions
  6667. $s .= '<li class="ui-state-default" id="'.$windowId.'">';
  6668. $s .= ' <div id="window_'.$windowId.'" class="window'.$questionId.'_question_draggable question_draggable">
  6669. '.$parsed_answer.'
  6670. </div>';
  6671. $s .= '<div style="display:none">';
  6672. $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']" class="select_option">';
  6673. $selectedValue = 0;
  6674. // fills the list-box
  6675. $item = 0;
  6676. foreach ($select_items as $val) {
  6677. // set $debug_mark_answer to true at function start to
  6678. // show the correct answer with a suffix '-x'
  6679. $selected = '';
  6680. if ($debug_mark_answer) {
  6681. if ($val['id'] == $answerCorrect) {
  6682. $selected = 'selected="selected"';
  6683. $selectedValue = $val['id'];
  6684. }
  6685. }
  6686. if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
  6687. $selected = 'selected="selected"';
  6688. $selectedValue = $val['id'];
  6689. }
  6690. $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
  6691. $item++;
  6692. }
  6693. $s .= '</select>';
  6694. if (!empty($answerCorrect) && !empty($selectedValue)) {
  6695. $s.= '<script>
  6696. $(function() {
  6697. deleteItem($("#'.$questionId.'_'.$selectedValue.'"), $("#drop_'.$windowId.'"));
  6698. });
  6699. </script>';
  6700. }
  6701. if (isset($select_items[$lines_count])) {
  6702. $s.= '<div id="window_'.$windowId.'_answer" class="">
  6703. <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
  6704. </div>';
  6705. } else {
  6706. $s.='&nbsp;';
  6707. }
  6708. $lines_count++;
  6709. //if the left side of the "matching" has been completely
  6710. // shown but the right side still has values to show...
  6711. if (($lines_count - 1) == $num_suggestions) {
  6712. // if it remains answers to shown at the right side
  6713. while (isset($select_items[$lines_count])) {
  6714. $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
  6715. $s .= $select_items[$lines_count]['answer'];
  6716. $lines_count++;
  6717. }
  6718. }
  6719. $s .= '</div>';
  6720. $matching_correct_answer++;
  6721. $s .= '</li>';
  6722. }
  6723. }
  6724. } // end for()
  6725. if ($show_comment) {
  6726. $s .= '</table>';
  6727. } else {
  6728. if ($answerType == MATCHING || $answerType == UNIQUE_ANSWER_NO_OPTION || $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
  6729. $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
  6730. $s .= '</table>';
  6731. }
  6732. }
  6733. if ($answerType == DRAGGABLE) {
  6734. $s .= '</ul><div class="clear"></div>';
  6735. $counterAnswer = 1;
  6736. foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
  6737. $answerCorrect = $objAnswerTmp->isCorrect($answerId);
  6738. $windowId = $questionId.'_'.$counterAnswer;
  6739. if ($answerCorrect == 0) {
  6740. $s .= '<div id="drop_'.$windowId.'" class="droppable ui-state-default">'.$counterAnswer.'</div>';
  6741. $counterAnswer++;
  6742. }
  6743. }
  6744. }
  6745. if ($answerType == MATCHING) {
  6746. $s .= '</div>';
  6747. }
  6748. $s .= '</div>';
  6749. // destruction of the Answer object
  6750. unset($objAnswerTmp);
  6751. // destruction of the Question object
  6752. unset($objQuestionTmp);
  6753. $html .= $s;
  6754. return $html;
  6755. } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
  6756. // Question is a HOT_SPOT
  6757. //checking document/images visibility
  6758. if (api_is_platform_admin() || api_is_course_admin()) {
  6759. $course = api_get_course_info();
  6760. $doc_id = DocumentManager::get_document_id($course, '/images/'.$pictureName);
  6761. if (is_numeric($doc_id)) {
  6762. $images_folder_visibility = api_get_item_visibility($course, 'document', $doc_id, api_get_session_id());
  6763. if (!$images_folder_visibility) {
  6764. //This message is shown only to the course/platform admin if the image is set to visibility = false
  6765. Display::display_warning_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'));
  6766. }
  6767. }
  6768. }
  6769. $questionName = $objQuestionTmp->selectTitle();
  6770. $questionDescription = $objQuestionTmp->selectDescription();
  6771. if ($freeze) {
  6772. $s .= Display::img($objQuestionTmp->selectPicturePath());
  6773. $html .= $s;
  6774. return $html;
  6775. }
  6776. // Get the answers, make a list
  6777. $objAnswerTmp = new Answer($questionId);
  6778. // get answers of hotpost
  6779. $answers_hotspot = array();
  6780. foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
  6781. //$answers = $objAnswerTmp->selectAnswerByAutoId($objAnswerTmp->selectAutoId($answerId));
  6782. $answers_hotspot[$answerId] = $objAnswerTmp->selectAnswer($answerId);
  6783. }
  6784. // display answers of hotpost order by id
  6785. $answer_list = '<div style="padding: 10px; margin-left: 0px; border: 1px solid #A4A4A4; height: 408px; width: 200px;"><b>'.get_lang('HotspotZones').'</b><dl>';
  6786. if (!empty($answers_hotspot)) {
  6787. ksort($answers_hotspot);
  6788. foreach ($answers_hotspot as $key => $value) {
  6789. $answer_list .= '<dt>'.$key.'.- '.$value.'</dt><br />';
  6790. }
  6791. }
  6792. $answer_list .= '</dl></div>';
  6793. if ($answerType == HOT_SPOT_DELINEATION) {
  6794. $answer_list = '';
  6795. $swf_file = 'hotspot_delineation_user';
  6796. $swf_height = 405;
  6797. } else {
  6798. $swf_file = 'hotspot_user';
  6799. $swf_height = 436;
  6800. }
  6801. if (!$only_questions) {
  6802. if ($show_title) {
  6803. $html .= TestCategory::getCategoryNamesForQuestion($objQuestionTmp->id);
  6804. $html .= '<div class="question_title">'.$current_item.'. '.$questionName.'</div>';
  6805. $html .= $questionDescription;
  6806. } else {
  6807. $html .= '<div class="media">';
  6808. $html .= '<div class="pull-left">';
  6809. $html .= '<div class="media-object">';
  6810. $html .= Display::div($current_item.'. ', array('class' => 'question_no_title'));
  6811. $html .= '</div>';
  6812. $html .= '</div>';
  6813. $html .= '<div class="media-body">';
  6814. if (!empty($questionDescription)) {
  6815. $html .= Display::div($questionDescription, array('class' => 'question_description'));
  6816. }
  6817. $html .= '</div>';
  6818. $html .= '</div>';
  6819. }
  6820. //@todo I need to the get the feedback type
  6821. $html .= '<input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />';
  6822. $html .= '<table class="exercise_questions">
  6823. <tr>
  6824. <td valign="top" colspan="2">';
  6825. $html .= '</td></tr>';
  6826. }
  6827. $canClick = isset($_GET['editQuestion']) ? '0' : (isset($_GET['modifyAnswers']) ? '0' : '1');
  6828. $s .= ' <script type="text/javascript" src="../plugin/hotspot/JavaScriptFlashGateway.js"></script>
  6829. <script src="../plugin/hotspot/hotspot.js" type="text/javascript" ></script>
  6830. <script type="text/javascript">
  6831. <!--
  6832. // Globals
  6833. // Major version of Flash required
  6834. var requiredMajorVersion = 7;
  6835. // Minor version of Flash required
  6836. var requiredMinorVersion = 0;
  6837. // Minor version of Flash required
  6838. var requiredRevision = 0;
  6839. // the version of javascript supported
  6840. var jsVersion = 1.0;
  6841. // -->
  6842. </script>
  6843. <script language="VBScript" type="text/vbscript">
  6844. <!-- // Visual basic helper required to detect Flash Player ActiveX control version information
  6845. Function VBGetSwfVer(i)
  6846. on error resume next
  6847. Dim swControl, swVersion
  6848. swVersion = 0
  6849. set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))
  6850. if (IsObject(swControl)) then
  6851. swVersion = swControl.GetVariable("$version")
  6852. end if
  6853. VBGetSwfVer = swVersion
  6854. End Function
  6855. // -->
  6856. </script>
  6857. <script language="JavaScript1.1" type="text/javascript">
  6858. <!-- // Detect Client Browser type
  6859. var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
  6860. var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
  6861. var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
  6862. jsVersion = 1.1;
  6863. // JavaScript helper required to detect Flash Player PlugIn version information
  6864. function JSGetSwfVer(i) {
  6865. // NS/Opera version >= 3 check for Flash plugin in plugin array
  6866. if (navigator.plugins != null && navigator.plugins.length > 0) {
  6867. if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
  6868. var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
  6869. var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
  6870. descArray = flashDescription.split(" ");
  6871. tempArrayMajor = descArray[2].split(".");
  6872. versionMajor = tempArrayMajor[0];
  6873. versionMinor = tempArrayMajor[1];
  6874. if ( descArray[3] != "" ) {
  6875. tempArrayMinor = descArray[3].split("r");
  6876. } else {
  6877. tempArrayMinor = descArray[4].split("r");
  6878. }
  6879. versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
  6880. flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
  6881. } else {
  6882. flashVer = -1;
  6883. }
  6884. }
  6885. // MSN/WebTV 2.6 supports Flash 4
  6886. else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
  6887. // WebTV 2.5 supports Flash 3
  6888. else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
  6889. // older WebTV supports Flash 2
  6890. else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
  6891. // Can\'t detect in all other cases
  6892. else {
  6893. flashVer = -1;
  6894. }
  6895. return flashVer;
  6896. }
  6897. // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
  6898. function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) {
  6899. reqVer = parseFloat(reqMajorVer + "." + reqRevision);
  6900. // loop backwards through the versions until we find the newest version
  6901. for (i=25;i>0;i--) {
  6902. if (isIE && isWin && !isOpera) {
  6903. versionStr = VBGetSwfVer(i);
  6904. } else {
  6905. versionStr = JSGetSwfVer(i);
  6906. }
  6907. if (versionStr == -1 ) {
  6908. return false;
  6909. } else if (versionStr != 0) {
  6910. if(isIE && isWin && !isOpera) {
  6911. tempArray = versionStr.split(" ");
  6912. tempString = tempArray[1];
  6913. versionArray = tempString .split(",");
  6914. } else {
  6915. versionArray = versionStr.split(".");
  6916. }
  6917. versionMajor = versionArray[0];
  6918. versionMinor = versionArray[1];
  6919. versionRevision = versionArray[2];
  6920. versionString = versionMajor + "." + versionRevision; // 7.0r24 == 7.24
  6921. versionNum = parseFloat(versionString);
  6922. // is the major.revision >= requested major.revision AND the minor version >= requested minor
  6923. if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
  6924. return true;
  6925. } else {
  6926. return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
  6927. }
  6928. }
  6929. }
  6930. }
  6931. // -->
  6932. </script>';
  6933. $s .= '<tr><td valign="top" colspan="2" width="520"><table><tr><td width="520">
  6934. <script>
  6935. // Version check based upon the values entered above in "Globals"
  6936. var hasReqestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);
  6937. // Check to see if the version meets the requirements for playback
  6938. if (hasReqestedVersion) { // if we\'ve detected an acceptable version
  6939. var oeTags = \'<object type="application/x-shockwave-flash" data="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" width="600" height="'.$swf_height.'">\'
  6940. + \'<param name="wmode" value="transparent">\'
  6941. + \'<param name="movie" value="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" />\'
  6942. + \'<\/object>\';
  6943. document.write(oeTags); // embed the Flash Content SWF when all tests are passed
  6944. } else { // flash is too old or we can\'t detect the plugin
  6945. var alternateContent = "Error<br \/>"
  6946. + "Hotspots requires Macromedia Flash 7.<br \/>"
  6947. + "<a href=\"http://www.macromedia.com/go/getflash/\">Get Flash<\/a>";
  6948. document.write(alternateContent); // insert non-flash content
  6949. }
  6950. </script>
  6951. </td>
  6952. <td valign="top" align="left">'.$answer_list.'</td></tr>
  6953. </table>
  6954. </td></tr>';
  6955. $html .= $s;
  6956. $html .= '</table>';
  6957. return $html;
  6958. }
  6959. return $nbrAnswers;
  6960. }
  6961. /**
  6962. * @param int $exeId
  6963. * @return array
  6964. */
  6965. public function returnQuestionListByAttempt($exeId)
  6966. {
  6967. return $this->displayQuestionListByAttempt($exeId, false, true);
  6968. }
  6969. /**
  6970. * Display the exercise results
  6971. * @param int $exe_id
  6972. * @param bool $saveUserResult save users results (true) or just show the results (false)
  6973. * @param bool $returnExerciseResult return array with exercise result info
  6974. * @return mixed
  6975. */
  6976. public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
  6977. {
  6978. global $origin, $debug;
  6979. //Getting attempt info
  6980. $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
  6981. //Getting question list
  6982. $question_list = array();
  6983. if (!empty($exercise_stat_info['data_tracking'])) {
  6984. $question_list = explode(',', $exercise_stat_info['data_tracking']);
  6985. } else {
  6986. //Try getting the question list only if save result is off
  6987. if ($saveUserResult == false) {
  6988. $question_list = $this->selectQuestionList();
  6989. }
  6990. error_log("Data tracking is empty! exe_id: $exe_id");
  6991. }
  6992. $counter = 1;
  6993. $total_score = 0;
  6994. $total_weight = 0;
  6995. $exercise_content = null;
  6996. //Hide results
  6997. $show_results = false;
  6998. $show_only_score = false;
  6999. if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
  7000. $show_results = true;
  7001. }
  7002. $showScoreOptions = [
  7003. RESULT_DISABLE_SHOW_SCORE_ONLY,
  7004. RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
  7005. RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
  7006. ];
  7007. if (in_array($this->results_disabled, $showScoreOptions)) {
  7008. $show_only_score = true;
  7009. }
  7010. if ($show_results || $show_only_score) {
  7011. $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
  7012. // Shows exercise header.
  7013. echo $this->show_exercise_result_header(
  7014. $user_info['complete_name'],
  7015. api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
  7016. $exercise_stat_info['duration']
  7017. );
  7018. }
  7019. // Display text when test is finished #4074 and for LP #4227
  7020. $end_of_message = $this->selectTextWhenFinished();
  7021. if (!empty($end_of_message)) {
  7022. Display::display_normal_message($end_of_message, false);
  7023. echo "<div class='clear'>&nbsp;</div>";
  7024. }
  7025. $question_list_answers = array();
  7026. $media_list = array();
  7027. $category_list = array();
  7028. $tempParentId = null;
  7029. $mediaCounter = 0;
  7030. $exerciseResultInfo = array();
  7031. // Loop over all question to show results for each of them, one by one
  7032. if (!empty($question_list)) {
  7033. if ($debug) {
  7034. error_log('Looping question_list '.print_r($question_list, 1));
  7035. }
  7036. foreach ($question_list as $questionId) {
  7037. // Creates a temporary Question object
  7038. $objQuestionTmp = Question::read($questionId);
  7039. // This variable commes from exercise_submit_modal.php
  7040. ob_start();
  7041. $hotspot_delineation_result = null;
  7042. // We're inside *one* question. Go through each possible answer for this question
  7043. $result = $this->manageAnswers(
  7044. $exercise_stat_info['exe_id'],
  7045. $questionId,
  7046. null,
  7047. 'exercise_result',
  7048. array(),
  7049. $saveUserResult,
  7050. true,
  7051. $show_results,
  7052. $hotspot_delineation_result
  7053. );
  7054. if (empty($result)) {
  7055. continue;
  7056. }
  7057. $total_score += $result['score'];
  7058. $total_weight += $result['weight'];
  7059. $question_list_answers[] = array(
  7060. 'question' => $result['open_question'],
  7061. 'answer' => $result['open_answer'],
  7062. 'answer_type' => $result['answer_type']
  7063. );
  7064. $my_total_score = $result['score'];
  7065. $my_total_weight = $result['weight'];
  7066. // Category report
  7067. $category_was_added_for_this_test = false;
  7068. $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
  7069. $category_list = array();
  7070. if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
  7071. foreach ($categoryExerciseList as $category_id => $categoryInfo) {
  7072. if (!isset($category_list[$category_id])) {
  7073. $category_list[$category_id] = array();
  7074. $category_list[$category_id]['score'] = 0;
  7075. $category_list[$category_id]['total'] = 0;
  7076. }
  7077. $category_list[$category_id]['score'] += $my_total_score;
  7078. $category_list[$category_id]['total'] += $my_total_weight;
  7079. $category_was_added_for_this_test = true;
  7080. }
  7081. }
  7082. // No category for this question!
  7083. if ($category_was_added_for_this_test == false) {
  7084. if (!isset($category_list['none'])) {
  7085. $category_list['none'] = array();
  7086. $category_list['none']['score'] = 0;
  7087. $category_list['none']['total'] = 0;
  7088. }
  7089. $category_list['none']['score'] += $my_total_score;
  7090. $category_list['none']['total'] += $my_total_weight;
  7091. }
  7092. if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
  7093. $my_total_score = 0;
  7094. }
  7095. $comnt = null;
  7096. if ($show_results) {
  7097. $comnt = get_comments($exe_id, $questionId);
  7098. if (!empty($comnt)) {
  7099. echo '<b>'.get_lang('Feedback').'</b>';
  7100. echo '<div id="question_feedback">'.$comnt.'</div>';
  7101. }
  7102. }
  7103. $score = array();
  7104. $score['result'] = get_lang('Score')." : ".ExerciseLib::show_score($my_total_score, $my_total_weight, false, true);
  7105. $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
  7106. $score['score'] = $my_total_score;
  7107. $score['weight'] = $my_total_weight;
  7108. $score['comments'] = $comnt;
  7109. $exerciseResultInfo[$questionId]['score'] = $score;
  7110. $exerciseResultInfo[$questionId]['details'] = $result;
  7111. // If no results we hide the results
  7112. if ($show_results == false) {
  7113. $score = array();
  7114. }
  7115. $contents = ob_get_clean();
  7116. $question_content = '<div class="question_row">';
  7117. if ($show_results) {
  7118. $show_media = false;
  7119. $counterToShow = $counter;
  7120. if ($objQuestionTmp->parent_id != 0) {
  7121. if (!in_array($objQuestionTmp->parent_id, $media_list)) {
  7122. $media_list[] = $objQuestionTmp->parent_id;
  7123. $show_media = true;
  7124. }
  7125. if ($tempParentId == $objQuestionTmp->parent_id) {
  7126. $mediaCounter++;
  7127. } else {
  7128. $mediaCounter = 0;
  7129. }
  7130. $counterToShow = chr(97 + $mediaCounter);
  7131. $tempParentId = $objQuestionTmp->parent_id;
  7132. }
  7133. // Shows question title an description.
  7134. $question_content .= $objQuestionTmp->return_header(null, $counterToShow, $score, $show_media, $this->getHideQuestionTitle());
  7135. // display question category, if any
  7136. $question_content .= TestCategory::getCategoryNamesForQuestion($questionId, null, true, $this->categoryMinusOne);
  7137. }
  7138. $counter++;
  7139. $question_content .= $contents;
  7140. $question_content .= '</div>';
  7141. $exercise_content .= $question_content;
  7142. } // end foreach() block that loops over all questions
  7143. }
  7144. $total_score_text = null;
  7145. if ($returnExerciseResult) {
  7146. return $exerciseResultInfo;
  7147. }
  7148. if ($origin != 'learnpath') {
  7149. if ($show_results || $show_only_score) {
  7150. $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
  7151. }
  7152. }
  7153. if (!empty($category_list) && ($show_results || $show_only_score)) {
  7154. //Adding total
  7155. $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
  7156. echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
  7157. }
  7158. echo $total_score_text;
  7159. echo $exercise_content;
  7160. if (!$show_only_score) {
  7161. echo $total_score_text;
  7162. }
  7163. if ($saveUserResult) {
  7164. // Tracking of results
  7165. $learnpath_id = $exercise_stat_info['orig_lp_id'];
  7166. $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
  7167. $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
  7168. if (api_is_allowed_to_session_edit()) {
  7169. update_event_exercise(
  7170. $exercise_stat_info['exe_id'],
  7171. $this->selectId(),
  7172. $total_score,
  7173. $total_weight,
  7174. api_get_session_id(),
  7175. $learnpath_id,
  7176. $learnpath_item_id,
  7177. $learnpath_item_view_id,
  7178. $exercise_stat_info['exe_duration'],
  7179. '',
  7180. array()
  7181. );
  7182. }
  7183. // Send notification.
  7184. if (!api_is_allowed_to_edit(null, true)) {
  7185. $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
  7186. $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
  7187. $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
  7188. $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
  7189. }
  7190. }
  7191. }
  7192. /**
  7193. * Returns an HTML ribbon to show on top of the exercise result, with
  7194. * colouring depending on the success or failure of the student
  7195. * @param integer $score
  7196. * @param integer $weight
  7197. * @param bool $check_pass_percentage
  7198. * @return string
  7199. */
  7200. public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
  7201. {
  7202. $eventMessage = null;
  7203. $ribbon = '<div class="question_row">';
  7204. $ribbon .= '<div class="ribbon">';
  7205. if ($check_pass_percentage) {
  7206. $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
  7207. // Color the final test score if pass_percentage activated
  7208. $ribbon_total_success_or_error = "";
  7209. if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
  7210. if ($is_success) {
  7211. $eventMessage = $this->getOnSuccessMessage();
  7212. $ribbon_total_success_or_error = ' ribbon-total-success';
  7213. } else {
  7214. $eventMessage = $this->getOnFailedMessage();
  7215. $ribbon_total_success_or_error = ' ribbon-total-error';
  7216. }
  7217. }
  7218. $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
  7219. } else {
  7220. $ribbon .= '<div class="rib rib-total">';
  7221. }
  7222. $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
  7223. $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
  7224. $ribbon .= '</h3>';
  7225. $ribbon .= '</div>';
  7226. if ($check_pass_percentage) {
  7227. $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
  7228. }
  7229. $ribbon .= '</div>';
  7230. $ribbon .= '</div>';
  7231. $ribbon .= $eventMessage;
  7232. return $ribbon;
  7233. }
  7234. /**
  7235. * Returns an array of categories details for the questions of the current
  7236. * exercise.
  7237. * @return array
  7238. */
  7239. public function getQuestionWithCategories()
  7240. {
  7241. $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  7242. $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  7243. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  7244. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  7245. $sql = "SELECT DISTINCT cat.*
  7246. FROM $TBL_EXERCICE_QUESTION e
  7247. INNER JOIN $TBL_QUESTIONS q
  7248. ON (e.question_id = q.id AND e.c_id = q.c_id)
  7249. INNER JOIN $categoryRelTable catRel
  7250. ON (catRel.question_id = e.question_id)
  7251. INNER JOIN $categoryTable cat
  7252. ON (cat.id = catRel.category_id)
  7253. WHERE
  7254. e.c_id = {$this->course_id} AND
  7255. e.exercice_id = ".intval($this->id);
  7256. $result = Database::query($sql);
  7257. $categoriesInExercise = array();
  7258. if (Database::num_rows($result)) {
  7259. $categoriesInExercise = Database::store_result($result, 'ASSOC');
  7260. }
  7261. return $categoriesInExercise;
  7262. }
  7263. /**
  7264. * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
  7265. */
  7266. public function get_max_score()
  7267. {
  7268. $out_max_score = 0;
  7269. // list of question's id !!! the array key start at 1 !!!
  7270. $questionList = $this->selectQuestionList(true);
  7271. // test is randomQuestions - see field random of test
  7272. if ($this->random > 0 && $this->randomByCat == 0) {
  7273. $numberRandomQuestions = $this->random;
  7274. $questionScoreList = array();
  7275. for ($i = 1; $i <= count($questionList); $i++) {
  7276. $tmpobj_question = Question::read($questionList[$i]);
  7277. $questionScoreList[] = $tmpobj_question->weighting;
  7278. }
  7279. rsort($questionScoreList);
  7280. // add the first $numberRandomQuestions value of score array to get max_score
  7281. for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
  7282. $out_max_score += $questionScoreList[$i];
  7283. }
  7284. } else if ($this->random > 0 && $this->randomByCat > 0) {
  7285. // test is random by category
  7286. // get the $numberRandomQuestions best score question of each category
  7287. $numberRandomQuestions = $this->random;
  7288. $tab_categories_scores = array();
  7289. for ($i = 1; $i <= count($questionList); $i++) {
  7290. $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
  7291. if (!is_array($tab_categories_scores[$question_category_id])) {
  7292. $tab_categories_scores[$question_category_id] = array();
  7293. }
  7294. $tmpobj_question = Question::read($questionList[$i]);
  7295. $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
  7296. }
  7297. // here we've got an array with first key, the category_id, second key, score of question for this cat
  7298. while (list($key, $tab_scores) = each($tab_categories_scores)) {
  7299. rsort($tab_scores);
  7300. for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
  7301. $out_max_score += $tab_scores[$i];
  7302. }
  7303. }
  7304. } else {
  7305. // standard test, just add each question score
  7306. foreach ($questionList as $questionId) {
  7307. $question = Question::read($questionId, $this->course_id);
  7308. $out_max_score += $question->weighting;
  7309. }
  7310. }
  7311. return $out_max_score;
  7312. }
  7313. /**
  7314. * @return string
  7315. */
  7316. public function get_formated_title()
  7317. {
  7318. return api_html_entity_decode($this->selectTitle());
  7319. }
  7320. /**
  7321. * @param $in_title
  7322. * @return string
  7323. */
  7324. public static function get_formated_title_variable($in_title)
  7325. {
  7326. return api_html_entity_decode($in_title);
  7327. }
  7328. /**
  7329. * @return string
  7330. */
  7331. public function format_title()
  7332. {
  7333. return api_htmlentities($this->title);
  7334. }
  7335. /**
  7336. * @param $in_title
  7337. * @return string
  7338. */
  7339. public static function format_title_variable($in_title)
  7340. {
  7341. return api_htmlentities($in_title);
  7342. }
  7343. /**
  7344. * @param int $courseId
  7345. * @param int $sessionId
  7346. * @return array exercises
  7347. */
  7348. public function getExercisesByCouseSession($courseId, $sessionId)
  7349. {
  7350. $courseId = intval($courseId);
  7351. $sessionId = intval($sessionId);
  7352. $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
  7353. $sql = "SELECT * FROM $tbl_quiz cq
  7354. WHERE
  7355. cq.c_id = %s AND
  7356. (cq.session_id = %s OR cq.session_id = 0) AND
  7357. cq.active = 0
  7358. ORDER BY cq.id";
  7359. $sql = sprintf($sql, $courseId, $sessionId);
  7360. $result = Database::query($sql);
  7361. $rows = array();
  7362. while ($row = Database::fetch_array($result, 'ASSOC')) {
  7363. $rows[] = $row;
  7364. }
  7365. return $rows;
  7366. }
  7367. /**
  7368. *
  7369. * @param int $courseId
  7370. * @param int $sessionId
  7371. * @param array $quizId
  7372. * @return array exercises
  7373. */
  7374. public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
  7375. {
  7376. if (empty($quizId)) {
  7377. return array();
  7378. }
  7379. $sessionId = intval($sessionId);
  7380. $ids = is_array($quizId) ? $quizId : array($quizId);
  7381. $ids = array_map('intval', $ids);
  7382. $ids = implode(',', $ids);
  7383. $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  7384. if ($sessionId != 0) {
  7385. $sql = "SELECT * FROM $track_exercises te
  7386. INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
  7387. WHERE
  7388. te.id = %s AND
  7389. te.session_id = %s AND
  7390. cq.id IN (%s)
  7391. ORDER BY cq.id";
  7392. $sql = sprintf($sql, $courseId, $sessionId, $ids);
  7393. } else {
  7394. $sql = "SELECT * FROM $track_exercises te
  7395. INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
  7396. WHERE
  7397. te.id = %s AND
  7398. cq.id IN (%s)
  7399. ORDER BY cq.id";
  7400. $sql = sprintf($sql, $courseId, $ids);
  7401. }
  7402. $result = Database::query($sql);
  7403. $rows = array();
  7404. while ($row = Database::fetch_array($result, 'ASSOC')) {
  7405. $rows[] = $row;
  7406. }
  7407. return $rows;
  7408. }
  7409. /**
  7410. * @param $exeId
  7411. * @param $exercise_stat_info
  7412. * @param $remindList
  7413. * @param $currentQuestion
  7414. * @return int|null
  7415. */
  7416. public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
  7417. {
  7418. $result = get_exercise_results_by_attempt($exeId, 'incomplete');
  7419. if (isset($result[$exeId])) {
  7420. $result = $result[$exeId];
  7421. } else {
  7422. return null;
  7423. }
  7424. $data_tracking = $exercise_stat_info['data_tracking'];
  7425. $data_tracking = explode(',', $data_tracking);
  7426. // if this is the final question do nothing.
  7427. if ($currentQuestion == count($data_tracking)) {
  7428. return null;
  7429. }
  7430. $currentQuestion = $currentQuestion - 1;
  7431. if (!empty($result['question_list'])) {
  7432. $answeredQuestions = array();
  7433. foreach ($result['question_list'] as $question) {
  7434. if (!empty($question['answer'])) {
  7435. $answeredQuestions[] = $question['question_id'];
  7436. }
  7437. }
  7438. // Checking answered questions
  7439. $counterAnsweredQuestions = 0;
  7440. foreach ($data_tracking as $questionId) {
  7441. if (!in_array($questionId, $answeredQuestions)) {
  7442. if ($currentQuestion != $counterAnsweredQuestions) {
  7443. break;
  7444. }
  7445. }
  7446. $counterAnsweredQuestions++;
  7447. }
  7448. $counterRemindListQuestions = 0;
  7449. // Checking questions saved in the reminder list
  7450. if (!empty($remindList)) {
  7451. foreach ($data_tracking as $questionId) {
  7452. if (in_array($questionId, $remindList)) {
  7453. // Skip the current question
  7454. if ($currentQuestion != $counterRemindListQuestions) {
  7455. break;
  7456. }
  7457. }
  7458. $counterRemindListQuestions++;
  7459. }
  7460. if ($counterRemindListQuestions < $currentQuestion) {
  7461. return null;
  7462. }
  7463. if (!empty($counterRemindListQuestions)) {
  7464. if ($counterRemindListQuestions > $counterAnsweredQuestions) {
  7465. return $counterAnsweredQuestions;
  7466. } else {
  7467. return $counterRemindListQuestions;
  7468. }
  7469. }
  7470. }
  7471. return $counterAnsweredQuestions;
  7472. }
  7473. }
  7474. /**
  7475. * Gets the position of a questionId in the question list
  7476. * @param $questionId
  7477. * @return int
  7478. */
  7479. public function getPositionInCompressedQuestionList($questionId)
  7480. {
  7481. $questionList = $this->getQuestionListWithMediasCompressed();
  7482. $mediaQuestions = $this->getMediaList();
  7483. $position = 1;
  7484. foreach ($questionList as $id) {
  7485. if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
  7486. $mediaQuestionList = $mediaQuestions[$id];
  7487. if (in_array($questionId, $mediaQuestionList)) {
  7488. return $position;
  7489. } else {
  7490. $position++;
  7491. }
  7492. } else {
  7493. if ($id == $questionId) {
  7494. return $position;
  7495. } else {
  7496. $position++;
  7497. }
  7498. }
  7499. }
  7500. return 1;
  7501. }
  7502. /**
  7503. * Get the correct answers in all attempts
  7504. * @param int $learnPathId
  7505. * @param int $learnPathItemId
  7506. * @return array
  7507. */
  7508. public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
  7509. {
  7510. $attempts = Event::getExerciseResultsByUser(
  7511. api_get_user_id(),
  7512. $this->id,
  7513. api_get_course_int_id(),
  7514. api_get_session_id(),
  7515. $learnPathId,
  7516. $learnPathItemId,
  7517. 'asc'
  7518. );
  7519. $corrects = [];
  7520. foreach ($attempts as $attempt) {
  7521. foreach ($attempt['question_list'] as $answer) {
  7522. $objAnswer = new Answer($answer['question_id']);
  7523. $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
  7524. if ($isCorrect) {
  7525. $corrects[$answer['question_id']][] = $answer;
  7526. }
  7527. }
  7528. }
  7529. return $corrects;
  7530. }
  7531. }