--- loncom/interface/lonhelper.pm 2004/10/07 22:12:47 1.88 +++ loncom/interface/lonhelper.pm 2012/10/29 17:38:56 1.189 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # .helper XML handler to implement the LON-CAPA helper # -# $Id: lonhelper.pm,v 1.88 2004/10/07 22:12:47 albertel Exp $ +# $Id: lonhelper.pm,v 1.189 2012/10/29 17:38:56 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,10 +25,6 @@ # # http://www.lon-capa.org/ # -# (Page Handler -# -# (.helper handler -# =pod @@ -85,25 +81,28 @@ State tags are also required to have an human name of the state, and will be displayed as the header on top of the screen for the user. +State tags may also optionally have an attribute "help" which should be +the filename of a help file, this will add a blue ? to the title. + =head2 Example Helper Skeleton An example of the tags so far: - + -Of course this does nothing. In order for the wizard to do something, it is -necessary to put actual elements into the wizard. Documentation for each +Of course this does nothing. In order for the helper to do something, it is +necessary to put actual elements into the helper. Documentation for each of these elements follows. =head1 Creating a Helper With Code, Not XML -In some situations, such as the printing wizard (see lonprintout.pm), +In some situations, such as the printing helper (see lonprintout.pm), writing the helper in XML would be too complicated, because of scope issues or the fact that the code actually outweighs the XML. It is possible to create a helper via code, though it is a little odd. @@ -186,6 +185,12 @@ use Apache::Constants qw(:common); use Apache::File; use Apache::lonxml; use Apache::lonlocal; +use Apache::lonnet; +use Apache::longroup; +use Apache::lonselstudent; + + +use LONCAPA; # Register all the tags with the helper, so the helper can # push and pop them @@ -255,15 +260,15 @@ sub real_handler { my $r = shift; my $uri = shift; if (!defined($uri)) { $uri = $r->uri(); } - $ENV{'request.uri'} = $uri; - my $filename = '/home/httpd/html' . $uri; + $env{'request.uri'} = $uri; + my $filename = $r->dir_config('lonDocRoot').$uri; my $fh = Apache::File->new($filename); my $file; read $fh, $file, 100000000; # Send header, don't cache this page - if ($ENV{'browser.mathml'}) { + if ($env{'browser.mathml'}) { &Apache::loncommon::content_type($r,'text/xml'); } else { &Apache::loncommon::content_type($r,'text/html'); @@ -278,7 +283,7 @@ sub real_handler { my $allowed = $helper->allowedCheck(); if (!$allowed) { - $ENV{'user.error.msg'} = $ENV{'request.uri'}.':'.$helper->{REQUIRED_PRIV}. + $env{'user.error.msg'} = $env{'request.uri'}.':'.$helper->{REQUIRED_PRIV}. ":0:0:Permission denied to access this helper."; return HTTP_NOT_ACCEPTABLE; } @@ -334,7 +339,8 @@ sub start_state { } Apache::lonhelper::state->new($token->[2]{'name'}, - $token->[2]{'title'}); + $token->[2]{'title'}, + $token->[2]{'help'}); return ''; } @@ -363,6 +369,8 @@ use HTML::Entities(); use Apache::loncommon; use Apache::File; use Apache::lonlocal; +use Apache::lonnet; +use LONCAPA; sub new { my $proto = shift; @@ -374,16 +382,16 @@ sub new { # If there is a state from the previous form, use that. If there is no # state, use the start state parameter. - if (defined $ENV{"form.CURRENT_STATE"}) + if (defined $env{"form.CURRENT_STATE"}) { - $self->{STATE} = $ENV{"form.CURRENT_STATE"}; + $self->{STATE} = $env{"form.CURRENT_STATE"}; } else { $self->{STATE} = "START"; } - $self->{TOKEN} = $ENV{'form.TOKEN'}; + $self->{TOKEN} = $env{'form.TOKEN'}; # If a token was passed, we load that in. Otherwise, we need to create a # new storage file # Tried to use standard Tie'd hashes, but you can't seem to take a @@ -416,16 +424,16 @@ sub new { return undef; } # Must create the storage - $self->{TOKEN} = md5_hex($ENV{'user.name'} . $ENV{'user.domain'} . + $self->{TOKEN} = md5_hex($env{'user.name'} . $env{'user.domain'} . time() . rand()); $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN}); } # OK, we now have our persistent storage. - if (defined $ENV{"form.RETURN_PAGE"}) + if (defined $env{"form.RETURN_PAGE"}) { - $self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"}; + $self->{RETURN_PAGE} = $env{"form.RETURN_PAGE"}; } else { @@ -468,9 +476,8 @@ sub _saveVars { sub _varsInFile { my $self = shift; my @vars = (); - for my $key (keys %{$self->{VARS}}) { - push @vars, &Apache::lonnet::escape($key) . '=' . - &Apache::lonnet::escape($self->{VARS}->{$key}); + for my $key (keys(%{$self->{VARS}})) { + push(@vars, &escape($key) . '=' . &escape($self->{VARS}->{$key})); } return join ('&', @vars); } @@ -485,12 +492,12 @@ sub declareVar { $self->{VARS}->{$var} = ''; } - my $envname = 'form.' . $var . '.forminput'; - if (defined($ENV{$envname})) { - if (ref($ENV{$envname})) { - $self->{VARS}->{$var} = join('|||', @{$ENV{$envname}}); + my $envname = 'form.' . $var . '_forminput'; + if (defined($env{$envname})) { + if (ref($env{$envname})) { + $self->{VARS}->{$var} = join('|||', @{$env{$envname}}); } else { - $self->{VARS}->{$var} = $ENV{$envname}; + $self->{VARS}->{$var} = $env{$envname}; } } } @@ -502,7 +509,7 @@ sub allowedCheck { return 1; } - return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $ENV{'request.course.id'}); + return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $env{'request.course.id'}); } sub changeState { @@ -524,7 +531,7 @@ sub process { # Phase 1: Post processing for state of previous screen (which is actually # the "current state" in terms of the helper variables), if it wasn't the # beginning state. - if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq &mt("Next ->")) { + if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next")) { my $prevState = $self->{STATES}{$self->{STATE}}; $prevState->postprocess(); } @@ -564,7 +571,7 @@ sub process { # 4: Render the current state to the screen as an HTML page. sub display { my $self = shift; - + my $footer = shift; my $state = $self->{STATES}{$self->{STATE}}; my $result = ""; @@ -576,87 +583,85 @@ sub display { # Phase 4: Display. my $stateTitle=&mt($state->title()); - my $helperTitle = &mt($self->{TITLE}); - my $bodytag = &Apache::loncommon::bodytag($helperTitle,'',''); - my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"'); - my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"'); - # FIXME: This should be parameterized, not concatenated - Jeremy - my $loncapaHelper = &mt("LON-CAPA Helper:"); + my $stateHelp= $state->help(); + my $browser_searcher_js = + ''; + + # Breadcrumbs + my $brcrum = [{'href' => '', + 'text' => 'Helper'}]; + # FIXME: Dynamically add context sensitive breadcrumbs + # depending on the caller, + # e.g. printing, parametrization, etc. + # FIXME: Add breadcrumbs to reflect current helper state + + $result .= &Apache::loncommon::start_page($self->{TITLE}, + $browser_searcher_js, + {'bread_crumbs' => $brcrum,}); - $result .= < - - - $loncapaHelper: $helperTitle - - $bodytag -HEADER - if (!$state->overrideForm()) { $result.="
"; } - $result .= < -

$stateTitle

-HEADER + my $previous = HTML::Entities::encode(&mt("Back"), '<>&"'); + my $next = HTML::Entities::encode(&mt("Next"), '<>&"'); + # FIXME: This should be parameterized, not concatenated - Jeremy - $result .= "
"; - if (!$state->overrideForm()) { - $result .= $self->_saveVars(); + if (!$state->overrideForm()) { $result.=''; } + if ($stateHelp) { + $stateHelp = &Apache::loncommon::help_open_topic($stateHelp); } - $result .= $state->render(); - $result .= ""; - - # Warning: Copy and pasted from below, because it's too much trouble to - # turn this into a subroutine + # Prepare buttons + my $buttons; if (!$state->overrideForm()) { if ($self->{STATE} ne $self->{START_STATE}) { #$result .= '  '; } + $buttons = '

'; # '

'; if ($self->{DONE}) { my $returnPage = $self->{RETURN_PAGE}; - $result .= "" . &mt("End Helper") . ""; + $buttons .= ''.&mt('End Helper').''; } else { - $result .= ' '; - $result .= ''; + $buttons .= '' + .' ' + .'' + .''; } + $buttons .= '

'; # '
'; } - $result .= "
"; - # Warning: Copy and pasted from above, because it's too much trouble to - # turn this into a subroutine + + $result .= '

'.$stateTitle.$stateHelp.'

'; + +# $result .= '
'; + + # Top buttons + $result .= $buttons; + + # Main content of current helper screen if (!$state->overrideForm()) { - if ($self->{STATE} ne $self->{START_STATE}) { - #$result .= '  '; - } - if ($self->{DONE}) { - my $returnPage = $self->{RETURN_PAGE}; - $result .= "" . &mt('End Helper') . ""; - } - else { - $result .= ' '; - $result .= ''; - } + $result .= $self->_saveVars(); } + $result .= $state->render(); + + # Bottom buttons + $result .= $buttons; + #foreach my $key (keys %{$self->{VARS}}) { # $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; #} - $result .= "
"; +# $result .= ''; $result .= < - - - - FOOTER + $result .= $footer.&Apache::loncommon::end_page(); # Handle writing out the vars to the file my $file = Apache::File->new('>'.$self->{FILENAME}); print $file $self->_varsInFile(); @@ -682,6 +687,7 @@ sub new { $self->{NAME} = shift; $self->{TITLE} = shift; + $self->{HELP} = shift; $self->{ELEMENTS} = []; bless($self, $class); @@ -703,6 +709,11 @@ sub title { return $self->{TITLE}; } +sub help { + my $self = shift; + return $self->{HELP}; +} + sub preprocess { my $self = shift; for my $element (@{$self->{ELEMENTS}}) { @@ -794,7 +805,7 @@ the element. How this value is interpret the element itself, and possibly the settings the element has (such as multichoice vs. single choice for tags). -This is also intended for things like the course initialization wizard, where the +This is also intended for things like the course initialization helper, where the user is setting various parameters. By correctly grabbing current settings and including them into the helper, it allows the user to come back to the helper later and re-execute it, without needing to worry about overwriting @@ -996,6 +1007,9 @@ sub start_message { if (defined($token->[2]{'nextstate'})) { $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'}; } + if (defined($token->[2]{'type'})) { + $paramHash->{TYPE} = $token->[2]{'type'}; + } return ''; } @@ -1011,8 +1025,16 @@ sub end_message { sub render { my $self = shift; - - return &mtn($self->{MESSAGE_TEXT}); + + if ($self->{TYPE} =~ /^\s*warning\s*$/i) { + $self->{MESSAGE_TEXT} = + ''. $self->{MESSAGE_TEXT}.''; + } + if ($self->{TYPE} =~ /^\s*error\s*$/i) { + $self->{MESSAGE_TEXT} = + ''. $self->{MESSAGE_TEXT}.''; + } + return $self->{MESSAGE_TEXT}; } # If a NEXTSTATE was given, switch to it sub postprocess { @@ -1025,6 +1047,175 @@ sub postprocess { } 1; +package Apache::lonhelper::helpicon; + +=pod + +=head1 Elements + +=head2 Element: helpiconX + +Helpicon elements add a help icon at the current location. +Example: + + + General Help + + +In this example will generate a help icon to the Help.hlp url with a +description of 'General Help'. The description is not required and if +left out (Example: only the icon will be +added.) + +=head3 Localization + +The description text will be run through the normalize_string function +and that will be used as a call to &mt. + +=cut + +no strict; +@ISA = ("Apache::lonhelper::element"); +use strict; +use Apache::lonlocal; + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::helpicon', + ('helpicon')); +} + +sub new { + my $ref = Apache::lonhelper::element->new(); + bless($ref); +} + +# CONSTRUCTION: Construct the message element from the XML +sub start_helpicon { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + $paramHash->{HELP_TEXT} = &mtn(&Apache::lonxml::get_all_text('/helpicon', + $parser)); + + $paramHash->{HELP_TEXT} =~s/^\s+//; + $paramHash->{HELP_TEXT} =~s/\s+$//; + + if (defined($token->[2]{'file'})) { + $paramHash->{HELP_FILE} = $token->[2]{'file'}; + } + return ''; +} + +sub end_helpicon { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + Apache::lonhelper::helpicon->new(); + return ''; +} + +sub render { + my $self = shift; + + my $text; + if ( $self->{HELP_TEXT} ne '') { + $text=&mtn($self->{HELP_TEXT}); + } + + return &Apache::loncommon::help_open_topic($self->{HELP_FILE}, + $text); +} +sub postprocess { + my $self = shift; + if (defined($self->{NEXTSTATE})) { + $helper->changeState($self->{NEXTSTATE}); + } + + return 1; +} + +1; + +package Apache::lonhelper::skip; + +=pod + +=head1 Elements + +=head2 Element: skipX + +The tag allows you define conditions under which the current state +should be skipped over and define what state to skip to. + + + + + #some code that decides whether to skip the state or not + + FINISH + + A possibly skipped state + + +=cut + +no strict; +@ISA = ("Apache::lonhelper::element"); +use strict; + +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::skip', + ('skip')); +} + +sub new { + my $ref = Apache::lonhelper::element->new(); + bless($ref); +} + +sub start_skip { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + # let know what text to skip to + $paramHash->{SKIPTAG}='/skip'; + return ''; +} + +sub end_skip { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + Apache::lonhelper::skip->new(); + return ''; +} + +sub render { + my $self = shift; + return ''; +} +# If a NEXTSTATE is set, switch to it +sub preprocess { + my ($self) = @_; + + if (defined($self->{NEXTSTATE})) { + $helper->changeState($self->{NEXTSTATE}); + } + + return 1; +} + +1; + package Apache::lonhelper::choices; =pod @@ -1074,6 +1265,16 @@ will be the state transistioned to if th the choice is not multichoice. This will override the nextstate passed to the parent C tag. + may optionally contain a 'relatedvalue' attribute, which +if present will cause a text entry to appear to the right of the +selection. The value of the relatedvalue attribute is a variable +into which the text entry will be stored e.g.: +[2]{'computer'}; my $human = &mt(&Apache::lonxml::get_all_text('/choice', $parser)); - my $nextstate = $token->[2]{'nextstate'}; - my $evalFlag = $token->[2]{'eval'}; - push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate, - $evalFlag]; + my $nextstate = $token->[2]{'nextstate'}; + my $evalFlag = $token->[2]{'eval'}; + my $relatedVar = $token->[2]{'relatedvalue'}; + my $relatedDefault = $token->[2]{'relateddefault'}; + push @{$paramHash->{CHOICES}}, [&mtn($human), $computer, $nextstate, + $evalFlag, $relatedVar, $relatedDefault]; return ''; } @@ -1189,15 +1393,17 @@ sub render { if ($self->{'multichoice'}) { $result .= < + SCRIPT } @@ -1265,23 +1471,29 @@ BUTTONS foreach my $choice (@{$self->{CHOICES}}) { my $id = &new_id(); $result .= "\n \n"; - $result .= "&"') + $result .= "[1]}) { - $result .= " checked "; + $result .= " checked='checked'"; } - $result .= qq{id="$id"}; + $result .= qq{ id="id$id"}; my $choiceLabel = $choice->[0]; - if ($choice->[4]) { # if we need to evaluate this choice + if ($choice->[3]) { # if we need to evaluate this choice $choiceLabel = "sub { my $helper = shift; my $state = shift;" . $choiceLabel . "}"; $choiceLabel = eval($choiceLabel); $choiceLabel = &$choiceLabel($helper, $self); } - $result .= "/> ".qq{\n"; + $result .= " /> ".qq{"; + if ($choice->[4]) { + $result .=''; + } + $result .= "\n"; } $result .= "\n\n\n"; $result .= $buttons; @@ -1293,7 +1505,8 @@ BUTTONS # given, switch to it sub postprocess { my $self = shift; - my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'}; + my $chosenValue = $env{'form.' . $self->{'variable'} . '_forminput'}; + if (!defined($chosenValue) && !$self->{'allowempty'}) { $self->{ERROR_MSG} = @@ -1301,6 +1514,8 @@ sub postprocess { return 0; } + + if (ref($chosenValue)) { $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue); } @@ -1315,6 +1530,10 @@ sub postprocess { $helper->changeState($choice->[2]); } } + if ($choice->[4]) { + my $varname = $choice->[4]; + $helper->{'VARS'}->{$varname} = $env{'form.'."${varname}_forminput"}; + } } return 1; } @@ -1347,6 +1566,7 @@ no strict; @ISA = ("Apache::lonhelper::element"); use strict; use Apache::lonlocal; +use Apache::lonnet; BEGIN { &Apache::lonhelper::register('Apache::lonhelper::dropdown', @@ -1421,13 +1641,13 @@ sub render { $checkedChoices{$self->{CHOICES}->[0]->[1]} = 1; } - $result .= "\n"; foreach my $choice (@{$self->{CHOICES}}) { $result .= "\n"; } $result .= "\n"; @@ -1447,7 +1667,7 @@ sub render { # given, switch to it sub postprocess { my $self = shift; - my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'}; + my $chosenValue = $env{'form.' . $self->{'variable'} . '_forminput'}; if (!defined($chosenValue) && !$self->{'allowempty'}) { $self->{ERROR_MSG} = "You must choose one or more choices to" . @@ -1506,8 +1726,8 @@ no strict; @ISA = ("Apache::lonhelper::element"); use strict; use Apache::lonlocal; # A localization nightmare - -use Time::localtime; +use Apache::lonnet; +use DateTime; BEGIN { &Apache::lonhelper::register('Apache::lonhelper::date', @@ -1534,6 +1754,7 @@ sub start_date { $paramHash->{'variable'} = $token->[2]{'variable'}; $helper->declareVar($paramHash->{'variable'}); $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'}; + $paramHash->{'anytime'} = $token->[2]{'anytime'}; } sub end_date { @@ -1552,10 +1773,42 @@ sub render { my $var = $self->{'variable'}; my $date; - + + my $time=time; + my ($anytime,$onclick); + + # first check VARS for a valid new value from the user + # then check DEFAULT_VALUE for a valid default time value + # otherwise pick now as reasonably good time + + if (defined($helper->{VARS}{$var}) + && $helper->{VARS}{$var} > 0) { + $date = &get_date_object($helper->{VARS}{$var}); + } elsif (defined($self->{DEFAULT_VALUE})) { + my $valueFunc = eval($self->{DEFAULT_VALUE}); + die('Error in default value code for variable ' . + $self->{'variable'} . ', Perl said: ' . $@) if $@; + $time = &$valueFunc($helper, $self); + if (lc($time) eq 'anytime') { + $anytime=1; + $date = &get_date_object(time); + $date->min(0); + } elsif (defined($time) && $time ne 0) { + $date = &get_date_object($time); + } else { + # leave date undefined so it'll default to now + } + } + + if (!defined($date)) { + $date = &get_date_object(time); + $date->min(0); + } + + if ($anytime) { + $onclick = "onclick=\"javascript:updateCheck(this.form,'${var}anytime',false)\""; + } # Default date: The current hour. - $date = localtime(); - $date->min(0); if (defined $self->{ERROR_MSG}) { $result .= '' . $self->{ERROR_MSG} . '

'; @@ -1563,22 +1816,22 @@ sub render { # Month my $i; - $result .= "\n"; for ($i = 0; $i < 12; $i++) { - if ($i == $date->mon) { - $result .= "\n"; + $result .= &mt($months[$i])."\n"; } $result .= "\n"; # Day - $result .= "\n"; for ($i = 1; $i < 32; $i++) { if ($i == $date->mday) { - $result .= '\n"; for ($i = 1; $i < 12; $i++) { if ($date->hour == $i) { - $result .= "\n"; + $result .= "\n"; } else { $result .= "\n"; } } - $result .= "\n"; for ($i = 13; $i < 24; $i++) { my $printedHour = $i - 12; if ($date->hour == $i) { - $result .= "\n"; + $result .= "\n"; } else { $result .= "\n"; } @@ -1627,14 +1880,16 @@ sub render { $result .= " :\n"; - $result .= "\n"; + my $selected=0; + for my $i ((0,15,30,45,59,undef,0..59)) { my $printedMinute = $i; - if ($i < 10) { + if (defined($i) && $i < 10) { $printedMinute = "0" . $printedMinute; } - if ($date->min == $i) { - $result .= "