# The LearningOnline Network # Custom Edit Course Routines for Assembly of Valid Concept Tests from # Geoscience Concept Inventory. # # $Id: londocsgci.pm,v 1.12 2010/09/14 06:23:15 gci Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA 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 2 of the License, or # (at your option) any later version. # # LON-CAPA 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 LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # package Apache::londocsgci; use strict; use Apache::lonnet; use Apache::loncommon; use LONCAPA::map(); use Apache::lonindexer; use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); { #scope variables my $path; my $version; my $reqnum; my @mandcats; my @bincats; my @categories; my @allprobs; my %probcat; my %prereqs; my %revreqs; my @defchosen; my @chosen; my @mandprobs; my @bins; my @optional; my %mandatory; my @development; sub setdefaults { $path='/res/gci/gci'; $version='GCIv2-1-1'; $reqnum=15; @allprobs=('01','02','37','2004_73','03','04','05','06','07', '08','10', '09', '11','12','13','14','15','16','17', '18','69', '19','20', '21','22','23','24','25','26','27','28','29','30', '31','32','33','34','35','36','38', '39A','39B', '40', '41','42','43','44','45','46','47','48','49','50', '51', '52','57', '53','54','55','56','58', '60', '61','62','63','64','65','66','67','68','70', '71'); @bins = ( ['03','04','06'], ['07','08','09'], ['10','12','13','14','15','16','17'], ['18','19','20','21','22','23','24','25'], ['26','27','28','30'], ['32','33','34','35','36'], ['38','39A','39B','40','41','42','43','44','45','46'], ['47','48','49','50','51','52','53'], ['54','55','56','57','58','60','61','62'], ['63','64','65'], ['66','67','68','69','70','71']); @optional = ('05','11','29','31'); @mandcats = ('M1','M2','M3','M4'); @bincats = ('A','B','C','D','E','F','G','H','I','J','K'); @categories=(@mandcats,@bincats); %probcat =('01' => 'M1' ,'02' => 'M2' ,'37' => 'M3' ,'2004_73' => 'M4' , '03' => 'A' ,'04' => 'A','05' => '' ,'06' => 'A' ,'07' => 'B' , '08' => 'B' ,'09' => 'B' ,'10' => 'C' ,'11' => '' ,'12' => 'C' , '13' => 'C' ,'14' => 'C' ,'15' => 'C' ,'16' => 'C' ,'17' => 'C' ,'18' => 'D' ,'19' => 'D' ,'20' => 'D' , '21' => 'D' ,'22' => 'D' ,'23' => 'D' ,'24' => 'D' ,'25' => 'D' ,'26' => 'E' ,'27' => 'E' ,'28' => 'E' ,'29' => '' ,'30' => 'E' , '31' => '' ,'32' => 'F' ,'33' => 'F' ,'34' => 'F' ,'35' => 'F' ,'36' => 'F' ,'38' => 'G' , '39A'=> 'G' ,'39B'=> 'G' , '40' => 'G' , '41' => 'G' ,'42' => 'G' ,'43' => 'G' ,'44' => 'G' ,'45' => 'G' ,'46' => 'G' ,'47' => 'H' ,'48' => 'H' ,'49' => 'H' ,'50' => 'H' , '51' => 'H' ,'52' => 'H' ,'53' => 'H' ,'54' => 'I' ,'55' => 'I' ,'56' => 'I' ,'57' => 'I' ,'58' => 'I' , '60' => 'I' , '61' => 'I' ,'62' => 'I' ,'63' => 'J' ,'64' => 'J' ,'65' => 'J' ,'66' => 'K' ,'67' => 'K' ,'68' => 'K' ,'69' => 'K' ,'70' => 'K' , '71' => 'K'); %mandatory=('01' => 1 ,'02' => 1,'37' => 1, '2004_73' => 1); @mandprobs = ('01','02','37','2004_73'); %prereqs=('10' => '08', '57' => '52', '69' => '18'); foreach my $item (keys(%prereqs)) { $revreqs{$prereqs{$item}} = $item; } @defchosen=('01','02','03','07','12','18','26','32','37','38','47','54','63','66','2004_73'); @development = ('001','002','003','004','005','006','007','008','009','010','011','012','013','014','015','016','017','018','019','020','021','022'); } sub checkvalid { my %covered=(); my %chosenproblems=(); my @errors=(); my $num=$#chosen+1; if ($num<$reqnum) { push(@errors,&mt('Test requires at least [_1] items, but has only [_2].',$reqnum,$num)); } foreach my $item (@chosen) { $chosenproblems{$item}=1; $covered{$probcat{$item}}=1; } foreach my $cat (@categories) { unless ($covered{$cat}) { push(@errors,&mt('Bin [_1] not covered.',$cat)); } } foreach my $item (@chosen) { if ($prereqs{$item}) { unless ($chosenproblems{$prereqs{$item}}) { my $url = &fullurl($item); my $title = &Apache::lonnet::metadata($url,'title'); my $prerequrl = &fullurl($prereqs{$item}); my $prereqtitle = &Apache::lonnet::metadata($prerequrl,'title'); push(@errors,&mt('[_1] requires [_2].',"'$title'","'$prereqtitle'")); } } } return @errors; } sub fullurl { my ($item,$catname)=@_; unless ($item=~/\_/) { $item='_'.$item; } if ($catname eq 'development') { return $path.'/pilot/pilot'.$item.'.problem'; } return $path.'/'.$version.'/GCI'.$item.'.problem'; } sub item_from_url { my ($url)=@_; if ($url =~ m{\Q$path\E/\Q$version\E/GCI_?([^.]+)\.problem$}) { return $1; } } sub validcheck { my ($r)=@_; my @errors=&checkvalid(); if ($#errors>-1) { $r->print(''.&mt('Your test is not yet valid.').'

'.&mt('The following issues must be addressed before you can use the test:').'

'); return 0; } return 1; } sub listresources { my ($r,$context,$cdom,$cnum)=@_; my $numchosen = scalar(@chosen); my $multipart = 0; my $composites; if ($numchosen > 0) { foreach my $key (keys(%prereqs)) { if (grep(/^\Q$key\E/,@chosen)) { $multipart ++; $numchosen --; } } if ($multipart) { $composites = &mt(' (of which [quant,_1,is a combination question,are combination questions] in which students select both an answer, and the reasoning used).',$multipart).'
'; } else { $composites = '. '; } } unless ($numchosen > 0) { $r->print('

'.&mt('Create a Concept Test').'

'. '

'.&mt('Choose how the test should be built:').'
'. '

'. '
'. '

'. ''. '

'); return; } if ((!&validcheck($r)) || ($env{'form.concepttest'} eq 'editmyown')) { &editor($r,$context,$cdom,$cnum); } else { my $numsub = &get_submissions_count($cdom,$cnum); unless ($env{'form.phase'} eq 'storemap') { $r->print('

'.&mt('This course contains a valid concept test which includes [quant,_1,question]',$numchosen).$composites.''.&mt('Display Contents').'
'); } if ($numsub > 0) { $r->print(&mt('As there are existing student submissions, modification of the [_1]contents[_2] of the Concept Test will result in loss of student performance data, and is [_3]not[_4] permitted.','','','','').'
'.&mt('Modification of open and close dates [_1]is[_2] permitted.','','').'

'); } else { $r->print('
'.&mt('Modify Test').''. &mt('Currently no student submissions have been recorded, so you may modify the contents of the Concept Test.'). '
'. ''. '

'); } my ($crsparms,$ineffect,$parmlev) = ¤t_parms($cdom,$cnum); my $formname = 'datesform'; my $datebutton = &mt('Save'); my $startform = &Apache::lonhtmlcommon::date_setter($formname,'opendate', $crsparms->{'opendate'}); my $endform = &Apache::lonhtmlcommon::date_setter($formname,'duedate', $crsparms->{'duedate'}); $r->print('

'.&mt('Test Availability').''. &mt('Open and close dates for test items for the concept test are currently set as follows:'). '
'. '
'. &Apache::lonhtmlcommon::start_pick_box(). &Apache::lonhtmlcommon::row_title(&mt('Open date')). $startform. &Apache::lonhtmlcommon::row_closure(1). &Apache::lonhtmlcommon::row_title(&mt('Close date')). $endform. &Apache::lonhtmlcommon::row_closure(1). &Apache::lonhtmlcommon::end_pick_box().'
'. ''. ''. '

'); } } sub editor { my ($r,$context,$cdom,$cnum)=@_; my %chosenitems=(); my $havedev = 0; foreach my $item (@chosen) { $chosenitems{$item}=1; } my @devitems = &get_development_questions($cdom,$cnum); &Apache::lonnet::appenv({'request.gcicontext' => 'buildtest'}); $r->print('
'); $r->print(&mt('Select test items from the numbered bins below and then press [_1]"Store Problem Selection"[_2] at the bottom of the page.','','').'
'); $r->print(''); my $mandleg = &mt('Mandatory Questions'); $r->print(&display_questions(\@mandprobs,'mandatory',$mandleg,\%chosenitems)); for (my $i=0; $i<@bins; $i++) { my $num = $i+1; my $legend = &mt('Bin [_1]',$num); my $catname = 'bin'.$i; $r->print(&display_questions($bins[$i],$catname,$legend,\%chosenitems)); } my $optleg = &mt('Optional Questions'); $r->print(&display_questions(\@optional,'optional',$optleg,\%chosenitems)); my $devleg = &mt('Development Questions'); $r->print(&display_questions(\@devitems,'development',$devleg,\%chosenitems)); $r->print(''. ''. ''. '
'); &Apache::lonnet::delenv('request.gcicontext'); } sub display_questions { my ($questions,$catname,$catlegend,$chosenitems) = @_; return unless((ref($questions) eq 'ARRAY') && (ref($chosenitems) eq 'HASH')); my $total = 0; foreach my $item (@{$questions}) { if ($chosenitems->{$item}) { $total ++; } } my $fieldid = 'GCI_'.$catname.'_q'; my $titleid = 'GCI_'.$catname.'_t'; my $countid = 'GCI_'.$catname.'_count'; my $output = '
'; my %fixed = ( mandatory => 4, development => 2, ); if (($catname eq 'mandatory') || ($catname eq 'development')) { $output .= ''.&mt('[_1] [_2] selected automatically',$catlegend, '').''; } else { $output .= ''.&mt('[_1]: currently [_2] selected',$catlegend, '').''; } $output .= ''. ''. &mt('Show').' ...
'. '
'. &Apache::loncommon::start_data_table(). &Apache::loncommon::start_data_table_header_row(); unless (($catname eq 'development') || ($catname eq 'mandatory')) { $output .= ''.&mt('Select').''; } $output .= ''.&mt('Problem'). ''.&mt('Preview').''. &Apache::loncommon::end_data_table_header_row(); foreach my $item (@{$questions}) { my $url = &fullurl($item,$catname); my $title = &Apache::lonnet::metadata($url,'title'); $output .= &Apache::loncommon::start_data_table_row().''; if ($catname eq 'mandatory') { $output .= ''; } elsif ($catname eq 'development') { $output .= ''; } else { $output .= '{$item}) { $output .= ' checked="checked"'; } $output .= ' onclick="countChecked('."'$catname'".');'; my $binname; if ($prereqs{$item}) { for (my $i=0; $i<@bincats; $i++) { if ($bincats[$i] eq $probcat{$prereqs{$item}}) { $binname = 'bin'.$i; last; } } $output .= 'checkPrereqs('."'dep','$item','$prereqs{$item}','$binname'".');'; } elsif ($revreqs{$item}) { for (my $i=0; $i<@bincats; $i++) { if ($bincats[$i] eq $probcat{$item}) { $binname = 'bin'.$i; last; } } $output .= 'checkPrereqs('."'pre','$revreqs{$item}','$item','$binname'".');'; } $output .= '" />'. ''; } $output .= ''.$title.''; my $content=&Apache::lonindexer::showpreview($url); my $startformtag = '
'; my $endtag = '

'; return $output; } sub get_development_questions { my ($cdom,$cnum) = @_; my $cid = $cdom.'_'.$cnum; my %courseenv = &Apache::lonnet::userenvironment($cdom,$cnum, ('internal.courseowner')); my $seed = $courseenv{'internal.courseowner'}; my $rndseed=&Apache::lonnet::rndseed($seed,$cid,$cdom,$cnum); &Apache::lonnet::setup_random_from_rndseed($rndseed); my @devitems = &Math::Random::random_permutation(@development); return ($devitems[0],$devitems[1]); } sub evaluate { if ($env{'form.phase'} eq 'storemap') { @chosen=(); foreach my $item (@allprobs) { if ($env{'form.item'.$item}) { push(@chosen,$item); } } } } sub mapread_gci { my $coursedom=$env{'course.'.$env{'request.course.id'}.'.domain'}; my $coursenum=$env{'course.'.$env{'request.course.id'}.'.num'}; return &LONCAPA::map::mapread('/uploaded/'.$coursedom.'/'.$coursenum.'/default_1261144274.sequence'); } sub storemap_gci { my ($coursedom,$coursenum) = @_; if (($coursedom !~ /^$match_domain$/) || ($coursenum !~ /^$match_courseid$/)) { $coursedom=$env{'course.'.$env{'request.course.id'}.'.domain'}; $coursenum=$env{'course.'.$env{'request.course.id'}.'.num'}; } return ('unauthorized',2) if (($coursedom eq '') || ($coursedom eq '') || (!&Apache::lonnet::allowed('mdc',$coursedom.'_'.$coursenum))); my ($outtext,$errtext)= &LONCAPA::map::storemap('/uploaded/'.$coursedom.'/'.$coursenum.'/default_1261144274.sequence',1); if ($errtext) { return ($errtext,2); } return ($errtext,0); } sub chosen_to_map { my %chosenproblems=(); foreach my $item (@chosen) { $chosenproblems{$item}=1; } @LONCAPA::map::order=(); @LONCAPA::map::resources=(); my $counter = 0; my $residx; for (my $idx=0;$idx<=$#allprobs;$idx++) { $residx=$idx+1; if ($chosenproblems{$allprobs[$idx]}) { my $url = &LONCAPA::map::qtunescape(&fullurl($allprobs[$idx])); if (($revreqs{$allprobs[$idx]}) && ($chosenproblems{$revreqs{$allprobs[$idx]}})) { my $probnum = '_'.$allprobs[$idx].'_'.$revreqs{$allprobs[$idx]}; $url = &LONCAPA::map::qtunescape(&fullurl($probnum)); } elsif ($prereqs{$allprobs[$idx]}) { next; } push(@LONCAPA::map::order,$residx); $counter ++; my $name = &LONCAPA::map::qtunescape('Problem '.$counter); $LONCAPA::map::resources[$residx]=join(':', ($name, $url, 'false', 'normal', 'res')); } } foreach my $devitem (@development) { if ($env{'form.pilot'.$devitem}) { my $url = &LONCAPA::map::qtunescape(&fullurl($devitem,'development')); $residx ++; push(@LONCAPA::map::order,$residx); $counter ++; my $name = &LONCAPA::map::qtunescape('Problem '.$counter); $LONCAPA::map::resources[$residx]=join(':', ($name, $url, 'false', 'normal', 'res')); } } } sub map_to_chosen { @chosen=(); foreach my $idx (@LONCAPA::map::order) { my ($title,$url)=split(':',$LONCAPA::map::resources[$idx]); my $item; unless ($url eq '') { $item = &item_from_url($url); if (($item =~ /^(\d+)_(\d+)$/) && ($prereqs{$2} eq $1)) { push(@chosen,($1,$2)); } else { push(@chosen,$item); } } } if (($env{'form.concepttest'} eq 'defchosen') || ((@chosen == 0) && ($env{'form.concepttest'} eq 'editmyown'))) { @chosen = @defchosen; } } sub store { my ($caller,$cdom,$cnum)=@_; if ($env{'form.concepttest'} eq 'defchosen') { @chosen = @defchosen; } my @errors=&checkvalid(); if (@errors > 0) { if (($caller eq 'requestcrs') && ($env{'form.concepttest'} eq 'defchosen')) { return &mt('Invalid concept test.'); } else { my $errormsg = ''.&mt('Invalid concept test:'); if (@errors > 1) { $errormsg .= '
  • '.join('
  • ',@errors).'
'; } else { $errormsg .= '
'.$errors[0]; } $errormsg .= '
'; return $errormsg; } } &chosen_to_map(); my ($err,$errnum) = &storemap_gci($cdom,$cnum); if ($caller eq 'requestcrs') { if ($errnum == 0) { return; } else { return 'An error occurred when storing the concept test'; } } if ($env{'form.phase'} eq 'storemap') { if ($errnum == 0) { &Apache::lonuserstate::readmap($cdom.'/'.$cnum); return '

'.&mt('You have successfully assembled a valid test.').'

'. '

'.&mt('Display Table of Contents'). ''.(' ' x4).''.&mt('Return to Main Menu').'

'; } else { return '
'.&mt('An error occurred when storing your concept test: [_1].',$err).'
'; } } return; } sub load { &mapread_gci(); &map_to_chosen(); if (@chosen > 0) { my @errors=&checkvalid(); if ($#errors>1) { @chosen=@defchosen; } } } sub current_parms { my ($cdom,$cnum) = @_; my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom); my (%crsparms,%ineffect,%parmlev); foreach my $item ('opendate','duedate') { $crsparms{$item} = $courseopt->{$env{'request.course.id'}.'.0.'.$item}; my ($result,@outpar) = &Apache::lonparmset::parmval('0.'.$item,'0.0',undef,undef, $cdom,undef,undef,$courseopt); if ($result) { $ineffect{$item} = $outpar[$result]; $parmlev{$item} = $result; } } return (\%crsparms,\%ineffect,\%parmlev); } sub store_dates_parms { my ($cdom,$cnum) = @_; my $topsymb = '___0___uploaded/'.$cdom.'/'.$cnum.'/default.sequence'; my ($opendate,$duedate) = &Apache::lonuserutils::get_dates_from_form('opendate','duedate'); my %dates = ( opendate => { value => $opendate, type => 'date_start', }, duedate => { value => $duedate, type => 'date_end', }, ); my %parmresult; foreach my $date (keys(%dates)) { $parmresult{$date} = &Apache::lonparmset::storeparm_by_symb($topsymb, '0_'.$date,14,$dates{$date}{'value'}, $dates{$date}{'type'},undef,$cdom); } my $output = &mt('Open and Close dates set for test items').'
    '. '
  • '.&mt('Concept Test Questions open:').' '. &Apache::lonlocal::locallocaltime($opendate).'
  • '. '
  • '.&mt('Concept Test Questions close:').' '. &Apache::lonlocal::locallocaltime($duedate).'
  • '. '
'; return $output; } sub get_submissions_count { my ($cdom,$cnum) = @_; my $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { my $itemserror = ''.&mt('An error occurred retrieving information about the course.').'
'.&mt('It is recommended that you [_1]re-select the course[_2].','','').'
'; return $itemserror; } my @allres=$navmap->retrieveResources('/uploaded/'.$cdom.'/'.$cnum.'/default_1261144274.sequence',sub {if ($_[0]->is_problem) { $_[0]->parts();} return 1;}); my (%resourcetracker,$submissioncount); my %resourcetracker = &Apache::lonnet::dump('nohist_resourcetracker', $cdom,$cnum); foreach my $resource (@allres) { my $symb = $resource->symb(); my @parts = @{$resource->parts()}; foreach my $part (@parts) { if ($resource->handgrade($part) eq 'yes') { next; } if ($resource->is_survey($part)) { next; } if (exists($resourcetracker{$symb."\0".$part."\0users"})) { $submissioncount += $resourcetracker{$symb."\0".$part."\0users"}; } } } return $submissioncount; } sub builder_javascript { my %lt = &Apache::lonlocal::texthash( show => 'Show', hide => 'Hide', ); my $prereqjs = " function checkPrereqs(caller,item,prereq,binname) { var changedPrereq = 0; element = document.getElementById('item'+item); if (element.checked == false) { return; } else { prereqelement = document.getElementById('item'+prereq); if (!prereqelement.checked) { prereqelement.checked = true; changedPrereq = 1; countChecked(binname); } } "; my ($hasdep,$prereq,$hasdeptitle,$prereqtitle) = ('','','',''); foreach my $item (sort(keys(%prereqs))) { $hasdep .= "'$item',"; $prereq .= "'$prereqs{$item}',"; my $url = &fullurl($item); $hasdeptitle .= "'".&Apache::lonnet::metadata($url,'title')."',"; my $purl = &fullurl($prereqs{$item}); $prereqtitle .= "'".&Apache::lonnet::metadata($purl,'title')."',"; } $hasdep =~ s/,$//; $prereq =~ s/,$//; $hasdeptitle =~ s/,$//; $prereqtitle =~ s/,$//; $prereqjs .= <<"ENDFN"; var hasDeps = Array($hasdep); var preReqs = Array($prereq); var hasDepTitles = Array($hasdeptitle); var preReqTitles = Array($prereqtitle); for (var i=0; i$lt{'hide'} ...'; return; } function hideQuestions(content,title) { document.getElementById(content).style.display = "none"; document.getElementById(title).innerHTML='$lt{'show'} ...'; return; } function setInitialVisibility() { if (document.getElementById('GCI_mandatory_q') == null) { return; } document.getElementById('GCI_mandatory_q').style.display = "none"; document.getElementById('GCI_bin0_q').style.display = "none"; document.getElementById('GCI_bin1_q').style.display = "none"; document.getElementById('GCI_bin2_q').style.display = "none"; document.getElementById('GCI_bin3_q').style.display = "none"; document.getElementById('GCI_bin4_q').style.display = "none"; document.getElementById('GCI_bin5_q').style.display = "none"; document.getElementById('GCI_bin6_q').style.display = "none"; document.getElementById('GCI_bin7_q').style.display = "none"; document.getElementById('GCI_bin8_q').style.display = "none"; document.getElementById('GCI_bin9_q').style.display = "none"; document.getElementById('GCI_bin10_q').style.display = "none"; document.getElementById('GCI_optional_q').style.display = "none"; document.getElementById('GCI_development_q').style.display = "none"; } function countChecked(binname) { var count = 0; for (var i=0; i