--- loncom/interface/lonhtmlcommon.pm 2013/07/15 14:32:45 1.351 +++ loncom/interface/lonhtmlcommon.pm 2018/04/14 02:29:44 1.392 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common html routines # -# $Id: lonhtmlcommon.pm,v 1.351 2013/07/15 14:32:45 bisitz Exp $ +# $Id: lonhtmlcommon.pm,v 1.392 2018/04/14 02:29:44 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -64,15 +64,25 @@ use HTML::Entities(); use LONCAPA qw(:DEFAULT :match); sub java_not_enabled { - return "\n".''. - &mt('The required Java applet could not be started. Please make sure to have Java installed and active in your browser.'). - "\n"; + if (($env{'browser.mobile'}) && ($env{'browser.mobile'} =~ /^ipad|ipod|iphone$/i)) { + return "\n".''. + &mt('The required Java applet could not be started, because Java is not supported by your mobile device.'). + "\n"; + } else { + return "\n".''. + &mt('The required Java applet could not be started. Please make sure to have Java installed and active in your browser.'). + "\n"; + } } sub coursepreflink { my ($text,$category)=@_; if (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) { - return '&"').'">'.$text.''; + my $target =' target="_top"'; + if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) { + $target =''; + } + return '&"').'">'.$text.''; } else { return ''; } @@ -86,7 +96,7 @@ sub raw_href_to_link { sub entity_encode { my ($text)=@_; - return &HTML::Entities::encode($text, '<>&"'); + return &HTML::Entities::encode($text, '\'<>&"'); } sub direct_parm_link { @@ -170,7 +180,7 @@ sub dragmath_js { function mathedit(textarea, doc) { targetEntry = textarea; targetDoc = doc; - newwin = window.open("/adm/dragmath/applet/$popup.html","","width=565,height=500,resizable"); + newwin = window.open("/adm/dragmath/$popup.html","","width=565,height=500,resizable"); } // ]]> @@ -401,7 +411,7 @@ sub textbox { ############################################## ############################################## sub checkbox { - my ($name,$checked,$value) = @_; + my ($name,$checked,$value,$special) = @_; my $Str = ''; return $Str; } @@ -445,10 +455,12 @@ sub radio { &date_setter returns html and javascript for a compact date-setting form. To retrieve values from it, use &get_date_from_form. -Inputs - =over 4 +=item Inputs + +=over + =item $dname The name to prepend to the form elements. @@ -476,21 +488,52 @@ the date/time fields are left empty. =item $state Specifies the initial state of the form elements. Either 'disabled' or empty. -Defaults to empty, which indiciates the form elements are not disabled. +Defaults to empty, which indicates the form elements are not disabled. + +=item $no_hh_mm_ss + +If true, text boxes for hours, minutes and seconds are omitted. + +=item $defhour + +Default value for hours (a default of 0 is used otherwise). + +=item $defmin + +Default value for minutes (a default of 0 is used otherwise). + +=item defsec + +Default value for seconds (a default of 0 is used otherwise). + +=item $nolink + +If true, a "Select calendar" link (to pop-up a calendar) is not displayed +to the right of the items. + +=item $no_mm_ss + +If true, text boxes for minutes and seconds are omitted. + +=item $no_ss + +If true, text boxes for seconds are omitted. =back -Bugs +=item Bugs The method used to restrict user input will fail in the year 2400. +=back + =cut ############################################## ############################################## sub date_setter { my ($formname,$dname,$currentvalue,$special,$includeempty,$state, - $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink) = @_; + $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink,$no_mm_ss,$no_ss) = @_; my $now = time; my $tzname; @@ -499,6 +542,8 @@ sub date_setter { if (! defined($state) || $state ne 'disabled') { $state = ''; + } else { + $state = 'disabled="disabled"'; } if (! defined($no_hh_mm_ss)) { $no_hh_mm_ss = 0; @@ -634,7 +679,7 @@ ENDJS my $minuteselector = qq{}; my $secondselector= qq{}; my $cal_link; - if (!$nolink) { + unless (($nolink) || ($state eq 'disabled')) { $cal_link = qq{}; } # @@ -643,17 +688,26 @@ ENDJS $result .= &mt('[_1] [_2] [_3] ', $monthselector,$dayselector,$yearselector). $tzone; - if (!$nolink) { - $result .= &mt('[_1]Select Date[_2]',$cal_link,''); - } + } elsif ($no_mm_ss) { + $result .= &mt('[_1] [_2] [_3] [_4]', + $monthselector,$dayselector,$yearselector, + $hourselector). + $tzone; + + } elsif ($no_ss) { + $result .= &mt('[_1] [_2] [_3] [_4] [_5]m', + $monthselector,$dayselector,$yearselector, + $hourselector,$minuteselector). + $tzone; + } else { $result .= &mt('[_1] [_2] [_3] [_4] [_5]m [_6]s ', $monthselector,$dayselector,$yearselector, $hourselector,$minuteselector,$secondselector). $tzone; - if (!$nolink) { - $result .= &mt('[_1]Select Date[_2]',$cal_link,''); - } + } + unless (($nolink) || ($state eq 'disabled')) { + $result .= &mt('[_1]Select Date[_2]',$cal_link,''); } $result .= "\n\n"; return $result; @@ -697,7 +751,9 @@ sub build_url { get_date_from_form retrieves the date specified in an &date_setter form. -Inputs: +=over + +=item Inputs: =over 4 @@ -711,6 +767,8 @@ The unix time to use as the default in c =back +=back + Returns: Unix time represented in the form. =cut @@ -882,7 +940,9 @@ sub javascript_docopen { Returns html for a selection box which allows the user to choose the enrollment status of students. The selection box name is 'Status'. -Inputs: +=over + +=item Inputs: $status: the currently selected status. If undefined the value of $env{'form.Status'} is taken. If that is undefined, a value of 'Active' @@ -896,6 +956,8 @@ $size: the size (number of lines) of the $onchange: javascript to use when the value is changed. Enclosed in double quotes, ""s, not single quotes. +=back + Returns: a perl string as described. =cut @@ -943,7 +1005,7 @@ sub StatusOptions { =pod -=item Progess Window Handling Routines +=item Progress Window Handling Routines These routines handle the creation, update, increment, and closure of progress windows. The progress window reports to the user the number @@ -957,25 +1019,34 @@ of items completed and an estimate of th Writes javascript to the client to open a progress window and returns a data structure used for bookkeeping. -Inputs +=over -=over 4 +=item Inputs + +=over =item $r Apache request =item $number_to_do The total number of items being processed. +=item $preamble Optional HTML to display before the progress bar. + =back -Returns a hash containing the progress state data structure. +=back +Returns a hash containing the progress state data structure. +If $number_to_do is zero or null, an indeterminate progress bar will +be used. =item &Update_PrgWin() Updates the text in the progress indicator. Does not increment the count. See &Increment_PrgWin. -Inputs: +=over + +=item Inputs: =over 4 @@ -987,25 +1058,38 @@ Inputs: =back +=back + Returns: none -=item Increment_PrgWin() +=item &Increment_PrgWin() Increment the count of items completed for the progress window by $step or 1 if no step is provided. -Inputs: +=over + +=item Inputs: =over 4 -=item $r Apache request +=item $r -=item $prog_state Pointer to the data structure returned by Create_PrgWin +Apache request + +=item $prog_state + +Pointer to the data structure returned by Create_PrgWin -=item $extraInfo A description of the items being iterated over. Typically -'student'. +=item $extraInfo -=item $step (optional) counter step. Will be set to default 1 if ommited. step must be greater than 0 or empty. +A description of the items being iterated over. Typically 'student'. + +=item $step + +(optional) counter step. Will be set to default 1 if ommited. step must be greater than 0 or empty. + +=back =back @@ -1016,7 +1100,9 @@ Returns: none Closes the progress window. -Inputs: +=over + +=item Inputs: =over 4 @@ -1026,6 +1112,8 @@ Inputs: =back +=back + Returns: none =back @@ -1038,20 +1126,20 @@ Returns: none # Create progress sub Create_PrgWin { - my ($r,$number_to_do)=@_; + my ($r,$number_to_do,$preamble)=@_; my %prog_state; $prog_state{'done'}=0; $prog_state{'firststart'}=&Time::HiRes::time(); $prog_state{'laststart'}=&Time::HiRes::time(); $prog_state{'max'}=$number_to_do; - &Apache::loncommon::LCprogressbar($r); + &Apache::loncommon::LCprogressbar($r,$prog_state{'max'},$preamble); return %prog_state; } # update progress sub Update_PrgWin { my ($r,$prog_state,$displayString)=@_; - &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString); + &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString,$$prog_state{'max'}); $$prog_state{'laststart'}=&Time::HiRes::time(); } @@ -1101,7 +1189,7 @@ sub Increment_PrgWin { if ($$prog_state{'max'}) { $percent=int(100.*$current/$$prog_state{'max'}); } - &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo); + &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo,$$prog_state{'max'}); $$prog_state{'laststart'}=&Time::HiRes::time(); } @@ -1116,9 +1204,9 @@ sub Close_PrgWin { # ------------------------------------------------------- Puts directory header sub crumbs { - my ($uri,$target,$prefix,$form,$skiplast)=@_; + my ($uri,$target,$prefix,$form,$skiplast,$onclick)=@_; # You cannot crumbnify uploaded or adm resources - if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Group Content)'); } + if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Community Content)'); } if ($target) { $target = ' target="'. &Apache::loncommon::escape_single($target).'"'; @@ -1138,13 +1226,19 @@ sub crumbs { } else { $path.='/'; } + if ($path eq '/res/') { + unless (&Apache::lonnet::allowed('bre',$path)) { + $output.="$dir/"; + next; + } + } my $href_path = &HTML::Entities::encode($path,'<>&"'); &Apache::loncommon::inhibit_menu_check(\$href_path); if ($form) { my $href = 'javascript:'.$form.".action='".$href_path."';".$form.'.submit();'; - $output.=qq{$dir/}; + $output.=qq{$dir/}; } else { - $output.=qq{$dir/}; + $output.=qq{$dir/}; } } } else { @@ -1215,13 +1309,13 @@ sub htmlareaheaders { ENDEDITOR } $s.=(< - - + + + - + @@ -1254,6 +1348,68 @@ $(document).ready(function(){ });'; } +sub countdown { + + # Code to put a due date countdown in 'duedatecountdown' span. + # This is currently located in the breadcrumb headers. + # note that the dueDateLayout is internatinoalized below. + # Here document is used to support the substitution into the javascript below. + # ..which unforunately necessitates escaping the $'s in the javascript. + # There are several times of importance + # + # serverDueDate - The absolute time at which the problem expires. + # serverTime - The server's time when the problem finished computing. + # clientTime - The client's time...as close to serverTime as possible. + # The clientTime will be slightly later due to + # 1. The latency between problem computation and + # the first network action. + # 2. The time required between the page load-start and the actual + # initial javascript execution that got clientTime. + # These are used as follows: + # The difference between clientTime and serverTime are used to + # correct for differences in clock settings between the browser's system and the + # server's. + # + # The difference between clientTime and the time at which the ready() method + # starts executing is used to estimate latencies for page load and submission. + # Since this is an estimate, it is doubled. The latency estimate + one minute + # is used to determine when the countdown timer turns red to warn the user + # to think about submitting. + + + my $dueDateLayout = &mt('Due in: {dn} {dl} {hnn}{sep}{mnn}{sep}{snn} [_1]', + ""); + my $early = '- '.&mt('Submit Early').''; + my $pastdue = '- '.&mt('Past Due').''; + return <<"JAVASCRIPT"; + + var documentReadyTime; + +\$(document).ready(function() { + if (typeof(dueDate) != "undefined") { + documentReadyTime = (new Date()).getTime(); + \$("#duedatecountdown").countdown({until: dueDate, compact: true, + layout: "$dueDateLayout", + onTick: function (periods) { + var latencyEstimate = (documentReadyTime - clientTime) * 2; + if(\$.countdown.periodsToSeconds(periods) < (300 + latencyEstimate)) { + \$("#submitearly").html("$early"); + if (\$.countdown.periodsToSeconds(periods) < 1) { + \$("#submitearly").html("$pastdue"); + } + } + if(\$.countdown.periodsToSeconds(periods) < (60 + latencyEstimate)) { + \$(this).css("color", "red"); //Highlight last minute. + } + } + }); + } +}); + +JAVASCRIPT + +} + # ----------------------------------------- Script to activate only some fields sub htmlareaselectactive { @@ -1300,6 +1456,41 @@ sub htmlareaselectactive { } function startRichEditor(id) { + // fix character entities inside + // NOTE: this is not fixing characters inside + // NOTE: < and > inside should fix automatically because there should not be a letter after <. + var ta = document.getElementById(id); + var value = ta.value; + var in_m = false; // in the m element + var in_text = false; // in the text inside the m element + var im = -1; // position of + var it = -1; // position of the text inside + for (var i=0; i if found twice + in_m = true; + in_text = false; + im = i; + it = -1; + } else if (in_m) { + if (!in_text) { + if (value.charAt(i) == ">") { + in_text = true; + it = i+1; + } + } else if (value.substr(i, 4) == "") { + in_m = false; + var text = value.substr(it, i-it); + var l1 = text.length; + text = text.replace(//g, ">"); + var l2 = text.length; + value = value.substr(0, it) + text + "" + value.substr(i+4); + i = i + (l2-l1); + } + } + } + ta.value = value; CKEDITOR.replace(id, { customConfig: "/ckeditor/loncapaconfig.js", @@ -1311,6 +1502,68 @@ sub htmlareaselectactive { function destroyRichEditor(id) { CKEDITOR.instances[id].destroy(); + // replace character entities < and > in and + // and "&fctname(" by "&fctname(" + // and the quotes inside functions: "&fct(1, "a")" -> "&fct(1, "a")" + var ta = document.getElementById(id); + var value = ta.value; + var in_element = false; // in the m or chem element + var tagname = ""; // m or chem + var in_text = false; // in the text inside the element + var im = -1; // position of start tag + var it = -1; // position of the text inside + for (var i=0; i") { + in_text = true; + it = i+1; + } + } else if (value.substr(i, 3+tagname.length) == "") { + in_element = false; + var text = value.substr(it, i-it); + var l1 = text.length; + text = text.replace(/</g, "<"); + text = text.replace(/>/g, ">"); + var l2 = text.length; + value = value.substr(0, it) + text + value.substr(i); + i = i + (l2-l1); + } + } + } + // fix function names + value = value.replace(/&([a-zA-Z_]+)\(/g, "&$1("); + // fix quotes in functions + var pos_next_fct = value.search(/&[a-zA-Z_]+\(/); + var depth = 0; + for (var i=0; i 0) { + if (value.charAt(i) == ")") + depth--; + else if (value.substr(i, 6) == """) + value = value.substr(0, i) + "\"" + value.substr(i+6); + } + } + // replace the text value + ta.value = value; } function editorHandler(event) { @@ -1380,62 +1633,11 @@ sub htmlareaselectactive { }); '; - $output .= &color_picker; + $output .= &color_picker(); - # Code to put a due date countdown in 'duedatecountdown' span. - # This is currently located in the breadcrumb headers. - # note that the dueDateLayout is internatinoalized below. - # Here document is used to support the substitution into the javascript below. - # ..which unforunately necessitates escaping the $'s in the javascript. - # There are several times of importance - # - # serverDueDate - The absolute time at which the problem expires. - # serverTime - The server's time when the problem finished computing. - # clientTime - The client's time...as close to serverTime as possible. - # The clientTime will be slightly later due to - # 1. The latency between problem computation and - # the first network action. - # 2. The time required between the page load-start and the actual - # initial javascript execution that got clientTime. - # These are used as follows: - # The difference between clientTime and serverTime are used to - # correct for differences in clock settings between the browser's system and the - # server's. - # - # The difference between clientTime and the time at which the ready() method - # starts executing is used to estimate latencies for page load and submission. - # Since this is an estimate, it is doubled. The latency estimate + one minute - # is used to determine when the countdown timer turns red to warn the user - # to think about submitting. - - my $dueDateLayout = &mt('Due in: {dn} {dl} {hnn}{sep}{mnn}{sep}{snn} [_1]', - ""); - my $early = '- '.&mt('Submit Early').''; - my $pastdue = '- '.&mt('Past Due').''; - $output .= <'/adm/menu', title =>'Go to main menu', - target =>'_top', + target =>$target, text =>$description, no_mt =>$no_mt_descr, }; if($last) { @@ -1700,11 +1949,15 @@ returns: nothing my $links; if ((&show_return_link) && (!$CourseBreadcrumbs) && (ref($last) eq 'HASH')) { my $alttext = &mt('Go Back'); + my $hashref = { href => '/adm/flip?postdata=return:', + title => &mt('Back to most recent content resource'), + class => 'LC_menubuttons_link', + }; + if ($env{'request.noversionuri'} eq '/adm/searchcat') { + $hashref->{'target'} = '_top'; + } $links=&htmltag( 'a',''.$alttext.'', - { href => '/adm/flip?postdata=return:', - title => &mt('Back to most recent content resource'), - class => 'LC_menubuttons_link', - }); + $hashref); $links=&htmltag('li',$links); } $links.= join "", @@ -1754,6 +2007,10 @@ returns: nothing $component_help, $faq,$bug); } + if ($topic_help && $topic_help_text) { + $icons .= ' '.&Apache::loncommon::help_open_topic($topic_help,&mt($topic_help_text),'', + undef,600); + } # @@ -1766,7 +2023,7 @@ returns: nothing } - if ($component) { + if (($component) || ($topic_help && $topic_help_text)) { $links = &htmltag('span', ( $no_mt ? $component : mt($component) ). ( $icons ? $icons : '' ), @@ -1821,7 +2078,9 @@ Adds $html to $category of the breadcrum $html is usually a link to a page that invokes a function on the currently displayed data (e.g. print when viewing a problem) -Currently there are 3 possible values for $category: +=over + +=item Currently there are 3 possible values for $category: =over @@ -1835,7 +2094,9 @@ remaining items in right of breadcrumbs advanced tools shown in a separate box below breadcrumbs line =back - + +=back + returns: nothing =cut @@ -1869,13 +2130,32 @@ returns: nothing undef(%tools); } +=item ¤t_breadcrumb_tools() + +returns: a hash containing the current breadcrumb tools. + +=cut + + sub current_breadcrumb_tools { + return %tools; + } + =item &render_tools(\$breadcrumbs) Creates html for breadcrumb tools (categories navigation and tools) and inserts \$breadcrumbs at the correct position. -input: \$breadcrumbs - a reference to the string containing prepared -breadcrumbs. +=over + +=item input: + +=over + +=item \$breadcrumbs - a reference to the string containing prepared breadcrumbs. + +=back + +=back returns: nothing @@ -1901,8 +2181,17 @@ returns: nothing Creates html for advanced tools (category advtools) and inserts \$breadcrumbs at the correct position. -input: \$breadcrumbs - a reference to the string containing prepared -breadcrumbs (after render_tools call). +=over + +=item input: + +=over + +=item \$breadcrumbs - a reference to the string containing prepared breadcrumbs (after render_tools call). + +=back + +=back returns: nothing @@ -1935,11 +2224,11 @@ sub docs_breadcrumbs { my $foldername=shift(@folders); if ($folderpath) {$folderpath.='&';} $folderpath.=$folder.'&'.$foldername; - my $url; + my $url = $env{'request.use_absolute'}; if ($allowed) { - $url = '/adm/coursedocs?folderpath='; + $url .= '/adm/coursedocs?folderpath='; } else { - $url = '/adm/supplemental?folderpath='; + $url .= '/adm/supplemental?folderpath='; } $url .= &escape($folderpath); my $name=&unescape($foldername); @@ -2231,7 +2520,7 @@ sub course_selection { if ($totcodes > 0) { my $numtitles = @$codetitles; if ($numtitles > 0) { - $output .= '
'; + $output .= '
'; $output .= '
'.$$codetitles[0].'
'."\n". '