ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PK3\wKbb scorm_13.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . // Used need to debug cmi content (if you uncomment this, you must comment the definition inside SCORMapi1_3) //var cmi = new Object(); // // SCORM 1.3 API Implementation // function SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit) { var prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "¤torg=" + currentorg + "&sesskey=" + sesskey; var datamodelurl = cfgwwwroot + "/mod/scorm/datamodel.php"; var datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid; // Standard Data Type Definition // language key has to be checked for language dependent strings var validLanguages = {'aa':'aa', 'ab':'ab', 'ae':'ae', 'af':'af', 'ak':'ak', 'am':'am', 'an':'an', 'ar':'ar', 'as':'as', 'av':'av', 'ay':'ay', 'az':'az', 'ba':'ba', 'be':'be', 'bg':'bg', 'bh':'bh', 'bi':'bi', 'bm':'bm', 'bn':'bn', 'bo':'bo', 'br':'br', 'bs':'bs', 'ca':'ca', 'ce':'ce', 'ch':'ch', 'co':'co', 'cr':'cr', 'cs':'cs', 'cu':'cu', 'cv':'cv', 'cy':'cy', 'da':'da', 'de':'de', 'dv':'dv', 'dz':'dz', 'ee':'ee', 'el':'el', 'en':'en', 'eo':'eo', 'es':'es', 'et':'et', 'eu':'eu', 'fa':'fa', 'ff':'ff', 'fi':'fi', 'fj':'fj', 'fo':'fo', 'fr':'fr', 'fy':'fy', 'ga':'ga', 'gd':'gd', 'gl':'gl', 'gn':'gn', 'gu':'gu', 'gv':'gv', 'ha':'ha', 'he':'he', 'hi':'hi', 'ho':'ho', 'hr':'hr', 'ht':'ht', 'hu':'hu', 'hy':'hy', 'hz':'hz', 'ia':'ia', 'id':'id', 'ie':'ie', 'ig':'ig', 'ii':'ii', 'ik':'ik', 'io':'io', 'is':'is', 'it':'it', 'iu':'iu', 'ja':'ja', 'jv':'jv', 'ka':'ka', 'kg':'kg', 'ki':'ki', 'kj':'kj', 'kk':'kk', 'kl':'kl', 'km':'km', 'kn':'kn', 'ko':'ko', 'kr':'kr', 'ks':'ks', 'ku':'ku', 'kv':'kv', 'kw':'kw', 'ky':'ky', 'la':'la', 'lb':'lb', 'lg':'lg', 'li':'li', 'ln':'ln', 'lo':'lo', 'lt':'lt', 'lu':'lu', 'lv':'lv', 'mg':'mg', 'mh':'mh', 'mi':'mi', 'mk':'mk', 'ml':'ml', 'mn':'mn', 'mo':'mo', 'mr':'mr', 'ms':'ms', 'mt':'mt', 'my':'my', 'na':'na', 'nb':'nb', 'nd':'nd', 'ne':'ne', 'ng':'ng', 'nl':'nl', 'nn':'nn', 'no':'no', 'nr':'nr', 'nv':'nv', 'ny':'ny', 'oc':'oc', 'oj':'oj', 'om':'om', 'or':'or', 'os':'os', 'pa':'pa', 'pi':'pi', 'pl':'pl', 'ps':'ps', 'pt':'pt', 'qu':'qu', 'rm':'rm', 'rn':'rn', 'ro':'ro', 'ru':'ru', 'rw':'rw', 'sa':'sa', 'sc':'sc', 'sd':'sd', 'se':'se', 'sg':'sg', 'sh':'sh', 'si':'si', 'sk':'sk', 'sl':'sl', 'sm':'sm', 'sn':'sn', 'so':'so', 'sq':'sq', 'sr':'sr', 'ss':'ss', 'st':'st', 'su':'su', 'sv':'sv', 'sw':'sw', 'ta':'ta', 'te':'te', 'tg':'tg', 'th':'th', 'ti':'ti', 'tk':'tk', 'tl':'tl', 'tn':'tn', 'to':'to', 'tr':'tr', 'ts':'ts', 'tt':'tt', 'tw':'tw', 'ty':'ty', 'ug':'ug', 'uk':'uk', 'ur':'ur', 'uz':'uz', 've':'ve', 'vi':'vi', 'vo':'vo', 'wa':'wa', 'wo':'wo', 'xh':'xh', 'yi':'yi', 'yo':'yo', 'za':'za', 'zh':'zh', 'zu':'zu', 'aar':'aar', 'abk':'abk', 'ave':'ave', 'afr':'afr', 'aka':'aka', 'amh':'amh', 'arg':'arg', 'ara':'ara', 'asm':'asm', 'ava':'ava', 'aym':'aym', 'aze':'aze', 'bak':'bak', 'bel':'bel', 'bul':'bul', 'bih':'bih', 'bis':'bis', 'bam':'bam', 'ben':'ben', 'tib':'tib', 'bod':'bod', 'bre':'bre', 'bos':'bos', 'cat':'cat', 'che':'che', 'cha':'cha', 'cos':'cos', 'cre':'cre', 'cze':'cze', 'ces':'ces', 'chu':'chu', 'chv':'chv', 'wel':'wel', 'cym':'cym', 'dan':'dan', 'ger':'ger', 'deu':'deu', 'div':'div', 'dzo':'dzo', 'ewe':'ewe', 'gre':'gre', 'ell':'ell', 'eng':'eng', 'epo':'epo', 'spa':'spa', 'est':'est', 'baq':'baq', 'eus':'eus', 'per':'per', 'fas':'fas', 'ful':'ful', 'fin':'fin', 'fij':'fij', 'fao':'fao', 'fre':'fre', 'fra':'fra', 'fry':'fry', 'gle':'gle', 'gla':'gla', 'glg':'glg', 'grn':'grn', 'guj':'guj', 'glv':'glv', 'hau':'hau', 'heb':'heb', 'hin':'hin', 'hmo':'hmo', 'hrv':'hrv', 'hat':'hat', 'hun':'hun', 'arm':'arm', 'hye':'hye', 'her':'her', 'ina':'ina', 'ind':'ind', 'ile':'ile', 'ibo':'ibo', 'iii':'iii', 'ipk':'ipk', 'ido':'ido', 'ice':'ice', 'isl':'isl', 'ita':'ita', 'iku':'iku', 'jpn':'jpn', 'jav':'jav', 'geo':'geo', 'kat':'kat', 'kon':'kon', 'kik':'kik', 'kua':'kua', 'kaz':'kaz', 'kal':'kal', 'khm':'khm', 'kan':'kan', 'kor':'kor', 'kau':'kau', 'kas':'kas', 'kur':'kur', 'kom':'kom', 'cor':'cor', 'kir':'kir', 'lat':'lat', 'ltz':'ltz', 'lug':'lug', 'lim':'lim', 'lin':'lin', 'lao':'lao', 'lit':'lit', 'lub':'lub', 'lav':'lav', 'mlg':'mlg', 'mah':'mah', 'mao':'mao', 'mri':'mri', 'mac':'mac', 'mkd':'mkd', 'mal':'mal', 'mon':'mon', 'mol':'mol', 'mar':'mar', 'may':'may', 'msa':'msa', 'mlt':'mlt', 'bur':'bur', 'mya':'mya', 'nau':'nau', 'nob':'nob', 'nde':'nde', 'nep':'nep', 'ndo':'ndo', 'dut':'dut', 'nld':'nld', 'nno':'nno', 'nor':'nor', 'nbl':'nbl', 'nav':'nav', 'nya':'nya', 'oci':'oci', 'oji':'oji', 'orm':'orm', 'ori':'ori', 'oss':'oss', 'pan':'pan', 'pli':'pli', 'pol':'pol', 'pus':'pus', 'por':'por', 'que':'que', 'roh':'roh', 'run':'run', 'rum':'rum', 'ron':'ron', 'rus':'rus', 'kin':'kin', 'san':'san', 'srd':'srd', 'snd':'snd', 'sme':'sme', 'sag':'sag', 'slo':'slo', 'sin':'sin', 'slk':'slk', 'slv':'slv', 'smo':'smo', 'sna':'sna', 'som':'som', 'alb':'alb', 'sqi':'sqi', 'srp':'srp', 'ssw':'ssw', 'sot':'sot', 'sun':'sun', 'swe':'swe', 'swa':'swa', 'tam':'tam', 'tel':'tel', 'tgk':'tgk', 'tha':'tha', 'tir':'tir', 'tuk':'tuk', 'tgl':'tgl', 'tsn':'tsn', 'ton':'ton', 'tur':'tur', 'tso':'tso', 'tat':'tat', 'twi':'twi', 'tah':'tah', 'uig':'uig', 'ukr':'ukr', 'urd':'urd', 'uzb':'uzb', 'ven':'ven', 'vie':'vie', 'vol':'vol', 'wln':'wln', 'wol':'wol', 'xho':'xho', 'yid':'yid', 'yor':'yor', 'zha':'zha', 'chi':'chi', 'zho':'zho', 'zul':'zul'}; var CMIString200 = '^[\\u0000-\\uFFFF]{0,200}$'; var CMIString250 = '^[\\u0000-\\uFFFF]{0,250}$'; var CMIString1000 = '^[\\u0000-\\uFFFF]{0,1000}$'; var CMIString4000 = '^[\\u0000-\\uFFFF]{0,4000}$'; var CMIString64000 = '^[\\u0000-\\uFFFF]{0,64000}$'; var CMILang = '^([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?$|^$'; var CMILangString250 = '^(\{lang=([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?\})?([^\{].{0,250}$)?'; var CMILangcr = '^((\{lang=([a-zA-Z]{2,3}|i|x)?(\-[a-zA-Z0-9\-]{2,8})?\}))(.*?)$'; var CMILangString250cr = '^((\{lang=([a-zA-Z]{2,3}|i|x)?(\-[a-zA-Z0-9\-]{2,8})?\})?(.{0,250})?)?$'; var CMILangString4000 = '^(\{lang=([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?\})?([^\{].{0,4000}$)?'; var CMITime = '^(19[7-9]{1}[0-9]{1}|20[0-2]{1}[0-9]{1}|203[0-8]{1})((-(0[1-9]{1}|1[0-2]{1}))((-(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]{1}))(T([0-1]{1}[0-9]{1}|2[0-3]{1})((:[0-5]{1}[0-9]{1})((:[0-5]{1}[0-9]{1})((\\.[0-9]{1,2})((Z|([+|-]([0-1]{1}[0-9]{1}|2[0-3]{1})))(:[0-5]{1}[0-9]{1})?)?)?)?)?)?)?)?$'; var CMITimespan = '^P(\\d+Y)?(\\d+M)?(\\d+D)?(T(((\\d+H)(\\d+M)?(\\d+(\.\\d{1,2})?S)?)|((\\d+M)(\\d+(\.\\d{1,2})?S)?)|((\\d+(\.\\d{1,2})?S))))?$'; var CMIInteger = '^\\d+$'; var CMISInteger = '^-?([0-9]+)$'; var CMIDecimal = '^-?([0-9]{1,5})(\\.[0-9]{1,18})?$'; var CMIIdentifier = '^\\S{1,250}[a-zA-Z0-9]$'; var CMIShortIdentifier = '^[\\w\.]{1,250}$'; var CMILongIdentifier = '^(?:(?!urn:)\\S{1,4000}|urn:[A-Za-z0-9-]{1,31}:\\S{1,4000})$'; var CMIFeedback = '^.*$'; // This must be redefined var CMIIndex = '[._](\\d+).'; var CMIIndexStore = '.N(\\d+).'; // Vocabulary Data Type Definition var CMICStatus = '^completed$|^incomplete$|^not attempted$|^unknown$'; var CMISStatus = '^passed$|^failed$|^unknown$'; var CMIExit = '^time-out$|^suspend$|^logout$|^normal$|^$'; var CMIType = '^true-false$|^choice$|^(long-)?fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$|^other$'; var CMIResult = '^correct$|^incorrect$|^unanticipated$|^neutral$|^-?([0-9]{1,4})(\\.[0-9]{1,18})?$'; var NAVEvent = '^previous$|^continue$|^exit$|^exitAll$|^abandon$|^abandonAll$|^suspendAll$|^\{target=\\S{0,200}[a-zA-Z0-9]\}choice|jump$'; var NAVBoolean = '^unknown$|^true$|^false$'; var NAVTarget = '^previous$|^continue$|^choice.{target=\\S{0,200}[a-zA-Z0-9]}$' // Children lists var cmi_children = '_version,comments_from_learner,comments_from_lms,completion_status,credit,entry,exit,interactions,launch_data,learner_id,learner_name,learner_preference,location,max_time_allowed,mode,objectives,progress_measure,scaled_passing_score,score,session_time,success_status,suspend_data,time_limit_action,total_time'; var comments_children = 'comment,timestamp,location'; var score_children = 'max,raw,scaled,min'; var objectives_children = 'progress_measure,completion_status,success_status,description,score,id'; var correct_responses_children = 'pattern'; var student_data_children = 'mastery_score,max_time_allowed,time_limit_action'; var student_preference_children = 'audio_level,audio_captioning,delivery_speed,language'; var interactions_children = 'id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description'; // Data ranges var scaled_range = '-1#1'; var audio_range = '0#*'; var speed_range = '0#*'; var text_range = '-1#1'; var progress_range = '0#1'; var learner_response = { 'true-false':{'format':'^true$|^false$', 'max':1, 'delimiter':'', 'unique':false}, 'choice':{'format':CMIShortIdentifier, 'max':36, 'delimiter':'[,]', 'unique':true}, 'fill-in':{'format':CMILangString250, 'max':10, 'delimiter':'[,]', 'unique':false}, 'long-fill-in':{'format':CMILangString4000, 'max':1, 'delimiter':'', 'unique':false}, 'matching':{'format':CMIShortIdentifier, 'format2':CMIShortIdentifier, 'max':36, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false}, 'performance':{'format':'^$|' + CMIShortIdentifier, 'format2':CMIDecimal + '|^$|' + CMIShortIdentifier, 'max':250, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false}, 'sequencing':{'format':CMIShortIdentifier, 'max':36, 'delimiter':'[,]', 'unique':false}, 'likert':{'format':CMIShortIdentifier, 'max':1, 'delimiter':'', 'unique':false}, 'numeric':{'format':CMIDecimal, 'max':1, 'delimiter':'', 'unique':false}, 'other':{'format':CMIString4000, 'max':1, 'delimiter':'', 'unique':false} } var correct_responses = { 'true-false':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':false, 'format':'^true$|^false$', 'limit':1}, 'choice':{'max':36, 'delimiter':'[,]', 'unique':true, 'duplicate':false, 'format':CMIShortIdentifier}, 'fill-in':{'max':10, 'delimiter':'[,]', 'unique':false, 'duplicate':false, 'format':CMILangString250cr}, 'long-fill-in':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':true, 'format':CMILangString4000}, 'matching':{'max':36, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false, 'duplicate':false, 'format':CMIShortIdentifier, 'format2':CMIShortIdentifier}, 'performance':{'max':250, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false, 'duplicate':false, 'format':'^$|' + CMIShortIdentifier, 'format2':CMIDecimal + '|^$|' + CMIShortIdentifier}, 'sequencing':{'max':36, 'delimiter':'[,]', 'unique':false, 'duplicate':false, 'format':CMIShortIdentifier}, 'likert':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':false, 'format':CMIShortIdentifier, 'limit':1}, 'numeric':{'max':2, 'delimiter':'[:]', 'unique':false, 'duplicate':false, 'format':CMIDecimal, 'limit':1}, 'other':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':false, 'format':CMIString4000, 'limit':1} } // The SCORM 1.3 data model // Set up data model for each sco var datamodel = {}; for(scoid in def){ datamodel[scoid] = { 'cmi._children':{'defaultvalue':cmi_children, 'mod':'r'}, 'cmi._version':{'defaultvalue':'1.0', 'mod':'r'}, 'cmi.comments_from_learner._children':{'defaultvalue':comments_children, 'mod':'r'}, 'cmi.comments_from_learner._count':{'mod':'r', 'defaultvalue':'0'}, 'cmi.comments_from_learner.n.comment':{'format':CMILangString4000, 'mod':'rw'}, 'cmi.comments_from_learner.n.location':{'format':CMIString250, 'mod':'rw'}, 'cmi.comments_from_learner.n.timestamp':{'format':CMITime, 'mod':'rw'}, 'cmi.comments_from_lms._children':{'defaultvalue':comments_children, 'mod':'r'}, 'cmi.comments_from_lms._count':{'mod':'r', 'defaultvalue':'0'}, 'cmi.comments_from_lms.n.comment':{'format':CMILangString4000, 'mod':'r'}, 'cmi.comments_from_lms.n.location':{'format':CMIString250, 'mod':'r'}, 'cmi.comments_from_lms.n.timestamp':{'format':CMITime, 'mod':'r'}, 'cmi.completion_status':{'defaultvalue':def[scoid]['cmi.completion_status'], 'format':CMICStatus, 'mod':'rw'}, 'cmi.completion_threshold':{'defaultvalue':def[scoid]['cmi.completion_threshold'], 'mod':'r'}, 'cmi.credit':{'defaultvalue':def[scoid]['cmi.credit'], 'mod':'r'}, 'cmi.entry':{'defaultvalue':def[scoid]['cmi.entry'], 'mod':'r'}, 'cmi.exit':{'defaultvalue':def[scoid]['cmi.exit'], 'format':CMIExit, 'mod':'w'}, 'cmi.interactions._children':{'defaultvalue':interactions_children, 'mod':'r'}, 'cmi.interactions._count':{'mod':'r', 'defaultvalue':'0'}, 'cmi.interactions.n.id':{'pattern':CMIIndex, 'format':CMILongIdentifier, 'mod':'rw'}, 'cmi.interactions.n.type':{'pattern':CMIIndex, 'format':CMIType, 'mod':'rw'}, 'cmi.interactions.n.objectives._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0'}, 'cmi.interactions.n.objectives.n.id':{'pattern':CMIIndex, 'format':CMILongIdentifier, 'mod':'rw'}, 'cmi.interactions.n.timestamp':{'pattern':CMIIndex, 'format':CMITime, 'mod':'rw'}, 'cmi.interactions.n.correct_responses._count':{'defaultvalue':'0', 'pattern':CMIIndex, 'mod':'r'}, 'cmi.interactions.n.correct_responses.n.pattern':{'pattern':CMIIndex, 'format':'CMIFeedback', 'mod':'rw'}, 'cmi.interactions.n.weighting':{'pattern':CMIIndex, 'format':CMIDecimal, 'mod':'rw'}, 'cmi.interactions.n.learner_response':{'pattern':CMIIndex, 'format':'CMIFeedback', 'mod':'rw'}, 'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'rw'}, 'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'rw'}, 'cmi.interactions.n.description':{'pattern':CMIIndex, 'format':CMILangString250, 'mod':'rw'}, 'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.launch_data'], 'mod':'r'}, 'cmi.learner_id':{'defaultvalue':def[scoid]['cmi.learner_id'], 'mod':'r'}, 'cmi.learner_name':{'defaultvalue':def[scoid]['cmi.learner_name'], 'mod':'r'}, 'cmi.learner_preference._children':{'defaultvalue':student_preference_children, 'mod':'r'}, 'cmi.learner_preference.audio_level':{'defaultvalue':def[scoid]['cmi.learner_preference.audio_level'], 'format':CMIDecimal, 'range':audio_range, 'mod':'rw'}, 'cmi.learner_preference.language':{'defaultvalue':def[scoid]['cmi.learner_preference.language'], 'format':CMILang, 'mod':'rw'}, 'cmi.learner_preference.delivery_speed':{'defaultvalue':def[scoid]['cmi.learner_preference.delivery_speed'], 'format':CMIDecimal, 'range':speed_range, 'mod':'rw'}, 'cmi.learner_preference.audio_captioning':{'defaultvalue':def[scoid]['cmi.learner_preference.audio_captioning'], 'format':CMISInteger, 'range':text_range, 'mod':'rw'}, 'cmi.location':{'defaultvalue':def[scoid]['cmi.location'], 'format':CMIString1000, 'mod':'rw'}, 'cmi.max_time_allowed':{'defaultvalue':def[scoid]['cmi.max_time_allowed'], 'mod':'r'}, 'cmi.mode':{'defaultvalue':def[scoid]['cmi.mode'], 'mod':'r'}, 'cmi.objectives._children':{'defaultvalue':objectives_children, 'mod':'r'}, 'cmi.objectives._count':{'mod':'r', 'defaultvalue':'0'}, 'cmi.objectives.n.id':{'pattern':CMIIndex, 'format':CMILongIdentifier, 'mod':'rw'}, 'cmi.objectives.n.score._children':{'defaultvalue':score_children, 'pattern':CMIIndex, 'mod':'r'}, 'cmi.objectives.n.score.scaled':{'defaultvalue':null, 'pattern':CMIIndex, 'format':CMIDecimal, 'range':scaled_range, 'mod':'rw'}, 'cmi.objectives.n.score.raw':{'defaultvalue':null, 'pattern':CMIIndex, 'format':CMIDecimal, 'mod':'rw'}, 'cmi.objectives.n.score.min':{'defaultvalue':null, 'pattern':CMIIndex, 'format':CMIDecimal, 'mod':'rw'}, 'cmi.objectives.n.score.max':{'defaultvalue':null, 'pattern':CMIIndex, 'format':CMIDecimal, 'mod':'rw'}, 'cmi.objectives.n.success_status':{'defaultvalue':'unknown', 'pattern':CMIIndex, 'format':CMISStatus, 'mod':'rw'}, 'cmi.objectives.n.completion_status':{'defaultvalue':'unknown', 'pattern':CMIIndex, 'format':CMICStatus, 'mod':'rw'}, 'cmi.objectives.n.progress_measure':{'defaultvalue':null, 'format':CMIDecimal, 'range':progress_range, 'mod':'rw'}, 'cmi.objectives.n.description':{'pattern':CMIIndex, 'format':CMILangString250, 'mod':'rw'}, 'cmi.progress_measure':{'defaultvalue':def[scoid]['cmi.progress_measure'], 'format':CMIDecimal, 'range':progress_range, 'mod':'rw'}, 'cmi.scaled_passing_score':{'defaultvalue':def[scoid]['cmi.scaled_passing_score'], 'format':CMIDecimal, 'range':scaled_range, 'mod':'r'}, 'cmi.score._children':{'defaultvalue':score_children, 'mod':'r'}, 'cmi.score.scaled':{'defaultvalue':def[scoid]['cmi.score.scaled'], 'format':CMIDecimal, 'range':scaled_range, 'mod':'rw'}, 'cmi.score.raw':{'defaultvalue':def[scoid]['cmi.score.raw'], 'format':CMIDecimal, 'mod':'rw'}, 'cmi.score.min':{'defaultvalue':def[scoid]['cmi.score.min'], 'format':CMIDecimal, 'mod':'rw'}, 'cmi.score.max':{'defaultvalue':def[scoid]['cmi.score.max'], 'format':CMIDecimal, 'mod':'rw'}, 'cmi.session_time':{'format':CMITimespan, 'mod':'w', 'defaultvalue':'PT0H0M0S'}, 'cmi.success_status':{'defaultvalue':def[scoid]['cmi.success_status'], 'format':CMISStatus, 'mod':'rw'}, 'cmi.suspend_data':{'defaultvalue':def[scoid]['cmi.suspend_data'], 'format':CMIString64000, 'mod':'rw'}, 'cmi.time_limit_action':{'defaultvalue':def[scoid]['cmi.time_limit_action'], 'mod':'r'}, 'cmi.total_time':{'defaultvalue':def[scoid]['cmi.total_time'], 'mod':'r'}, 'adl.nav.request':{'defaultvalue':'_none_', 'format':NAVEvent, 'mod':'rw'} }; } var cmi, adl; function initdatamodel(scoid){ prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "¤torg=" + currentorg + "&sesskey=" + sesskey; datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid; // // Datamodel inizialization // cmi = new Object(); cmi.comments_from_learner = new Object(); cmi.comments_from_learner._count = 0; cmi.comments_from_lms = new Object(); cmi.comments_from_lms._count = 0; cmi.interactions = new Object(); cmi.interactions._count = 0; cmi.learner_preference = new Object(); cmi.objectives = new Object(); cmi.objectives._count = 0; cmi.score = new Object(); // Navigation Object adl = new Object(); adl.nav = new Object(); adl.nav.request_valid = new Array(); for (element in datamodel[scoid]) { if (element.match(/\.n\./) == null) { if (typeof datamodel[scoid][element].defaultvalue != 'undefined') { eval(element + ' = datamodel["' + scoid + '"]["' + element + '"].defaultvalue;'); } else { eval(element + ' = "";'); } } } eval(cmiobj[scoid]); eval(cmiint[scoid]); eval(cmicommentsuser[scoid]); eval(cmicommentslms[scoid]); if (cmi.completion_status == '') { cmi.completion_status = 'not attempted'; } } // // API Methods definition // var Initialized = false; var Terminated = false; var diagnostic = ""; var errorCode = "0"; function Initialize (param) { scoid = scorm_current_node ? scorm_current_node.scoid : scoid; initdatamodel(scoid); errorCode = "0"; if (param == "") { if ((!Initialized) && (!Terminated)) { Initialized = true; errorCode = "0"; if (scormdebugging) { LogAPICall("Initialize", param, "", errorCode); } return "true"; } else { if (Initialized) { errorCode = "103"; } else { errorCode = "104"; } } } else { errorCode = "201"; } if (scormdebugging) { LogAPICall("Initialize", param, "", errorCode); } return "false"; } function Terminate (param) { errorCode = "0"; if (param == "") { if ((Initialized) && (!Terminated)) { var AJAXResult = StoreData(cmi,true); if (scormdebugging) { LogAPICall("Terminate", "AJAXResult", AJAXResult, 0); } result = ('true' == AJAXResult) ? 'true' : 'false'; errorCode = ('true' == result) ? '0' : '101'; // General exception for any AJAX fault. if (scormdebugging) { LogAPICall("Terminate", "result", result, errorCode); } if ('true' == result) { Initialized = false; Terminated = true; if (adl.nav.request != '_none_') { switch (adl.nav.request) { case 'continue': setTimeout('mod_scorm_launch_next_sco();',500); break; case 'previous': setTimeout('mod_scorm_launch_prev_sco();',500); break; case 'choice': break; case 'exit': break; case 'exitAll': break; case 'abandon': break; case 'abandonAll': break; } } else { if (scormauto == 1) { setTimeout('mod_scorm_launch_next_sco();',500); } } // trigger TOC update var callback = M.mod_scorm.connectPrereqCallback; YUI().use('io-base', function(Y) { Y.on('io:complete', callback.success, Y); Y.io(prerequrl); }); } else { diagnostic = "Failure calling the Terminate remote callback: the server replied with HTTP Status " + AJAXResult; } return result; } else { if (Terminated) { errorCode = "113"; } else { errorCode = "112"; } } } else { errorCode = "201"; } if (scormdebugging) { LogAPICall("Terminate", param, "", errorCode); } return "false"; } function GetValue (element) { errorCode = "0"; diagnostic = ""; if ((Initialized) && (!Terminated)) { if (element != "") { var expression = new RegExp(CMIIndex,'g'); var elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'w') { element = String(element).replace(/\.(\d+)\./, ".N$1."); element = element.replace(/\.(\d+)\./, ".N$1."); var elementIndexes = element.split('.'); var subelement = element.substr(0,3); var i = 1; while ((i < elementIndexes.length) && (typeof eval(subelement) != "undefined")) { subelement += '.' + elementIndexes[i++]; } if (subelement == element) { if ((typeof eval(subelement) != "undefined") && (eval(subelement) != null)) { errorCode = "0"; if (scormdebugging) { LogAPICall("GetValue", element, eval(element), 0); } return eval(element); } else { errorCode = "403"; } } else { errorCode = "301"; } } else { //errorCode = eval('datamodel["' + scoid + '"]["' + elementmodel + '"].readerror'); errorCode = "405"; } } else { var childrenstr = '._children'; var countstr = '._count'; var parentmodel = ''; if (elementmodel.substr(elementmodel.length - childrenstr.length,elementmodel.length) == childrenstr) { parentmodel = elementmodel.substr(0,elementmodel.length - childrenstr.length); if (datamodel[scoid][parentmodel] != "undefined") { errorCode = "301"; diagnostic = "Data Model Element Does Not Have Children"; } else { errorCode = "401"; } } else if (elementmodel.substr(elementmodel.length - countstr.length,elementmodel.length) == countstr) { parentmodel = elementmodel.substr(0,elementmodel.length - countstr.length); if (typeof datamodel[scoid][parentmodel] != "undefined") { errorCode = "301"; diagnostic = "Data Model Element Cannot Have Count"; } else { errorCode = "401"; } } else { parentmodel = 'adl.nav.request_valid.'; if (element.substr(0,parentmodel.length) == parentmodel) { if (element.substr(parentmodel.length).match(NAVTarget) == null) { errorCode = "301"; } else { if (adl.nav.request == element.substr(parentmodel.length)) { return "true"; } else if (adl.nav.request == '_none_') { return "unknown"; } else { return "false"; } } } else { errorCode = "401"; } } } } else { errorCode = "301"; } } else { if (Terminated) { errorCode = "123"; } else { errorCode = "122"; } } if (scormdebugging) { LogAPICall("GetValue", element, "", errorCode); } return ""; } function SetValue (element,value) { errorCode = "0"; diagnostic = ""; if ((Initialized) && (!Terminated)) { if (element != "") { var expression = new RegExp(CMIIndex,'g'); var elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'r') { if (datamodel[scoid][elementmodel].format != 'CMIFeedback') { expression = new RegExp(datamodel[scoid][elementmodel].format); } else { // cmi.interactions.n.type depending format accept everything at this stage expression = new RegExp(CMIFeedback); } value = value + ''; var matches = value.match(expression); if ((matches != null) && ((matches.join('').length > 0) || (value.length == 0))) { // Value match dataelement format if (element != elementmodel) { //This is a dynamic datamodel element var elementIndexes = element.split('.'); var subelement = 'cmi'; var parentelement = 'cmi'; for (var i = 1; (i < elementIndexes.length - 1) && (errorCode == "0"); i++) { var elementIndex = elementIndexes[i]; if (elementIndexes[i + 1].match(/^\d+$/)) { if ((parseInt(elementIndexes[i + 1]) > 0) && (elementIndexes[i + 1].charAt(0) == 0)) { // Index has a leading 0 (zero), this is not a number errorCode = "351"; } parentelement = subelement + '.' + elementIndex; if ((typeof eval(parentelement) == "undefined") || (typeof eval(parentelement + '._count') == "undefined")) { errorCode = "408"; } else { if (elementIndexes[i + 1] > eval(parentelement + '._count')) { errorCode = "351"; diagnostic = "Data Model Element Collection Set Out Of Order"; } subelement = subelement.concat('.' + elementIndex + '.N' + elementIndexes[i + 1]); i++; if (((typeof eval(subelement)) == "undefined") && (i < elementIndexes.length - 2)) { errorCode = "408"; } } } else { subelement = subelement.concat('.' + elementIndex); } } if (errorCode == "0") { // Till now it's a real datamodel element element = subelement.concat('.' + elementIndexes[elementIndexes.length - 1]); if ((typeof eval(subelement)) == "undefined") { switch (elementmodel) { case 'cmi.objectives.n.id': if (!duplicatedID(element,parentelement,value)) { if (elementIndexes[elementIndexes.length - 2] == eval(parentelement + '._count')) { eval(parentelement + '._count++;'); eval(subelement + ' = new Object();'); var subobject = eval(subelement); subobject.success_status = datamodel[scoid]["cmi.objectives.n.success_status"].defaultvalue; subobject.completion_status = datamodel[scoid]["cmi.objectives.n.completion_status"].defaultvalue; subobject.progress_measure = datamodel[scoid]["cmi.objectives.n.progress_measure"].defaultvalue; subobject.score = new Object(); subobject.score._children = score_children; subobject.score.scaled = datamodel[scoid]["cmi.objectives.n.score.scaled"].defaultvalue; subobject.score.raw = datamodel[scoid]["cmi.objectives.n.score.raw"].defaultvalue; subobject.score.min = datamodel[scoid]["cmi.objectives.n.score.min"].defaultvalue; subobject.score.max = datamodel[scoid]["cmi.objectives.n.score.max"].defaultvalue; } } else { errorCode = "351"; diagnostic = "Data Model Element ID Already Exists"; } break; case 'cmi.interactions.n.id': if (elementIndexes[elementIndexes.length - 2] == eval(parentelement + '._count')) { eval(parentelement + '._count++;'); eval(subelement + ' = new Object();'); var subobject = eval(subelement); subobject.objectives = new Object(); subobject.objectives._count = 0; } break; case 'cmi.interactions.n.objectives.n.id': if (typeof eval(parentelement) != "undefined") { if (!duplicatedID(element,parentelement,value)) { if (elementIndexes[elementIndexes.length - 2] == eval(parentelement + '._count')) { eval(parentelement + '._count++;'); eval(subelement + ' = new Object();'); } } else { errorCode = "351"; diagnostic = "Data Model Element ID Already Exists"; } } else { errorCode = "408"; } break; case 'cmi.interactions.n.correct_responses.n.pattern': if (typeof eval(parentelement) != "undefined") { // Use cmi.interactions.n.type value to check the right dataelement format if (elementIndexes[elementIndexes.length - 2] == eval(parentelement + '._count')) { var interactiontype = eval(String(parentelement).replace('correct_responses','type')); var interactioncount = eval(parentelement + '._count'); // trap duplicate values, which is not allowed for type choice if (interactiontype == 'choice') { for (var i = 0; (i < interactioncount) && (errorCode == "0"); i++) { if (eval(parentelement + '.N' + i + '.pattern') == value) { errorCode = "351"; } } } if ((typeof correct_responses[interactiontype].limit == 'undefined') || (eval(parentelement + '._count') < correct_responses[interactiontype].limit)) { var nodes = new Array(); if (correct_responses[interactiontype].delimiter != '') { nodes = value.split(correct_responses[interactiontype].delimiter); } else { nodes[0] = value; } if ((nodes.length > 0) && (nodes.length <= correct_responses[interactiontype].max)) { errorCode = CRcheckValueNodes (element, interactiontype, nodes, value, errorCode); } else if (nodes.length > correct_responses[interactiontype].max) { errorCode = "351"; diagnostic = "Data Model Element Pattern Too Long"; } if ((errorCode == "0") && ((correct_responses[interactiontype].duplicate == false) || (!duplicatedPA(element,parentelement,value))) || (errorCode == "0" && value == "")) { eval(parentelement + '._count++;'); eval(subelement + ' = new Object();'); } else { if (errorCode == "0") { errorCode = "351"; diagnostic = "Data Model Element Pattern Already Exists"; } } } else { errorCode = "351"; diagnostic = "Data Model Element Collection Limit Reached"; } } else { errorCode = "351"; diagnostic = "Data Model Element Collection Set Out Of Order"; } } else { errorCode = "408"; } break; default: if ((parentelement != 'cmi.objectives') && (parentelement != 'cmi.interactions') && (typeof eval(parentelement) != "undefined")) { if (elementIndexes[elementIndexes.length - 2] == eval(parentelement + '._count')) { eval(parentelement + '._count++;'); eval(subelement + ' = new Object();'); } else { errorCode = "351"; diagnostic = "Data Model Element Collection Set Out Of Order"; } } else { errorCode = "408"; } break; } } else { switch (elementmodel) { case 'cmi.objectives.n.id': if (eval(element) != value) { errorCode = "351"; diagnostic = "Write Once Violation"; } break; case 'cmi.interactions.n.objectives.n.id': if (duplicatedID(element,parentelement,value)) { errorCode = "351"; diagnostic = "Data Model Element ID Already Exists"; } break; case 'cmi.interactions.n.type': var subobject = eval(subelement); subobject.correct_responses = new Object(); subobject.correct_responses._count = 0; break; case 'cmi.interactions.n.learner_response': if (typeof eval(subelement + '.type') == "undefined") { errorCode = "408"; } else { // Use cmi.interactions.n.type value to check the right dataelement format interactiontype = eval(subelement + '.type'); var nodes = new Array(); if (learner_response[interactiontype].delimiter != '') { nodes = value.split(learner_response[interactiontype].delimiter); } else { nodes[0] = value; } if ((nodes.length > 0) && (nodes.length <= learner_response[interactiontype].max)) { expression = new RegExp(learner_response[interactiontype].format); for (var i = 0; (i < nodes.length) && (errorCode == "0"); i++) { if (typeof learner_response[interactiontype].delimiter2 != 'undefined') { values = nodes[i].split(learner_response[interactiontype].delimiter2); if (values.length == 2) { matches = values[0].match(expression); if (matches == null) { errorCode = "406"; } else { var expression2 = new RegExp(learner_response[interactiontype].format2); matches = values[1].match(expression2); if (matches == null) { errorCode = "406"; } } } else { errorCode = "406"; } } else { matches = nodes[i].match(expression); if (matches == null) { errorCode = "406"; } else { if ((nodes[i] != '') && (learner_response[interactiontype].unique)) { for (var j = 0; (j < i) && (errorCode == "0"); j++) { if (nodes[i] == nodes[j]) { errorCode = "406"; } } } } } } } else if (nodes.length > learner_response[interactiontype].max) { errorCode = "351"; diagnostic = "Data Model Element Pattern Too Long"; } } break; case 'cmi.interactions.n.correct_responses.n.pattern': subel = subelement.split('.'); subel1 = 'cmi.interactions.' + subel[2]; if (typeof eval(subel1 + '.type') == "undefined") { errorCode = "408"; } else { // Use cmi.interactions.n.type value to check the right //dataelement format var interactiontype = eval(subel1 + '.type'); var interactioncount = eval(parentelement + '._count'); // trap duplicate values, which is not allowed for type choice if (interactiontype == 'choice') { for (var i = 0; (i < interactioncount) && (errorCode == "0"); i++) { if (eval(parentelement + '.N' + i + '.pattern') == value) { errorCode = "351"; } } } var nodes = new Array(); if (correct_responses[interactiontype].delimiter != '') { nodes = value.split(correct_responses[interactiontype].delimiter); } else { nodes[0] = value; } if ((nodes.length > 0) && (nodes.length <= correct_responses[interactiontype].max)) { errorCode = CRcheckValueNodes (element, interactiontype, nodes, value, errorCode); } else if (nodes.length > correct_responses[interactiontype].max) { errorCode = "351"; diagnostic = "Data Model Element Pattern Too Long"; } } break; } } } } //Store data if (errorCode == "0") { if (autocommit && !(SCORMapi1_3.timeout)) { SCORMapi1_3.timeout = Y.later(60000, API_1484_11, 'Commit', [""], false); } if (typeof datamodel[scoid][elementmodel].range != "undefined") { range = datamodel[scoid][elementmodel].range; ranges = range.split('#'); value = value * 1.0; if (value >= ranges[0]) { if ((ranges[1] == '*') || (value <= ranges[1])) { eval(element + '=value;'); errorCode = "0"; if (scormdebugging) { LogAPICall("SetValue", element, value, errorCode); } return "true"; } else { errorCode = '407'; } } else { errorCode = '407'; } } else { eval(element + '=value;'); errorCode = "0"; if (scormdebugging) { LogAPICall("SetValue", element, value, errorCode); } return "true"; } } } else { errorCode = "406"; } } else { errorCode = "404"; } } else { errorCode = "401" } } else { errorCode = "351"; } } else { if (Terminated) { errorCode = "133"; } else { errorCode = "132"; } } if (scormdebugging) { LogAPICall("SetValue", element, value, errorCode); } return "false"; } function CRremovePrefixes (node) { // check for prefixes lang, case, order // case and then order var seenOrder = false; var seenCase = false; var seenLang = false; var errorCode = "0"; while (matches = node.match('^(\{(lang|case_matters|order_matters)=([^\}]+)\})')) { switch (matches[2]) { case 'lang': // check for language prefix on each node langmatches = node.match(CMILangcr); if (langmatches != null) { lang = langmatches[3]; // check that language string definition is valid if (lang.length > 0 && lang != undefined) { if (validLanguages[lang.toLowerCase()] == undefined) { errorCode = "406"; } } } seenLang = true; break; case 'case_matters': // check for correct case answer if (! seenLang && ! seenOrder && ! seenCase) { if (matches[3] != 'true' && matches[3] != 'false') { errorCode = "406"; } } seenCase = true; break; case 'order_matters': // check for correct case answer if (! seenCase && ! seenLang && ! seenOrder) { if (matches[3] != 'true' && matches[3] != 'false') { errorCode = "406"; } } seenOrder = true; break; default: break; } node = node.substr(matches[1].length); } return {'errorCode': errorCode, 'node': node}; } function CRcheckValueNodes(element, interactiontype, nodes, value, errorCode) { expression = new RegExp(correct_responses[interactiontype].format); for (var i = 0; (i < nodes.length) && (errorCode == "0"); i++) { if (interactiontype.match('^(fill-in|long-fill-in|matching|performance|sequencing)$')) { result = CRremovePrefixes(nodes[i]); errorCode = result.errorCode; nodes[i] = result.node; } if (correct_responses[interactiontype].delimiter2 != undefined) { values = nodes[i].split(correct_responses[interactiontype].delimiter2); if (values.length == 2) { matches = values[0].match(expression); if (matches == null) { errorCode = "406"; } else { var expression2 = new RegExp(correct_responses[interactiontype].format2); matches = values[1].match(expression2); if (matches == null) { errorCode = "406"; } } } else { errorCode = "406"; } } else { matches = nodes[i].match(expression); //if ((matches == null) || (matches.join('').length == 0)) { if ((matches == null && value != "") || (matches == null && interactiontype == "true-false")){ errorCode = "406"; } else { // numeric range - left must be <= right if (interactiontype == 'numeric' && nodes.length > 1) { if (parseFloat(nodes[0]) > parseFloat(nodes[1])) { errorCode = "406"; } } else { if ((nodes[i] != '') && (correct_responses[interactiontype].unique)) { for (var j = 0; (j < i) && (errorCode == "0"); j++) { if (nodes[i] == nodes[j]) { errorCode = "406"; } } } } } } } // end of for each nodes return errorCode; } function Commit (param) { if (SCORMapi1_3.timeout) { SCORMapi1_3.timeout.cancel(); SCORMapi1_3.timeout = null; } errorCode = "0"; if (param == "") { if ((Initialized) && (!Terminated)) { var AJAXResult = StoreData(cmi,false); if (scormdebugging) { LogAPICall("Commit", "AJAXResult", AJAXResult, 0); } var result = ('true' == AJAXResult) ? 'true' : 'false'; errorCode = ('true' == result) ? '0' : '101'; // General exception for any AJAX fault if (scormdebugging) { LogAPICall("Commit", "result", result, errorCode); } if ('false' == result) { diagnostic = "Failure calling the Commit remote callback: the server replied with HTTP Status " + AJAXResult; } return result; } else { if (Terminated) { errorCode = "143"; } else { errorCode = "142"; } } } else { errorCode = "201"; } if (scormdebugging) { LogAPICall("Commit", param, "", errorCode); } return "false"; } function GetLastError () { if (scormdebugging) { LogAPICall("GetLastError", "", "", errorCode); } return errorCode; } function GetErrorString (param) { if (param != "") { var errorString = ""; switch(param) { case "0": errorString = "No error"; break; case "101": errorString = "General exception"; break; case "102": errorString = "General Inizialization Failure"; break; case "103": errorString = "Already Initialized"; break; case "104": errorString = "Content Instance Terminated"; break; case "111": errorString = "General Termination Failure"; break; case "112": errorString = "Termination Before Inizialization"; break; case "113": errorString = "Termination After Termination"; break; case "122": errorString = "Retrieve Data Before Initialization"; break; case "123": errorString = "Retrieve Data After Termination"; break; case "132": errorString = "Store Data Before Inizialization"; break; case "133": errorString = "Store Data After Termination"; break; case "142": errorString = "Commit Before Inizialization"; break; case "143": errorString = "Commit After Termination"; break; case "201": errorString = "General Argument Error"; break; case "301": errorString = "General Get Failure"; break; case "351": errorString = "General Set Failure"; break; case "391": errorString = "General Commit Failure"; break; case "401": errorString = "Undefinited Data Model"; break; case "402": errorString = "Unimplemented Data Model Element"; break; case "403": errorString = "Data Model Element Value Not Initialized"; break; case "404": errorString = "Data Model Element Is Read Only"; break; case "405": errorString = "Data Model Element Is Write Only"; break; case "406": errorString = "Data Model Element Type Mismatch"; break; case "407": errorString = "Data Model Element Value Out Of Range"; break; case "408": errorString = "Data Model Dependency Not Established"; break; } if (scormdebugging) { LogAPICall("GetErrorString", param, errorString, 0); } return errorString; } else { if (scormdebugging) { LogAPICall("GetErrorString", param, "No error string found!", 0); } return ""; } } function GetDiagnostic (param) { if (diagnostic != "") { if (scormdebugging) { LogAPICall("GetDiagnostic", param, diagnostic, 0); } return diagnostic; } if (scormdebugging) { LogAPICall("GetDiagnostic", param, param, 0); } return param; } function duplicatedID (element, parent, value) { var found = false; var elements = eval(parent + '._count'); for (var n = 0; (n < elements) && (!found); n++) { if ((parent + '.N' + n + '.id' != element) && (eval(parent + '.N' + n + '.id') == value)) { found = true; } } return found; } function duplicatedPA (element, parent, value) { var found = false; var elements = eval(parent + '._count'); for (var n = 0; (n < elements) && (!found); n++) { if ((parent + '.N' + n + '.pattern' != element) && (eval(parent + '.N' + n + '.pattern') == value)) { found = true; } } return found; } function getElementModel(element) { if (typeof datamodel[scoid][element] != "undefined") { return element; } else { var expression = new RegExp(CMIIndex,'g'); var elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { return elementmodel; } } return false; } function AddTime (first, second) { var timestring = 'P'; var matchexpr = /^P((\d+)Y)?((\d+)M)?((\d+)D)?(T((\d+)H)?((\d+)M)?((\d+(\.\d{1,2})?)S)?)?$/; var firstarray = first.match(matchexpr); var secondarray = second.match(matchexpr); if ((firstarray != null) && (secondarray != null)) { var firstsecs = 0; if(parseFloat(firstarray[13],10) > 0){ firstsecs = parseFloat(firstarray[13],10); } var secondsecs = 0; if(parseFloat(secondarray[13],10) > 0){ secondsecs = parseFloat(secondarray[13],10); } var secs = firstsecs + secondsecs; //Seconds var change = Math.floor(secs / 60); secs = Math.round((secs - (change * 60)) * 100) / 100; var firstmins = 0; if(parseInt(firstarray[11],10) > 0){ firstmins = parseInt(firstarray[11],10); } var secondmins = 0; if(parseInt(secondarray[11],10) > 0){ secondmins = parseInt(secondarray[11],10); } var mins = firstmins + secondmins + change; //Minutes change = Math.floor(mins / 60); mins = Math.round(mins - (change * 60)); var firsthours = 0; if(parseInt(firstarray[9],10) > 0){ firsthours = parseInt(firstarray[9],10); } var secondhours = 0; if(parseInt(secondarray[9],10) > 0){ secondhours = parseInt(secondarray[9],10); } var hours = firsthours + secondhours + change; //Hours change = Math.floor(hours / 24); hours = Math.round(hours - (change * 24)); var firstdays = 0; if(parseInt(firstarray[6],10) > 0){ firstdays = parseInt(firstarray[6],10); } var seconddays = 0; if(parseInt(secondarray[6],10) > 0){ firstdays = parseInt(secondarray[6],10); } var days = Math.round(firstdays + seconddays + change); // Days var firstmonths = 0; if(parseInt(firstarray[4],10) > 0){ firstmonths = parseInt(firstarray[4],10); } var secondmonths = 0; if(parseInt(secondarray[4],10) > 0){ secondmonths = parseInt(secondarray[4],10); } var months = Math.round(firstmonths + secondmonths); var firstyears = 0; if(parseInt(firstarray[2],10) > 0){ firstyears = parseInt(firstarray[2],10); } var secondyears = 0; if(parseInt(secondarray[2],10) > 0){ secondyears = parseInt(secondarray[2],10); } var years = Math.round(firstyears + secondyears); } if (years > 0) { timestring += years + 'Y'; } if (months > 0) { timestring += months + 'M'; } if (days > 0) { timestring += days + 'D'; } if ((hours > 0) || (mins > 0) || (secs > 0)) { timestring += 'T'; if (hours > 0) { timestring += hours + 'H'; } if (mins > 0) { timestring += mins + 'M'; } if (secs > 0) { timestring += secs + 'S'; } } return timestring; } function TotalTime() { var total_time = AddTime(cmi.total_time, cmi.session_time); return '&' + underscore('cmi.total_time') + '=' + encodeURIComponent(total_time); } function CollectData(data,parent) { var datastring = ''; for (property in data) { if (typeof data[property] == 'object') { datastring += CollectData(data[property],parent + '.' + property); } else { var element = parent + '.' + property; var expression = new RegExp(CMIIndexStore,'g'); var elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'r') { var elementstring = '&' + underscore(element) + '=' + encodeURIComponent(data[property]); if (typeof datamodel[scoid][elementmodel].defaultvalue != "undefined") { if (datamodel[scoid][elementmodel].defaultvalue != data[property] || typeof datamodel[scoid][elementmodel].defaultvalue != typeof data[property]) { datastring += elementstring; } } else { datastring += elementstring; } } } } } return datastring; } function StoreData(data,storetotaltime) { var datastring = ''; if (storetotaltime) { if (cmi.mode == 'normal') { if (cmi.credit == 'credit') { if ((cmi.completion_threshold) && (cmi.progress_measure)) { if (cmi.progress_measure >= cmi.completion_threshold) { cmi.completion_status = 'completed'; } else { cmi.completion_status = 'incomplete'; } } if ((cmi.scaled_passed_score != null) && (cmi.score.scaled != '')) { if (cmi.score.scaled >= cmi.scaled_passed_score) { cmi.success_status = 'passed'; } else { cmi.success_status = 'failed'; } } } } datastring += TotalTime(); } datastring += CollectData(data,'cmi'); var element = 'adl.nav.request'; var navrequest = eval(element) != datamodel[scoid][element].defaultvalue ? '&' + underscore(element) + '=' + encodeURIComponent(eval(element)) : ''; datastring += navrequest; var myRequest = NewHttpReq(); var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring); if (result === false) { return false; } var results = String(result).split('\n'); if ((results.length > 2) && (navrequest != '')) { eval(results[2]); } errorCode = results[1]; return results[0]; } this.Initialize = Initialize; this.Terminate = Terminate; this.GetValue = GetValue; this.SetValue = SetValue; this.Commit = Commit; this.GetLastError = GetLastError; this.GetErrorString = GetErrorString; this.GetDiagnostic = GetDiagnostic; this.version = '1.0'; } M.scorm_api = {}; M.scorm_api.init = function(Y, def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit) { window.API_1484_11 = new SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit); } PK3\w  scorm_12.phpnu[. require_once($CFG->dirroot.'/mod/scorm/locallib.php'); // Set some vars to use as default values. $userdata = new stdClass(); $def = new stdClass(); $cmiobj = new stdClass(); $cmiint = new stdClass(); if (!isset($currentorg)) { $currentorg = ''; } if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) { // Drop keys so that it is a simple array. $scoes = array_values($scoes); foreach ($scoes as $sco) { $def->{($sco->id)} = new stdClass(); $userdata->{($sco->id)} = new stdClass(); $def->{($sco->id)} = get_scorm_default($userdata->{($sco->id)}, $scorm, $sco->id, $attempt, $mode); // Reconstitute objectives. $cmiobj->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)}, 'cmi.objectives', array('score')); $cmiint->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)}, 'cmi.interactions', array('objectives', 'correct_responses')); } } // If SCORM 1.2 standard mode is disabled allow higher datamodel limits. if (intval(get_config("scorm", "scormstandard"))) { $cmistring256 = '^[\\u0000-\\uFFFF]{0,255}$'; $cmistring4096 = '^[\\u0000-\\uFFFF]{0,4096}$'; } else { $cmistring256 = '^[\\u0000-\\uFFFF]{0,64000}$'; $cmistring4096 = $cmistring256; } $scorm->autocommit = ($scorm->autocommit === "1") ? true : false; $scorm->masteryoverride = ($scorm->masteryoverride === "1") ? true : false; $PAGE->requires->js_init_call('M.scorm_api.init', array($def, $cmiobj, $cmiint, $cmistring256, $cmistring4096, scorm_debugging($scorm), $scorm->auto, $scorm->id, $CFG->wwwroot, sesskey(), $scoid, $attempt, $mode, $id, $currentorg, $scorm->autocommit, $scorm->masteryoverride, $scorm->hidetoc)); // Pull in the debugging utilities. if (scorm_debugging($scorm)) { require_once($CFG->dirroot.'/mod/scorm/datamodels/debug.js.php'); echo html_writer::script('AppendToLog("Moodle SCORM 1.2 API Loaded, Activity: '. $scorm->name.', SCO: '.$sco->identifier.'", 0);'); } PK3\jibz aicc.phpnu[. require_once($CFG->dirroot.'/mod/scorm/locallib.php'); $userdata = new stdClass(); $def = new stdClass(); $cmiobj = new stdClass(); if (!isset($currentorg)) { $currentorg = ''; } if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) { // Drop keys so that it is a simple array. $scoes = array_values($scoes); foreach ($scoes as $sco) { $def->{($sco->id)} = new stdClass(); $userdata->{($sco->id)} = new stdClass(); $def->{($sco->id)} = get_scorm_default($userdata->{($sco->id)}, $scorm, $sco->id, $attempt, $mode); // Reconstitute objectives, comments_from_learner and comments_from_lms. $cmiobj->{($sco->id)} = ''; $currentobj = ''; $count = 0; foreach ($userdata as $element => $value) { if (substr($element, 0, 14) == 'cmi.objectives') { $element = preg_replace('/\.(\d+)\./', "_\$1.", $element); preg_match('/\_(\d+)\./', $element, $matches); if (count($matches) > 0 && $currentobj != $matches[1]) { $currentobj = $matches[1]; $count++; $end = strpos($element, $matches[1]) + strlen($matches[1]); $subelement = substr($element, 0, $end); $cmiobj->{($sco->id)} .= ' '.$subelement." = new Object();\n"; $cmiobj->{($sco->id)} .= ' '.$subelement.".score = new Object();\n"; $cmiobj->{($sco->id)} .= ' '.$subelement.".score._children = score_children;\n"; $cmiobj->{($sco->id)} .= ' '.$subelement.".score.raw = '';\n"; $cmiobj->{($sco->id)} .= ' '.$subelement.".score.min = '';\n"; $cmiobj->{($sco->id)} .= ' '.$subelement.".score.max = '';\n"; } $cmiobj->{($sco->id)} .= ' '.$element.' = \''.$value."';\n"; } } if ($count > 0) { $cmiobj->{($sco->id)} .= ' cmi.objectives._count = '.$count.";\n"; } } } $PAGE->requires->js_init_call('M.scorm_api.init', array($def, $cmiobj, $scorm->auto, $CFG->wwwroot, $scorm->id, $scoid, $attempt, $mode, $currentorg, sesskey(), $id, $scorm->autocommit)); PK3\NsNsaicc.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . // // SCORM 1.2 API Implementation // function AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, viewmode, currentorg, sesskey, cmid, autocommit) { var prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "¤torg=" + currentorg + "&sesskey=" + sesskey; var datamodelurl = cfgwwwroot + "/mod/scorm/datamodel.php"; var datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid; // Standard Data Type Definition CMIString256 = '^.{0,255}$'; CMIString4096 = '^.{0,4096}$'; CMITime = '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,2})?$'; CMITimespan = '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$'; CMIInteger = '^\\d+$'; CMISInteger = '^-?([0-9]+)$'; CMIDecimal = '^-?([0-9]{0,3})(\.[0-9]{1,2})?$'; CMIIdentifier = '^\\w{1,255}$'; CMIFeedback = CMIString256; // This must be redefined CMIIndex = '[._](\\d+).'; // Vocabulary Data Type Definition CMIStatus = '^passed$|^completed$|^failed$|^incomplete$|^browsed$'; CMIStatus2 = '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$'; CMIExit = '^time-out$|^suspend$|^logout$|^$'; CMIType = '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$'; CMIResult = '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]{1,2})?$'; NAVEvent = '^previous$|^continue$'; // Children lists cmi_children = 'core, suspend_data, launch_data, comments, objectives, student_data, student_preference, interactions'; core_children = 'student_id, student_name, lesson_location, credit, lesson_status, entry, score, total_time, lesson_mode, exit, session_time'; score_children = 'raw, min, max'; comments_children = 'content, location, time'; objectives_children = 'id, score, status'; student_data_children = 'attempt_number, tries, mastery_score, max_time_allowed, time_limit_action'; student_preference_children = 'audio, language, speed, text'; interactions_children = 'id, objectives, time, type, correct_responses, weighting, student_response, result, latency'; // Data ranges score_range = '0#100'; audio_range = '-1#100'; speed_range = '-100#100'; weighting_range = '-100#100'; text_range = '-1#1'; // The AICC data model var datamodel = {}; for(scoid in def){ datamodel[scoid] = { 'cmi._children':{'defaultvalue':cmi_children, 'mod':'r', 'writeerror':'402'}, 'cmi._version':{'defaultvalue':'3.4', 'mod':'r', 'writeerror':'402'}, 'cmi.core._children':{'defaultvalue':core_children, 'mod':'r', 'writeerror':'402'}, 'cmi.core.student_id':{'defaultvalue':def[scoid]['cmi.core.student_id'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.student_name':{'defaultvalue':def[scoid]['cmi.core.student_name'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.lesson_location':{'defaultvalue':def[scoid]['cmi.core.lesson_location'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.credit':{'defaultvalue':def[scoid]['cmi.core.credit'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.lesson_status':{'defaultvalue':def[scoid]['cmi.core.lesson_status'], 'format':CMIStatus, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.exit':{'defaultvalue':def[scoid]['cmi.core.exit'], 'format':CMIExit, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.core.entry':{'defaultvalue':def[scoid]['cmi.core.entry'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.score._children':{'defaultvalue':score_children, 'mod':'r', 'writeerror':'402'}, 'cmi.core.score.raw':{'defaultvalue':def[scoid]['cmi.core.score.raw'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.score.max':{'defaultvalue':def[scoid]['cmi.core.score.max'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.score.min':{'defaultvalue':def[scoid]['cmi.core.score.min'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.session_time':{'format':CMITimespan, 'mod':'w', 'defaultvalue':'00:00:00', 'readerror':'404', 'writeerror':'405'}, 'cmi.core.total_time':{'defaultvalue':def[scoid]['cmi.core.total_time'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.lesson_mode':{'defaultvalue':def[scoid]['cmi.core.lesson_mode'], 'mod':'r', 'writeerror':'403'}, 'cmi.suspend_data':{'defaultvalue':def[scoid]['cmi.suspend_data'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'}, 'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.launch_data'], 'mod':'r', 'writeerror':'403'}, 'cmi.comments':{'defaultvalue':def[scoid]['cmi.comments'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'}, // deprecated evaluation attributes 'cmi.evaluation.comments._count':{'defaultvalue':'0', 'mod':'r', 'writeerror':'402'}, 'cmi.evaluation.comments._children':{'defaultvalue':comments_children, 'mod':'r', 'writeerror':'402'}, 'cmi.evaluation.comments.n.content':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.evaluation.comments.n.location':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.evaluation.comments.n.time':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'}, 'cmi.comments_from_lms':{'mod':'r', 'writeerror':'403'}, 'cmi.objectives._children':{'defaultvalue':objectives_children, 'mod':'r', 'writeerror':'402'}, 'cmi.objectives._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.score._children':{'pattern':CMIIndex, 'mod':'r', 'writeerror':'402'}, 'cmi.objectives.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data._children':{'defaultvalue':student_data_children, 'mod':'r', 'writeerror':'402'}, 'cmi.student_data.attempt_number':{'defaultvalue':def[scoid]['cmi.student_data.attempt_number'], 'mod':'r', 'writeerror':'402'}, 'cmi.student_data.tries.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data.tries.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data.tries.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data.tries.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data.tries.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data.mastery_score':{'defaultvalue':def[scoid]['cmi.student_data.mastery_score'], 'mod':'r', 'writeerror':'403'}, 'cmi.student_data.max_time_allowed':{'defaultvalue':def[scoid]['cmi.student_data.max_time_allowed'], 'mod':'r', 'writeerror':'403'}, 'cmi.student_data.time_limit_action':{'defaultvalue':def[scoid]['cmi.student_data.time_limit_action'], 'mod':'r', 'writeerror':'403'}, 'cmi.student_data.tries_during_lesson':{'defaultvalue':def[scoid]['cmi.student_data.tries_during_lesson'], 'mod':'r', 'writeerror':'402'}, 'cmi.student_preference._children':{'defaultvalue':student_preference_children, 'mod':'r', 'writeerror':'402'}, 'cmi.student_preference.audio':{'defaultvalue':'0', 'format':CMISInteger, 'range':audio_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_preference.language':{'defaultvalue':'', 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_preference.speed':{'defaultvalue':'0', 'format':CMISInteger, 'range':speed_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_preference.text':{'defaultvalue':'0', 'format':CMISInteger, 'range':text_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.interactions._children':{'defaultvalue':interactions_children, 'mod':'r', 'writeerror':'402'}, 'cmi.interactions._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.interactions.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.objectives._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.interactions.n.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.type':{'pattern':CMIIndex, 'format':CMIType, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.correct_responses._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.interactions.n.correct_responses.n.pattern':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.weighting':{'pattern':CMIIndex, 'format':CMIDecimal, 'range':weighting_range, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.student_response':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'nav.event':{'defaultvalue':'', 'format':NAVEvent, 'mod':'w', 'readerror':'404', 'writeerror':'405'} }; } var cmi, nav; function initdatamodel(scoid){ prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "¤torg=" + currentorg + "&sesskey=" + sesskey; datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid; // // Datamodel inizialization // cmi = new Object(); cmi.core = new Object(); cmi.core.score = new Object(); cmi.objectives = new Object(); cmi.student_data = new Object(); cmi.student_preference = new Object(); cmi.interactions = new Object(); // deprecated evaluation attributes cmi.evaluation = new Object(); cmi.evaluation.comments = new Object(); // Navigation Object nav = new Object(); for (element in datamodel[scoid]) { if (element.match(/\.n\./) == null) { if (typeof datamodel[scoid][element].defaultvalue != 'undefined') { eval(element + ' = datamodel["' + scoid + '"]["' + element + '"].defaultvalue;'); } else { eval(element + ' = "";'); } } } eval(cmiobj[scoid]); if (cmi.core.lesson_status == '') { cmi.core.lesson_status = 'not attempted'; } } // // API Methods definition // var Initialized = false; function LMSInitialize (param) { scoid = scorm_current_node ? scorm_current_node.scoid : scoid; initdatamodel(scoid); errorCode = "0"; if (param == "") { if (!Initialized) { Initialized = true; errorCode = "0"; return "true"; } else { errorCode = "101"; } } else { errorCode = "201"; } return "false"; } function LMSFinish (param) { errorCode = "0"; if (param == "") { if (Initialized) { Initialized = false; result = StoreData(cmi,true); if (nav.event != '') { if (nav.event == 'continue') { setTimeout('mod_scorm_launch_next_sco();',500); } else { setTimeout('mod_scorm_launch_prev_sco();',500); } } else { if (scormauto == 1) { setTimeout('mod_scorm_launch_next_sco();',500); } } // trigger TOC update var callback = M.mod_scorm.connectPrereqCallback; YUI().use('io-base', function(Y) { Y.on('io:complete', callback.success, Y); Y.io(prerequrl); }); return "true"; } else { errorCode = "301"; } } else { errorCode = "201"; } return "false"; } function LMSGetValue (element) { errorCode = "0"; if (Initialized) { if (element != "") { expression = new RegExp(CMIIndex,'g'); elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'w') { element = String(element).replace(expression, "_$1."); elementIndexes = element.split('.'); subelement = 'cmi'; i = 1; while ((i < elementIndexes.length) && (typeof eval(subelement) != "undefined")) { subelement += '.' + elementIndexes[i++]; } if (subelement == element) { errorCode = "0"; return eval(element); } else { errorCode = "0"; // Need to check if it is the right errorCode } } else { errorCode = datamodel[scoid][elementmodel].readerror; } } else { childrenstr = '._children'; countstr = '._count'; if (elementmodel.substr(elementmodel.length - childrenstr.length,elementmodel.length) == childrenstr) { parentmodel = elementmodel.substr(0,elementmodel.length - childrenstr.length); if (typeof datamodel[scoid][parentmodel] != "undefined") { errorCode = "202"; } else { errorCode = "201"; } } else if (elementmodel.substr(elementmodel.length - countstr.length,elementmodel.length) == countstr) { parentmodel = elementmodel.substr(0,elementmodel.length - countstr.length); if (typeof datamodel[scoid][parentmodel] != "undefined") { errorCode = "203"; } else { errorCode = "201"; } } else { errorCode = "201"; } } } else { errorCode = "201"; } } else { errorCode = "301"; } return ""; } function LMSSetValue (element,value) { errorCode = "0"; if (Initialized) { if (element != "") { expression = new RegExp(CMIIndex,'g'); elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'r') { expression = new RegExp(datamodel[scoid][elementmodel].format); value = value + ''; matches = value.match(expression); if (matches != null) { //Create dynamic data model element if (element != elementmodel) { elementIndexes = element.split('.'); subelement = 'cmi'; for (i = 1; i < elementIndexes.length - 1; i++) { elementIndex = elementIndexes[i]; if (elementIndexes[i + 1].match(/^\d+$/)) { if ((typeof eval(subelement + '.' + elementIndex)) == "undefined") { eval(subelement + '.' + elementIndex + ' = new Object();'); eval(subelement + '.' + elementIndex + '._count = 0;'); } if (elementIndexes[i + 1] == eval(subelement + '.' + elementIndex + '._count')) { eval(subelement + '.' + elementIndex + '._count++;'); } if (elementIndexes[i + 1] > eval(subelement + '.' + elementIndex + '._count')) { errorCode = "201"; } subelement = subelement.concat('.' + elementIndex + '_' + elementIndexes[i + 1]); i++; } else { subelement = subelement.concat('.' + elementIndex); } if ((typeof eval(subelement)) == "undefined") { eval(subelement + ' = new Object();'); if (subelement.substr(0,14) == 'cmi.objectives') { eval(subelement + '.score = new Object();'); eval(subelement + '.score._children = score_children;'); eval(subelement + '.score.raw = "";'); eval(subelement + '.score.min = "";'); eval(subelement + '.score.max = "";'); } if (subelement.substr(0,16) == 'cmi.interactions') { eval(subelement + '.objectives = new Object();'); eval(subelement + '.objectives._count = 0;'); eval(subelement + '.correct_responses = new Object();'); eval(subelement + '.correct_responses._count = 0;'); } } } element = subelement.concat('.' + elementIndexes[elementIndexes.length - 1]); } // Store data. if (errorCode == "0") { if (autocommit && !(AICCapi.timeout)) { AICCapi.timeout = Y.later(60000, API, 'LMSCommit', [""], false); } if (typeof datamodel[scoid][elementmodel].range != "undefined") { range = datamodel[scoid][elementmodel].range; ranges = range.split('#'); value = value * 1.0; if ((value >= ranges[0]) && (value <= ranges[1])) { eval(element + '="' + value + '";'); errorCode = "0"; return "true"; } else { errorCode = datamodel[scoid][elementmodel].writeerror; } } else { if (element == 'cmi.comments') { eval(element + '+="' + value + '";'); } else { eval(element + '="' + value + '";'); } errorCode = "0"; return "true"; } } } else { errorCode = datamodel[scoid][elementmodel].writeerror; } } else { errorCode = datamodel[scoid][elementmodel].writeerror; } } else { errorCode = "201" } } else { errorCode = "201"; } } else { errorCode = "301"; } return "false"; } function LMSCommit (param) { if (AICCapi.timeout) { AICCapi.timeout.cancel(); AICCapi.timeout = null; } errorCode = "0"; if (param == "") { if (Initialized) { result = StoreData(cmi,false); return "true"; } else { errorCode = "301"; } } else { errorCode = "201"; } return "false"; } function LMSGetLastError () { return errorCode; } function LMSGetErrorString (param) { if (param != "") { var errorString = new Array(); errorString["0"] = "No error"; errorString["101"] = "General exception"; errorString["201"] = "Invalid argument error"; errorString["202"] = "Element cannot have children"; errorString["203"] = "Element not an array - cannot have count"; errorString["301"] = "Not initialized"; errorString["401"] = "Not implemented error"; errorString["402"] = "Invalid set value, element is a keyword"; errorString["403"] = "Element is read only"; errorString["404"] = "Element is write only"; errorString["405"] = "Incorrect data type"; return errorString[param]; } else { return ""; } } function LMSGetDiagnostic (param) { if (param == "") { param = errorCode; } return param; } function AddTime (first, second) { var sFirst = first.split(":"); var sSecond = second.split(":"); var cFirst = sFirst[2].split("."); var cSecond = sSecond[2].split("."); var change = 0; FirstCents = 0; //Cents if (cFirst.length > 1) { FirstCents = parseInt(cFirst[1],10); } SecondCents = 0; if (cSecond.length > 1) { SecondCents = parseInt(cSecond[1],10); } var cents = FirstCents + SecondCents; change = Math.floor(cents / 100); cents = cents - (change * 100); if (Math.floor(cents) < 10) { cents = "0" + cents.toString(); } var secs = parseInt(cFirst[0],10) + parseInt(cSecond[0],10) + change; //Seconds change = Math.floor(secs / 60); secs = secs - (change * 60); if (Math.floor(secs) < 10) { secs = "0" + secs.toString(); } mins = parseInt(sFirst[1],10) + parseInt(sSecond[1],10) + change; //Minutes change = Math.floor(mins / 60); mins = mins - (change * 60); if (mins < 10) { mins = "0" + mins.toString(); } hours = parseInt(sFirst[0],10) + parseInt(sSecond[0],10) + change; //Hours if (hours < 10) { hours = "0" + hours.toString(); } if (cents != '0') { return hours + ":" + mins + ":" + secs + '.' + cents; } else { return hours + ":" + mins + ":" + secs; } } function TotalTime() { total_time = AddTime(cmi.core.total_time, cmi.core.session_time); return '&' + underscore('cmi.core.total_time') + '=' + escape(total_time); } function CollectData(data,parent) { var datastring = ''; for (property in data) { if (typeof data[property] == 'object') { datastring += CollectData(data[property],parent + '.' + property); } else { element = parent + '.' + property; expression = new RegExp(CMIIndex,'g'); elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'r') { elementstring = '&' + underscore(element) + '=' + escape(data[property]); if (typeof datamodel[scoid][elementmodel].defaultvalue != "undefined") { if (datamodel[scoid][elementmodel].defaultvalue != data[property]) { datastring += elementstring; } } else { datastring += elementstring; } } } } } return datastring; } function StoreData(data,storetotaltime) { var datastring = ''; if (storetotaltime) { if (cmi.core.lesson_mode == 'normal') { if (cmi.core.credit == 'credit') { if (cmi.student_data.mastery_score != '' && cmi.core.score.raw != '') { if (cmi.core.score.raw >= cmi.student_data.mastery_score) { cmi.core.lesson_status = 'passed'; } else { cmi.core.lesson_status = 'failed'; } } } } if (cmi.core.lesson_mode == 'browse') { if (datamodel[scoid]['cmi.core.lesson_status'].defaultvalue == '' && cmi.core.lesson_status == 'not attempted') { cmi.core.lesson_status = 'browsed'; } } datastring = CollectData(data,'cmi'); datastring += TotalTime(); } else { datastring = CollectData(data,'cmi'); } var myRequest = NewHttpReq(); var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring); if (result === false) { return false; } results = String(result).split('\n'); errorCode = results[1]; return results[0]; } this.LMSInitialize = LMSInitialize; this.LMSFinish = LMSFinish; this.LMSGetValue = LMSGetValue; this.LMSSetValue = LMSSetValue; this.LMSCommit = LMSCommit; this.LMSGetLastError = LMSGetLastError; this.LMSGetErrorString = LMSGetErrorString; this.LMSGetDiagnostic = LMSGetDiagnostic; } M.scorm_api = {}; M.scorm_api.init = function(Y, def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, viewmode, currentorg, sesskey, cmid, autocommit) { window.API = new AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, viewmode, currentorg, sesskey, cmid, autocommit); } PK3\hJJscorm_13lib.phpnu[. defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot.'/mod/scorm/datamodels/scormlib.php'); require_once($CFG->dirroot.'/mod/scorm/datamodels/sequencinglib.php'); function scorm_seq_overall ($scoid, $userid, $request, $attempt) { $seq = scorm_seq_navigation($scoid, $userid, $request, $attempt); if ($seq->navigation) { if ($seq->termination != null) { $seq = scorm_seq_termination($scoid, $userid, $seq); } if ($seq->sequencing != null) { $seq = scorm_seq_sequencing($scoid, $userid, $seq); if ($seq->sequencing == 'exit') { // Return the control to the LTS. return 'true'; } } if ($seq->delivery != null) { $seq = scorm_sequencing_delivery($scoid, $userid, $seq); $seq = scorm_content_delivery_environment ($seq, $userid); } } if ($seq->exception != null) { $seq = scorm_sequencing_exception($seq); } return 'true'; } function scorm_seq_navigation ($scoid, $userid, $request, $attempt=0) { global $DB; // Sequencing structure. $seq = new stdClass(); $seq->currentactivity = scorm_get_sco($scoid); $seq->traversaldir = null; $seq->nextactivity = null; $seq->deliveryvalid = null; $seq->attempt = $attempt; $seq->identifiedactivity = null; $seq->delivery = null; $seq->deliverable = false; $seq->active = scorm_seq_is('active', $scoid, $userid); $seq->suspended = scorm_seq_is('suspended', $scoid, $userid); $seq->navigation = null; $seq->termination = null; $seq->sequencing = null; $seq->target = null; $seq->endsession = null; $seq->exception = null; $seq->reachable = true; $seq->prevact = true; $sco = scorm_get_sco($scoid); switch ($request) { case 'start_': if (empty($seq->currentactivity)) { $seq->navigation = true; $seq->sequencing = 'start'; } else { $seq->exception = 'NB.2.1-1'; // Sequencing session already begun. } break; case 'resumeall_': if (empty($seq->currentactivity)) { // TODO: I think it's suspend instead of suspendedactivity. if (scorm_get_sco_value($scoid, $userid, 'suspendedactivity')) { $seq->navigation = true; $seq->sequencing = 'resumeall'; } else { $seq->exception = 'NB.2.1-3'; // No suspended activity found. } } else { $seq->exception = 'NB.2.1-1'; // Sequencing session already begun. } break; case 'continue_': case 'previous_': if (!empty($seq->currentactivity)) { $sco = $seq->currentactivity; if ($sco->parent != '/') { if ($parentsco = scorm_get_parent($sco)) { if (isset($parentsco->flow) && ($parentsco->flow == true)) { // I think it's parentsco. // Current activity is active! if (scorm_seq_is('active', $sco->id, $userid)) { if ($request == 'continue_') { $seq->navigation = true; $seq->termination = 'exit'; $seq->sequencing = 'continue'; } else { if (!isset($parentsco->forwardonly) || ($parentsco->forwardonly == false)) { $seq->navigation = true; $seq->termination = 'exit'; $seq->sequencing = 'previous'; } else { $seq->exception = 'NB.2.1-5'; // Violates control mode. } } } } } } } else { $seq->exception = 'NB.2.1-2'; // Current activity not defined. } break; case 'forward_': case 'backward_': $seq->exception = 'NB.2.1-7'; // None to be done, behavior not defined. break; case 'exit_': case 'abandon_': if (!empty($seq->currentactivity)) { // Current activity is active ! $seq->navigation = true; $seq->termination = substr($request, 0, -1); $seq->sequencing = 'exit'; } else { $seq->exception = 'NB.2.1-2'; // Current activity not defined. } case 'exitall_': case 'abandonall_': case 'suspendall_': if (!empty($seq->currentactivity)) { $seq->navigation = true; $seq->termination = substr($request, 0, -1); $seq->sequencing = 'exit'; } else { $seq->exception = 'NB.2.1-2'; // Current activity not defined. } break; default: // Example {target=}choice. if ($targetsco = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm, 'identifier' => $request))) { if ($targetsco->parent != '/') { $seq->target = $request; } else { if ($parentsco = scorm_get_parent($targetsco)) { if (!isset($parentsco->choice) || ($parentsco->choice == true)) { $seq->target = $request; } } } if ($seq->target != null) { if (empty($seq->currentactivity)) { $seq->navigation = true; $seq->sequencing = 'choice'; } else { if (!$sco = scorm_get_sco($scoid)) { return $seq; } if ($sco->parent != $targetsco->parent) { $ancestors = scorm_get_ancestors($sco); $commonpos = scorm_find_common_ancestor($ancestors, $targetsco); if ($commonpos !== false) { if ($activitypath = array_slice($ancestors, 0, $commonpos)) { foreach ($activitypath as $activity) { if (scorm_seq_is('active', $activity->id, $userid) && (isset($activity->choiceexit) && ($activity->choiceexit == false))) { $seq->navigation = false; $seq->termination = null; $seq->sequencing = null; $seq->target = null; $seq->exception = 'NB.2.1-8'; // Violates control mode. return $seq; } } } else { $seq->navigation = false; $seq->termination = null; $seq->sequencing = null; $seq->target = null; $seq->exception = 'NB.2.1-9'; } } } // Current activity is active ! $seq->navigation = true; $seq->sequencing = 'choice'; } } else { $seq->exception = 'NB.2.1-10'; // Violates control mode. } } else { $seq->exception = 'NB.2.1-11'; // Target activity does not exists. } break; } return $seq; } function scorm_seq_termination ($seq, $userid) { if (empty($seq->currentactivity)) { $seq->termination = false; $seq->exception = 'TB.2.3-1'; return $seq; } $sco = $seq->currentactivity; if ((($seq->termination == 'exit') || ($seq->termination == 'abandon')) && !$seq->active) { $seq->termination = false; $seq->exception = 'TB.2.3-2'; return $seq; } switch ($seq->termination) { case 'exit': scorm_seq_end_attempt($sco, $userid, $seq); $seq = scorm_seq_exit_action_rules($seq, $userid); do { $exit = false;// I think this is false. Originally this was true. $seq = scorm_seq_post_cond_rules($seq, $userid); if ($seq->termination == 'exitparent') { if ($sco->parent != '/') { $sco = scorm_get_parent($sco); $seq->currentactivity = $sco; $seq->active = scorm_seq_is('active', $sco->id, $userid); scorm_seq_end_attempt($sco, $userid, $seq); $exit = true; // I think it's true. Originally this was false. } else { $seq->termination = false; $seq->exception = 'TB.2.3-4'; return $seq; } } } while (($exit == false) && ($seq->termination == 'exit')); if ($seq->termination == 'exit') { $seq->termination = true; return $seq; } case 'exitall': if ($seq->active) { scorm_seq_end_attempt($sco, $userid, $seq); } // Terminate Descendent Attempts Process. if ($ancestors = scorm_get_ancestors($sco)) { foreach ($ancestors as $ancestor) { scorm_seq_end_attempt($ancestor, $userid, $seq); $seq->currentactivity = $ancestor; } } $seq->active = scorm_seq_is('active', $seq->currentactivity->id, $userid); $seq->termination = true; $seq->sequencing = 'exit'; break; case 'suspendall': if (($seq->active) || ($seq->suspended)) { scorm_seq_set('suspended', $sco->id, $userid, $attempt); } else { if ($sco->parent != '/') { $parentsco = scorm_get_parent($sco); scorm_seq_set('suspended', $parentsco->id, $userid, $attempt); } else { $seq->termination = false; $seq->exception = 'TB.2.3-3'; } } if ($ancestors = scorm_get_ancestors($sco)) { foreach ($ancestors as $ancestor) { scorm_seq_set('active', $ancestor->id, $userid, $attempt, false); scorm_seq_set('suspended', $ancestor->id, $userid, $attempt); $seq->currentactivity = $ancestor; } $seq->termination = true; $seq->sequencing = 'exit'; } else { $seq->termination = false; $seq->exception = 'TB.2.3-5'; } break; case 'abandon': scorm_seq_set('active', $sco->id, $userid, $attempt, false); $seq->active = null; $seq->termination = true; break; case 'abandonall': if ($ancestors = scorm_get_ancestors($sco)) { foreach ($ancestors as $ancestor) { scorm_seq_set('active', $ancestor->id, $userid, $attempt, false); $seq->currentactivity = $ancestor; } $seq->termination = true; $seq->sequencing = 'exit'; } else { $seq->termination = false; $seq->exception = 'TB.2.3-6'; } break; default: $seq->termination = false; $seq->exception = 'TB.2.3-7'; break; } return $seq; } function scorm_seq_end_attempt($sco, $userid, $seq) { global $DB; if (scorm_is_leaf($sco)) { if (!isset($sco->tracked) || ($sco->tracked == 1)) { if (!scorm_seq_is('suspended', $sco->id, $userid)) { if (!isset($sco->completionsetbycontent) || ($sco->completionsetbycontent == 0)) { if (!scorm_seq_is('attemptprogressstatus', $sco->id, $userid, $seq->attempt)) { $r = scorm_get_sco_value($sco->id, $userid, 'cmi.completion_status'); if ($r->value != 'incomplete') { scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt); scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt); } } } if (!isset($sco->objectivesetbycontent) || ($sco->objectivesetbycontent == 0)) { if ($objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id))) { foreach ($objectives as $objective) { if ($objective->primaryobj) { if (!scorm_seq_is('objectiveprogressstatus', $sco->id, $userid, $seq->attempt)) { scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $seq->attempt); scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $seq->attempt); } } } } } } } } else if ($children = scorm_get_children($sco)) { $suspended = false; foreach ($children as $child) { if (scorm_seq_is('suspended', $child, $userid, $seq->attempt)) { $suspended = true; break; } } if ($suspended) { scorm_seq_set('suspended', $sco, $userid, $seq->attempt); } else { scorm_seq_set('suspended', $sco, $userid, $seq->attempt, false); } } scorm_seq_set('active', $sco->id, $userid, $seq->attempt, false); scorm_seq_overall_rollup($sco, $userid, $seq); } function scorm_seq_is($what, $scoid, $userid, $attempt=0) { // Check if passed activity $what is active. $active = false; if (scorm_get_sco_value($scoid, $userid, $what, $attempt)) { $active = true; } return $active; } function scorm_seq_set($what, $scoid, $userid, $attempt=0, $value='true') { global $DB; $sco = scorm_get_sco($scoid); // Set passed activity to active or not. if ($value == false) { $params = ['userid' => $userid, 'scormid' => $sco->scorm, 'attempt' => $attempt, 'element' => $what]; $sql = "WHERE scoid = :scoid AND attemptid = :attemptid AND elementid = (SELECT id FROM {scorm_element} WHERE element = :element)"; $DB->delete_records_select('scorm_scoes_value', $sql, $params); } else { scorm_insert_track($userid, $sco->scorm, $sco->id, $attempt, $what, $value); } // Update grades in gradebook. $scorm = $DB->get_record('scorm', array('id' => $sco->scorm)); scorm_update_grades($scorm, $userid, true); } function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) { global $DB; $res = false; if (strpos($rollupruleconds, 'and ')) { $rollupruleconds = array_filter(explode(' and ', $rollupruleconds)); $conditioncombination = 'all'; } else { $rollupruleconds = array_filter(explode(' or ', $rollupruleconds)); $conditioncombination = 'or'; } foreach ($rollupruleconds as $rolluprulecond) { $notflag = false; if (strpos($rolluprulecond, 'not') !== false) { $rolluprulecond = str_replace('not', '', $rolluprulecond); $notflag = true; } $conditionarray['condition'] = $rolluprulecond; $conditionarray['notflag'] = $notflag; $conditions[] = $conditionarray; } foreach ($conditions as $condition) { $checknot = true; $res = false; if ($condition['notflag']) { $checknot = false; } switch ($condition['condition']) { case 'satisfied': $r = scorm_get_sco_value($sco->id, $userid, 'objectivesatisfiedstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } } break; case 'objectiveStatusKnown': $r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } break; case 'notobjectiveStatusKnown': $r = scorm_get_sco_value($sco->id, $userid, 'objectiveprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } break; case 'objectiveMeasureKnown': $r = scorm_get_sco_value($sco->id, $userid, 'objectivemeasurestatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } break; case 'notobjectiveMeasureKnown': $r = scorm_get_sco_value($sco->id, $userid, 'objectivemeasurestatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } break; case 'completed': $r = scorm_get_sco_value($sco->id, $userid, 'attemptcompletionstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $r = scorm_get_sco_value($sco->id, $userid, 'attemptprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } } break; case 'attempted': $r = scorm_get_sco_value($sco->id, $userid, 'x.start.time'); if ($checknot && $r->attempt > 0) { $res = true; } else if (!$checknot && $r->attempt <= 0) { $res = true; } break; case 'attemptLimitExceeded': $r = scorm_get_sco_value($sco->id, $userid, 'activityprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $r = scorm_get_sco_value($sco->id, $userid, 'limitconditionattemptlimitcontrol'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $sql = "SELECT max(attempt) as attempt FROM {scorm_attempt} a JOIN {scorm_scoes_value} v on v.attemptid = a.id WHERE v.scoid = :scoid AND a.userid = :userid"; $r2 = scorm_get_sco_value($sco->id, $userid, 'limitconditionattemptlimit'); $attempts = $DB->get_field_sql($sql, ['scoid' => $sco->id, 'userid' => $userid]); if (!empty($attempts) && !empty($r2)) { if ($checknot && ($attempts >= $r2->value)) { $res = true; } else if (!$checknot && ($attempts < $r2->value)) { $res = true; } } } } break; case 'activityProgressKnown': $r = scorm_get_sco_value($sco->id, $userid, 'activityprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $r = scorm_get_sco_value($sco->id, $userid, 'attemptprogressstatus'); if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) { $res = true; } } break; } if ($conditioncombination == 'all' && !$res) { break; } else if ($conditioncombination == 'or' && $res) { break; } } return $res; } function scorm_check_activity ($activity, $userid) { $act = scorm_seq_rules_check($activity, 'disabled'); if ($act != null) { return true; } if (scorm_limit_cond_check ($activity, $userid)) { return true; } return false; } function scorm_limit_cond_check ($activity, $userid) { global $DB; if (isset($activity->tracked) && ($activity->tracked == 0)) { return false; } if (scorm_seq_is('active', $activity->id, $userid) || scorm_seq_is('suspended', $activity->id, $userid)) { return false; } if (!isset($activity->limitcontrol) || ($activity->limitcontrol == 1)) { $r = scorm_get_sco_value($activity->id, $userid, 'activityattemptcount'); if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattempt)) { return true; } } if (!isset($activity->limitabsdurcontrol) || ($activity->limitabsdurcontrol == 1)) { $r = scorm_get_sco_value($activity->id, $userid, 'activityabsoluteduration'); if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitabsduration)) { return true; } } if (!isset($activity->limitexpdurcontrol) || ($activity->limitexpdurcontrol == 1)) { $r = scorm_get_sco_value($activity->id, $userid, 'activityexperiencedduration'); if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitexpduration)) { return true; } } if (!isset($activity->limitattabsdurcontrol) || ($activity->limitattabsdurcontrol == 1)) { $r = scorm_get_sco_value($activity->id, $userid, 'attemptabsoluteduration'); if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattabsduration)) { return true; } } if (!isset($activity->limitattexpdurcontrol) || ($activity->limitattexpdurcontrol == 1)) { $r = scorm_get_sco_value($activity->id, $userid, 'attemptexperiencedduration'); if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattexpduration)) { return true; } } if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) { $r = scorm_get_sco_value($activity->id, $userid, 'begintime'); if (isset($activity->limitbegintime) && time() >= $activity->limitbegintime) { return true; } } if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) { if (isset($activity->limitbegintime) && time() < $activity->limitbegintime) { return true; } } if (!isset($activity->limitendcontrol) || ($activity->limitendcontrol == 1)) { if (isset($activity->limitendtime) && time() > $activity->limitendtime) { return true; } } return false; } function scorm_seq_rules_check ($sco, $action) { global $DB; $act = null; if ($rules = $DB->get_records('scorm_seq_ruleconds', array('scoid' => $sco->id, 'action' => $action))) { foreach ($rules as $rule) { if ($act = scorm_seq_rule_check($sco, $rule)) { return $act; } } } return $act; } function scorm_seq_rule_check ($sco, $rule) { global $DB; $bag = Array(); $cond = ''; $ruleconds = $DB->get_records('scorm_seq_rulecond', array('scoid' => $sco->id, 'ruleconditionsid' => $rule->id)); foreach ($ruleconds as $rulecond) { if ($rulecond->operator == 'not') { if ($rulecond->cond != 'unknown' ) { $rulecond->cond = 'not'.$rulecond->cond; } } $bag[] = $rulecond->cond; } if (empty($bag)) { $cond = 'unknown'; return $cond; } if ($rule->conditioncombination == 'all') { foreach ($bag as $con) { $cond = $cond.' and '.$con; } } else { foreach ($bag as $con) { $cond = $cond.' or '.$con; } } return $cond; } function scorm_seq_overall_rollup($sco, $userid, $seq) { if ($ancestors = scorm_get_ancestors($sco)) { foreach ($ancestors as $ancestor) { if (!scorm_is_leaf($ancestor)) { scorm_seq_measure_rollup($sco, $userid, $seq->attempt); } scorm_seq_objective_rollup($sco, $userid, $seq->attempt); scorm_seq_activity_progress_rollup($sco, $userid, $seq); } } } function scorm_seq_measure_rollup($sco, $userid, $attempt = 0) { global $DB; $totalmeasure = 0; // Check if there is something similar in the database. $valid = false; // Same as in the last line. $countedmeasures = 0; // Same too. $targetobjective = null; $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id)); foreach ($objectives as $objective) { if ($objective->primaryobj == true) { // Objective contributes to rollup. $targetobjective = $objective; break; } } if ($targetobjective != null) { $children = scorm_get_children($sco); if (!empty ($children)) { foreach ($children as $child) { $child = scorm_get_sco ($child); if (!isset($child->tracked) || ($child->tracked == 1)) { $rolledupobjective = null;// We set the rolled up activity to undefined. $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $child->id)); foreach ($objectives as $objective) { if ($objective->primaryobj == true) {// Objective contributes to rollup I'm using primaryobj field, but not. $rolledupobjective = $objective; break; } } if ($rolledupobjective != null) { $child = scorm_get_sco($child->id); $countedmeasures = $countedmeasures + ($child->measureweight); if (!scorm_seq_is('objectivemeasurestatus', $sco->id, $userid, $attempt)) { $normalizedmeasure = scorm_get_sco_value($child->id, $userid, 'objectivenormalizedmeasure'); $totalmeasure = $totalmeasure + (($normalizedmeasure->value) * ($child->measureweight)); $valid = true; } } } } } if (!$valid) { scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt, false); } else { if ($countedmeasures > 0) { scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt); $val = $totalmeasure / $countedmeasures; scorm_seq_set('objectivenormalizedmeasure', $sco->id, $userid, $attempt, $val); } else { scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt, false); } } } } function scorm_seq_objective_rollup($sco, $userid, $attempt = 0) { global $DB; scorm_seq_objective_rollup_measure($sco, $userid, $attempt); scorm_seq_objective_rollup_rules($sco, $userid, $attempt); scorm_seq_objective_rollup_default($sco, $userid, $attempt); /* if ($targetobjective->satisfiedbymeasure) { scorm_seq_objective_rollup_measure($sco, $userid); } else{ if ((scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) || (scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) { scorm_seq_objective_rollup_rules($sco, $userid); } else{ $rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid'=>$sco->id, 'userid'=>$userid)); foreach ($rolluprules as $rolluprule) { $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid'=>$rolluprule->id)); foreach ($rollupruleconds as $rolluprulecond) { switch ($rolluprulecond->cond!='satisfied' && $rolluprulecond->cond!='completed' && $rolluprulecond->cond!='attempted') { scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, false); break; } } } } */ } function scorm_seq_objective_rollup_measure($sco, $userid, $attempt = 0) { global $DB; $targetobjective = null; $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id)); foreach ($objectives as $objective) { if ($objective->primaryobj == true) { $targetobjective = $objective; break; } } if ($targetobjective != null) { if ($targetobjective->satisfiedbymeasure) { if (!scorm_seq_is('objectiveprogressstatus', $sco->id, $userid, $attempt)) { scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt, false); } else { if (scorm_seq_is('active', $sco->id, $userid, $attempt)) { $isactive = true; } else { $isactive = false; } $normalizedmeasure = scorm_get_sco_value($sco->id, $userid, 'objectivenormalizedmeasure'); $sco = scorm_get_sco ($sco->id); if (!$isactive || ($isactive && (!isset($sco->measuresatisfactionifactive) || $sco->measuresatisfactionifactive == true))) { if (isset($normalizedmeasure->value) && ($normalizedmeasure->value >= $targetobjective->minnormalizedmeasure)) { scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt); scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt); } else { // TODO: handle the case where cmi.success_status is passed and objectivenormalizedmeasure undefined. scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt); } } else { scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt, false); } } } } } function scorm_seq_objective_rollup_default($sco, $userid, $attempt = 0) { global $DB; if (!(scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) && !(scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) { if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id))) { foreach ($rolluprules as $rolluprule) { $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id)); foreach ($rollupruleconds as $rolluprulecond) { if ($rolluprulecond->cond != 'satisfied' && $rolluprulecond->cond != 'completed' && $rolluprulecond->cond != 'attempted') { scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt, false); break; } } } } } } function scorm_seq_objective_rollup_rules($sco, $userid, $attempt = 0) { global $DB; $targetobjective = null; $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id)); foreach ($objectives as $objective) { if ($objective->primaryobj == true) {// Objective contributes to rollup I'm using primaryobj field, but not. $targetobjective = $objective; break; } } if ($targetobjective != null) { if (scorm_seq_rollup_rule_check($sco, $userid, 'notsatisfied')) {// With not satisfied rollup for the activity. scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt); scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt, false); } if (scorm_seq_rollup_rule_check($sco, $userid, 'satisfied')) {// With satisfied rollup for the activity. scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt); scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt); } } } function scorm_seq_activity_progress_rollup ($sco, $userid, $seq) { if (scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) { // Incomplete rollup action. scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt, false); scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt, true); } if (scorm_seq_rollup_rule_check($sco, $userid, 'completed')) { // Incomplete rollup action. scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt, true); scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt, true); } } function scorm_seq_rollup_rule_check ($sco, $userid, $action) { global $DB; if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id, 'action' => $action))) { $childrenbag = Array (); $children = scorm_get_children ($sco); foreach ($rolluprules as $rolluprule) { foreach ($children as $child) { $child = scorm_get_sco ($child); if (!isset($child->tracked) || ($child->tracked == 1)) { if (scorm_seq_check_child ($child, $action, $userid)) { $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id)); $evaluate = scorm_seq_evaluate_rollupcond($child, $rolluprule->conditioncombination, $rollupruleconds, $userid); if ($evaluate == 'unknown') { array_push($childrenbag, 'unknown'); } else { if ($evaluate == true) { array_push($childrenbag, true); } else { array_push($childrenbag, false); } } } } } $change = false; switch ($rolluprule->childactivityset) { case 'all': // I think I can use this condition instead equivalent to OR. if ((array_search(false, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) { $change = true; } break; case 'any': // I think I can use this condition instead equivalent to OR. if (array_search(true, $childrenbag) !== false) { $change = true; } break; case 'none': // I think I can use this condition instead equivalent to OR. if ((array_search(true, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) { $change = true; } break; case 'atleastcount': // I think I can use this condition instead equivalent to OR. foreach ($childrenbag as $itm) { $cont = 0; if ($itm === true) { $cont++; } if ($cont >= $rolluprule->minimumcount) { $change = true; } } break; case 'atleastcount': foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR. $cont = 0; if ($itm === true) { $cont++; } if ($cont >= $rolluprule->minimumcount) { $change = true; } } break; case 'atleastpercent': foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR. $cont = 0; if ($itm === true) { $cont++; } if (($cont / count($childrenbag)) >= $rolluprule->minimumcount) { $change = true; } } break; } if ($change == true) { return true; } } } return false; } function scorm_seq_flow_tree_traversal($activity, $direction, $childrenflag, $prevdirection, $seq, $userid, $skip = false) { $revdirection = false; $parent = scorm_get_parent($activity); if (!empty($parent)) { $children = scorm_get_available_children($parent); } else { $children = array(); } $childrensize = count($children); if (($prevdirection != null && $prevdirection == 'backward') && ($children[$childrensize - 1]->id == $activity->id)) { $direction = 'backward'; $activity = $children[0]; $revdirection = true; } if ($direction == 'forward') { $ancestors = scorm_get_ancestors($activity); $ancestorsroot = array_reverse($ancestors); $preorder = array(); $preorder = scorm_get_preorder($preorder, $ancestorsroot[0]); $preordersize = count($preorder); if (($activity->id == $preorder[$preordersize - 1]->id) || (($activity->parent == '/') && !($childrenflag))) { $seq->endsession = true; $seq->nextactivity = null; return $seq; } if (scorm_is_leaf ($activity) || !$childrenflag) { if ($children[$childrensize - 1]->id == $activity->id) { $seq = scorm_seq_flow_tree_traversal ($parent, $direction, false, null, $seq, $userid); if ($seq->nextactivity->launch == null) { $seq = scorm_seq_flow_tree_traversal ($seq->nextactivity, $direction, true, null, $seq, $userid); } return $seq; } else { $position = 0; foreach ($children as $sco) { if ($sco->id == $activity->id) { break; } $position++; } if ($position != ($childrensize - 1)) { $seq->nextactivity = $children[$position + 1]; $seq->traversaldir = $direction; return $seq; } else { $siblings = scorm_get_siblings($activity); $children = scorm_get_children($siblings[0]); $seq->nextactivity = $children[0]; return $seq; } } } else { $children = scorm_get_available_children($activity); if (!empty($children)) { $seq->traversaldir = $direction; $seq->nextactivity = $children[0]; return $seq; } else { $seq->traversaldir = null; $seq->nextactivity = null; $seq->exception = 'SB.2.1-2'; return $seq; } } } else if ($direction == 'backward') { if ($activity->parent == '/') { $seq->traversaldir = null; $seq->nextactivity = null; $seq->exception = 'SB.2.1-3'; return $seq; } if (scorm_is_leaf ($activity) || !$childrenflag) { if (!$revdirection) { if (isset($parent->forwardonly) && ($parent->forwardonly == true && !$skip)) { $seq->traversaldir = null; $seq->nextactivity = null; $seq->exception = 'SB.2.1-4'; return $seq; } } if ($children[0]->id == $activity->id) { $seq = scorm_seq_flow_tree_traversal($parent, 'backward', false, null, $seq, $userid); return $seq; } else { $ancestors = scorm_get_ancestors($activity); $ancestorsroot = array_reverse($ancestors); $preorder = array(); $preorder = scorm_get_preorder($preorder, $ancestorsroot[0]); $position = 0; foreach ($preorder as $sco) { if ($sco->id == $activity->id) { break; } $position++; } if (isset($preorder[$position])) { $seq->nextactivity = $preorder[$position - 1]; $seq->traversaldir = $direction; } return $seq; } } else { $children = scorm_get_available_children($activity); if (!empty($children)) { if (isset($parent->flow) && ($parent->flow == true)) { $seq->traversaldir = 'forward'; $seq->nextactivity = $children[0]; return $seq; } else { $seq->traversaldir = 'backward'; $seq->nextactivity = $children[count($children) - 1]; return $seq; } } else { $seq->traversaldir = null; $seq->nextactivity = null; $seq->exception = 'SB.2.1-2'; return $seq; } } } } // Returns the next activity on the tree, traversal direction, control returned to the LTS, (may) exception. function scorm_seq_flow_activity_traversal ($activity, $userid, $direction, $childrenflag, $prevdirection, $seq) { $parent = scorm_get_parent ($activity); if (!isset($parent->flow) || ($parent->flow == false)) { $seq->deliverable = false; $seq->exception = 'SB.2.2-1'; $seq->nextactivity = $activity; return $seq; } $rulecheck = scorm_seq_rules_check($activity, 'skip'); if ($rulecheck != null) { $skip = scorm_evaluate_condition ($rulecheck, $activity, $userid); if ($skip) { $seq = scorm_seq_flow_tree_traversal($activity, $direction, false, $prevdirection, $seq, $userid, $skip); $seq = scorm_seq_flow_activity_traversal($seq->nextactivity, $userid, $direction, $childrenflag, $prevdirection, $seq); } else if (!empty($seq->identifiedactivity)) { $seq->nextactivity = $activity; } return $seq; } $ch = scorm_check_activity ($activity, $userid); if ($ch) { $seq->deliverable = false; $seq->exception = 'SB.2.2-2'; $seq->nextactivity = $activity; return $seq; } if (!scorm_is_leaf($activity)) { $seq = scorm_seq_flow_tree_traversal ($activity, $direction, true, null, $seq, $userid); if ($seq->identifiedactivity == null) { $seq->deliverable = false; $seq->nextactivity = $activity; return $seq; } else { if ($direction == 'backward' && $seq->traversaldir == 'forward') { $seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity, $userid, 'forward', $childrenflag, 'backward', $seq); } else { $seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity, $userid, $direction, $childrenflag, null, $seq); } return $seq; } } $seq->deliverable = true; $seq->nextactivity = $activity; $seq->exception = null; return $seq; } function scorm_seq_flow ($activity, $direction, $seq, $childrenflag, $userid) { // TODO: $PREVDIRECTION NOT DEFINED YET. $prevdirection = null; $seq = scorm_seq_flow_tree_traversal ($activity, $direction, $childrenflag, $prevdirection, $seq, $userid); if ($seq->nextactivity == null) { $seq->nextactivity = $activity; $seq->deliverable = false; return $seq; } else { $activity = $seq->nextactivity; $seq = scorm_seq_flow_activity_traversal($activity, $userid, $direction, $childrenflag, null, $seq); return $seq; } } /** * Sets up $userdata array and default values for SCORM 1.3 . * * @param stdClass $userdata an empty stdClass variable that should be set up with user values * @param object $scorm package record * @param string $scoid SCO Id * @param string $attempt attempt number for the user * @param string $mode scorm display mode type * @return array The default values that should be used for SCORM 1.3 package */ function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) { global $DB, $USER; $userdata->student_id = $USER->username; if (empty(get_config('scorm', 'scormstandard'))) { $userdata->student_name = fullname($USER); } else { $userdata->student_name = $USER->lastname .', '. $USER->firstname; } if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) { // According to SCORM 2004(RTE V1, 4.2.8), only cmi.exit==suspend should allow previous datamodel elements on re-launch. if (isset($usertrack->{'cmi.exit'}) && ($usertrack->{'cmi.exit'} == 'suspend')) { foreach ($usertrack as $key => $value) { $userdata->$key = $value; } } else { $userdata->status = ''; $userdata->score_raw = ''; } } else { $userdata->status = ''; $userdata->score_raw = ''; } if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) { foreach ($scodatas as $key => $value) { $userdata->$key = $value; } } else { throw new \moodle_exception('cannotfindsco', 'scorm'); } if (!$sco = scorm_get_sco($scoid)) { throw new \moodle_exception('cannotfindsco', 'scorm'); } if (isset($userdata->status)) { if (!isset($userdata->{'cmi.exit'}) || $userdata->{'cmi.exit'} == 'time-out' || $userdata->{'cmi.exit'} == 'normal') { $userdata->entry = 'ab-initio'; } else { if (isset($userdata->{'cmi.exit'}) && ($userdata->{'cmi.exit'} == 'suspend' || $userdata->{'cmi.exit'} == 'logout')) { $userdata->entry = 'resume'; } else { $userdata->entry = ''; } } } $userdata->mode = 'normal'; if (!empty($mode)) { $userdata->mode = $mode; } if ($userdata->mode == 'normal') { $userdata->credit = 'credit'; } else { $userdata->credit = 'no-credit'; } $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $scoid)); $index = 0; foreach ($objectives as $objective) { if (!empty($objective->minnormalizedmeasure)) { $userdata->{'cmi.scaled_passing_score'} = $objective->minnormalizedmeasure; } if (!empty($objective->objectiveid)) { $userdata->{'cmi.objectives.N'.$index.'.id'} = $objective->objectiveid; $index++; } } $def = array(); $def['cmi.learner_id'] = $userdata->student_id; $def['cmi.learner_name'] = $userdata->student_name; $def['cmi.mode'] = $userdata->mode; $def['cmi.entry'] = $userdata->entry; $def['cmi.exit'] = scorm_isset($userdata, 'cmi.exit'); $def['cmi.credit'] = scorm_isset($userdata, 'credit'); $def['cmi.completion_status'] = scorm_isset($userdata, 'cmi.completion_status', 'unknown'); $def['cmi.completion_threshold'] = scorm_isset($userdata, 'threshold'); $def['cmi.learner_preference.audio_level'] = scorm_isset($userdata, 'cmi.learner_preference.audio_level', 1); $def['cmi.learner_preference.language'] = scorm_isset($userdata, 'cmi.learner_preference.language'); $def['cmi.learner_preference.delivery_speed'] = scorm_isset($userdata, 'cmi.learner_preference.delivery_speed'); $def['cmi.learner_preference.audio_captioning'] = scorm_isset($userdata, 'cmi.learner_preference.audio_captioning', 0); $def['cmi.location'] = scorm_isset($userdata, 'cmi.location'); $def['cmi.max_time_allowed'] = scorm_isset($userdata, 'attemptAbsoluteDurationLimit'); $def['cmi.progress_measure'] = scorm_isset($userdata, 'cmi.progress_measure'); $def['cmi.scaled_passing_score'] = scorm_isset($userdata, 'cmi.scaled_passing_score'); $def['cmi.score.scaled'] = scorm_isset($userdata, 'cmi.score.scaled'); $def['cmi.score.raw'] = scorm_isset($userdata, 'cmi.score.raw'); $def['cmi.score.min'] = scorm_isset($userdata, 'cmi.score.min'); $def['cmi.score.max'] = scorm_isset($userdata, 'cmi.score.max'); $def['cmi.success_status'] = scorm_isset($userdata, 'cmi.success_status', 'unknown'); $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data'); $def['cmi.time_limit_action'] = scorm_isset($userdata, 'timelimitaction'); $def['cmi.total_time'] = scorm_isset($userdata, 'cmi.total_time', 'PT0H0M0S'); $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms'); return $def; } PK3\   sequencinghandler.phpnu[. require_once('../../../config.php'); require_once($CFG->dirroot.'/mod/scorm/locallib.php'); $id = optional_param('id', '', PARAM_INT); // Course Module ID, or $a = optional_param('a', '', PARAM_INT); // scorm ID. $scoid = required_param('scoid', PARAM_INT); // Sco ID. $attempt = required_param('attempt', PARAM_INT); // Attempt number. $function = required_param('function', PARAM_RAW); // Function to call. $request = optional_param('request', '', PARAM_RAW); // Scorm ID. if (!empty($id)) { $cm = get_coursemodule_from_id('scorm', $id, 0, false, MUST_EXIST); $course = $DB->get_record("course", array("id" => $cm->course), '*', MUST_EXIST); $scorm = $DB->get_record("scorm", array("id" => $cm->instance), '*', MUST_EXIST); } else if (!empty($a)) { $scorm = $DB->get_record("scorm", array("id" => $a), '*', MUST_EXIST); $course = $DB->get_record("course", array("id" => $scorm->course), '*', MUST_EXIST); $cm = get_coursemodule_from_instance("scorm", $scorm->id, $course->id, false, MUST_EXIST); } else { throw new \moodle_exception('missingparameter'); } $PAGE->set_url('/mod/scorm/datamodels/sequencinghandler.php', array('scoid' => $scoid, 'attempt' => $attempt, 'id' => $cm->id, 'function' => $function, 'request' => $request)); require_login($course, false, $cm); if (!empty($scoid) && !empty($function)) { require_once($CFG->dirroot.'/mod/scorm/datamodels/scorm_13lib.php'); if (has_capability('mod/scorm:savetrack', context_module::instance($cm->id))) { $result = null; switch ($function) { case 'scorm_seq_flow' : if ($request == 'forward' || $request == 'backward') { $seq = scorm_seq_navigation ($scoid, $USER->id, $request.'_', $attempt); $sco = scorm_get_sco($scoid); $seq = scorm_seq_flow($sco, $request, $seq, true, $USER->id); if (!empty($seq->nextactivity)) { scorm_seq_end_attempt($sco, $USER->id, $seq); } } echo json_encode($seq); break; } } } PK3\+j" " scorm_13.phpnu[. require_once($CFG->dirroot.'/mod/scorm/locallib.php'); $userdata = new stdClass(); $def = new stdClass(); $cmiobj = new stdClass(); $cmiint = new stdClass(); $cmicommentsuser = new stdClass(); $cmicommentslms = new stdClass(); if (!isset($currentorg)) { $currentorg = ''; } if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) { // Drop keys so that it is a simple array. $scoes = array_values($scoes); foreach ($scoes as $sco) { $def->{($sco->id)} = new stdClass(); $userdata->{($sco->id)} = new stdClass(); $def->{($sco->id)} = get_scorm_default($userdata->{($sco->id)}, $scorm, $sco->id, $attempt, $mode); // Reconstitute objectives, comments_from_learner and comments_from_lms. $cmiobj->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)}, 'cmi.objectives', array('score')); $cmiint->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)}, 'cmi.interactions', array('objectives', 'correct_responses')); $cmicommentsuser->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)}, 'cmi.comments_from_learner', array()); $cmicommentslms->{($sco->id)} = scorm_reconstitute_array_element($scorm->version, $userdata->{($sco->id)}, 'cmi.comments_from_lms', array()); } } $scorm->autocommit = ($scorm->autocommit === "1") ? true : false; $PAGE->requires->js_init_call('M.scorm_api.init', array($def, $cmiobj, $cmiint, $cmicommentsuser, $cmicommentslms, scorm_debugging($scorm), $scorm->auto, $scorm->id, $CFG->wwwroot, sesskey(), $scoid, $attempt, $mode, $id, $currentorg, $scorm->autocommit)); // Pull in the debugging utilities. if (scorm_debugging($scorm)) { require_once($CFG->dirroot.'/mod/scorm/datamodels/debug.js.php'); echo html_writer::script('AppendToLog("Moodle SCORM 1.3 API Loaded, Activity: '. $scorm->name.', SCO: '.$sco->identifier.'", 0);'); } PK3\r<*yyscorm_12lib.phpnu[. /** * Sets up $userdata array and default values for SCORM 1.2 . * * @param stdClass $userdata an empty stdClass variable that should be set up with user values * @param object $scorm package record * @param string $scoid SCO Id * @param string $attempt attempt number for the user * @param string $mode scorm display mode type * @return array The default values that should be used for SCORM 1.2 package */ function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) { global $USER; $userdata->student_id = $USER->username; if (empty(get_config('scorm', 'scormstandard'))) { $userdata->student_name = fullname($USER); } else { $userdata->student_name = $USER->lastname .', '. $USER->firstname; } if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) { foreach ($usertrack as $key => $value) { $userdata->$key = $value; } } else { $userdata->status = ''; $userdata->score_raw = ''; } if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) { foreach ($scodatas as $key => $value) { $userdata->$key = $value; } } else { throw new \moodle_exception('cannotfindsco', 'scorm'); } if (!$sco = scorm_get_sco($scoid)) { throw new \moodle_exception('cannotfindsco', 'scorm'); } if (isset($userdata->status)) { if ($userdata->status == '') { $userdata->entry = 'ab-initio'; } else { if (isset($userdata->{'cmi.core.exit'}) && ($userdata->{'cmi.core.exit'} == 'suspend')) { $userdata->entry = 'resume'; } else { $userdata->entry = ''; } } } $userdata->mode = 'normal'; if (!empty($mode)) { $userdata->mode = $mode; } if ($userdata->mode == 'normal') { $userdata->credit = 'credit'; } else { $userdata->credit = 'no-credit'; } $def = array(); $def['cmi.core.student_id'] = $userdata->student_id; $def['cmi.core.student_name'] = $userdata->student_name; $def['cmi.core.credit'] = $userdata->credit; $def['cmi.core.entry'] = $userdata->entry; $def['cmi.core.lesson_mode'] = $userdata->mode; $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms'); $def['cmi.student_data.mastery_score'] = scorm_isset($userdata, 'masteryscore'); $def['cmi.student_data.max_time_allowed'] = scorm_isset($userdata, 'maxtimeallowed'); $def['cmi.student_data.time_limit_action'] = scorm_isset($userdata, 'timelimitaction'); $def['cmi.core.total_time'] = scorm_isset($userdata, 'cmi.core.total_time', '00:00:00'); // Now handle standard userdata items. $def['cmi.core.lesson_location'] = scorm_isset($userdata, 'cmi.core.lesson_location'); $def['cmi.core.lesson_status'] = scorm_isset($userdata, 'cmi.core.lesson_status'); $def['cmi.core.score.raw'] = scorm_isset($userdata, 'cmi.core.score.raw'); $def['cmi.core.score.max'] = scorm_isset($userdata, 'cmi.core.score.max'); $def['cmi.core.score.min'] = scorm_isset($userdata, 'cmi.core.score.min'); $def['cmi.core.exit'] = scorm_isset($userdata, 'cmi.core.exit'); $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data'); $def['cmi.comments'] = scorm_isset($userdata, 'cmi.comments'); $def['cmi.student_preference.language'] = scorm_isset($userdata, 'cmi.student_preference.language'); $def['cmi.student_preference.audio'] = scorm_isset($userdata, 'cmi.student_preference.audio', '0'); $def['cmi.student_preference.speed'] = scorm_isset($userdata, 'cmi.student_preference.speed', '0'); $def['cmi.student_preference.text'] = scorm_isset($userdata, 'cmi.student_preference.text', '0'); return $def; } PK3\4߉ scormlib.phpnu[. /** * functions used by SCORM 1.2/2004 packages. * * @package mod_scorm * @copyright 1999 onwards Roberto Pinna * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ function scorm_get_resources($blocks) { $resources = array(); foreach ($blocks as $block) { if ($block['name'] == 'RESOURCES' && isset($block['children'])) { foreach ($block['children'] as $resource) { if ($resource['name'] == 'RESOURCE') { $resources[addslashes_js($resource['attrs']['IDENTIFIER'])] = $resource['attrs']; } } } } return $resources; } function scorm_get_manifest($blocks, $scoes) { global $OUTPUT; static $parents = array(); static $resources; static $manifest; static $organization; $manifestresourcesnotfound = array(); if (count($blocks) > 0) { foreach ($blocks as $block) { switch ($block['name']) { case 'METADATA': if (isset($block['children'])) { foreach ($block['children'] as $metadata) { if ($metadata['name'] == 'SCHEMAVERSION') { if (empty($scoes->version)) { $isversionset = (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/", $metadata['tagData'], $matches)); if (isset($metadata['tagData']) && $isversionset) { $scoes->version = 'SCORM_'.$matches[count($matches) - 1]; } else { $isversionset = (preg_match("/^2004 (3rd|4th) Edition$/", $metadata['tagData'], $matches)); if (isset($metadata['tagData']) && $isversionset) { $scoes->version = 'SCORM_1.3'; } else { $scoes->version = 'SCORM_1.2'; } } } } } } break; case 'MANIFEST': $manifest = $block['attrs']['IDENTIFIER']; $organization = ''; $resources = array(); $resources = scorm_get_resources($block['children']); $scoes = scorm_get_manifest($block['children'], $scoes); if (empty($scoes->elements) || count($scoes->elements) <= 0) { foreach ($resources as $item => $resource) { if (!empty($resource['HREF'])) { $sco = new stdClass(); $sco->identifier = $item; $sco->title = $item; $sco->parent = '/'; $sco->launch = $resource['HREF']; $sco->scormtype = $resource['ADLCP:SCORMTYPE']; $scoes->elements[$manifest][$organization][$item] = $sco; } } } break; case 'ORGANIZATIONS': if (!isset($scoes->defaultorg) && isset($block['attrs']['DEFAULT'])) { $scoes->defaultorg = $block['attrs']['DEFAULT']; } if (!empty($block['children'])) { $scoes = scorm_get_manifest($block['children'], $scoes); } break; case 'ORGANIZATION': $identifier = $block['attrs']['IDENTIFIER']; $organization = ''; $scoes->elements[$manifest][$organization][$identifier] = new stdClass(); $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier; $scoes->elements[$manifest][$organization][$identifier]->parent = '/'; $scoes->elements[$manifest][$organization][$identifier]->launch = ''; $scoes->elements[$manifest][$organization][$identifier]->scormtype = ''; $parents = array(); $parent = new stdClass(); $parent->identifier = $identifier; $parent->organization = $organization; array_push($parents, $parent); $organization = $identifier; if (!empty($block['children'])) { $scoes = scorm_get_manifest($block['children'], $scoes); } array_pop($parents); break; case 'ITEM': $parent = array_pop($parents); array_push($parents, $parent); $identifier = $block['attrs']['IDENTIFIER']; $scoes->elements[$manifest][$organization][$identifier] = new stdClass(); $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier; $scoes->elements[$manifest][$organization][$identifier]->parent = $parent->identifier; if (!isset($block['attrs']['ISVISIBLE'])) { $block['attrs']['ISVISIBLE'] = 'true'; } $scoes->elements[$manifest][$organization][$identifier]->isvisible = $block['attrs']['ISVISIBLE']; if (!isset($block['attrs']['PARAMETERS'])) { $block['attrs']['PARAMETERS'] = ''; } $scoes->elements[$manifest][$organization][$identifier]->parameters = $block['attrs']['PARAMETERS']; if (!isset($block['attrs']['IDENTIFIERREF'])) { $scoes->elements[$manifest][$organization][$identifier]->launch = ''; $scoes->elements[$manifest][$organization][$identifier]->scormtype = 'asset'; } else { $idref = addslashes_js($block['attrs']['IDENTIFIERREF']); $base = ''; if (isset($resources[$idref]['XML:BASE'])) { $base = $resources[$idref]['XML:BASE']; } if (!isset($resources[$idref])) { $manifestresourcesnotfound[] = $idref; $scoes->elements[$manifest][$organization][$identifier]->launch = ''; } else { $scoes->elements[$manifest][$organization][$identifier]->launch = $base.$resources[$idref]['HREF']; if (empty($resources[$idref]['ADLCP:SCORMTYPE'])) { $resources[$idref]['ADLCP:SCORMTYPE'] = 'asset'; } $scoes->elements[$manifest][$organization][$identifier]->scormtype = $resources[$idref]['ADLCP:SCORMTYPE']; } } $parent = new stdClass(); $parent->identifier = $identifier; $parent->organization = $organization; array_push($parents, $parent); if (!empty($block['children'])) { $scoes = scorm_get_manifest($block['children'], $scoes); } array_pop($parents); break; case 'TITLE': $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['tagData'])) { $block['tagData'] = ''; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->title = $block['tagData']; break; case 'ADLCP:PREREQUISITES': if ($block['attrs']['TYPE'] == 'aicc_script') { $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['tagData'])) { $block['tagData'] = ''; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->prerequisites = $block['tagData']; } break; case 'ADLCP:MAXTIMEALLOWED': $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['tagData'])) { $block['tagData'] = ''; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->maxtimeallowed = $block['tagData']; break; case 'ADLCP:TIMELIMITACTION': $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['tagData'])) { $block['tagData'] = ''; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->timelimitaction = $block['tagData']; break; case 'ADLCP:DATAFROMLMS': $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['tagData'])) { $block['tagData'] = ''; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->datafromlms = $block['tagData']; break; case 'ADLCP:MASTERYSCORE': $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['tagData'])) { $block['tagData'] = ''; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->masteryscore = $block['tagData']; break; case 'ADLCP:COMPLETIONTHRESHOLD': $parent = array_pop($parents); array_push($parents, $parent); if (!isset($block['attrs']['MINPROGRESSMEASURE'])) { $block['attrs']['MINPROGRESSMEASURE'] = '1.0'; } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->threshold = $block['attrs']['MINPROGRESSMEASURE']; break; case 'ADLNAV:PRESENTATION': $parent = array_pop($parents); array_push($parents, $parent); if (!empty($block['children'])) { foreach ($block['children'] as $adlnav) { if ($adlnav['name'] == 'ADLNAV:NAVIGATIONINTERFACE') { foreach ($adlnav['children'] as $adlnavinterface) { if ($adlnavinterface['name'] == 'ADLNAV:HIDELMSUI') { if ($adlnavinterface['tagData'] == 'continue') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidecontinue = 1; } if ($adlnavinterface['tagData'] == 'previous') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideprevious = 1; } if ($adlnavinterface['tagData'] == 'exit') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexit = 1; } if ($adlnavinterface['tagData'] == 'exitAll') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexitall = 1; } if ($adlnavinterface['tagData'] == 'abandon') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandon = 1; } if ($adlnavinterface['tagData'] == 'abandonAll') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandonall = 1; } if ($adlnavinterface['tagData'] == 'suspendAll') { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidesuspendall = 1; } } } } } } break; case 'IMSSS:SEQUENCING': $parent = array_pop($parents); array_push($parents, $parent); if (!empty($block['children'])) { foreach ($block['children'] as $sequencing) { if ($sequencing['name'] == 'IMSSS:CONTROLMODE') { if (isset($sequencing['attrs']['CHOICE'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->choice = $sequencing['attrs']['CHOICE'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['CHOICEEXIT'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->choiceexit = $sequencing['attrs']['CHOICEEXIT'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['FLOW'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->flow = $sequencing['attrs']['FLOW'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['FORWARDONLY'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->forwardonly = $sequencing['attrs']['FORWARDONLY'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptobjectinfo = $sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptprogressinfo = $sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'] == 'true' ? 1 : 0; } } if ($sequencing['name'] == 'IMSSS:DELIVERYCONTROLS') { if (isset($sequencing['attrs']['TRACKED'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->tracked = $sequencing['attrs']['TRACKED'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['COMPLETIONSETBYCONTENT'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->completionsetbycontent = $sequencing['attrs']['COMPLETIONSETBYCONTENT'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['OBJECTIVESETBYCONTENT'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivesetbycontent = $sequencing['attrs']['OBJECTIVESETBYCONTENT'] == 'true' ? 1 : 0; } } if ($sequencing['name'] == 'ADLSEQ:CONSTRAINEDCHOICECONSIDERATIONS') { if (isset($sequencing['attrs']['CONSTRAINCHOICE'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->constrainChoice = $sequencing['attrs']['CONSTRAINCHOICE'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['PREVENTACTIVATION'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->preventactivation = $sequencing['attrs']['PREVENTACTIVATION'] == 'true' ? 1 : 0; } } if ($sequencing['name'] == 'IMSSS:OBJECTIVES') { $objectives = array(); foreach ($sequencing['children'] as $objective) { $objectivedata = new stdClass(); $objectivedata->primaryobj = 0; switch ($objective['name']) { case 'IMSSS:PRIMARYOBJECTIVE': $objectivedata->primaryobj = 1; case 'IMSSS:OBJECTIVE': $objectivedata->satisfiedbymeasure = 0; if (isset($objective['attrs']['SATISFIEDBYMEASURE'])) { $objectivedata->satisfiedbymeasure = $objective['attrs']['SATISFIEDBYMEASURE'] == 'true' ? 1 : 0; } $objectivedata->objectiveid = ''; if (isset($objective['attrs']['OBJECTIVEID'])) { $objectivedata->objectiveid = $objective['attrs']['OBJECTIVEID']; } $objectivedata->minnormalizedmeasure = 1.0; if (!empty($objective['children'])) { $mapinfos = array(); foreach ($objective['children'] as $objectiveparam) { if ($objectiveparam['name'] == 'IMSSS:MINNORMALIZEDMEASURE') { if (isset($objectiveparam['tagData'])) { $objectivedata->minnormalizedmeasure = $objectiveparam['tagData']; } else { $objectivedata->minnormalizedmeasure = 0; } } if ($objectiveparam['name'] == 'IMSSS:MAPINFO') { $mapinfo = new stdClass(); $mapinfo->targetobjectiveid = ''; if (isset($objectiveparam['attrs']['TARGETOBJECTIVEID'])) { $mapinfo->targetobjectiveid = $objectiveparam['attrs']['TARGETOBJECTIVEID']; } $mapinfo->readsatisfiedstatus = 1; if (isset($objectiveparam['attrs']['READSATISFIEDSTATUS'])) { $mapinfo->readsatisfiedstatus = $objectiveparam['attrs']['READSATISFIEDSTATUS'] == 'true' ? 1 : 0; } $mapinfo->writesatisfiedstatus = 0; if (isset($objectiveparam['attrs']['WRITESATISFIEDSTATUS'])) { $mapinfo->writesatisfiedstatus = $objectiveparam['attrs']['WRITESATISFIEDSTATUS'] == 'true' ? 1 : 0; } $mapinfo->readnormalizemeasure = 1; if (isset($objectiveparam['attrs']['READNORMALIZEDMEASURE'])) { $mapinfo->readnormalizemeasure = $objectiveparam['attrs']['READNORMALIZEDMEASURE'] == 'true' ? 1 : 0; } $mapinfo->writenormalizemeasure = 0; if (isset($objectiveparam['attrs']['WRITENORMALIZEDMEASURE'])) { $mapinfo->writenormalizemeasure = $objectiveparam['attrs']['WRITENORMALIZEDMEASURE'] == 'true' ? 1 : 0; } array_push($mapinfos, $mapinfo); } } if (!empty($mapinfos)) { $objectivesdata->mapinfos = $mapinfos; } } break; } array_push($objectives, $objectivedata); } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectives = $objectives; } if ($sequencing['name'] == 'IMSSS:LIMITCONDITIONS') { if (isset($sequencing['attrs']['ATTEMPTLIMIT'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptLimit = $sequencing['attrs']['ATTEMPTLIMIT']; } if (isset($sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptAbsoluteDurationLimit = $sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT']; } } if ($sequencing['name'] == 'IMSSS:ROLLUPRULES') { if (isset($sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupobjectivesatisfied = $sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupprogresscompletion = $sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'] == 'true' ? 1 : 0; } if (isset($sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'])) { $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivemeasureweight = $sequencing['attrs']['OBJECTIVEMEASUREWEIGHT']; } if (!empty($sequencing['children'])) { $rolluprules = array(); foreach ($sequencing['children'] as $sequencingrolluprule) { if ($sequencingrolluprule['name'] == 'IMSSS:ROLLUPRULE' ) { $rolluprule = new stdClass(); $rolluprule->childactivityset = 'all'; if (isset($sequencingrolluprule['attrs']['CHILDACTIVITYSET'])) { $rolluprule->childactivityset = $sequencingrolluprule['attrs']['CHILDACTIVITYSET']; } $rolluprule->minimumcount = 0; if (isset($sequencingrolluprule['attrs']['MINIMUMCOUNT'])) { $rolluprule->minimumcount = $sequencingrolluprule['attrs']['MINIMUMCOUNT']; } $rolluprule->minimumpercent = 0.0000; if (isset($sequencingrolluprule['attrs']['MINIMUMPERCENT'])) { $rolluprule->minimumpercent = $sequencingrolluprule['attrs']['MINIMUMPERCENT']; } if (!empty($sequencingrolluprule['children'])) { foreach ($sequencingrolluprule['children'] as $rolluproleconditions) { if ($rolluproleconditions['name'] == 'IMSSS:ROLLUPCONDITIONS') { $conditions = array(); $rolluprule->conditioncombination = 'all'; if (isset($rolluproleconditions['attrs']['CONDITIONCOMBINATION'])) { $rolluprule->CONDITIONCOMBINATION = $rolluproleconditions['attrs']['CONDITIONCOMBINATION']; } foreach ($rolluproleconditions['children'] as $rolluprulecondition) { if ($rolluprulecondition['name'] == 'IMSSS:ROLLUPCONDITION') { $condition = new stdClass(); if (isset($rolluprulecondition['attrs']['CONDITION'])) { $condition->cond = $rolluprulecondition['attrs']['CONDITION']; } $condition->operator = 'noOp'; if (isset($rolluprulecondition['attrs']['OPERATOR'])) { $condition->operator = $rolluprulecondition['attrs']['OPERATOR']; } array_push($conditions, $condition); } } $rolluprule->conditions = $conditions; } if ($rolluproleconditions['name'] == 'IMSSS:ROLLUPACTION') { $rolluprule->rollupruleaction = $rolluproleconditions['attrs']['ACTION']; } } } array_push($rolluprules, $rolluprule); } } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rolluprules = $rolluprules; } } if ($sequencing['name'] == 'IMSSS:SEQUENCINGRULES') { if (!empty($sequencing['children'])) { $sequencingrules = array(); foreach ($sequencing['children'] as $conditionrules) { $conditiontype = -1; switch($conditionrules['name']) { case 'IMSSS:PRECONDITIONRULE': $conditiontype = 0; break; case 'IMSSS:POSTCONDITIONRULE': $conditiontype = 1; break; case 'IMSSS:EXITCONDITIONRULE': $conditiontype = 2; break; } if (!empty($conditionrules['children'])) { $sequencingrule = new stdClass(); foreach ($conditionrules['children'] as $conditionrule) { if ($conditionrule['name'] == 'IMSSS:RULECONDITIONS') { $ruleconditions = array(); $sequencingrule->conditioncombination = 'all'; if (isset($conditionrule['attrs']['CONDITIONCOMBINATION'])) { $sequencingrule->conditioncombination = $conditionrule['attrs']['CONDITIONCOMBINATION']; } foreach ($conditionrule['children'] as $rulecondition) { if ($rulecondition['name'] == 'IMSSS:RULECONDITION') { $condition = new stdClass(); if (isset($rulecondition['attrs']['CONDITION'])) { $condition->cond = $rulecondition['attrs']['CONDITION']; } $condition->operator = 'noOp'; if (isset($rulecondition['attrs']['OPERATOR'])) { $condition->operator = $rulecondition['attrs']['OPERATOR']; } $condition->measurethreshold = 0.0000; if (isset($rulecondition['attrs']['MEASURETHRESHOLD'])) { $condition->measurethreshold = $rulecondition['attrs']['MEASURETHRESHOLD']; } $condition->referencedobjective = ''; if (isset($rulecondition['attrs']['REFERENCEDOBJECTIVE'])) { $condition->referencedobjective = $rulecondition['attrs']['REFERENCEDOBJECTIVE']; } array_push($ruleconditions, $condition); } } $sequencingrule->ruleconditions = $ruleconditions; } if ($conditionrule['name'] == 'IMSSS:RULEACTION') { $sequencingrule->action = $conditionrule['attrs']['ACTION']; } $sequencingrule->type = $conditiontype; } array_push($sequencingrules, $sequencingrule); } } $scoes->elements[$manifest][$parent->organization][$parent->identifier]->sequencingrules = $sequencingrules; } } } } break; } } } if (!empty($manifestresourcesnotfound)) { // Throw warning to user to let them know manifest contains references to resources that don't appear to exist. if (!defined('DEBUGGING_PRINTED')) { // Prevent redirect and display warning. define('DEBUGGING_PRINTED', 1); } echo $OUTPUT->notification(get_string('invalidmanifestresource', 'scorm').' '. implode(', ', $manifestresourcesnotfound)); } return $scoes; } /** * Sets up SCORM 1.2/2004 packages using the manifest file. * Called whenever SCORM changes * @param object $scorm instance - fields are updated and changes saved into database * @param stored_file|string $manifest - path to manifest file or stored_file. * @return bool */ function scorm_parse_scorm(&$scorm, $manifest) { global $CFG, $DB; // Load manifest into string. if ($manifest instanceof stored_file) { $xmltext = $manifest->get_content(); } else { require_once("$CFG->libdir/filelib.php"); $xmltext = download_file_content($manifest); } $defaultorgid = 0; $firstinorg = 0; $pattern = '/&(?!\w{2,6};)/'; $replacement = '&'; $xmltext = preg_replace($pattern, $replacement, $xmltext); $objxml = new xml2Array(); $manifests = $objxml->parse($xmltext); $scoes = new stdClass(); $scoes->version = ''; $scoes = scorm_get_manifest($manifests, $scoes); $newscoes = array(); $sortorder = 0; if (!empty($scoes->elements) && is_iterable($scoes->elements)) { $olditems = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id)); foreach ($scoes->elements as $manifest => $organizations) { foreach ($organizations as $organization => $items) { foreach ($items as $identifier => $item) { $sortorder++; // This new db mngt will support all SCORM future extensions. $newitem = new stdClass(); $newitem->scorm = $scorm->id; $newitem->manifest = $manifest; $newitem->organization = $organization; $newitem->sortorder = $sortorder; $standarddatas = array('parent', 'identifier', 'launch', 'scormtype', 'title'); foreach ($standarddatas as $standarddata) { if (isset($item->$standarddata)) { $newitem->$standarddata = $item->$standarddata; } else { $newitem->$standarddata = ''; } } if (!empty($defaultorgid) && !empty($scoes->defaultorg) && empty($firstinorg) && $newitem->parent == $scoes->defaultorg) { $firstinorg = $sortorder; } if (!empty($olditems) && ($olditemid = scorm_array_search('identifier', $newitem->identifier, $olditems))) { $newitem->id = $olditemid; // Update the Sco sortorder but keep id so that user tracks are kept against the same ids. $DB->update_record('scorm_scoes', $newitem); $id = $olditemid; // Remove all old data so we don't duplicate it. $DB->delete_records('scorm_scoes_data', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_objective', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditemid)); // Now remove this SCO from the olditems object as we have dealt with it. unset($olditems[$olditemid]); } else { // Insert the new SCO, and retain the link between the old and new for later adjustment. $id = $DB->insert_record('scorm_scoes', $newitem); } // Save this sco in memory so we can use it later. $newscoes[$id] = $newitem; if ($optionaldatas = scorm_optionals_data($item, $standarddatas)) { $data = new stdClass(); $data->scoid = $id; foreach ($optionaldatas as $optionaldata) { if (isset($item->$optionaldata)) { $data->name = $optionaldata; $data->value = $item->$optionaldata; $dataid = $DB->insert_record('scorm_scoes_data', $data); } } } if (isset($item->sequencingrules)) { foreach ($item->sequencingrules as $sequencingrule) { $rule = new stdClass(); $rule->scoid = $id; $rule->ruletype = $sequencingrule->type; $rule->conditioncombination = $sequencingrule->conditioncombination; $rule->action = $sequencingrule->action; $ruleid = $DB->insert_record('scorm_seq_ruleconds', $rule); if (isset($sequencingrule->ruleconditions)) { foreach ($sequencingrule->ruleconditions as $rulecondition) { $rulecond = new stdClass(); $rulecond->scoid = $id; $rulecond->ruleconditionsid = $ruleid; $rulecond->referencedobjective = $rulecondition->referencedobjective; $rulecond->measurethreshold = $rulecondition->measurethreshold; $rulecond->operator = $rulecondition->operator; $rulecond->cond = $rulecondition->cond; $rulecondid = $DB->insert_record('scorm_seq_rulecond', $rulecond); } } } } if (isset($item->rolluprules)) { foreach ($item->rolluprules as $rolluprule) { $rollup = new stdClass(); $rollup->scoid = $id; $rollup->childactivityset = $rolluprule->childactivityset; $rollup->minimumcount = $rolluprule->minimumcount; $rollup->minimumpercent = $rolluprule->minimumpercent; $rollup->rollupruleaction = $rolluprule->rollupruleaction; $rollup->conditioncombination = $rolluprule->conditioncombination; $rollupruleid = $DB->insert_record('scorm_seq_rolluprule', $rollup); if (isset($rollup->conditions)) { foreach ($rollup->conditions as $condition) { $cond = new stdClass(); $cond->scoid = $rollup->scoid; $cond->rollupruleid = $rollupruleid; $cond->operator = $condition->operator; $cond->cond = $condition->cond; $conditionid = $DB->insert_record('scorm_seq_rolluprulecond', $cond); } } } } if (isset($item->objectives)) { foreach ($item->objectives as $objective) { $obj = new stdClass(); $obj->scoid = $id; $obj->primaryobj = $objective->primaryobj; $obj->satisfiedbumeasure = $objective->satisfiedbymeasure; $obj->objectiveid = $objective->objectiveid; $obj->minnormalizedmeasure = trim($objective->minnormalizedmeasure); $objectiveid = $DB->insert_record('scorm_seq_objective', $obj); if (isset($objective->mapinfos)) { foreach ($objective->mapinfos as $objmapinfo) { $mapinfo = new stdClass(); $mapinfo->scoid = $id; $mapinfo->objectiveid = $objectiveid; $mapinfo->targetobjectiveid = $objmapinfo->targetobjectiveid; $mapinfo->readsatisfiedstatus = $objmapinfo->readsatisfiedstatus; $mapinfo->writesatisfiedstatus = $objmapinfo->writesatisfiedstatus; $mapinfo->readnormalizedmeasure = $objmapinfo->readnormalizedmeasure; $mapinfo->writenormalizedmeasure = $objmapinfo->writenormalizedmeasure; $mapinfoid = $DB->insert_record('scorm_seq_mapinfo', $mapinfo); } } } } if (empty($defaultorgid) && ((empty($scoes->defaultorg)) || ($scoes->defaultorg == $identifier))) { $defaultorgid = $id; } } } } if (!empty($olditems)) { foreach ($olditems as $olditem) { $DB->delete_records('scorm_scoes', ['id' => $olditem->id]); $DB->delete_records('scorm_scoes_data', ['scoid' => $olditem->id]); scorm_delete_tracks($scorm->id, $olditem->id); $DB->delete_records('scorm_seq_objective', ['scoid' => $olditem->id]); $DB->delete_records('scorm_seq_mapinfo', ['scoid' => $olditem->id]); $DB->delete_records('scorm_seq_ruleconds', ['scoid' => $olditem->id]); $DB->delete_records('scorm_seq_rulecond', ['scoid' => $olditem->id]); $DB->delete_records('scorm_seq_rolluprule', ['scoid' => $olditem->id]); $DB->delete_records('scorm_seq_rolluprulecond', ['scoid' => $olditem->id]); } } if (empty($scoes->version)) { $scoes->version = 'SCORM_1.2'; } $DB->set_field('scorm', 'version', $scoes->version, array('id' => $scorm->id)); $scorm->version = $scoes->version; } $scorm->launch = 0; // Check launch sco is valid. if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && !empty($newscoes[$defaultorgid]->launch)) { // Launch param is valid - do nothing. $scorm->launch = $defaultorgid; } else if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && empty($newscoes[$defaultorgid]->launch)) { // The launch is probably the default org so we need to find the first launchable item inside this org. $sqlselect = 'scorm = ? AND sortorder >= ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true); // We use get_records here as we need to pass a limit in the query that works cross db. $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id, $firstinorg), 'sortorder', 'id', 0, 1); if (!empty($scoes)) { $sco = reset($scoes); // We only care about the first record - the above query only returns one. $scorm->launch = $sco->id; } } if (empty($scorm->launch)) { // No valid Launch is specified - find the first launchable sco instead. $sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true); // We use get_records here as we need to pass a limit in the query that works cross db. $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1); if (!empty($scoes)) { $sco = reset($scoes); // We only care about the first record - the above query only returns one. $scorm->launch = $sco->id; } } return true; } function scorm_optionals_data($item, $standarddata) { $result = array(); $sequencingdata = array('sequencingrules', 'rolluprules', 'objectives'); foreach ($item as $element => $value) { if (! in_array($element, $standarddata)) { if (! in_array($element, $sequencingdata)) { $result[] = $element; } } } return $result; } function scorm_is_leaf($sco) { global $DB; if ($DB->record_exists('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier))) { return false; } return true; } function scorm_get_parent($sco) { global $DB; if ($sco->parent != '/') { if ($parent = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm, 'identifier' => $sco->parent))) { return scorm_get_sco($parent->id); } } return null; } function scorm_get_children($sco) { global $DB; $children = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id'); if (!empty($children)) { return $children; } return null; } function scorm_get_available_children($sco) { global $DB; $res = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id'); if (!$res || $res == null) { return false; } else { foreach ($res as $sco) { $result[] = $sco; } return $result; } } function scorm_get_available_descendent($descend, $sco) { if ($sco == null) { return $descend; } else { $avchildren = scorm_get_available_children($sco); foreach ($avchildren as $avchild) { array_push($descend, $avchild); } foreach ($avchildren as $avchild) { scorm_get_available_descendent($descend, $avchild); } } } function scorm_get_siblings($sco) { global $DB; if ($siblings = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->parent), 'sortorder, id')) { unset($siblings[$sco->id]); if (!empty($siblings)) { return $siblings; } } return null; } // Get an array that contains all the parent scos for this sco. function scorm_get_ancestors($sco) { $ancestors = array(); $continue = true; while ($continue) { $ancestor = scorm_get_parent($sco); if (!empty($ancestor) && $ancestor->id !== $sco->id) { $sco = $ancestor; $ancestors[] = $ancestor; if ($sco->parent == '/') { $continue = false; } } else { $continue = false; } } return $ancestors; } function scorm_get_preorder(&$preorder = array(), $sco = null) { if ($sco != null) { array_push($preorder, $sco); if ($children = scorm_get_children($sco)) { foreach ($children as $child) { scorm_get_preorder($preorder, $child); } } } return $preorder; } function scorm_find_common_ancestor($ancestors, $sco) { $pos = scorm_array_search('identifier', $sco->parent, $ancestors); if ($sco->parent != '/') { if ($pos === false) { return scorm_find_common_ancestor($ancestors, scorm_get_parent($sco)); } } return $pos; } /* Usage Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML; $objXML = new xml2Array(); $arroutput = $objXML->parse($strYourXML); print_r($arroutput); //print it out, or do whatever! */ class xml2Array { public $arroutput = array(); public $resparser; public $strxmldata; /** * Convert a utf-8 string to html entities * * @param string $str The UTF-8 string * @return string */ public function utf8_to_entities($str) { global $CFG; $entities = ''; $values = array(); $lookingfor = 1; return $str; } /** * Parse an XML text string and create an array tree that rapresent the XML structure * * @param string $strinputxml The XML string * @return array */ public function parse($strinputxml) { $this->resparser = xml_parser_create ('UTF-8'); xml_set_element_handler($this->resparser, [$this, "tagopen"], [$this, "tagclosed"]); xml_set_character_data_handler($this->resparser, [$this, "tagdata"]); $this->strxmldata = xml_parse($this->resparser, $strinputxml ); if (!$this->strxmldata) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($this->resparser)), xml_get_current_line_number($this->resparser))); } xml_parser_free($this->resparser); return $this->arroutput; } public function tagopen($parser, $name, $attrs) { $tag = array("name" => $name, "attrs" => $attrs); array_push($this->arroutput, $tag); } public function tagdata($parser, $tagdata) { if (trim($tagdata)) { if (isset($this->arroutput[count($this->arroutput) - 1]['tagData'])) { $this->arroutput[count($this->arroutput) - 1]['tagData'] .= $this->utf8_to_entities($tagdata); } else { $this->arroutput[count($this->arroutput) - 1]['tagData'] = $this->utf8_to_entities($tagdata); } } } public function tagclosed($parser, $name) { $this->arroutput[count($this->arroutput) - 2]['children'][] = $this->arroutput[count($this->arroutput) - 1]; array_pop($this->arroutput); } } PK3\[BVV aicclib.phpnu[. /** * functions used by AICC packages. * * @package mod_scorm * @copyright 1999 onwards Roberto Pinna * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ function scorm_add_time($a, $b) { $aes = explode(':', $a); $bes = explode(':', $b); $aseconds = explode('.', $aes[2]); $bseconds = explode('.', $bes[2]); $change = 0; $acents = 0; // Cents. if (count($aseconds) > 1) { $acents = $aseconds[1]; } $bcents = 0; if (count($bseconds) > 1) { $bcents = $bseconds[1]; } $cents = $acents + $bcents; $change = floor($cents / 100); $cents = $cents - ($change * 100); if (floor($cents) < 10) { $cents = '0'. $cents; } $secs = $aseconds[0] + $bseconds[0] + $change; // Seconds. $change = floor($secs / 60); $secs = $secs - ($change * 60); if (floor($secs) < 10) { $secs = '0'. $secs; } $mins = $aes[1] + $bes[1] + $change; // Minutes. $change = floor($mins / 60); $mins = $mins - ($change * 60); if ($mins < 10) { $mins = '0' . $mins; } $hours = $aes[0] + $bes[0] + $change; // Hours. if ($hours < 10) { $hours = '0' . $hours; } if ($cents != '0') { return $hours . ":" . $mins . ":" . $secs . '.' . $cents; } else { return $hours . ":" . $mins . ":" . $secs; } } /** * Take the header row of an AICC definition file * and returns sequence of columns and a pointer to * the sco identifier column. * * @param string $row AICC header row * @param string $mastername AICC sco identifier column * @return mixed */ function scorm_get_aicc_columns($row, $mastername='system_id') { $tok = strtok(strtolower($row), "\",\n\r"); $result = new stdClass(); $result->columns = array(); $result->mastercol = 0; $i = 0; while ($tok) { if ($tok != '') { $result->columns[] = $tok; if ($tok == $mastername) { $result->mastercol = $i; } $i++; } $tok = strtok("\",\n\r"); } return $result; } /** * Given a colums array return a string containing the regular * expression to match the columns in a text row. * * @param array $column The header columns * @param string $remodule The regular expression module for a single column * @return string */ function scorm_forge_cols_regexp($columns, $remodule='(".*")?,') { $regexp = '/^'; foreach ($columns as $column) { $regexp .= $remodule; } $regexp = substr($regexp, 0, -1) . '/'; return $regexp; } /** * Sets up AICC packages * Called whenever package changes * @param object $scorm instance - fields are updated and changes saved into database * @return bool */ function scorm_parse_aicc(&$scorm) { global $DB; if ($scorm->scormtype == SCORM_TYPE_AICCURL) { return scorm_aicc_generate_simple_sco($scorm); } if (!isset($scorm->cmid)) { $cm = get_coursemodule_from_instance('scorm', $scorm->id); $scorm->cmid = $cm->id; } $context = context_module::instance($scorm->cmid); $fs = get_file_storage(); $files = $fs->get_area_files($context->id, 'mod_scorm', 'content', 0, 'sortorder, itemid, filepath, filename', false); $version = 'AICC'; $ids = array(); $courses = array(); $extaiccfiles = array('crs', 'des', 'au', 'cst', 'ort', 'pre', 'cmp'); foreach ($files as $file) { $filename = $file->get_filename(); $ext = substr($filename, strrpos($filename, '.')); $extension = strtolower(substr($ext, 1)); if (in_array($extension, $extaiccfiles)) { $id = strtolower(basename($filename, $ext)); if (!isset($ids[$id])) { $ids[$id] = new stdClass(); } $ids[$id]->$extension = $file; } } foreach ($ids as $courseid => $id) { if (!isset($courses[$courseid])) { $courses[$courseid] = new stdClass(); } if (isset($id->crs)) { $contents = $id->crs->get_content(); $rows = explode("\r\n", $contents); if (is_array($rows)) { foreach ($rows as $row) { if (preg_match("/^(.+)=(.+)$/", $row, $matches)) { switch (strtolower(trim($matches[1]))) { case 'course_id': $courses[$courseid]->id = trim($matches[2]); break; case 'course_title': $courses[$courseid]->title = trim($matches[2]); break; case 'version': $courses[$courseid]->version = 'AICC_'.trim($matches[2]); break; } } } } } if (isset($id->des)) { $contents = $id->des->get_content(); $rows = explode("\r\n", $contents); $columns = scorm_get_aicc_columns($rows[0]); $regexp = scorm_forge_cols_regexp($columns->columns); for ($i = 1; $i < count($rows); $i++) { if (preg_match($regexp, $rows[$i], $matches)) { for ($j = 0; $j < count($columns->columns); $j++) { $column = $columns->columns[$j]; if (!isset($courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)])) { $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)] = new stdClass(); } $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)]->$column = substr(trim($matches[$j + 1]), 1, -1); } } } } if (isset($id->au)) { $contents = $id->au->get_content(); $rows = explode("\r\n", $contents); $columns = scorm_get_aicc_columns($rows[0]); $regexp = scorm_forge_cols_regexp($columns->columns); for ($i = 1; $i < count($rows); $i++) { if (preg_match($regexp, $rows[$i], $matches)) { for ($j = 0; $j < count($columns->columns); $j++) { $column = $columns->columns[$j]; $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1, -1)]->$column = substr(trim($matches[$j + 1]), 1, -1); } } } } if (isset($id->cst)) { $contents = $id->cst->get_content(); $rows = explode("\r\n", $contents); $columns = scorm_get_aicc_columns($rows[0], 'block'); $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,'); for ($i = 1; $i < count($rows); $i++) { if (preg_match($regexp, $rows[$i], $matches)) { for ($j = 0; $j < count($columns->columns); $j++) { if ($j != $columns->mastercol) { $element = substr(trim($matches[$j + 1]), 1 , -1); if (!empty($element)) { $courses[$courseid]->elements[$element]->parent = substr(trim($matches[$columns->mastercol + 1]), 1, -1); } } } } } } if (isset($id->ort)) { $contents = $id->ort->get_content(); $rows = explode("\r\n", $contents); $columns = scorm_get_aicc_columns($rows[0], 'course_element'); $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,'); for ($i = 1; $i < count($rows); $i++) { if (preg_match($regexp, $rows[$i], $matches)) { for ($j = 0; $j < count($matches) - 1; $j++) { if ($j != $columns->mastercol) { $courses[$courseid]->elements[substr(trim($matches[$j + 1]), 1, -1)]->parent = substr(trim($matches[$columns->mastercol + 1]), 1, -1); } } } } } if (isset($id->pre)) { $contents = $id->pre->get_content(); $rows = explode("\r\n", $contents); $columns = scorm_get_aicc_columns($rows[0], 'structure_element'); $regexp = scorm_forge_cols_regexp($columns->columns, '(.+),'); for ($i = 1; $i < count($rows); $i++) { if (preg_match($regexp, $rows[$i], $matches)) { $elementid = trim($matches[$columns->mastercol + 1]); $elementid = trim(trim($elementid, '"'), "'"); // Remove any quotes. $prereq = trim($matches[2 - $columns->mastercol]); $prereq = trim(trim($prereq, '"'), "'"); // Remove any quotes. $courses[$courseid]->elements[$elementid]->prerequisites = $prereq; } } } if (isset($id->cmp)) { $contents = $id->cmp->get_content(); $rows = explode("\r\n", $contents); } } $oldscoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id)); $sortorder = 0; $launch = 0; if (isset($courses)) { foreach ($courses as $course) { $sortorder++; $sco = new stdClass(); $sco->identifier = $course->id; $sco->scorm = $scorm->id; $sco->organization = ''; $sco->title = $course->title; $sco->parent = '/'; $sco->launch = ''; $sco->scormtype = ''; $sco->sortorder = $sortorder; if ($ss = $DB->get_record('scorm_scoes', array('scorm' => $scorm->id, 'identifier' => $sco->identifier))) { $id = $ss->id; $sco->id = $id; $DB->update_record('scorm_scoes', $sco); unset($oldscoes[$id]); } else { $id = $DB->insert_record('scorm_scoes', $sco); } if ($launch == 0) { $launch = $id; } if (isset($course->elements)) { foreach ($course->elements as $element) { unset($sco); $sco = new stdClass(); $sco->identifier = $element->system_id; $sco->scorm = $scorm->id; $sco->organization = $course->id; $sco->title = $element->title; if (!isset($element->parent)) { $sco->parent = '/'; } else if (strtolower($element->parent) == 'root') { $sco->parent = $course->id; } else { $sco->parent = $element->parent; } $sco->launch = ''; $sco->scormtype = ''; $sco->previous = 0; $sco->next = 0; $id = null; // Is it an Assignable Unit (AU)? if (isset($element->file_name)) { $sco->launch = $element->file_name; $sco->scormtype = 'sco'; } if ($oldscoid = scorm_array_search('identifier', $sco->identifier, $oldscoes)) { $sco->id = $oldscoid; $DB->update_record('scorm_scoes', $sco); $id = $oldscoid; $DB->delete_records('scorm_scoes_data', array('scoid' => $oldscoid)); unset($oldscoes[$oldscoid]); } else { $id = $DB->insert_record('scorm_scoes', $sco); } if (!empty($id)) { $scodata = new stdClass(); $scodata->scoid = $id; if (isset($element->web_launch)) { $scodata->name = 'parameters'; $scodata->value = $element->web_launch; $dataid = $DB->insert_record('scorm_scoes_data', $scodata); } if (isset($element->prerequisites)) { $scodata->name = 'prerequisites'; $scodata->value = $element->prerequisites; $dataid = $DB->insert_record('scorm_scoes_data', $scodata); } if (isset($element->max_time_allowed)) { $scodata->name = 'max_time_allowed'; $scodata->value = $element->max_time_allowed; $dataid = $DB->insert_record('scorm_scoes_data', $scodata); } if (isset($element->time_limit_action)) { $scodata->name = 'time_limit_action'; $scodata->value = $element->time_limit_action; $dataid = $DB->insert_record('scorm_scoes_data', $scodata); } if (isset($element->mastery_score)) { $scodata->name = 'mastery_score'; $scodata->value = $element->mastery_score; $dataid = $DB->insert_record('scorm_scoes_data', $scodata); } if (isset($element->core_vendor)) { $scodata->name = 'datafromlms'; $scodata->value = preg_replace('//i', "\r\n", $element->core_vendor); $dataid = $DB->insert_record('scorm_scoes_data', $scodata); } } if ($launch == 0) { $launch = $id; } } } } } if (!empty($oldscoes)) { foreach ($oldscoes as $oldsco) { scorm_delete_tracks($scorm->id, $oldsco->id); $DB->delete_records('scorm_scoes', ['id' => $oldsco->id]); } } // Find first launchable object. $sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true); // We use get_records here as we need to pass a limit in the query that works cross db. $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1); if (!empty($scoes)) { $sco = reset($scoes); // We only care about the first record - the above query only returns one. $scorm->launch = $sco->id; } else { $scorm->launch = $launch; } $scorm->version = 'AICC'; return true; } /** * Given a scormid creates an AICC Session record to allow HACP * * @param int $scormid - id from scorm table * @return string hacpsession */ function scorm_aicc_get_hacp_session($scormid) { global $USER, $DB, $SESSION; $cfgscorm = get_config('scorm'); if (empty($cfgscorm->allowaicchacp)) { return false; } $now = time(); $hacpsession = $SESSION->scorm; $hacpsession->scormid = $scormid; $hacpsession->hacpsession = random_string(20); $hacpsession->userid = $USER->id; $hacpsession->timecreated = $now; $hacpsession->timemodified = $now; $DB->insert_record('scorm_aicc_session', $hacpsession); return $hacpsession->hacpsession; } /** * Check the hacp_session for whether it is valid. * * @param string $hacpsession The hacpsession value to check (optional). Normally leave this blank * and this function will do required_param('sesskey', ...). * @return mixed - false if invalid, otherwise returns record from scorm_aicc_session table. */ function scorm_aicc_confirm_hacp_session($hacpsession) { global $DB; $cfgscorm = get_config('scorm'); if (empty($cfgscorm->allowaicchacp)) { return false; } $time = time() - ($cfgscorm->aicchacptimeout * 60); $sql = "hacpsession = ? AND timemodified > ?"; $hacpsession = $DB->get_record_select('scorm_aicc_session', $sql, array($hacpsession, $time)); if (!empty($hacpsession)) { // Update timemodified as this is still an active session - resets the timeout. $hacpsession->timemodified = time(); $DB->update_record('scorm_aicc_session', $hacpsession); } return $hacpsession; } /** * generate a simple single activity AICC object * structure to wrap around and externally linked * AICC package URL * * @param object $scorm package record */ function scorm_aicc_generate_simple_sco($scorm) { global $DB; // Find the oldest one. $scos = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'id'); if (!empty($scos)) { $sco = array_shift($scos); } else { $sco = new stdClass(); } // Get rid of old ones. foreach ($scos as $oldsco) { scorm_delete_tracks($scorm->id, $oldsco->id); $DB->delete_records('scorm_scoes', ['id' => $oldsco->id]); } $sco->identifier = 'A1'; $sco->scorm = $scorm->id; $sco->organization = ''; $sco->title = $scorm->name; $sco->parent = '/'; // Add the HACP signal to the activity launcher. if (preg_match('/\?/', $scorm->reference)) { $sco->launch = $scorm->reference.'&CMI=HACP'; } else { $sco->launch = $scorm->reference.'?CMI=HACP'; } $sco->scormtype = 'sco'; if (isset($sco->id)) { $DB->update_record('scorm_scoes', $sco); $id = $sco->id; } else { $id = $DB->insert_record('scorm_scoes', $sco); } return $id; } /** * Sets up $userdata array and default values for AICC package. * * @param stdClass $userdata an empty stdClass variable that should be set up with user values * @param object $scorm package record * @param string $scoid SCO Id * @param string $attempt attempt number for the user * @param string $mode scorm display mode type * @return array The default values that should be used for AICC package */ function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) { global $USER; $aiccuserid = get_config('scorm', 'aiccuserid'); if (!empty($aiccuserid)) { $userdata->student_id = $USER->id; } else { $userdata->student_id = $USER->username; } $userdata->student_name = $USER->lastname .', '. $USER->firstname; if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) { foreach ($usertrack as $key => $value) { $userdata->$key = $value; } } else { $userdata->status = ''; $userdata->score_raw = ''; } if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) { foreach ($scodatas as $key => $value) { $userdata->$key = $value; } } else { throw new \moodle_exception('cannotfindsco', 'scorm'); } if (!$sco = scorm_get_sco($scoid)) { throw new \moodle_exception('cannotfindsco', 'scorm'); } $userdata->mode = 'normal'; if (!empty($mode)) { $userdata->mode = $mode; } if ($userdata->mode == 'normal') { $userdata->credit = 'credit'; } else { $userdata->credit = 'no-credit'; } if (isset($userdata->status)) { if ($userdata->status == '') { $userdata->entry = 'ab-initio'; } else { if (isset($userdata->{'cmi.core.exit'}) && ($userdata->{'cmi.core.exit'} == 'suspend')) { $userdata->entry = 'resume'; } else { $userdata->entry = ''; } } } $def = array(); $def['cmi.core.student_id'] = $userdata->student_id; $def['cmi.core.student_name'] = $userdata->student_name; $def['cmi.core.credit'] = $userdata->credit; $def['cmi.core.entry'] = $userdata->entry; $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms'); $def['cmi.core.lesson_mode'] = $userdata->mode; $def['cmi.student_data.attempt_number'] = scorm_isset($userdata, 'cmi.student_data.attempt_number'); $def['cmi.student_data.mastery_score'] = scorm_isset($userdata, 'mastery_score'); $def['cmi.student_data.max_time_allowed'] = scorm_isset($userdata, 'max_time_allowed'); $def['cmi.student_data.time_limit_action'] = scorm_isset($userdata, 'time_limit_action'); $def['cmi.student_data.tries_during_lesson'] = scorm_isset($userdata, 'cmi.student_data.tries_during_lesson'); $def['cmi.core.lesson_location'] = scorm_isset($userdata, 'cmi.core.lesson_location'); $def['cmi.core.lesson_status'] = scorm_isset($userdata, 'cmi.core.lesson_status'); $def['cmi.core.exit'] = scorm_isset($userdata, 'cmi.core.exit'); $def['cmi.core.score.raw'] = scorm_isset($userdata, 'cmi.core.score.raw'); $def['cmi.core.score.max'] = scorm_isset($userdata, 'cmi.core.score.max'); $def['cmi.core.score.min'] = scorm_isset($userdata, 'cmi.core.score.min'); $def['cmi.core.total_time'] = scorm_isset($userdata, 'cmi.core.total_time', '00:00:00'); $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data'); $def['cmi.comments'] = scorm_isset($userdata, 'cmi.comments'); return $def; } PK3\h scorm_12.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . // // SCORM 1.2 API Implementation // function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit, masteryoverride, hidetoc) { var prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "¤torg=" + currentorg + "&sesskey=" + sesskey; var datamodelurl = cfgwwwroot + "/mod/scorm/datamodel.php"; var datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid; // Standard Data Type Definition CMIString256 = cmistring256; CMIString4096 = cmistring4096; CMITime = '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,2})?$'; CMITimespan = '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$'; CMIInteger = '^\\d+$'; CMISInteger = '^-?([0-9]+)$'; CMIDecimal = '^-?([0-9]{0,3})(\.[0-9]*)?$'; CMIIdentifier = '^[\\u0021-\\u007E]{0,255}$'; CMIFeedback = CMIString256; // This must be redefined CMIIndex = '[._](\\d+).'; // Vocabulary Data Type Definition CMIStatus = '^passed$|^completed$|^failed$|^incomplete$|^browsed$'; CMIStatus2 = '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$'; CMIExit = '^time-out$|^suspend$|^logout$|^$'; CMIType = '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$'; CMIResult = '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]*)?$'; NAVEvent = '^previous$|^continue$'; // Children lists cmi_children = 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions'; core_children = 'student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,lesson_mode,exit,session_time'; score_children = 'raw,min,max'; comments_children = 'content,location,time'; objectives_children = 'id,score,status'; correct_responses_children = 'pattern'; student_data_children = 'mastery_score,max_time_allowed,time_limit_action'; student_preference_children = 'audio,language,speed,text'; interactions_children = 'id,objectives,time,type,correct_responses,weighting,student_response,result,latency'; // Data ranges score_range = '0#100'; audio_range = '-1#100'; speed_range = '-100#100'; weighting_range = '-100#100'; text_range = '-1#1'; // The SCORM 1.2 data model // Set up data model for each sco var datamodel = {}; for(scoid in def){ datamodel[scoid] = { 'cmi._children':{'defaultvalue':cmi_children, 'mod':'r', 'writeerror':'402'}, 'cmi._version':{'defaultvalue':'3.4', 'mod':'r', 'writeerror':'402'}, 'cmi.core._children':{'defaultvalue':core_children, 'mod':'r', 'writeerror':'402'}, 'cmi.core.student_id':{'defaultvalue':def[scoid]['cmi.core.student_id'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.student_name':{'defaultvalue':def[scoid]['cmi.core.student_name'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.lesson_location':{'defaultvalue':def[scoid]['cmi.core.lesson_location'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.credit':{'defaultvalue':def[scoid]['cmi.core.credit'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.lesson_status':{'defaultvalue':def[scoid]['cmi.core.lesson_status'], 'format':CMIStatus, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.entry':{'defaultvalue':def[scoid]['cmi.core.entry'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.score._children':{'defaultvalue':score_children, 'mod':'r', 'writeerror':'402'}, 'cmi.core.score.raw':{'defaultvalue':def[scoid]['cmi.core.score.raw'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.score.max':{'defaultvalue':def[scoid]['cmi.core.score.max'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.score.min':{'defaultvalue':def[scoid]['cmi.core.score.min'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.core.total_time':{'defaultvalue':def[scoid]['cmi.core.total_time'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.lesson_mode':{'defaultvalue':def[scoid]['cmi.core.lesson_mode'], 'mod':'r', 'writeerror':'403'}, 'cmi.core.exit':{'defaultvalue':def[scoid]['cmi.core.exit'], 'format':CMIExit, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.core.session_time':{'format':CMITimespan, 'mod':'w', 'defaultvalue':'00:00:00', 'readerror':'404', 'writeerror':'405'}, 'cmi.suspend_data':{'defaultvalue':def[scoid]['cmi.suspend_data'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'}, 'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.launch_data'], 'mod':'r', 'writeerror':'403'}, 'cmi.comments':{'defaultvalue':def[scoid]['cmi.comments'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'}, // deprecated evaluation attributes 'cmi.evaluation.comments._count':{'defaultvalue':'0', 'mod':'r', 'writeerror':'402'}, 'cmi.evaluation.comments._children':{'defaultvalue':comments_children, 'mod':'r', 'writeerror':'402'}, 'cmi.evaluation.comments.n.content':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.evaluation.comments.n.location':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.evaluation.comments.n.time':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'}, 'cmi.comments_from_lms':{'mod':'r', 'writeerror':'403'}, 'cmi.objectives._children':{'defaultvalue':objectives_children, 'mod':'r', 'writeerror':'402'}, 'cmi.objectives._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.score._children':{'pattern':CMIIndex, 'mod':'r', 'writeerror':'402'}, 'cmi.objectives.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.objectives.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_data._children':{'defaultvalue':student_data_children, 'mod':'r', 'writeerror':'402'}, 'cmi.student_data.mastery_score':{'defaultvalue':def[scoid]['cmi.student_data.mastery_score'], 'mod':'r', 'writeerror':'403'}, 'cmi.student_data.max_time_allowed':{'defaultvalue':def[scoid]['cmi.student_data.max_time_allowed'], 'mod':'r', 'writeerror':'403'}, 'cmi.student_data.time_limit_action':{'defaultvalue':def[scoid]['cmi.student_data.time_limit_action'], 'mod':'r', 'writeerror':'403'}, 'cmi.student_preference._children':{'defaultvalue':student_preference_children, 'mod':'r', 'writeerror':'402'}, 'cmi.student_preference.audio':{'defaultvalue':def[scoid]['cmi.student_preference.audio'], 'format':CMISInteger, 'range':audio_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_preference.language':{'defaultvalue':def[scoid]['cmi.student_preference.language'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_preference.speed':{'defaultvalue':def[scoid]['cmi.student_preference.speed'], 'format':CMISInteger, 'range':speed_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.student_preference.text':{'defaultvalue':def[scoid]['cmi.student_preference.text'], 'format':CMISInteger, 'range':text_range, 'mod':'rw', 'writeerror':'405'}, 'cmi.interactions._children':{'defaultvalue':interactions_children, 'mod':'r', 'writeerror':'402'}, 'cmi.interactions._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.interactions.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.objectives._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.interactions.n.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.type':{'pattern':CMIIndex, 'format':CMIType, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.correct_responses._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'}, 'cmi.interactions.n.correct_responses.n.pattern':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.weighting':{'pattern':CMIIndex, 'format':CMIDecimal, 'range':weighting_range, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.student_response':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'w', 'readerror':'404', 'writeerror':'405'}, 'nav.event':{'defaultvalue':'', 'format':NAVEvent, 'mod':'w', 'readerror':'404', 'writeerror':'405'} }; } var cmi, nav; function initdatamodel(scoid){ prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a=" + scormid + "&scoid=" + scoid + "&attempt=" + attempt + "&mode=" + viewmode + "¤torg=" + currentorg + "&sesskey=" + sesskey; datamodelurlparams = "id=" + cmid + "&a=" + scormid + "&sesskey=" + sesskey + "&attempt=" + attempt + "&scoid=" + scoid; // // Datamodel inizialization // cmi = new Object(); cmi.core = new Object(); cmi.core.score = new Object(); cmi.objectives = new Object(); cmi.student_data = new Object(); cmi.student_preference = new Object(); cmi.interactions = new Object(); // deprecated evaluation attributes cmi.evaluation = new Object(); cmi.evaluation.comments = new Object(); // Navigation Object nav = new Object(); for (element in datamodel[scoid]) { if (element.match(/\.n\./) == null) { if (typeof datamodel[scoid][element].defaultvalue != 'undefined') { eval(element + ' = datamodel["' + scoid + '"]["' + element + '"].defaultvalue;'); } else { eval(element + ' = "";'); } } } eval(cmiobj[scoid]); eval(cmiint[scoid]); if (cmi.core.lesson_status == '') { cmi.core.lesson_status = 'not attempted'; } } // // API Methods definition // var Initialized = false; function LMSInitialize (param) { scoid = (scorm_current_node && scorm_current_node.scoid) ? scorm_current_node.scoid : scoid; initdatamodel(scoid); errorCode = "0"; if (param == "") { if (!Initialized) { Initialized = true; errorCode = "0"; if (scormdebugging) { LogAPICall("LMSInitialize", param, "", errorCode); } return "true"; } else { errorCode = "101"; } } else { errorCode = "201"; } if (scormdebugging) { LogAPICall("LMSInitialize", param, "", errorCode); } return "false"; } function LMSFinish (param) { errorCode = "0"; if (param == "") { if (Initialized) { Initialized = false; result = StoreData(cmi,true); if (nav.event != '') { if (nav.event == 'continue') { setTimeout('mod_scorm_launch_next_sco();',500); } else { setTimeout('mod_scorm_launch_prev_sco();',500); } } else { if (scormauto == 1) { setTimeout('mod_scorm_launch_next_sco();',500); } } if (scormdebugging) { LogAPICall("LMSFinish", "AJAXResult", result, 0); } result = ('true' == result) ? 'true' : 'false'; errorCode = (result == 'true') ? '0' : '101'; if (scormdebugging) { LogAPICall("LMSFinish", "result", result, 0); LogAPICall("LMSFinish", param, "", 0); } // trigger TOC update var callback = M.mod_scorm.connectPrereqCallback; YUI().use('io-base', function(Y) { Y.on('io:complete', callback.success, Y); Y.io(prerequrl); }); return result; } else { errorCode = "301"; } } else { errorCode = "201"; } if (scormdebugging) { LogAPICall("LMSFinish", param, "", errorCode); } return "false"; } function LMSGetValue (element) { errorCode = "0"; if (Initialized) { if (element != "") { expression = new RegExp(CMIIndex,'g'); elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'w') { element = String(element).replace(expression, "_$1."); elementIndexes = element.split('.'); subelement = 'cmi'; i = 1; while ((i < elementIndexes.length) && (typeof eval(subelement) != "undefined")) { subelement += '.' + elementIndexes[i++]; } if (subelement == element) { errorCode = "0"; if (scormdebugging) { LogAPICall("LMSGetValue", element, eval(element), 0); } return eval(element); } else { errorCode = "0"; // Need to check if it is the right errorCode } } else { errorCode = datamodel[scoid][elementmodel].readerror; } } else { childrenstr = '._children'; countstr = '._count'; if (elementmodel.substr(elementmodel.length - childrenstr.length,elementmodel.length) == childrenstr) { parentmodel = elementmodel.substr(0,elementmodel.length - childrenstr.length); if (typeof datamodel[scoid][parentmodel] != "undefined") { errorCode = "202"; } else { errorCode = "201"; } } else if (elementmodel.substr(elementmodel.length - countstr.length,elementmodel.length) == countstr) { parentmodel = elementmodel.substr(0,elementmodel.length - countstr.length); if (typeof datamodel[scoid][parentmodel] != "undefined") { errorCode = "203"; } else { errorCode = "201"; } } else { errorCode = "201"; } } } else { errorCode = "201"; } } else { errorCode = "301"; } if (scormdebugging) { LogAPICall("LMSGetValue", element, "", errorCode); } return ""; } function LMSSetValue (element,value) { errorCode = "0"; if (Initialized) { if (element != "") { expression = new RegExp(CMIIndex,'g'); elementmodel = String(element).replace(expression,'.n.'); if (typeof datamodel[scoid][elementmodel] != "undefined") { if (datamodel[scoid][elementmodel].mod != 'r') { expression = new RegExp(datamodel[scoid][elementmodel].format); value = value + ''; matches = value.match(expression); if (matches != null) { //Create dynamic data model element if (element != elementmodel) { elementIndexes = element.split('.'); subelement = 'cmi'; for (i = 1; i < elementIndexes.length - 1; i++) { elementIndex = elementIndexes[i]; if (elementIndexes[i + 1].match(/^\d+$/)) { if ((typeof eval(subelement + '.' + elementIndex)) == "undefined") { eval(subelement + '.' + elementIndex + ' = new Object();'); eval(subelement + '.' + elementIndex + '._count = 0;'); } if (elementIndexes[i + 1] == eval(subelement + '.' + elementIndex + '._count')) { eval(subelement + '.' + elementIndex + '._count++;'); } if (elementIndexes[i + 1] > eval(subelement + '.' + elementIndex + '._count')) { errorCode = "201"; } subelement = subelement.concat('.' + elementIndex + '_' + elementIndexes[i + 1]); i++; } else { subelement = subelement.concat('.' + elementIndex); } if ((typeof eval(subelement)) == "undefined") { eval(subelement + ' = new Object();'); if (subelement.substr(0,14) == 'cmi.objectives') { eval(subelement + '.score = new Object();'); eval(subelement + '.score._children = score_children;'); eval(subelement + '.score.raw = "";'); eval(subelement + '.score.min = "";'); eval(subelement + '.score.max = "";'); } if (subelement.substr(0,16) == 'cmi.interactions') { eval(subelement + '.objectives = new Object();'); eval(subelement + '.objectives._count = 0;'); eval(subelement + '.correct_responses = new Object();'); eval(subelement + '.correct_responses._count = 0;'); } } } element = subelement.concat('.' + elementIndexes[elementIndexes.length - 1]); } //Store data if (errorCode == "0") { if (autocommit && !(SCORMapi1_2.timeout)) { SCORMapi1_2.timeout = Y.later(60000, API, 'LMSCommit', [""], false); } if (typeof datamodel[scoid][elementmodel].range != "undefined") { range = datamodel[scoid][elementmodel].range; ranges = range.split('#'); value = value * 1.0; if ((value >= ranges[0]) && (value <= ranges[1])) { eval(element + '=value;'); errorCode = "0"; if (scormdebugging) { LogAPICall("LMSSetValue", element, value, errorCode); } return "true"; } else { errorCode = datamodel[scoid][elementmodel].writeerror; } } else { if (element == 'cmi.comments') { cmi.comments = cmi.comments + value; } else { eval(element + '=value;'); } errorCode = "0"; if (scormdebugging) { LogAPICall("LMSSetValue", element, value, errorCode); } return "true"; } } } else { errorCode = datamodel[scoid][elementmodel].writeerror; } } else { errorCode = datamodel[scoid][elementmodel].writeerror; } } else { errorCode = "201" } } else { errorCode = "201"; } } else { errorCode = "301"; } if (scormdebugging) { LogAPICall("LMSSetValue", element, value, errorCode); } return "false"; } function LMSCommit (param) { if (SCORMapi1_2.timeout) { SCORMapi1_2.timeout.cancel(); SCORMapi1_2.timeout = null; } errorCode = "0"; if (param == "") { if (Initialized) { result = StoreData(cmi,false); // Trigger TOC update only if TOC is displayed. // Checks against setting Display course structure in player: // 0 = To the side, 1 = Hidden, 2 = In a drop down menu, 3 = Disabled if (hidetoc !== '3') { Y.log('Refreshing toc'); var callback = M.mod_scorm.connectPrereqCallback; YUI().use('io-base', function(Y) { Y.on('io:complete', callback.success, Y); Y.io(prerequrl); }); } if (scormdebugging) { LogAPICall("Commit", param, "", 0); } if (scormdebugging) { LogAPICall("LMSCommit", "AJAXResult", result, 0); } result = ('true' == result) ? 'true' : 'false'; errorCode = (result == 'true') ? '0' : '101'; if (scormdebugging) { LogAPICall("LMSCommit", "result", result, 0); LogAPICall("LMSCommit", "errorCode", errorCode, 0); } return result; } else { errorCode = "301"; } } else { errorCode = "201"; } if (scormdebugging) { LogAPICall("LMSCommit", param, "", 0); } return "false"; } function LMSGetLastError () { if (scormdebugging) { LogAPICall("LMSGetLastError", "", "", errorCode); } return errorCode; } function LMSGetErrorString (param) { if (param != "") { var errorString = new Array(); errorString["0"] = "No error"; errorString["101"] = "General exception"; errorString["201"] = "Invalid argument error"; errorString["202"] = "Element cannot have children"; errorString["203"] = "Element not an array - cannot have count"; errorString["301"] = "Not initialized"; errorString["401"] = "Not implemented error"; errorString["402"] = "Invalid set value, element is a keyword"; errorString["403"] = "Element is read only"; errorString["404"] = "Element is write only"; errorString["405"] = "Incorrect data type"; if (scormdebugging) { LogAPICall("LMSGetErrorString", param, errorString[param], 0); } return errorString[param]; } else { if (scormdebugging) { LogAPICall("LMSGetErrorString", param, "No error string found!", 0); } return ""; } } function LMSGetDiagnostic (param) { if (param == "") { param = errorCode; } if (scormdebugging) { LogAPICall("LMSGetDiagnostic", param, param, 0); } return param; } function AddTime (first, second) { var sFirst = first.split(":"); var sSecond = second.split(":"); var cFirst = sFirst[2].split("."); var cSecond = sSecond[2].split("."); var change = 0; FirstCents = 0; //Cents if (cFirst.length > 1) { FirstCents = parseInt(cFirst[1],10); } SecondCents = 0; if (cSecond.length > 1) { SecondCents = parseInt(cSecond[1],10); } var cents = FirstCents + SecondCents; change = Math.floor(cents / 100); cents = cents - (change * 100); if (Math.floor(cents) < 10) { cents = "0" + cents.toString(); } var secs = parseInt(cFirst[0],10) + parseInt(cSecond[0],10) + change; //Seconds change = Math.floor(secs / 60); secs = secs - (change * 60); if (Math.floor(secs) < 10) { secs = "0" + secs.toString(); } mins = parseInt(sFirst[1],10) + parseInt(sSecond[1],10) + change; //Minutes change = Math.floor(mins / 60); mins = mins - (change * 60); if (mins < 10) { mins = "0" + mins.toString(); } hours = parseInt(sFirst[0],10) + parseInt(sSecond[0],10) + change; //Hours if (hours < 10) { hours = "0" + hours.toString(); } if (cents != '0') { return hours + ":" + mins + ":" + secs + '.' + cents; } else { return hours + ":" + mins + ":" + secs; } } function TotalTime() { total_time = AddTime(cmi.core.total_time, cmi.core.session_time); return '&' + underscore('cmi.core.total_time') + '=' + encodeURIComponent(total_time); } function CollectData(data,parent) { var datastring = ''; for (property in data) { if (typeof data[property] == 'object') { datastring += CollectData(data[property],parent + '.' + property); } else { element = parent + '.' + property; expression = new RegExp(CMIIndex,'g'); // get the generic name for this element (e.g. convert 'cmi.interactions.1.id' to 'cmi.interactions.n.id') elementmodel = String(element).replace(expression,'.n.'); // ignore the session time element if (element != "cmi.core.session_time") { // check if this specific element is not defined in the datamodel, // but the generic element name is if (typeof datamodel[scoid][element] == "undefined" && typeof datamodel[scoid][elementmodel] != "undefined") { // add this specific element to the data model (by cloning // the generic element) so we can track changes to it datamodel[scoid][element] = CloneObj(datamodel[scoid][elementmodel]); } // check if the current element exists in the datamodel if (typeof datamodel[scoid][element] != "undefined") { // make sure this is not a read only element if (datamodel[scoid][element].mod != 'r') { elementstring = '&' + underscore(element) + '=' + encodeURIComponent(data[property]); // check if the element has a default value if (typeof datamodel[scoid][element].defaultvalue != "undefined") { // check if the default value is different from the current value if (datamodel[scoid][element].defaultvalue != data[property] || typeof datamodel[scoid][element].defaultvalue != typeof data[property]) { // append the URI fragment to the string we plan to commit datastring += elementstring; // update the element default to reflect the current committed value datamodel[scoid][element].defaultvalue = data[property]; } } else { // append the URI fragment to the string we plan to commit datastring += elementstring; // no default value for the element, so set it now datamodel[scoid][element].defaultvalue = data[property]; } } } } } } return datastring; } function CloneObj(obj){ if(obj == null || typeof(obj) != 'object') { return obj; } var temp = new obj.constructor(); // changed (twice) for(var key in obj) { temp[key] = CloneObj(obj[key]); } return temp; } function StoreData(data,storetotaltime) { var datastring = ''; if (storetotaltime) { if (cmi.core.lesson_status == 'not attempted') { cmi.core.lesson_status = 'completed'; } if (cmi.core.lesson_mode == 'normal') { if (cmi.core.credit == 'credit') { if (masteryoverride && cmi.student_data.mastery_score !== '' && cmi.core.score.raw !== '') { if (parseFloat(cmi.core.score.raw) >= parseFloat(cmi.student_data.mastery_score)) { cmi.core.lesson_status = 'passed'; } else { cmi.core.lesson_status = 'failed'; } } } } if (cmi.core.lesson_mode == 'browse') { if (datamodel[scoid]['cmi.core.lesson_status'].defaultvalue == '' && cmi.core.lesson_status == 'not attempted') { cmi.core.lesson_status = 'browsed'; } } datastring = CollectData(data,'cmi'); datastring += TotalTime(); } else { datastring = CollectData(data,'cmi'); } var myRequest = NewHttpReq(); //alert('going to:' + "wwwroot) ?>/mod/scorm/datamodel.php" + "id=&a=&sesskey="+datastring); var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring); if (result === false) { return false; } results = String(result).split('\n'); errorCode = results[1]; return results[0]; } this.LMSInitialize = LMSInitialize; this.LMSFinish = LMSFinish; this.LMSGetValue = LMSGetValue; this.LMSSetValue = LMSSetValue; this.LMSCommit = LMSCommit; this.LMSGetLastError = LMSGetLastError; this.LMSGetErrorString = LMSGetErrorString; this.LMSGetDiagnostic = LMSGetDiagnostic; } M.scorm_api = {}; M.scorm_api.init = function(Y, def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit, masteryoverride, hidetoc) { window.API = new SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg, autocommit, masteryoverride, hidetoc); } PK3\wIffsequencinglib.phpnu[. function scorm_seq_exit_action_rules($seq, $userid) { $sco = $seq->currentactivity; $ancestors = scorm_get_ancestors($sco); $exittarget = null; foreach (array_reverse($ancestors) as $ancestor) { if (scorm_seq_rules_check($ancestor, 'exit') != null) { $exittarget = $ancestor; break; } } if ($exittarget != null) { $commons = array_slice($ancestors, 0, scorm_find_common_ancestor($ancestors, $exittarget)); // Terminate Descendent Attempts Process. if ($commons) { foreach ($commons as $ancestor) { scorm_seq_end_attempt($ancestor, $userid, $seq->attempt); $seq->currentactivity = $ancestor; } } } return $seq; } function scorm_seq_post_cond_rules($seq, $userid) { $sco = $seq->currentactivity; if (!$seq->suspended) { if ($action = scorm_seq_rules_check($sco, 'post') != null) { switch($action) { case 'retry': case 'continue': case 'previous': $seq->sequencing = $action; break; case 'exitparent': case 'exitall': $seq->termination = $action; break; case 'retryall': $seq->termination = 'exitall'; $seq->sequencing = 'retry'; break; } } } return $seq; } function scorm_seq_evaluate_rollupcond($sco, $conditioncombination, $rollupruleconds, $userid) { $bag = Array(); $con = ""; $val = false; $unk = false; foreach ($rollupruleconds as $rolluprulecond) { $condit = scorm_evaluate_condition($rolluprulecond, $sco, $userid); if ($rolluprulecond->operator == 'not') { // If operator is not, negate the condition. if ($rolluprulecond->cond != 'unknown') { if ($condit) { $condit = false; } else { $condit = true; } } else { $condit = 'unknown'; } array_push($childrenbag, $condit); } } if (empty($bag)) { return 'unknown'; } else { $i = 0; foreach ($bag as $b) { if ($rolluprulecond->conditioncombination == 'all') { $val = true; if ($b == 'unknown') { $unk = true; } if ($b === false) { return false; } } else { $val = false; if ($b == 'unknown') { $unk = true; } if ($b === true) { return true; } } } } if ($unk) { return 'unknown'; } return $val; } function scorm_seq_check_child ($sco, $action, $userid) { global $DB; $included = false; $sco = scorm_get_sco($sco->id); $r = scorm_get_sco_value($sco->id, $userid, 'activityattemptcount'); if ($action == 'satisfied' || $action == 'notsatisfied') { if (!$sco->rollupobjectivesatisfied) { $included = true; if (($action == 'satisfied' && $sco->requiredforsatisfied == 'ifnotsuspended') || ($action == 'notsatisfied' && $sco->requiredfornotsatisfied == 'ifnotsuspended')) { if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) || ((($r->value) > 0) && !scorm_seq_is('suspended', $sco->id, $userid))) { $included = false; } } else { if (($action == 'satisfied' && $sco->requiredforsatisfied == 'ifattempted') || ($action == 'notsatisfied' && $sco->requiredfornotsatisfied == 'ifattempted')) { if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) || (($r->value) == 0)) { $included = false; } } else { if (($action == 'satisfied' && $sco->requiredforsatisfied == 'ifnotskipped') || ($action == 'notsatisfied' && $sco->requiredfornotsatisfied == 'ifnotskipped')) { $rulch = scorm_seq_rules_check($sco, 'skip'); if ($rulch != null) { $included = false; } } } } } } if ($action == 'completed' || $action == 'incomplete') { if (!$sco->rollupprogresscompletion) { $included = true; if (($action == 'completed' && $sco->requiredforcompleted == 'ifnotsuspended') || ($action == 'incomplete' && $sco->requiredforincomplete == 'ifnotsuspended')) { if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) || ((($r->value) > 0)&& !scorm_seq_is('suspended', $sco->id, $userid))) { $included = false; } } else { if (($action == 'completed' && $sco->requiredforcompleted == 'ifattempted') || ($action == 'incomplete' && $sco->requiredforincomplete == 'ifattempted')) { if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) || (($r->value) == 0)) { $included = false; } } else { if (($action == 'completed' && $sco->requiredforsatisfied == 'ifnotskipped') || ($action == 'incomplete' && $sco->requiredfornotsatisfied == 'ifnotskipped')) { $rulch = scorm_seq_rules_check($sco, 'skip'); if ($rulch != null) { $included = false; } } } } } } return $included; } function scorm_seq_sequencing ($scoid, $userid, $seq) { switch ($seq->sequencing) { case 'start': // We'll see the parameters we have to send, this should update delivery and end. $seq = scorm_seq_start_sequencing($scoid, $userid, $seq); $seq->sequencing = true; break; case 'resumeall': // We'll see the parameters we have to send, this should update delivery and end. $seq = scorm_seq_resume_all_sequencing($scoid, $userid, $seq); $seq->sequencing = true; break; case 'exit': // We'll see the parameters we have to send, this should update delivery and end. $seq = scorm_seq_exit_sequencing($scoid, $userid, $seq); $seq->sequencing = true; break; case 'retry': // We'll see the parameters we have to send, this should update delivery and end. $seq = scorm_seq_retry_sequencing($scoid, $userid, $seq); $seq->sequencing = true; break; case 'previous': // We'll see the parameters we have to send, this should update delivery and end. $seq = scorm_seq_previous_sequencing($scoid, $userid, $seq); $seq->sequencing = true; break; case 'choice': // We'll see the parameters we have to send, this should update delivery and end. $seq = scorm_seq_choice_sequencing($scoid, $userid, $seq); $seq->sequencing = true; break; } if ($seq->exception != null) { $seq->sequencing = false; return $seq; } $seq->sequencing = true; return $seq; } function scorm_seq_start_sequencing($scoid, $userid, $seq) { global $DB; if (!empty($seq->currentactivity)) { $seq->delivery = null; $seq->exception = 'SB.2.5-1'; return $seq; } $sco = $DB->get_record('scorm_scoes', array('scoid' => $scoid, 'userid' => $userid)); if (($sco->parent == '/') && scorm_is_leaf($sco)) { // If the activity is the root and is leaf. $seq->delivery = $sco; } else { $ancestors = scorm_get_ancestors($sco); $ancestorsroot = array_reverse($ancestors); $res = scorm_seq_flow($ancestorsroot[0], 'forward', $seq, true, $userid); if ($res) { return $res; } } } function scorm_seq_resume_all_sequencing($scoid, $userid, $seq) { global $DB; if (!empty($seq->currentactivity)) { $seq->delivery = null; $seq->exception = 'SB.2.6-1'; return $seq; } $track = scorm_get_sco_value($scoid, $userid, 'suspendedactivity'); if (!$track) { $seq->delivery = null; $seq->exception = 'SB.2.6-2'; return $seq; } // We assign the sco to the delivery. $seq->delivery = $DB->get_record('scorm_scoes', array('scoid' => $scoid, 'userid' => $userid)); } function scorm_seq_continue_sequencing($scoid, $userid, $seq) { if (empty($seq->currentactivity)) { $seq->delivery = null; $seq->exception = 'SB.2.7-1'; return $seq; } $currentact = $seq->currentactivity; if ($currentact->parent != '/') { // If the activity is the root and is leaf. $parent = scorm_get_parent ($currentact); if (!isset($parent->flow) || ($parent->flow == false)) { $seq->delivery = null; $seq->exception = 'SB.2.7-2'; return $seq; } $res = scorm_seq_flow($currentact, 'forward', $seq, false, $userid); if ($res) { return $res; } } } function scorm_seq_previous_sequencing($scoid, $userid, $seq) { if (empty($seq->currentactivity)) { $seq->delivery = null; $seq->exception = 'SB.2.8-1'; return $seq; } $currentact = $seq->currentactivity; if ($currentact->parent != '/') { // If the activity is the root and is leaf. $parent = scorm_get_parent ($currentact); if (!isset($parent->flow) || ($parent->flow == false)) { $seq->delivery = null; $seq->exception = 'SB.2.8-2'; return $seq; } $res = scorm_seq_flow($currentact, 'backward', $seq, false, $userid); if ($res) { return $res; } } } function scorm_seq_exit_sequencing($scoid, $userid, $seq) { if (empty($seq->currentactivity)) { $seq->delivery = null; $seq->exception = 'SB.2.11-1'; return $seq; } if ($seq->active) { $seq->endsession = false; $seq->exception = 'SB.2.11-2'; return $seq; } $currentact = $seq->currentactivity; if ($currentact->parent == '/') { $seq->endsession = true; return $seq; } $seq->endsession = false; return $seq; } function scorm_seq_retry_sequencing($scoid, $userid, $seq) { if (empty($seq->currentactivity)) { $seq->delivery = null; $seq->exception = 'SB.2.10-1'; return $seq; } if ($seq->active || $seq->suspended) { $seq->delivery = null; $seq->exception = 'SB.2.10-2'; return $seq; } if (!scorm_is_leaf($seq->currentactivity)) { $res = scorm_seq_flow($seq->currentactivity, 'forward', $seq, true, $userid); if ($res != null) { return $res; } else { // Return deliver. $seq->delivery = null; $seq->exception = 'SB.2.10-3'; return $seq; } } else { $seq->delivery = $seq->currentactivity; return $seq; } } function scorm_seq_choice_sequencing($sco, $userid, $seq) { $avchildren = Array (); $comancestor = null; $traverse = null; if ($sco == null) { $seq->delivery = null; $seq->exception = 'SB.2.9-1'; return $seq; } $ancestors = scorm_get_ancestors($sco); $arrpath = array_reverse($ancestors); array_push ($arrpath, $sco); // Path from the root to the target. foreach ($arrpath as $activity) { if ($activity->parent != '/') { $avchildren = scorm_get_available_children (scorm_get_parent($activity)); $position = array_search($avchildren, $activity); if ($position !== false) { $seq->delivery = null; $seq->exception = 'SB.2.9-2'; return $seq; } } if (scorm_seq_rules_check($activity, 'hidefromchoice' != null)) { $seq->delivery = null; $seq->exception = 'SB.2.9-3'; return $seq; } } if ($sco->parent != '/') { $parent = scorm_sco_get_parent ($sco); if ( isset($parent->choice) && ($parent->choice == false)) { $seq->delivery = null; $seq->exception = 'SB.2.9-4'; return $seq; } } if ($seq->currentactivity != null) { $commonpos = scorm_find_common_ancestor($ancestors, $seq->currentactivity); $comancestor = $arrpath [$commonpos]; } else { $comancestor = $arrpath [0]; } if ($seq->currentactivity === $sco) { // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed. throw new \coding_exception('Unexpected state encountered'); } $sib = scorm_get_siblings($seq->currentactivity); $pos = array_search($sib, $sco); if ($pos !== false) { $siblings = array_slice($sib, 0, $pos - 1); if (empty($siblings)) { $seq->delivery = null; $seq->exception = 'SB.2.9-5'; return $seq; } $children = scorm_get_children (scorm_get_parent ($sco)); $pos1 = array_search($children, $sco); $pos2 = array_search($seq->currentactivity, $sco); if ($pos1 > $pos2) { $traverse = 'forward'; } else { $traverse = 'backward'; } foreach ($siblings as $sibling) { $seq = scorm_seq_choice_activity_traversal($sibling, $userid, $seq, $traverse); if (!$seq->reachable) { $seq->delivery = null; return $seq; } } // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed. throw new \coding_exception('Unexpected state encountered'); } if ($seq->currentactivity == null || $seq->currentactivity == $comancestor) { $commonpos = scorm_find_common_ancestor($ancestors, $seq->currentactivity); // Path from the common ancestor to the target activity. $comtarget = array_slice($ancestors, 1, $commonpos - 1); $comtarget = array_reverse($comtarget); if (empty($comtarget)) { $seq->delivery = null; $seq->exception = 'SB.2.9-5'; return $seq; } foreach ($comtarget as $act) { $seq = scorm_seq_choice_activity_traversal($act, $userid, $seq, 'forward'); if (!$seq->reachable) { $seq->delivery = null; return $seq; } $act = scorm_get_sco ($acti->id); if (scorm_seq_is('active', $act->id, $userid) && ($act->id != $comancestor->id && $act->preventactivation)) { $seq->delivery = null; $seq->exception = 'SB.2.9-6'; return $seq; } } // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed. throw new \coding_exception('Unexpected state encountered'); } if ($comancestor->id == $sco->id) { $ancestorscurrent = scorm_get_ancestors($seq->currentactivity); $possco = array_search($ancestorscurrent, $sco); // Path from the current activity to the target. $curtarget = array_slice($ancestorscurrent, 0, $possco); if (empty($curtarget)) { $seq->delivery = null; $seq->exception = 'SB.2.9-5'; return $seq; } $i = 0; foreach ($curtarget as $activ) { $i++; if ($i != count($curtarget)) { if (isset($activ->choiceexit) && ($activ->choiceexit == false)) { $seq->delivery = null; $seq->exception = 'SB.2.9-7'; return $seq; } } } // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed. throw new \coding_exception('Unexpected state encountered'); } if (array_search($ancestors, $comancestor) !== false) { $ancestorscurrent = scorm_get_ancestors($seq->currentactivity); $commonpos = scorm_find_common_ancestor($ancestors, $sco); $curcommon = array_slice($ancestorscurrent, 0, $commonpos - 1); if (empty($curcommon)) { $seq->delivery = null; $seq->exception = 'SB.2.9-5'; return $seq; } $constrained = null; foreach ($curcommon as $acti) { $acti = scorm_get_sco($acti->id); if (isset($acti->choiceexit) && ($acti->choiceexit == false)) { $seq->delivery = null; $seq->exception = 'SB.2.9-7'; return $seq; } if ($constrained == null) { if ($acti->constrainchoice == true) { $constrained = $acti; } } } if ($constrained != null) { $fwdir = scorm_get_preorder($constrained); if (array_search($fwdir, $sco) !== false) { $traverse = 'forward'; } else { $traverse = 'backward'; } $seq = scorm_seq_choice_flow($constrained, $traverse, $seq); $actconsider = $seq->identifiedactivity; $avdescendents = Array(); $avdescendents = scorm_get_available_descendents($actconsider); if (array_search ($avdescendents, $sco) !== false && $sco->id != $actconsider->id && $constrained->id != $sco->id ) { $seq->delivery = null; $seq->exception = 'SB.2.9-8'; return $seq; } // CONTINUE 11.5.5 ! } $commonpos = scorm_find_common_ancestor($ancestors, $seq->currentactivity); $comtarget = array_slice($ancestors, 1, $commonpos - 1);// Path from the common ancestor to the target activity. $comtarget = array_reverse($comtarget); if (empty($comtarget)) { $seq->delivery = null; $seq->exception = 'SB.2.9-5'; return $seq; } $fwdir = scorm_get_preorder($seq->currentactivity); if (array_search($fwdir, $sco) !== false) { foreach ($comtarget as $act) { $seq = scorm_seq_choice_activity_traversal($act, $userid, $seq, 'forward'); if (!$seq->reachable) { $seq->delivery = null; return $seq; } $act = scorm_get_sco($act->id); if (scorm_seq_is('active', $act->id, $userid) && ($act->id != $comancestor->id && ($act->preventactivation == true))) { $seq->delivery = null; $seq->exception = 'SB.2.9-6'; return $seq; } } } else { foreach ($comtarget as $act) { $act = scorm_get_sco($act->id); if (scorm_seq_is('active', $act->id, $userid) && $act->id != $comancestor->id && $act->preventactivation == true) { $seq->delivery = null; $seq->exception = 'SB.2.9-6'; return $seq; } } } // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed. throw new \coding_exception('Unexpected state encountered'); } if (scorm_is_leaf ($sco)) { $seq->delivery = $sco; $seq->exception = 'SB.2.9-6'; return $seq; } $seq = scorm_seq_flow($sco, 'forward', $seq, true, $userid); if ($seq->deliverable == false) { scorm_terminate_descendent_attempts($comancestor, $userid, $seq); scorm_seq_end_attempt($comancestor, $userid, $seq->attempt); $seq->currentactivity = $sco; $seq->delivery = null; $seq->exception = 'SB.2.9-9'; return $seq; } else { return $seq; } } function scorm_seq_choice_flow ($constrained, $traverse, $seq) { $seq = scorm_seq_choice_flow_tree ($constrained, $traverse, $seq); if ($seq->identifiedactivity == null) { $seq->identifiedactivity = $constrained; return $seq; } else { return $seq; } } function scorm_seq_choice_flow_tree ($constrained, $traverse, $seq) { $islast = false; $parent = scorm_get_parent ($constrained); if ($traverse == 'forward') { $preord = scorm_get_preorder($constrained); if (count($preorder) == 0 || (count($preorder) == 0 && $preorder[0]->id = $constrained->id)) { // TODO: undefined. $islast = true; // The function is the last activity available. } if ($constrained->parent == '/' || $islast) { $seq->nextactivity = null; return $seq; } $avchildren = scorm_get_available_children($parent); // Available children. if ($avchildren[count($avchildren) - 1]->id == $constrained->id) { $seq = scorm_seq_choice_flow_tree ($parent, 'forward', $seq); return $seq; } else { $i = 0; while ($i < count($avchildren)) { if ($avchildren [$i]->id == $constrained->id) { $seq->nextactivity = $avchildren [$i + 1]; return $seq; } else { $i++; } } } } if ($traverse == 'backward') { if ($constrained->parent == '/' ) { $seq->nextactivity = null; return $seq; } $avchildren = scorm_get_available_children($parent); // Available children. if ($avchildren [0]->id == $constrained->id) { $seq = scorm_seq_choice_flow_tree ($parent, 'backward', $seq); return $seq; } else { $i = count($avchildren) - 1; while ($i >= 0) { if ($avchildren [$i]->id == $constrained->id) { $seq->nextactivity = $avchildren [$i - 1]; return $seq; } else { $i--; } } } } } function scorm_seq_choice_activity_traversal($activity, $userid, $seq, $direction) { if ($direction == 'forward') { $act = scorm_seq_rules_check($activity, 'stopforwardtraversal'); if ($act != null) { $seq->reachable = false; $seq->exception = 'SB.2.4-1'; return $seq; } $seq->reachable = false; return $seq; } if ($direction == 'backward') { $parentsco = scorm_get_parent($activity); if ($parentsco != null) { if (isset($parentsco->forwardonly) && ($parentsco->forwardonly == true)) { $seq->reachable = false; $seq->exception = 'SB.2.4-2'; return $seq; } else { $seq->reachable = false; $seq->exception = 'SB.2.4-3'; return $seq; } } } $seq->reachable = true; return $seq; } // Delivery Request Process. function scorm_sequencing_delivery($scoid, $userid, $seq) { if (!scorm_is_leaf($seq->delivery)) { $seq->deliveryvalid = false; $seq->exception = 'DB.1.1-1'; return $seq; } $ancestors = scorm_get_ancestors($seq->delivery); $arrpath = array_reverse($ancestors); array_push ($arrpath, $seq->delivery); // Path from the root to the target. if (empty($arrpath)) { $seq->deliveryvalid = false; $seq->exception = 'DB.1.1-2'; return $seq; } foreach ($arrpath as $activity) { if (scorm_check_activity($activity, $userid)) { $seq->deliveryvalid = false; $seq->exception = 'DB.1.1-3'; return $seq; } } $seq->deliveryvalid = true; return $seq; } function scorm_content_delivery_environment($seq, $userid) { global $DB; $act = $seq->currentactivity; if (scorm_seq_is('active', $act->id, $userid)) { $seq->exception = 'DB.2-1'; return $seq; } $track = scorm_get_sco_value($act->id, $userid, 'suspendedactivity'); if ($track != null) { $seq = scorm_clear_suspended_activity($seq->delivery, $seq, $userid); } $attemptobject = scorm_get_attempt($userid, $track->scormid, 0); $seq = scorm_terminate_descendent_attempts ($seq->delivery, $userid, $seq); $ancestors = scorm_get_ancestors($seq->delivery); $arrpath = array_reverse($ancestors); array_push ($arrpath, $seq->delivery); foreach ($arrpath as $activity) { if (!scorm_seq_is('active', $activity->id, $userid)) { if (!isset($activity->tracked) || ($activity->tracked == 1)) { if (!scorm_seq_is('suspended', $activity->id, $userid)) { $r = scorm_get_sco_value($activity->id, $userid, 'activityattemptcount'); $value = new stdClass(); $value->id = $r->valueid; $value->value = ($r->value) + 1; $DB->update_record('scorm_scoes_value', $value); if ($r->value == 1) { scorm_seq_set('activityprogressstatus', $activity->id, $userid, 'true'); } scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'objectiveprogressstatus', 'false'); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'objectivesatisfiedstatus', 'false'); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'objectivemeasurestatus', 'false'); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'objectivenormalizedmeasure', 0.0); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'attemptprogressstatus', 'false'); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'attemptcompletionstatus', 'false'); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'attemptabsoluteduration', 0.0); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'attemptexperiencedduration', 0.0); scorm_insert_track($userid, $activity->scorm, $activity->id, $attemptobject, 'attemptcompletionamount', 0.0); } } scorm_seq_set('active', $activity->id, $userid, 'true'); } } $seq->delivery = $seq->currentactivity; scorm_seq_set('suspendedactivity', $activity->id, $userid, 'false'); return $seq; } function scorm_clear_suspended_activity($act, $seq, $userid) { global $DB; $currentact = $seq->currentactivity; $track = scorm_get_sco_value($currentact->id, $userid, 'suspendedactivity'); if ($track != null) { $ancestors = scorm_get_ancestors($act); $commonpos = scorm_find_common_ancestor($ancestors, $currentact); if ($commonpos !== false) { if ($activitypath = array_slice($ancestors, 0, $commonpos)) { if (!empty($activitypath)) { foreach ($activitypath as $activity) { if (scorm_is_leaf($activity)) { scorm_seq_set('suspended', $activity->id, $userid, false); } else { $children = scorm_get_children($activity); $bool = false; foreach ($children as $child) { if (scorm_seq_is('suspended', $child->id, $userid)) { $bool = true; } } if (!$bool) { scorm_seq_set('suspended', $activity->id, $userid, false); } } } } } } scorm_seq_set('suspendedactivity', $act->id, $userid, false); } } function scorm_select_children_process($scoid, $userid) { global $DB; $sco = scorm_get_sco($scoid); if (!scorm_is_leaf($sco)) { if (!scorm_seq_is('suspended', $scoid, $userid) && !scorm_seq_is('active', $scoid, $userid)) { $r = scorm_get_sco_value($scoid, $userid, 'selectiontiming'); switch ($r->value) { case 'oneachnewattempt': case 'never': break; case 'once': if (!scorm_seq_is('activityprogressstatus', $scoid, $userid)) { if (scorm_seq_is('selectioncountsstatus', $scoid, $userid)) { $childlist = ''; $res = scorm_get_sco_value($scoid, $userid, 'selectioncount'); $i = ($res->value) - 1; $children = scorm_get_children($sco); while ($i >= 0) { $pos = array_rand($children); array_push($childlist, $children[$pos]); array_splice($children, $pos, 1); $i--; } sort($childlist); $clist = serialize($childlist); scorm_seq_set('availablechildren', $scoid, $userid, false); scorm_seq_set('availablechildren', $scoid, $userid, $clist); } } break; } } } } function scorm_randomize_children_process($scoid, $userid) { global $DB; $sco = scorm_get_sco($scoid); if (!scorm_is_leaf($sco)) { if (!scorm_seq_is('suspended', $scoid, $userid) && !scorm_seq_is('active', $scoid, $userid)) { $r = scorm_get_sco_value($scoid, $userid, 'randomizationtiming'); switch ($r->value) { case 'never': break; case 'oneachnewattempt': case 'once': if (!scorm_seq_is('activityprogressstatus', $scoid, $userid)) { if (scorm_seq_is('randomizechildren', $scoid, $userid)) { $childlist = array(); $res = scorm_get_available_children($sco); $i = count($res) - 1; $children = $res->value; while ($i >= 0) { $pos = array_rand($children); array_push($childlist, $children[$pos]); array_splice($children, $pos, 1); $i--; } $clist = serialize ($childlist); scorm_seq_set('availablechildren', $scoid, $userid, false); scorm_seq_set('availablechildren', $scoid, $userid, $clist); } } break; } } } } function scorm_terminate_descendent_attempts($activity, $userid, $seq) { $ancestors = scorm_get_ancestors($seq->currentactivity); $commonpos = scorm_find_common_ancestor($ancestors, $activity); if ($commonpos !== false) { if ($activitypath = array_slice($ancestors, 1, $commonpos - 2)) { if (!empty($activitypath)) { foreach ($activitypath as $sco) { scorm_seq_end_attempt($sco, $userid, $seq->attempt); } } } } } function scorm_sequencing_exception($seq) { global $OUTPUT; if ($seq->exception != null) { switch ($seq->exception) { case 'NB.2.1-1': echo $OUTPUT->notification("Sequencing session has already begun"); break; case 'NB.2.1-2': echo $OUTPUT->notification("Sequencing session has not begun"); break; case 'NB.2.1-3': echo $OUTPUT->notification("Suspended activity is not defined"); break; case 'NB.2.1-4': echo $OUTPUT->notification("Flow Sequencing Control Model Violation"); break; case 'NB.2.1-5': echo $OUTPUT->notification("Flow or Forward only Sequencing Control Model Violation"); break; case 'NB.2.1-6': echo $OUTPUT->notification("No activity is previous to the root"); break; case 'NB.2.1-7': echo $OUTPUT->notification("Unsupported Navigation Request"); break; case 'NB.2.1-8': echo $OUTPUT->notification("Choice Exit Sequencing Control Model Violation"); break; case 'NB.2.1-9': echo $OUTPUT->notification("No activities to consider"); break; case 'NB.2.1-10': echo $OUTPUT->notification("Choice Sequencing Control Model Violation"); break; case 'NB.2.1-11': echo $OUTPUT->notification("Target Activity does not exist"); break; case 'NB.2.1-12': echo $OUTPUT->notification("Current Activity already terminated"); break; case 'NB.2.1-13': echo $OUTPUT->notification("Undefined Navigation Request"); break; case 'TB.2.3-1': echo $OUTPUT->notification("Current Activity already terminated"); break; case 'TB.2.3-2': echo $OUTPUT->notification("Current Activity already terminated"); break; case 'TB.2.3-4': echo $OUTPUT->notification("Current Activity already terminated"); break; case 'TB.2.3-5': echo $OUTPUT->notification("Nothing to suspend; No active activities"); break; case 'TB.2.3-6': echo $OUTPUT->notification("Nothing to abandon; No active activities"); break; case 'SB.2.1-1': echo $OUTPUT->notification("Last activity in the tree"); break; case 'SB.2.1-2': echo $OUTPUT->notification("Cluster has no available children"); break; case 'SB.2.1-3': echo $OUTPUT->notification("No activity is previous to the root"); break; case 'SB.2.1-4': echo $OUTPUT->notification("Forward Only Sequencing Control Model Violation"); break; case 'SB.2.2-1': echo $OUTPUT->notification("Flow Sequencing Control Model Violation"); break; case 'SB.2.2-2': echo $OUTPUT->notification("Activity unavailable"); break; case 'SB.2.3-1': echo $OUTPUT->notification("Forward Traversal Blocked"); break; case 'SB.2.3-2': echo $OUTPUT->notification("Forward Only Sequencing Control Model Violation"); break; case 'SB.2.3-3': echo $OUTPUT->notification("No activity is previous to the root"); break; case 'SB.2.5-1': echo $OUTPUT->notification("Sequencing session has already begun"); break; case 'SB.2.6-1': echo $OUTPUT->notification("Sequencing session has already begun"); break; case 'SB.2.6-2': echo $OUTPUT->notification("No Suspended activity is defined"); break; case 'SB.2.7-1': echo $OUTPUT->notification("Sequencing session has not begun"); break; case 'SB.2.7-2': echo $OUTPUT->notification("Flow Sequencing Control Model Violation"); break; case 'SB.2.8-1': echo $OUTPUT->notification("Sequencing session has not begun"); break; case 'SB.2.8-2': echo $OUTPUT->notification("Flow Sequencing Control Model Violation"); break; case 'SB.2.9-1': echo $OUTPUT->notification("No target for Choice"); break; case 'SB.2.9-2': echo $OUTPUT->notification("Target Activity does not exist or is unavailable"); break; case 'SB.2.9-3': echo $OUTPUT->notification("Target Activity hidden from choice"); break; case 'SB.2.9-4': echo $OUTPUT->notification("Choice Sequencing Control Model Violation"); break; case 'SB.2.9-5': echo $OUTPUT->notification("No activities to consider"); break; case 'SB.2.9-6': echo $OUTPUT->notification("Unable to activate target; target is not a child of the Current Activity"); break; case 'SB.2.9-7': echo $OUTPUT->notification("Choice Exit Sequencing Control Model Violation"); break; case 'SB.2.9-8': echo $OUTPUT->notification("Unable to choose target activity - constrained choice"); break; case 'SB.2.9-9': echo $OUTPUT->notification("Choice Request Prevented by Flow-only Activity"); break; case 'SB.2.10-1': echo $OUTPUT->notification("Sequencing session has not begun"); break; case 'SB.2.10-2': echo $OUTPUT->notification("Current Activity is active or suspended"); break; case 'SB.2.10-3': echo $OUTPUT->notification("Flow Sequencing Control Model Violation"); break; case 'SB.2.11-1': echo $OUTPUT->notification("Sequencing session has not begun"); break; case 'SB.2.11-2': echo $OUTPUT->notification("Current Activity has not been terminated"); break; case 'SB.2.12-2': echo $OUTPUT->notification("Undefined Sequencing Request"); break; case 'DB.1.1-1': echo $OUTPUT->notification("Cannot deliver a non-leaf activity"); break; case 'DB.1.1-2': echo $OUTPUT->notification("Nothing to deliver"); break; case 'DB.1.1-3': echo $OUTPUT->notification("Activity unavailable"); break; case 'DB.2-1': echo $OUTPUT->notification("Identified activity is already active"); break; } } } PK3\MTl debug.js.phpnu[. echo html_writer::start_tag('script'); ?>