--- loncom/interface/lonhtmlcommon.pm 2004/01/26 19:55:44 1.41 +++ loncom/interface/lonhtmlcommon.pm 2012/04/18 14:13:29 1.310 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common html routines # -# $Id: lonhtmlcommon.pm,v 1.41 2004/01/26 19:55:44 www Exp $ +# $Id: lonhtmlcommon.pm,v 1.310 2012/04/18 14:13:29 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -55,17 +55,180 @@ html. package Apache::lonhtmlcommon; +use strict; use Time::Local; +use Time::HiRes; use Apache::lonlocal; -use strict; +use Apache::lonnet; +use HTML::Entities(); +use LONCAPA; + +sub java_not_enabled { + return "\n".'<span class="LC_error">'. + &mt('The required Java applet could not be started. Please make sure to have Java installed and active in your browser.'). + "</span>\n"; +} + +sub coursepreflink { + my ($text,$category)=@_; + if (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) { + return '<a target="_top" href="'.&HTML::Entities::encode("/adm/courseprefs?phase=display&actions=$category",'<>&"').'"><span class="LC_setting">'.$text.'</span></a>'; + } else { + return ''; + } +} + +sub raw_href_to_link { + my ($message)=@_; + $message=~s/(https?\:\/\/[^\s\'\"\<]+)([\s\<]|$)/<a href="$1"><tt>$1<\/tt><\/a>$2/gi; + return $message; +} + +sub entity_encode { + my ($text)=@_; + return &HTML::Entities::encode($text, '<>&"'); +} + +sub direct_parm_link { + my ($linktext,$symb,$filter,$part,$target)=@_; + $symb=&entity_encode($symb); + $filter=&entity_encode($filter); + $part=&entity_encode($part); + if (($symb) && (&Apache::lonnet::allowed('opa')) && ($target ne 'tex')) { + return "<a target='_top' href='/adm/parmset?symb=$symb&filter=$filter&part=$part'><span class='LC_setting'>$linktext</span></a>"; + } else { + return $linktext; + } +} +############################################## +############################################## + +=item &confirm_success() + +Successful completion of an operation message + +=cut + +sub confirm_success { + my ($message,$failure)=@_; + if ($failure) { + return '<span class="LC_error" style="font-size: inherit;">'."\n" + .'<img src="/adm/lonIcons/navmap.wrong.gif" alt="'.&mt('Error').'" /> '."\n" + .$message."\n" + .'</span>'."\n"; + } else { + return '<span class="LC_success">'."\n" + .'<img src="/adm/lonIcons/navmap.correct.gif" alt="'.&mt('OK').'" /> '."\n" + .$message."\n" + .'</span>'."\n"; + } +} + +############################################## +############################################## + +=pod + +=item &dragmath_button() + +Creates a button that launches a dragmath popup-window, in which an +expression can be edited and pasted as LaTeX into a specified textarea. + + textarea - Name of the textarea to edit. + helpicon - If true, show a help icon to the right of the button. + +=cut + +sub dragmath_button { + my ($textarea,$helpicon) = @_; + my $help_text; + if ($helpicon) { + $help_text = &Apache::loncommon::help_open_topic('Authoring_Math_Editor',undef,undef,undef,undef,'mathhelpicon_'.$textarea); + } + my $buttontext=&mt('Edit Math'); + return <<ENDDRAGMATH; + <input type="button" value="$buttontext" onclick="javascript:mathedit('$textarea',document)" />$help_text +ENDDRAGMATH +} + +############################################## + +=pod + +=item &dragmath_js() + +Javascript used to open pop-up window containing dragmath applet which +can be used to paste LaTeX into a textarea. + +=cut + +sub dragmath_js { + my ($popup) = @_; + return <<ENDDRAGMATHJS; + <script type="text/javascript"> + // <![CDATA[ + function mathedit(textarea, doc) { + targetEntry = textarea; + targetDoc = doc; + newwin = window.open("/adm/dragmath/applet/$popup.html","","width=565,height=500,resizable"); + } + // ]]> + </script> + +ENDDRAGMATHJS +} + +############################################## +############################################## + +=pod + +=item &dependencies_button() + +Creates a button that launches a popup-window, in which dependencies +for the web page in the main window can be added to, replaced or deleted. + +=cut + +sub dependencies_button { + my $buttontext=&mt('Manage Dependencies'); + return <<"END"; + <input type="button" value="$buttontext" onclick="javascript:dependencycheck();" /> +END +} + +############################################## + +=pod +=item &dependencycheck_js() + +Javascript used to open pop-up window containing interface to manage +dependencies for a web page uploaded diretcly to a course. + +=cut + +sub dependencycheck_js { + my ($symb,$title) = @_; + my $link = '/adm/dependencies?symb='.&HTML::Entities::encode($symb,'<>&"'). + '&title='.&HTML::Entities::encode($title,'<>&"'); + return <<ENDJS; + <script type="text/javascript"> + // <![CDATA[ + function dependencycheck() { + depwin = window.open("$link","","width=750,height=500,resizable,scrollbars=yes"); + } + // ]]> + </script> +ENDJS +} ############################################## ############################################## =pod -=item authorbombs +=item &authorbombs() =cut @@ -75,12 +238,12 @@ use strict; sub authorbombs { my $url=shift; $url=&Apache::lonnet::declutter($url); - my ($udom,$uname)=($url=~/^(\w+)\/(\w+)\//); + my ($udom,$uname)=($url=~m{^($LONCAPA::domain_re)/($LONCAPA::username_re)/}); my %bombs=&Apache::lonmsg::all_url_author_res_msg($uname,$udom); - foreach (keys %bombs) { - if ($_=~/^$udom\/$uname\//) { + foreach my $bomb (keys(%bombs)) { + if ($bomb =~ /^$udom\/$uname\//) { return '<a href="/adm/bombs/'.$url. - '"><img src="/adm/lonMisc/bomb.gif" border="0" /></a>'. + '"><img src="'.&Apache::loncommon::lonhttpdurl('/adm/lonMisc/bomb.gif').'" alt="'.&mt('Bomb').'" border="0" /></a>'. &Apache::loncommon::help_open_topic('About_Bombs'); } } @@ -92,41 +255,58 @@ sub authorbombs { sub recent_filename { my $area=shift; - return 'nohist_recent_'.&Apache::lonnet::escape($area); + return 'nohist_recent_'.&escape($area); } sub store_recent { - my ($area,$name,$value)=@_; + my ($area,$name,$value,$freeze)=@_; my $file=&recent_filename($area); my %recent=&Apache::lonnet::dump($file); - if (scalar(keys(%recent))>10) { + if (scalar(keys(%recent))>20) { # remove oldest value - my $oldest=time; + my $oldest=time(); my $delkey=''; - foreach (keys %recent) { - my $thistime=(split(/\&/,$recent{$_}))[0]; - if ($thistime<$oldest) { + foreach my $item (keys(%recent)) { + my $thistime=(split(/\&/,$recent{$item}))[0]; + if (($thistime ne "always_include") && ($thistime<$oldest)) { $oldest=$thistime; - $delkey=$_; + $delkey=$item; } } &Apache::lonnet::del($file,[$delkey]); } # store new value + my $timestamp; + if ($freeze) { + $timestamp = "always_include"; + } else { + $timestamp = time(); + } &Apache::lonnet::put($file,{ $name => - time.'&'.&Apache::lonnet::escape($value) }); + $timestamp.'&'.&escape($value) }); +} + +sub remove_recent { + my ($area,$names)=@_; + my $file=&recent_filename($area); + return &Apache::lonnet::del($file,$names); } sub select_recent { my ($area,$fieldname,$event)=@_; my %recent=&Apache::lonnet::dump(&recent_filename($area)); my $return="\n<select name='$fieldname'". - ($event?" onChange='$event'":''). + ($event?" onchange='$event'":''). ">\n<option value=''>--- ".&mt('Recent')." ---</option>"; - foreach (sort keys %recent) { - unless ($_=~/^error\:/) { - $return.="\n<option value='$_'>". - &Apache::lonnet::unescape((split(/\&/,$recent{$_}))[1]). + foreach my $value (sort(keys(%recent))) { + unless ($value =~/^error\:/) { + my $escaped = &Apache::loncommon::escape_url($value); + &Apache::loncommon::inhibit_menu_check(\$escaped); + if ($area eq 'residx') { + next if ((!&Apache::lonnet::allowed('bre',$value)) && (!&Apache::lonnet::allowed('bro',$value))); + } + $return.="\n<option value='$escaped'>". + &unescape((split(/\&/,$recent{$value}))[1]). '</option>'; } } @@ -134,10 +314,55 @@ sub select_recent { return $return; } +sub get_recent { + my ($area, $n) = @_; + my %recent=&Apache::lonnet::dump(&recent_filename($area)); + +# Create hash with key as time and recent as value +# Begin filling return_hash with any 'always_include' option + my %time_hash = (); + my %return_hash = (); + foreach my $item (keys(%recent)) { + my ($thistime,$thisvalue)=(split(/\&/,$recent{$item})); + if ($thistime eq 'always_include') { + $return_hash{$item} = &unescape($thisvalue); + $n--; + } else { + $time_hash{$thistime} = $item; + } + } + +# Sort by decreasing time and return key value pairs + my $idx = 1; + foreach my $item (reverse(sort(keys(%time_hash)))) { + $return_hash{$time_hash{$item}} = + &unescape((split(/\&/,$recent{$time_hash{$item}}))[1]); + if ($n && ($idx++ >= $n)) {last;} + } + + return %return_hash; +} + +sub get_recent_frozen { + my ($area) = @_; + my %recent=&Apache::lonnet::dump(&recent_filename($area)); + +# Create hash with all 'frozen' items + my %return_hash = (); + foreach my $item (keys(%recent)) { + my ($thistime,$thisvalue)=(split(/\&/,$recent{$item})); + if ($thistime eq 'always_include') { + $return_hash{$item} = &unescape($thisvalue); + } + } + return %return_hash; +} + + =pod -=item textbox +=item &textbox() =cut @@ -146,6 +371,7 @@ sub select_recent { sub textbox { my ($name,$value,$size,$special) = @_; $size = 40 if (! defined($size)); + $value = &HTML::Entities::encode($value,'<>&"'); my $Str = '<input type="text" name="'.$name.'" size="'.$size.'" '. 'value="'.$value.'" '.$special.' />'; return $Str; @@ -156,30 +382,56 @@ sub textbox { =pod -=item checkbox +=item &checkbox() =cut ############################################## ############################################## sub checkbox { - my ($name,$value) = @_; - my $Str = '<input type="checkbox" name="'.$name.'"'. - ($value?' checked="1"':'').' />'; + my ($name,$checked,$value) = @_; + my $Str = '<input type="checkbox" name="'.$name.'" '; + if (defined($value)) { + $Str .= 'value="'.$value.'"'; + } + if ($checked) { + $Str .= ' checked="checked"'; + } + $Str .= ' />'; return $Str; } +=pod + +=item &radiobutton() + +=cut + +############################################## +############################################## +sub radio { + my ($name,$checked,$value) = @_; + my $Str = '<input type="radio" name="'.$name.'" '; + if (defined($value)) { + $Str .= 'value="'.$value.'"'; + } + if ($checked eq $value) { + $Str .= ' checked="checked"'; + } + $Str .= ' />'; + return $Str; +} ############################################## ############################################## =pod -=item &date_setter +=item &date_setter() &date_setter returns html and javascript for a compact date-setting form. -To retrieve values from it, use &get_date_from_form(). +To retrieve values from it, use &get_date_from_form. Inputs @@ -195,7 +447,8 @@ dname_hour, dname_min, and dname_sec. The current setting for this time parameter. A unix format time (time in seconds since the beginning of Jan 1st, 1970, GMT. -An undefined value is taken to indicate the value is the current time. +An undefined value is taken to indicate the value is the current time +unless it is requested to leave it empty. See $includeempty. Also, to be explicit, a value of 'now' also indicates the current time. =item $special @@ -203,6 +456,16 @@ Also, to be explicit, a value of 'now' a Additional html/javascript to be associated with each element in the date_setter. See lonparmset for example usage. +=item $includeempty + +If it is set (true) and no date/time value is provided, +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. + =back Bugs @@ -214,24 +477,44 @@ The method used to restrict user input w ############################################## ############################################## sub date_setter { - my ($formname,$dname,$currentvalue,$special,$includeempty) = @_; - if (! defined($currentvalue) || $currentvalue eq 'now') { - unless ($includeempty) { - $currentvalue = time; - } else { - $currentvalue = 0; - } + my ($formname,$dname,$currentvalue,$special,$includeempty,$state, + $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink) = @_; + my $now = time; + + my $tzname; + my ($sec,$min,$hour,$mday,$month,$year) = ('', '', undef,''.''.''); + #other potentially useful values: wkday,yrday,is_daylight_savings + + if (! defined($state) || $state ne 'disabled') { + $state = ''; + } + if (! defined($no_hh_mm_ss)) { + $no_hh_mm_ss = 0; } - # other potentially useful values: wkday,yrday,is_daylight_savings - my ($sec,$min,$hour,$mday,$month,$year)=('','','','','',''); + if ($currentvalue eq 'now') { + $currentvalue = $now; + } + + # Default value: Set empty date field to current time + # unless empty inclusion is requested + if ((!$includeempty) && (!$currentvalue)) { + $currentvalue = $now; + } + # Do we have a date? Split it! if ($currentvalue) { - ($sec,$min,$hour,$mday,$month,$year,undef,undef,undef) = - localtime($currentvalue); - $year += 1900; + ($tzname,$sec,$min,$hour,$mday,$month,$year) = &get_timedates($currentvalue); + + #No values provided for hour, min, sec? Use default 0 + if (($defhour) || ($defmin) || ($defsec)) { + $sec = ($defsec ? $defsec : 0); + $min = ($defmin ? $defmin : 0); + $hour = ($defhour ? $defhour : 0); + } } my $result = "\n<!-- $dname date setting form -->\n"; $result .= <<ENDJS; -<script language="Javascript"> +<script type="text/javascript"> +// <![CDATA[ function $dname\_checkday() { var day = document.$formname.$dname\_day.value; var month = document.$formname.$dname\_month.value; @@ -262,45 +545,67 @@ sub date_setter { document.$formname.$dname\_day.value = 30; } } + + function $dname\_disable() { + document.$formname.$dname\_month.disabled=true; + document.$formname.$dname\_day.disabled=true; + document.$formname.$dname\_year.disabled=true; + document.$formname.$dname\_hour.disabled=true; + document.$formname.$dname\_minute.disabled=true; + document.$formname.$dname\_second.disabled=true; + } + + function $dname\_enable() { + document.$formname.$dname\_month.disabled=false; + document.$formname.$dname\_day.disabled=false; + document.$formname.$dname\_year.disabled=false; + document.$formname.$dname\_hour.disabled=false; + document.$formname.$dname\_minute.disabled=false; + document.$formname.$dname\_second.disabled=false; + } function $dname\_opencalendar() { - var calwin=window.open( + if (! document.$formname.$dname\_month.disabled) { + var calwin=window.open( "/adm/announcements?pickdate=yes&formname=$formname&element=$dname&month="+ document.$formname.$dname\_month.value+"&year="+ document.$formname.$dname\_year.value, "LONCAPAcal", "height=350,width=350,scrollbars=yes,resizable=yes,menubar=no"); + } } +// ]]> </script> ENDJS - $result .= " <nobr><select name=\"$dname\_month\" ".$special.' '. - "onChange=\"javascript:$dname\_checkday()\" >\n"; + $result .= ' <span class="LC_nobreak">'; + my $monthselector = qq{<select name="$dname\_month" $special $state onchange="javascript:$dname\_checkday()" >}; + # Month my @Months = qw/January February March April May June July August September October November December/; # Pad @Months with a bogus value to make indexing easier unshift(@Months,'If you can read this an error occurred'); - if ($includeempty) { $result.="<option value=''></option>"; } + if ($includeempty) { $monthselector.="<option value=''></option>"; } for(my $m = 1;$m <=$#Months;$m++) { - $result .= " <option value=\"$m\" "; - $result .= "selected " if ($m-1 eq $month); - $result .= "> ".&mt($Months[$m])." </option>\n"; - } - $result .= " </select>\n"; - $result .= " <input type=\"text\" name=\"$dname\_day\" ". - "value=\"$mday\" size=\"3\" ".$special.' '. - "onChange=\"javascript:$dname\_checkday()\" />\n"; - $result .= " <input type=\"year\" name=\"$dname\_year\" ". - "value=\"$year\" size=\"5\" ".$special.' '. - "onChange=\"javascript:$dname\_checkday()\" />\n"; - $result .= " "; - $result .= " <select name=\"$dname\_hour\" ".$special." >\n"; - if ($includeempty) { $result.="<option value=''></option>"; } + $monthselector .= qq{ <option value="$m"}; + $monthselector .= ' selected="selected"' if ($m-1 eq $month); + $monthselector .= '> '.&mt($Months[$m]).' </option>'."\n"; + } + $monthselector.= ' </select>'; + # Day + my $dayselector = qq{<input type="text" name="$dname\_day" $state value="$mday" size="3" $special onchange="javascript:$dname\_checkday()" />}; + # Year + my $yearselector = qq{<input type="text" name="$dname\_year" $state value="$year" size="5" $special onchange="javascript:$dname\_checkday()" />}; + # + my $hourselector = qq{<select name="$dname\_hour" $special $state >}; + if ($includeempty) { + $hourselector.=qq{<option value=''></option>}; + } for (my $h = 0;$h<24;$h++) { - $result .= " <option value=\"$h\" "; - $result .= "selected " if ($hour == $h); - $result .= "> "; - my $timest=''; + $hourselector .= qq{<option value="$h"}; + $hourselector .= ' selected="selected"' if (defined($hour) && $hour == $h); + $hourselector .= ">"; + my $timest=''; if ($h == 0) { $timest .= "12 am"; } elsif($h == 12) { @@ -310,25 +615,73 @@ ENDJS } else { $timest .= $h-12 ." pm"; } - $timest=&mt($timest); - $result .= $timest." </option>\n"; - } - $result .= " </select>\n"; - $result .= " <input type=\"text\" name=\"$dname\_minute\" ".$special.' '. - "value=\"$min\" size=\"3\" /> m\n"; - $result .= " <input type=\"text\" name=\"$dname\_second\" ".$special.' '. - "value=\"$sec\" size=\"3\" /> s\n"; - $result .= "<a href=\"javascript:$dname\_opencalendar()\">". - &mt('Select Date')."</a></nobr>\n<!-- end $dname date setting form -->\n"; + $timest=&mt($timest); + $hourselector .= $timest." </option>\n"; + } + $hourselector .= " </select>\n"; + my $minuteselector = qq{<input type="text" name="$dname\_minute" $special $state value="$min" size="3" />}; + my $secondselector= qq{<input type="text" name="$dname\_second" $special $state value="$sec" size="3" />}; + my $cal_link; + if (!$nolink) { + $cal_link = qq{<a href="javascript:$dname\_opencalendar()">}; + } + # + my $tzone = ' '.$tzname.' '; + if ($no_hh_mm_ss) { + $result .= &mt('[_1] [_2] [_3] ', + $monthselector,$dayselector,$yearselector). + $tzone; + if (!$nolink) { + $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>'); + } + } 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,'</a>'); + } + } + $result .= "</span>\n<!-- end $dname date setting form -->\n"; return $result; } +sub get_timedates { + my ($epoch) = @_; + my $dt = DateTime->from_epoch(epoch => $epoch) + ->set_time_zone(&Apache::lonlocal::gettimezone()); + my $tzname = $dt->time_zone_short_name(); + my $sec = $dt->second; + my $min = $dt->minute; + my $hour = $dt->hour; + my $mday = $dt->day; + my $month = $dt->month; + if ($month) { + $month --; + } + my $year = $dt->year; + return ($tzname,$sec,$min,$hour,$mday,$month,$year); +} + +sub build_url { + my ($base, $fields)=@_; + my $url; + $url = $base.'?'; + foreach my $key (keys(%$fields)) { + $url.=&escape($key).'='.&escape($$fields{$key}).'&'; + } + $url =~ s/&$//; + return $url; +} + + ############################################## ############################################## =pod -=item &get_date_from_form +=item &get_date_from_form() get_date_from_form retrieves the date specified in an &date_setter form. @@ -338,7 +691,7 @@ Inputs: =item $dname -The name passed to &datesetter, which prefixes the form elements. +The name passed to &date_setter, which prefixes the form elements. =item $defaulttime @@ -356,47 +709,68 @@ sub get_date_from_form { my ($dname) = @_; my ($sec,$min,$hour,$day,$month,$year); # - if (defined($ENV{'form.'.$dname.'_second'})) { - my $tmpsec = $ENV{'form.'.$dname.'_second'}; + if (defined($env{'form.'.$dname.'_second'})) { + my $tmpsec = $env{'form.'.$dname.'_second'}; if (($tmpsec =~ /^\d+$/) && ($tmpsec >= 0) && ($tmpsec < 60)) { $sec = $tmpsec; } + if (!defined($tmpsec) || $tmpsec eq '') { $sec = 0; } + } else { + $sec = 0; } - if (defined($ENV{'form.'.$dname.'_minute'})) { - my $tmpmin = $ENV{'form.'.$dname.'_minute'}; + if (defined($env{'form.'.$dname.'_minute'})) { + my $tmpmin = $env{'form.'.$dname.'_minute'}; if (($tmpmin =~ /^\d+$/) && ($tmpmin >= 0) && ($tmpmin < 60)) { $min = $tmpmin; } + if (!defined($tmpmin) || $tmpmin eq '') { $min = 0; } + } else { + $min = 0; } - if (defined($ENV{'form.'.$dname.'_hour'})) { - my $tmphour = $ENV{'form.'.$dname.'_hour'}; + if (defined($env{'form.'.$dname.'_hour'})) { + my $tmphour = $env{'form.'.$dname.'_hour'}; if (($tmphour =~ /^\d+$/) && ($tmphour >= 0) && ($tmphour < 24)) { $hour = $tmphour; } + } else { + $hour = 0; } - if (defined($ENV{'form.'.$dname.'_day'})) { - my $tmpday = $ENV{'form.'.$dname.'_day'}; + if (defined($env{'form.'.$dname.'_day'})) { + my $tmpday = $env{'form.'.$dname.'_day'}; if (($tmpday =~ /^\d+$/) && ($tmpday > 0) && ($tmpday < 32)) { $day = $tmpday; } } - if (defined($ENV{'form.'.$dname.'_month'})) { - my $tmpmonth = $ENV{'form.'.$dname.'_month'}; + if (defined($env{'form.'.$dname.'_month'})) { + my $tmpmonth = $env{'form.'.$dname.'_month'}; if (($tmpmonth =~ /^\d+$/) && ($tmpmonth > 0) && ($tmpmonth < 13)) { - $month = $tmpmonth - 1; + $month = $tmpmonth; } } - if (defined($ENV{'form.'.$dname.'_year'})) { - my $tmpyear = $ENV{'form.'.$dname.'_year'}; - if (($tmpyear =~ /^\d+$/) && ($tmpyear > 1900)) { - $year = $tmpyear - 1900; + if (defined($env{'form.'.$dname.'_year'})) { + my $tmpyear = $env{'form.'.$dname.'_year'}; + if (($tmpyear =~ /^\d+$/) && ($tmpyear >= 1970)) { + $year = $tmpyear; } } - if (($year<70) || ($year>137)) { return undef; } + if (($year<1970) || ($year>2037)) { return undef; } if (defined($sec) && defined($min) && defined($hour) && - defined($day) && defined($month) && defined($year) && - eval(&timelocal($sec,$min,$hour,$day,$month,$year))) { - return &timelocal($sec,$min,$hour,$day,$month,$year); + defined($day) && defined($month) && defined($year)) { + my $timezone = &Apache::lonlocal::gettimezone(); + my $dt = DateTime->new( year => $year, + month => $month, + day => $day, + hour => $hour, + minute => $min, + second => $sec, + time_zone => $timezone, + ); + my $epoch_time = $dt->epoch; + if ($epoch_time ne '') { + return $epoch_time; + } else { + return undef; + } } else { return undef; } @@ -418,12 +792,13 @@ parameter setting wizard. ############################################## sub pjump_javascript_definition { my $Str = <<END; - function pjump(type,dis,value,marker,ret,call) { - parmwin=window.open("/adm/rat/parameter.html?type="+escape(type) + function pjump(type,dis,value,marker,ret,call,hour,min,sec) { + openMyModal("/adm/rat/parameter.html?type="+escape(type) +"&value="+escape(value)+"&marker="+escape(marker) +"&return="+escape(ret) - +"&call="+escape(call)+"&name="+escape(dis),"LONCAPAparms", - "height=350,width=350,scrollbars=no,menubar=no"); + +"&call="+escape(call)+"&name="+escape(dis) + +"&defhour="+escape(hour)+"&defmin="+escape(min) + +"&defsec="+escape(sec)+"&modal=1",350,350,'no'); } END return $Str; @@ -449,8 +824,8 @@ sub javascript_nothing { my $nothing = "''"; my $user_browser; my $user_os; - $user_browser = $ENV{'browser.type'} if (exists($ENV{'browser.type'})); - $user_os = $ENV{'browser.os'} if (exists($ENV{'browser.os'})); + $user_browser = $env{'browser.type'} if (exists($env{'browser.type'})); + $user_os = $env{'browser.os'} if (exists($env{'browser.os'})); if (! defined($user_browser) || ! defined($user_os)) { (undef,$user_browser,undef,undef,undef,$user_os) = &Apache::loncommon::decode_user_agent(); @@ -461,6 +836,29 @@ sub javascript_nothing { return $nothing; } +############################################## +############################################## +sub javascript_docopen { + my ($mimetype) = @_; + $mimetype ||= 'text/html'; + # safari does not understand document.open() and loads "text/html" + my $nothing = "''"; + my $user_browser; + my $user_os; + $user_browser = $env{'browser.type'} if (exists($env{'browser.type'})); + $user_os = $env{'browser.os'} if (exists($env{'browser.os'})); + if (! defined($user_browser) || ! defined($user_os)) { + (undef,$user_browser,undef,undef,undef,$user_os) = + &Apache::loncommon::decode_user_agent(); + } + if ($user_browser eq 'safari' && $user_os =~ 'mac') { + $nothing = "document.clear()"; + } else { + $nothing = "document.open('$mimetype','replace')"; + } + return $nothing; +} + ############################################## ############################################## @@ -475,7 +873,7 @@ enrollment status of students. The sele Inputs: $status: the currently selected status. If undefined the value of -$ENV{'form.Status'} is taken. If that is undefined, a value of 'Active' +$env{'form.Status'} is taken. If that is undefined, a value of 'Active' is used. $formname: The name of the form. If defined the onchange attribute of @@ -493,23 +891,18 @@ Returns: a perl string as described. ############################################## ############################################## sub StatusOptions { - my ($status, $formName,$size,$onchange)=@_; + my ($status, $formName,$size,$onchange,$mult)=@_; $size = 1 if (!defined($size)); if (! defined($status)) { $status = 'Active'; - $status = $ENV{'form.Status'} if (exists($ENV{'form.Status'})); + $status = $env{'form.Status'} if (exists($env{'form.Status'})); } - my $OpSel1 = ''; - my $OpSel2 = ''; - my $OpSel3 = ''; - - if($status eq 'Any') { $OpSel3 = ' selected'; } - elsif($status eq 'Expired' ) { $OpSel2 = ' selected'; } - else { $OpSel1 = ' selected'; } - my $Str = ''; $Str .= '<select name="Status"'; + if (defined($mult)){ + $Str .= ' multiple="multiple" '; + } if(defined($formName) && $formName ne '' && ! defined($onchange)) { $Str .= ' onchange="document.'.$formName.'.submit()"'; } @@ -518,121 +911,123 @@ sub StatusOptions { } $Str .= ' size="'.$size.'" '; $Str .= '>'."\n"; - $Str .= '<option value="Active" '.$OpSel1.'>'. - &mt('Currently Enrolled').'</option>'."\n"; - $Str .= '<option value="Expired" '.$OpSel2.'>'. - &mt('Previously Enrolled').'</option>'."\n"; - $Str .= '<option value="Any" '.$OpSel3.'>'. - &mt('Any Enrollment Status').'</option>'."\n"; + foreach my $type (['Active', &mt('Currently Has Access')], + ['Future', &mt('Will Have Future Access')], + ['Expired', &mt('Previously Had Access')], + ['Any', &mt('Any Access Status')]) { + my ($name,$label) = @$type; + $Str .= '<option value="'.$name.'" '; + if ($status eq $name) { + $Str .= 'selected="selected" '; + } + $Str .= '>'.$label.'</option>'."\n"; + } + $Str .= '</select>'."\n"; } - ######################################################## ######################################################## =pod -=item &MultipleSectionSelect() +=item Progess Window Handling Routines -Inputs: +These routines handle the creation, update, increment, and closure of +progress windows. The progress window reports to the user the number +of items completed and an estimate of the time required to complete the rest. =over 4 -=item $sections A references to an array containing the names of all the -sections used in a class. -=item $selectedSections A reference to an array containing the names of the -currently selected sections. +=item &Create_PrgWin() -=back +Writes javascript to the client to open a progress window and returns a +data structure used for bookkeeping. -Returns: a string containing HTML for a multiple select box for -selecting sections of a course. +Inputs -The form element name is 'Section'. @$sections is sorted prior to output. +=over 4 -=cut +=item $r Apache request -######################################################## -######################################################## -sub MultipleSectionSelect { - my ($sections,$selectedSections)=@_; +=item $title The title of the progress window - my $Str = ''; - $Str .= '<select name="Section" multiple="true" size="4">'."\n"; +=item $heading A description (usually 1 line) of the process being initiated. - foreach (sort @$sections) { - $Str .= '<option'; - foreach my $selected (@$selectedSections) { - if($_ eq $selected) { - $Str .= ' selected=""'; - } - } - $Str .= '>'.$_.'</option>'."\n"; - } - $Str .= '</select>'."\n"; - - return $Str; -} +=item $number_to_do The total number of items being processed. -######################################################## -######################################################## +=item $type Either 'popup' or 'inline' (popup is assumed if nothing is + specified) -=pod +=item $width Specify the width in charaters of the input field. -=item &Title() +=item $formname Only useful in the inline case, if a form already exists, this needs to be used and specfiy the name of the form, otherwise the Progress line will be created in a new form of it's own -Inputs: $pageName a string containing the name of the page to be sent -to &Apache::loncommon::bodytag. +=item $inputname Only useful in the inline case, if a form and an input of type text exists, use this to specify the name of the input field -Returns: string containing being <html> and complete <head> and <title> -as well as a <script> to focus the current window and change its width -and height to 500. Why? I do not know. If you find out, please update -this documentation. +=back -=cut +Returns a hash containing the progress state data structure. -######################################################## -######################################################## -sub Title { - my ($pageName)=@_; - my $Str = ''; +=item &Update_PrgWin() - $Str .= '<html><head><title>'.$pageName.'</title></head>'."\n"; - $Str .= &Apache::loncommon::bodytag($pageName)."\n"; - $Str .= '<script>window.focus(); window.width=500;window.height=500;'; - $Str .= '</script>'."\n"; +Updates the text in the progress indicator. Does not increment the count. +See &Increment_PrgWin. - return $Str; -} +Inputs: -######################################################## -######################################################## +=over 4 + +=item $r Apache request + +=item $prog_state Pointer to the data structure returned by &Create_PrgWin + +=item $displaystring The string to write to the status indicator + +=back + +Returns: none -=pod -=item &CreateHeadings() +=item Increment_PrgWin() -This function generates the column headings for the chart. +Increment the count of items completed for the progress window by $step or 1 if no step is provided. + +Inputs: =over 4 -Inputs: $CacheData, $keyID, $headings, $spacePadding +=item $r 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 $step (optional) counter step. Will be set to default 1 if ommited. step must be greater than 0 or empty. + +=back + +Returns: none + + +=item &Close_PrgWin() -$CacheData: pointer to a hash tied to the cached data database +Closes the progress window. + +Inputs: -$keyID: a pointer to an array containing the names of the data -held in a column and is used as part of a key into $CacheData +=over 4 -$headings: The names of the headings for the student information +=item $r Apache request -$spacePadding: The spaces to go between columns +=item $prog_state Pointer to the data structure returned by Create_PrgWin -Output: $Str +=back -$Str: A formatted string of the table column headings. +Returns: none =back @@ -640,307 +1035,2030 @@ $Str: A formatted string of the table co ######################################################## ######################################################## -sub CreateHeadings { - my ($data,$keyID,$headings,$displayString,$format)=@_; - my $Str=''; - my $formatting = ''; - - for(my $index=0; $index<(scalar @$headings); $index++) { - my $currentHeading=$headings->[$index]; - if($format eq 'preformatted') { - my @dataLength=split(//,$currentHeading); - my $length=scalar @dataLength; - $formatting = (' 'x - ($data->{$keyID->[$index].':columnWidth'}-$length)); - } - my $linkdata=$keyID->[$index]; - - my $tempString = $displayString; - $tempString =~ s/LINKDATA/$linkdata/; - $tempString =~ s/DISPLAYDATA/$currentHeading/; - $tempString =~ s/FORMATTING/$formatting/; - $Str .= $tempString; - } - return $Str; +# Create progress +sub Create_PrgWin { + my ($r,$number_to_do)=@_; + 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); + return %prog_state; } -######################################################## -######################################################## +# update progress +sub Update_PrgWin { + my ($r,$prog_state,$displayString)=@_; + &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString); + $$prog_state{'laststart'}=&Time::HiRes::time(); +} -=pod +# increment progress state +sub Increment_PrgWin { + my ($r,$prog_state,$extraInfo,$step)=@_; + $step = $step > 0 ? $step : 1; + $$prog_state{'done'} += $step; + + # Catch (max modulo step) <> 0 + my $current = $$prog_state{'done'}; + my $last = ($$prog_state{'max'} - $current); + if ($last <= 0) { + $last = 1; + $current = $$prog_state{'max'}; + } -=item &FormatStudentInformation() + my $time_est= (&Time::HiRes::time() - $$prog_state{'firststart'})/ + $current * $last; + $time_est = int($time_est); + # + my $min = int($time_est/60); + my $sec = $time_est % 60; -This function produces a formatted string of the student\'s information: -username, domain, section, full name, and PID. + my $lasttime = &Time::HiRes::time()-$$prog_state{'laststart'}; + if ($lasttime > 9) { + $lasttime = int($lasttime); + } elsif ($lasttime < 0.01) { + $lasttime = 0; + } else { + $lasttime = sprintf("%3.2f",$lasttime); + } -=over 4 + $sec = 0 if ($min >= 10); # Don't show seconds if remaining time >= 10 min. + $sec = 1 if ( ($min == 0) && ($sec == 0) ); # Little cheating: pretend to have 1 second remaining instead of 0 to have something to display -Input: $cache, $name, $keyID, $spacePadding + my $timeinfo = + &mt('[_1]/[_2]:' + .' [quant,_3,minute,minutes,] [quant,_4,second ,seconds ,]remaining' + .' ([quant,_5,second] for '.$extraInfo.')', + $current, + $$prog_state{'max'}, + $min, + $sec, + $lasttime); + my $percent=0; + if ($$prog_state{'max'}) { + $percent=int(100.*$current/$$prog_state{'max'}); + } + &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo); + $$prog_state{'laststart'}=&Time::HiRes::time(); +} -$cache: This is a pointer to a hash that is tied to the cached data +# close Progress Line +sub Close_PrgWin { + my ($r,$prog_state)=@_; + &Apache::loncommon::LCprogressbarClose($r); + undef(%$prog_state); +} -$name: The name and domain of the current student in name:domain format +# ------------------------------------------------------- Puts directory header -$keyID: A pointer to an array holding the names used to +sub crumbs { + my ($uri,$target,$prefix,$form,$skiplast)=@_; +# You cannot crumbnify uploaded or adm resources + if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Group Content)'); } + if ($target) { + $target = ' target="'. + &Apache::loncommon::escape_single($target).'"'; + } + my $output='<span class="LC_filename">'; + $output.=$prefix.'/'; + if (($env{'user.adv'}) || ($env{'user.author'})) { + my $path=$prefix.'/'; + foreach my $dir (split('/',$uri)) { + if (! $dir) { next; } + $path .= $dir; + if ($path eq $uri) { + if ($skiplast) { + $output.=$dir; + last; + } + } else { + $path.='/'; + } + 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{<a href="$href"$target>$dir</a>/}; + } else { + $output.=qq{<a href="$href_path"$target>$dir</a>/}; + } + } + } else { + foreach my $dir (split('/',$uri)) { + if (! $dir) { next; } + $output.=$dir.'/'; + } + } + if ($uri !~ m|/$|) { $output=~s|/$||; } + $output.='</span>'; -remove data from the hash. They represent the name of the data to be removed. -$spacePadding: Extra spaces that represent the space between columns + return $output; +} -Output: $Str +# --------------------- A function that generates a window for the spellchecker -$Str: Formatted string. +sub spellheader { + my $start_page= + &Apache::loncommon::start_page('Speller Suggestions',undef, + {'only_body' => 1, + 'js_ready' => 1, + 'bgcolor' => '#DDDDDD', + 'add_entries' => { + 'onload' => + 'document.forms.spellcheckform.submit()', + } + }); + my $end_page= + &Apache::loncommon::end_page({'js_ready' => 1}); + + my $nothing=&javascript_nothing(); + return (<<ENDCHECK); +<script type="text/javascript"> +// <![CDATA[ +//<!-- BEGIN LON-CAPA Internal +var checkwin; + +function spellcheckerwindow(string) { + var esc_string = string.replace(/\"/g,'"'); + checkwin=window.open($nothing,'spellcheckwin','height=320,width=280,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no'); + checkwin.document.writeln('$start_page<form name="spellcheckform" action="/adm/spellcheck" method="post"><input type="hidden" name="text" value="'+esc_string+'" /><\\/form>$end_page'); + checkwin.document.close(); +} +// END LON-CAPA Internal --> +// ]]> +</script> +ENDCHECK +} -=back +# ---------------------------------- Generate link to spell checker for a field -=cut +sub spelllink { + my ($form,$field)=@_; + my $linktext=&mt('Check Spelling'); + return (<<ENDLINK); +<a href="javascript:if (typeof(document.$form.onsubmit)!='undefined') { if (document.$form.onsubmit!=null) { document.$form.onsubmit();}};spellcheckerwindow(this.document.forms.$form.$field.value);">$linktext</a> +ENDLINK +} -######################################################## -######################################################## -sub FormatStudentInformation { - my ($data,$name,$keyID,$displayString,$format)=@_; - my $Str=''; - my $currentColumn; +# ------------------------------------------------- Output headers for CKEditor + +sub htmlareaheaders { + my $s=""; + if (&htmlareabrowser()) { + $s.=(<<ENDEDITOR); +<script type="text/javascript" src="/ckeditor/ckeditor.js"></script> +ENDEDITOR + } + $s.=(<<ENDJQUERY); +<script type="text/javascript" src="/adm/jQuery/js/jquery-1.6.2.min.js"></script> +<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.8.16.custom.min.js"></script> +<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.8.16.custom.css" /> +<script type="text/javascript" src="/adm/jpicker/js/jpicker-1.1.6.min.js" > +</script> +<link rel="stylesheet" type="text/css" href="/adm/jpicker/css/jPicker-1.1.6.min.css" /> +<script type="text/javascript" src="/adm/countdown/js/jquery.countdown.js"></script> +<link rel="stylesheet" type="text/css" href="/adm/countdown/css/jquery.countdown.css" /> +ENDJQUERY + return $s; +} - for(my $index=0; $index<(scalar @$keyID); $index++) { - $currentColumn=$data->{$name.':'.$keyID->[$index]}; +# ----------------------------------------------------------------- Preferences - if($format eq 'preformatted') { - my @dataLength=split(//,$currentColumn); - my $length=scalar @dataLength; - $currentColumn.= (' 'x - ($data->{$keyID->[$index].':columnWidth'}-$length)); - } +# ------------------------------------------------- lang to use in html editor +sub htmlarea_lang { + my $lang='en'; + if (&mt('htmlarea_lang') ne 'htmlarea_lang') { + $lang=&mt('htmlarea_lang'); + } + return $lang; +} - my $tempString = $displayString; - $tempString =~ s/DISPLAYDATA/$currentColumn/; +# ----------------------------------------- Script to activate only some fields - $Str .= $tempString; +sub htmlareaselectactive { + my ($args) = @_; + unless (&htmlareabrowser()) { return ''; } + my $output='<script type="text/javascript" defer="defer">'."\n" + .'// <![CDATA['."\n"; + my $lang = &htmlarea_lang(); + my $fullpage = 'false'; + my ($dragmath_prefix,$dragmath_helpicon,$dragmath_whitespace); + if (ref($args) eq 'HASH') { + if (exists($args->{'lang'})) { + if ($args->{'lang'} ne '') { + $lang = $args->{'lang'}; + } + } + if (exists($args->{'fullpage'})) { + if ($args->{'fullpage'} eq 'true') { + $fullpage = $args->{'fullpage'}; + } + } + if (exists($args->{'dragmath'})) { + if ($args->{'dragmath'} ne '') { + $dragmath_prefix = $args->{'dragmath'}; + $dragmath_helpicon=&Apache::loncommon::lonhttpdurl("/adm/help/help.png"); + $dragmath_whitespace=&Apache::loncommon::lonhttpdurl("/adm/lonIcons/transparent1x1.gif"); + } + } + } + $output.=' + + function containsBlockHtml(id) { + var re = $("#"+id).html().search(/(?:\<\;|\<)(br|h1|h2|h3|h4|h5|h6|p|ol|ul|table|pre|address|blockquote|center|div)[\s]*((?:[\/]*[\s]*(?:\>\;|\>)|(?:\>\;|\>)[\s\S]*(?:\<\;|\<)\/[\s]*\1[\s]*\(?:\>\;|\>))/im); + return (re >= 0); } + + function startRichEditor(id) { + CKEDITOR.replace(id, + { + customConfig: "/ckeditor/loncapaconfig.js", + language : "'.$lang.'", + fullPage : '.$fullpage.', + } + ); + } + + function destroyRichEditor(id) { + CKEDITOR.instances[id].destroy(); + } + + function editorHandler(event) { + var rawid = $(this).attr("id"); + var id = new RegExp("LC_rt_(.*)").exec(rawid)[1]; + event.preventDefault(); + var rt_enabled = $(this).hasClass("LC_enable_rt"); + if (rt_enabled) { + startRichEditor(id); + $("#LC_rt_"+id).html("<b>« Plain text</b>"); + $("#LC_rt_"+id).attr("title", "Disable rich text formatting and edit in plain text"); + $("#LC_rt_"+id).addClass("LC_disable_rt"); + $("#LC_rt_"+id).removeClass("LC_enable_rt"); + } else { + destroyRichEditor(id); + $("#LC_rt_"+id).html("<b>Rich formatting »</b>"); + $("#LC_rt_"+id).attr("title", "Enable rich text formatting (bold, italic, etc.)"); + $("#LC_rt_"+id).addClass("LC_enable_rt"); + $("#LC_rt_"+id).removeClass("LC_disable_rt"); + }'; + if ($dragmath_prefix ne '') { + $output .= "\n var visible = ''; + if (rt_enabled) { + visible = 'none'; + } + editmath_visibility(id,visible);\n"; + } + $output .= ' + } + $(document).ready(function(){ + $(".LC_richAlwaysOn").each(function() { + startRichEditor($(this).attr("id")); + }); + $(".LC_richDetectHtml").each(function() { + var id = $(this).attr("id"); + var rt_enabled = containsBlockHtml(id); + if(rt_enabled) { + $(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"Disable rich text formatting and edit in plain text\" class=\"LC_disable_rt\"><b>« Plain text</b></a></div>"); + startRichEditor(id); + $("#LC_rt_"+id).click(editorHandler); + } + else { + $(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"Enable rich text formatting (bold, italic, etc.)\" class=\"LC_enable_rt\"><b>Rich formatting »</b></a></div>"); + $("#LC_rt_"+id).click(editorHandler); + }'; + if ($dragmath_prefix ne '') { + $output .= "\n var visible = ''; + if (rt_enabled) { + visible = 'none'; + } + editmath_visibility(id,visible);\n"; + } + $output .= ' + }); + $(".LC_richDefaultOn").each(function() { + var id = $(this).attr("id"); + $(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"Disable rich text formatting and edit in plain text\" class=\"LC_disable_rt\"><b>« Plain text</b></a></div>"); + startRichEditor(id); + $("#LC_rt_"+id).click(editorHandler); + }); + $(".LC_richDefaultOff").each(function() { + var id = $(this).attr("id"); + $(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"Enable rich text formatting (bold, italic, etc.)\" class=\"LC_enable_rt\"><b>Rich formatting »</b></a></div>"); + $("#LC_rt_"+id).click(editorHandler); + }); + $.fn.jPicker.defaults.images.clientPath="/adm/jpicker/images/"; + $(".colorchooser").jPicker(); + + + }); +'; + # 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 = '<b>' . &mt('Due in: {dn} {dl} {hnn}{sep}{mnn}{sep}{snn} - Submit early!') . '</b>'; + $output .= <<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) < (60 + latencyEstimate)) { + \$(this).css("color", "red"); //Highlight last minute. + } + } + }); + } +}); +JAVASCRIPT + if ($dragmath_prefix ne '') { + $output .= ' + + function editmath_visibility(id,value) { + + if ((id == "") || (id == null)) { + return; + } + var mathid = "'.$dragmath_prefix.'_"+id; + mathele = document.getElementById(mathid); + if (mathele == null) { + return; + } + mathele.style.display = value; + var mathhelpicon = "'.$dragmath_prefix.'helpicon'.'_"+id; + mathhelpiconele = document.getElementById(mathhelpicon); + if (mathhelpiconele == null) { + return; + } + if (value == "none") { + mathhelpiconele.src = "'.$dragmath_whitespace.'"; + } else { + mathhelpiconele.src = "'.$dragmath_helpicon.'"; + } + } +'; + + } + $output.="\nwindow.status='Activated Editfields';\n" + .'// ]]>'."\n" + .'</script>'; + return $output; +} - return $Str; +# --------------------------------------------------------------------- Blocked + +sub htmlareablocked { + unless ($env{'environment.wysiwygeditor'} eq 'on') { return 1; } + return 0; } -######################################################## -######################################################## +# ---------------------------------------- Browser capable of running HTMLArea? -=pod +sub htmlareabrowser { + return 1; +} -=item Progess Window Handling Routines +# +# Should the "return to content" link be shown? +# -These routines handle the creation, update, increment, and closure of -progress windows. The progress window reports to the user the number -of items completed and an estimate of the time required to complete the rest. +sub show_return_link { -=over 4 + unless ($env{'request.course.id'}) { return 0; } + if ($env{'request.noversionuri'}=~m{^/priv/} || + $env{'request.uri'}=~m{^/~}) { return 1; } + + if (($env{'request.noversionuri'} =~ m{^/adm/(viewclasslist|navmaps)($|\?)}) + || ($env{'request.noversionuri'} =~ m{^/adm/.*/aboutme($|\?)})) { + + return if ($env{'form.register'}); + } + return (($env{'request.noversionuri'}=~m{^/(res|public)/} && + $env{'request.symb'} eq '') + || + ($env{'request.noversionuri'}=~ m{^/cgi-bin/printout.pl}) + || + (($env{'request.noversionuri'}=~/^\/adm\//) && + ($env{'request.noversionuri'}!~/^\/adm\/wrapper\//) && + ($env{'request.noversionuri'}!~ + m{^/adm/.*/(smppg|bulletinboard)($|\?)}) + )); +} -=item &Create_PrgWin +## +# Set the dueDate variable...note this is done in the timezone +# of the browser. +# +# @param epoch relative time at which the problem is due. +# +# @return the javascript fragment to set the date: +# +sub set_due_date { + my $dueStamp = shift; + my $duems = $dueStamp * 1000; # Javascript Date object needs ms not seconds. + + my $now = time()*1000; + + # This slightly obscure bit of javascript sets the dueDate variable + # to the time in the browser at which the problem was due. + # The code should correct for gross differences between the server + # and client's time setting + + my $js = " +<script type='text/javascript'> + //<![CDATA[ +var serverDueDate = $duems; +var serverTime = $now; +var clientTime = (new Date()).getTime(); +var dueDate = new Date(serverDueDate + (clientTime - serverTime)); -Writes javascript to the client to open a progress window and returns a -data structure used for bookkeeping. + //]]> +</script> +"; -Inputs + return $js; +} +## +# Sets the time at which the problem finished computing. +# This just updates the serverTime and clientTime variables above. +# Calling this in e.g. end_problem provides a better estimate of the +# difference beetween the server and client time setting as +# the difference contains less of the latency/problem compute time. +# +sub set_compute_end_time { -=over 4 + my $now = time()*1000; # Javascript times are in ms. + my $js = " +<script type='text/javascript'> +//<![CDATA[ +serverTime = $now; +clientTime = (new Date()).getTime(); +//]]> +</script> -=item $r Apache request +"; + return $js; + +} -=item $title The title of the progress window +############################################################ +############################################################ -=item $heading A description (usually 1 line) of the process being initiated. +=pod -=item $number_to_do The total number of items being processed. +=item &breadcrumbs() -=back +Compiles the previously registered breadcrumbs into an series of links. +Additionally supports a 'component', which will be displayed on the +right side of the breadcrumbs enclosing div (without a link). +A link to help for the component will be included if one is specified. -Returns a hash containing the progress state data structure. +All inputs can be undef without problems. +Inputs: $component (the text on the right side of the breadcrumbs trail), + $component_help + $menulink (boolean, controls whether to include a link to /adm/menu) + $helplink (if 'nohelp' don't include the orange help link) + $css_class (optional name for the class to apply to the table for CSS) + $no_mt (optional flag, 1 if &mt() is _not_ to be applied to $component + when including the text on the right. +Returns a string containing breadcrumbs for the current page. -=item &Update_PrgWin +=item &clear_breadcrumbs() -Updates the text in the progress indicator. Does not increment the count. -See &Increment_PrgWin. +Clears the previously stored breadcrumbs. -Inputs: +=item &add_breadcrumb() -=over 4 +Pushes a breadcrumb on the stack of crumbs. -=item $r Apache request +input: $breadcrumb, a hash reference. The keys 'href','title', and 'text' +are required. If present the keys 'faq' and 'bug' will be used to provide +links to the FAQ and bug sites. If the key 'no_mt' is present the 'title' +and 'text' values won't be sent through &mt() -=item $prog_state Pointer to the data structure returned by &Create_PrgWin +returns: nothing -=item $displaystring The string to write to the status indicator +=cut -=back +############################################################ +############################################################ +{ + my @Crumbs; + my %tools = (); + + sub breadcrumbs { + my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, $CourseBreadcrumbs) = @_; + # + $css_class ||= 'LC_breadcrumbs'; + + # Make the faq and bug data cascade + my $faq = ''; + my $bug = ''; + my $help = ''; + # Crumb Symbol + my $crumbsymbol = '»'; + # The last breadcrumb does not have a link, so handle it separately. + my $last = pop(@Crumbs); + # + # The first one should be the course or a menu link + if (!defined($menulink)) { $menulink=1; } + if ($menulink) { + my $description = 'Menu'; + my $no_mt_descr = 0; + if ((exists($env{'request.course.id'})) && + ($env{'request.course.id'} ne '') && + ($env{'course.'.$env{'request.course.id'}.'.description'} ne '')) { + $description = + $env{'course.'.$env{'request.course.id'}.'.description'}; + $no_mt_descr = 1; + } + $menulink = { href =>'/adm/menu', + title =>'Go to main menu', + target =>'_top', + text =>$description, + no_mt =>$no_mt_descr, }; + if($last) { + #$last set, so we have some crumbs + unshift(@Crumbs,$menulink); + } else { + #only menulink crumb present + $last = $menulink; + } + } + my $links; + if ((&show_return_link) && (!$CourseBreadcrumbs)) { + my $alttext = &mt('Go Back'); + $links=&htmltag( 'a',"<img src='/res/adm/pages/reload.png' border='0' style='vertical-align:middle;' alt='$alttext' />", + { href => '/adm/flip?postdata=return:', + title => &mt("Back to most recent content resource") }); + $links=&htmltag('li',$links); + } + $links.= join "", + map { + $faq = $_->{'faq'} if (exists($_->{'faq'})); + $bug = $_->{'bug'} if (exists($_->{'bug'})); + $help = $_->{'help'} if (exists($_->{'help'})); + + my $result = $_->{no_mt} ? $_->{text} : &mt($_->{text}); + + if ($_->{href}){ + $result = &htmltag( 'a', $result, + { href => $_->{href}, + title => $_->{no_mt} ? $_->{title} : &mt($_->{title}), + target => $_->{target}, }); + } + + $result = &htmltag( 'li', "$result $crumbsymbol"); + } @Crumbs; + + #should the last Element be translated? + + my $lasttext = $last->{'no_mt'} ? $last->{'text'} + : mt( $last->{'text'} ); + + # last breadcrumb is the first order heading of a page + # for course breadcrumbs it's just bold + + $links .= &htmltag( 'li', htmltag($CourseBreadcrumbs ? 'b' : 'h1', + $lasttext), {title => $lasttext}); + + $links .= '<li> <span id="duedatecountdown"></span></li>'; + + my $icons = ''; + $faq = $last->{'faq'} if (exists($last->{'faq'})); + $bug = $last->{'bug'} if (exists($last->{'bug'})); + $help = $last->{'help'} if (exists($last->{'help'})); + $component_help=($component_help?$component_help:$help); +# if ($faq ne '') { +# $icons .= &Apache::loncommon::help_open_faq($faq); +# } +# if ($bug ne '') { +# $icons .= &Apache::loncommon::help_open_bug($bug); +# } + if ($faq ne '' || $component_help ne '' || $bug ne '') { + $icons .= &Apache::loncommon::help_open_menu($component, + $component_help, + $faq,$bug); + } + # -Returns: none + + unless ($CourseBreadcrumbs) { + $links = &htmltag('ol', $links, { id => "LC_MenuBreadcrumbs" }); + } else { + $links = &htmltag('ul', $links, { class => "LC_CourseBreadcrumbs" }); + } -=item Increment_PrgWin -Increment the count of items completed for the progress window by 1. + if ($component) { + $links = &htmltag('span', + ( $no_mt ? $component : mt($component) ). + ( $icons ? $icons : '' ), + { class => 'LC_breadcrumbs_component' } ) + .$links +; + } + + &render_tools(\$links); + $links = &htmltag('div', $links, + { id => "LC_breadcrumbs" }) unless ($CourseBreadcrumbs) ; + &render_advtools(\$links); + + # Return the @Crumbs stack to what we started with + push(@Crumbs,$last); + shift(@Crumbs); -Inputs: -=over 4 + # Return the breadcrumb's line -=item $r Apache request + -=item $prog_state Pointer to the data structure returned by Create_PrgWin + return "$links"; + } -=item $extraInfo A description of the items being iterated over. Typically -'student'. + sub clear_breadcrumbs { + undef(@Crumbs); + undef(%tools); + } + + sub add_breadcrumb { + push(@Crumbs,@_); + } + +=item &add_breadcrumb_tool($category, $html) + +Adds $html to $category of the breadcrumb toolbar container. + +$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 navigation +left of breadcrumbs line + +=item tools +right of breadcrumbs line + +=item advtools +advanced tools shown in a separate box below breadcrumbs line =back + +returns: nothing -Returns: none +=cut + sub add_breadcrumb_tool { + my ($category, @html) = @_; + return unless @html; + if (!keys(%tools)) { + %tools = ( navigation => [], tools => [], advtools => []); + } -=item Close_PrgWin + #this cleans data received from lonmenu::innerregister + @html = grep {defined $_ && $_ ne ''} @html; + for (@html) { + s/align="(right|left)"//; +# s/<span.*?\/span>// if $category ne 'advtools'; + } -Closes the progress window. + push @{$tools{$category}}, @html; + } -Inputs: +=item &clear_breadcrumb_tools() -=over 4 +Clears the breadcrumb toolbar container. -=item $r Apache request +returns: nothing -=item $prog_state Pointer to the data structure returned by Create_PrgWin +=cut -=back + sub clear_breadcrumb_tools { + undef(%tools); + } -Returns: none +=item &render_tools(\$breadcrumbs) -=back +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. + +returns: nothing =cut -######################################################## -######################################################## +#TODO might split this in separate functions for each category + sub render_tools { + my ($breadcrumbs) = @_; + return unless (keys(%tools)); + + my $navigation = list_from_array($tools{navigation}, + { listattr => { class=>"LC_breadcrumb_tools_navigation" } }); + my $tools = list_from_array($tools{tools}, + { listattr => { class=>"LC_breadcrumb_tools_tools" } }); + $$breadcrumbs = list_from_array([$navigation, $tools, $$breadcrumbs], + { listattr => { class=>'LC_breadcrumb_tools_outerlist' } }); + } -# Create progress -sub Create_PrgWin { - my ($r, $title, $heading, $number_to_do)=@_; - $r->print('<script>'. - "popwin=open(\'\',\'popwin\',\'width=400,height=100\');". - "popwin.document.writeln(\'<html><head><title>$title</title></head>". - "<body bgcolor=\"#88DDFF\">". - "<h4>$heading</h4>". - "<form name=popremain>". - '<input type="text" size="55" name="remaining" value="'. - &mt('Starting').'"></form>'. - "</body></html>\');". - "popwin.document.close();". - "</script>"); +=pod - 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; +=item &render_advtools(\$breadcrumbs) - $r->rflush(); - return %prog_state; +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). + +returns: nothing + +=cut + + sub render_advtools { + my ($breadcrumbs) = @_; + return unless (defined $tools{'advtools'}) + and (scalar(@{$tools{'advtools'}}) > 0); + + $$breadcrumbs .= Apache::loncommon::head_subbox( + funclist_from_array($tools{'advtools'}) ); + } + +} # End of scope for @Crumbs + +############################################################ +############################################################ + +# Nested table routines. +# +# Routines to display form items in a multi-row table with 2 columns. +# Uses nested tables to divide form elements into segments. +# For examples of use see loncom/interface/lonnotify.pm +# +# Can be used in following order: ... +# &start_pick_box() +# row1 +# row2 +# row3 ... etc. +# &submit_row() +# &end_pick_box() +# +# where row1, row 2 etc. are chosen from &role_select_row,&course_select_row, +# &status_select_row and &email_default_row +# +# Can also be used in following order: +# +# &start_pick_box() +# &row_title() +# &row_closure() +# &row_title() +# &row_closure() ... etc. +# &submit_row() +# &end_pick_box() +# +# In general a &submit_row() call should proceed the call to &end_pick_box(), +# as this routine adds a button for form submission. +# &submit_row() does not require a &row_closure after it. +# +# &start_pick_box() creates a bounding table with 1-pixel wide black border. +# rows should be placed between calls to &start_pick_box() and &end_pick_box. +# +# &row_title() adds a title in the left column for each segment. +# &row_closure() closes a row with a 1-pixel wide black line. +# +# &role_select_row() provides a select box from which to choose 1 or more roles +# &course_select_row provides ways of picking groups of courses +# radio buttons: all, by category or by picking from a course picker pop-up +# note: by category option is only displayed if a domain has implemented +# selection by year, semester, department, number etc. +# +# &status_select_row() provides a select box from which to choose 1 or more +# access types (current access, prior access, and future access) +# +# &email_default_row() provides text boxes for default e-mail suffixes for +# different authentication types in a domain. +# +# &row_title() and &row_closure() are called internally by the &*_select_row +# routines, but can also be called directly to start and end rows which have +# needs that are not accommodated by the *_select_row() routines. + +{ # Start: row_count block for pick_box +my @row_count; + +sub start_pick_box { + my ($css_class) = @_; + if (defined($css_class)) { + $css_class = 'class="'.$css_class.'"'; + } else { + $css_class= 'class="LC_pick_box"'; + } + unshift(@row_count,0); + my $output = <<"END"; + <table $css_class> +END + return $output; } -# update progress -sub Update_PrgWin { - my ($r,$prog_state,$displayString)=@_; - $r->print('<script>popwin.document.popremain.remaining.value="'. - $displayString.'";</script>'); - $$prog_state{'laststart'}=&Time::HiRes::time(); - $r->rflush(); +sub end_pick_box { + shift(@row_count); + my $output = <<"END"; + </table> +END + return $output; } -# increment progress state -sub Increment_PrgWin { - my ($r,$prog_state,$extraInfo)=@_; - $$prog_state{'done'}++; - my $time_est= (&Time::HiRes::time() - $$prog_state{'firststart'})/ - $$prog_state{'done'} * - ($$prog_state{'max'}-$$prog_state{'done'}); - $time_est = int($time_est); - if (int ($time_est/60) > 0) { - my $min = int($time_est/60); - my $sec = $time_est % 60; - $time_est = $min.' '.&mt('minutes'); - if ($min < 10) { - if ($sec > 1) { - $time_est.= ', '.$sec.' '.&mt('seconds'); - } elsif ($sec > 0) { - $time_est.= ', '.$sec.' '.&mt('second'); +sub row_headline { + my $output = <<"END"; + <tr><td colspan="2"> +END + return $output; +} + +sub row_title { + my ($title,$css_title_class,$css_value_class, $css_value_furtherAttributes) = @_; + $row_count[0]++; + my $css_class = ($row_count[0] % 2)?'LC_odd_row':'LC_even_row'; + $css_title_class ||= 'LC_pick_box_title'; + $css_title_class = 'class="'.$css_title_class.'"'; + + $css_value_class ||= 'LC_pick_box_value'; + + if ($title ne '') { + $title .= ':'; + } + my $output = <<"ENDONE"; + <tr class="LC_pick_box_row" $css_value_furtherAttributes> + <td $css_title_class> + $title + </td> + <td class="$css_value_class $css_class"> +ENDONE + return $output; +} + +sub row_closure { + my ($no_separator) =@_; + my $output = <<"ENDTWO"; + </td> + </tr> +ENDTWO + if (!$no_separator) { + $output .= <<"ENDTWO"; + <tr> + <td colspan="2" class="LC_pick_box_separator"> + </td> + </tr> +ENDTWO + } + return $output; +} + +} # End: row_count block for pick_box + +sub role_select_row { + my ($roles,$title,$css_class,$show_separate_custom,$cdom,$cnum) = @_; + my $crstype = 'Course'; + if ($cdom ne '' && $cnum ne '') { + $crstype = &Apache::loncommon::course_type($cdom.'_'.$cnum); + } + my $output; + if (defined($title)) { + $output = &row_title($title,$css_class); + } + $output .= qq| + <select name="roles" multiple="multiple">\n|; + foreach my $role (@$roles) { + my $plrole; + if ($role eq 'ow') { + $plrole = &mt('Course Owner'); + } elsif ($role eq 'cr') { + if ($show_separate_custom) { + if ($cdom ne '' && $cnum ne '') { + my %course_customroles = &course_custom_roles($cdom,$cnum); + foreach my $crrole (sort(keys(%course_customroles))) { + my ($plcrrole) = ($crrole =~ m|^cr/[^/]+/[^/]+/(.+)$|); + $output .= ' <option value="'.$crrole.'">'.$plcrrole. + '</option>'; + } + } + } else { + $plrole = &mt('Custom Role'); } + } else { + $plrole=&Apache::lonnet::plaintext($role,$crstype); + } + if (($role ne 'cr') || (!$show_separate_custom)) { + $output .= ' <option value="'.$role.'">'.$plrole.'</option>'; } - } else { - $time_est .= ' '.&mt('seconds'); } - my $lasttime = &Time::HiRes::time()-$$prog_state{'laststart'}; - if ($lasttime > 9) { - $lasttime = int($lasttime); - } elsif ($lasttime < 0.01) { - $lasttime = 0; + $output .= qq| </select>\n|; + if (defined($title)) { + $output .= &row_closure(); + } + return $output; +} + +sub course_select_row { + my ($title,$formname,$totcodes,$codetitles,$idlist,$idlist_titles, + $css_class,$crstype,$standardnames) = @_; + my $output = &row_title($title,$css_class); + $output .= &course_selection($formname,$totcodes,$codetitles,$idlist,$idlist_titles,$crstype,$standardnames); + $output .= &row_closure(); + return $output; +} + +sub course_selection { + my ($formname,$totcodes,$codetitles,$idlist,$idlist_titles,$crstype,$standardnames) = @_; + my $output = qq| +<script type="text/javascript"> +// <![CDATA[ + function coursePick (formname) { + for (var i=0; i<formname.coursepick.length; i++) { + if (formname.coursepick[i].value == 'category') { + courseSet(''); + } + if (!formname.coursepick[i].checked) { + if (formname.coursepick[i].value == 'specific') { + formname.coursetotal.value = 0; + formname.courselist = ''; + } + } + } + } + function setPick (formname) { + for (var i=0; i<formname.coursepick.length; i++) { + if (formname.coursepick[i].value == 'category') { + formname.coursepick[i].checked = true; + } + formname.coursetotal.value = 0; + formname.courselist = ''; + } + } +// ]]> +</script> + |; + + my ($allcrs,$pickspec); + if ($crstype eq 'Community') { + $allcrs = &mt('All communities'); + $pickspec = &mt('Pick specific communities:'); } else { - $lasttime = sprintf("%3.2f",$lasttime); + $allcrs = &mt('All courses'); + $pickspec = &mt('Pick specific course(s):'); + } + + my $courseform='<b>'.&Apache::loncommon::selectcourse_link + ($formname,'pickcourse','pickdomain','coursedesc','',1,$crstype).'</b>'; + $output .= '<input type="radio" name="coursepick" value="all" onclick="coursePick(this.form)" />'.$allcrs.'<br />'; + if ($totcodes > 0) { + my $numtitles = @$codetitles; + if ($numtitles > 0) { + $output .= '<input type="radio" name="coursepick" value="category" onclick="coursePick(this.form);alert('."'".&mt('Choose categories, from left to right')."'".')" />'.&mt('Pick courses by category:').' <br />'; + $output .= '<table><tr><td>'.$$codetitles[0].'<br />'."\n". + '<select name="'.$standardnames->[0]. + '" onChange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n". + ' <option value="-1" />Select'."\n"; + my @items = (); + my @longitems = (); + if ($$idlist{$$codetitles[0]} =~ /","/) { + @items = split(/","/,$$idlist{$$codetitles[0]}); + } else { + $items[0] = $$idlist{$$codetitles[0]}; + } + if (defined($$idlist_titles{$$codetitles[0]})) { + if ($$idlist_titles{$$codetitles[0]} =~ /","/) { + @longitems = split(/","/,$$idlist_titles{$$codetitles[0]}); + } else { + $longitems[0] = $$idlist_titles{$$codetitles[0]}; + } + for (my $i=0; $i<@longitems; $i++) { + if ($longitems[$i] eq '') { + $longitems[$i] = $items[$i]; + } + } + } else { + @longitems = @items; + } + for (my $i=0; $i<@items; $i++) { + $output .= ' <option value="'.$items[$i].'">'.$longitems[$i].'</option>'; + } + $output .= '</select></td>'; + for (my $i=1; $i<$numtitles; $i++) { + $output .= '<td>'.$$codetitles[$i].'<br />'."\n". + '<select name="'.$standardnames->[$i]. + '" onChange="courseSet('."'$$codetitles[$i]'".')">'."\n". + '<option value="-1"><-Pick '.$$codetitles[$i-1].'</option>'."\n". + '</select>'."\n". + '</td>'; + } + $output .= '</tr></table><br />'; + } + } + $output .= '<input type="radio" name="coursepick" value="specific" onclick="coursePick(this.form);opencrsbrowser('."'".$formname."','dccourse','dcdomain','coursedesc','','1','$crstype'".')" />'.$pickspec.' '.$courseform.' <input type="text" value="0" size="4" name="coursetotal" /><input type="hidden" name="courselist" value="" />selected.<br />'."\n"; + return $output; +} + +sub status_select_row { + my ($types,$title,$css_class) = @_; + my $output; + if (defined($title)) { + $output = &row_title($title,$css_class,'LC_pick_box_select'); + } + $output .= qq| + <select name="types" multiple="multiple">\n|; + foreach my $status_type (sort(keys(%{$types}))) { + $output .= ' <option value="'.$status_type.'">'.$$types{$status_type}.'</option>'; + } + $output .= qq| </select>\n|; + if (defined($title)) { + $output .= &row_closure(); + } + return $output; +} + +sub email_default_row { + my ($authtypes,$title,$descrip,$css_class) = @_; + my $output = &row_title($title,$css_class); + $output .= $descrip. + &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row(). + '<th>'.&mt('Authentication Method').'</th>'. + '<th align="right">'.&mt('Username -> e-mail conversion').'</th>'."\n". + &Apache::loncommon::end_data_table_header_row(); + my $rownum = 0; + foreach my $auth (sort(keys(%{$authtypes}))) { + my ($userentry,$size); + if ($auth =~ /^krb/) { + $userentry = ''; + $size = 25; + } else { + $userentry = 'username@'; + $size = 15; + } + $output .= &Apache::loncommon::start_data_table_row(). + '<td> '.$$authtypes{$auth}.'</td>'. + '<td align="right">'.$userentry. + '<input type="text" name="'.$auth.'" size="'.$size.'" /></td>'. + &Apache::loncommon::end_data_table_row(); + } + $output .= &Apache::loncommon::end_data_table(); + $output .= &row_closure(); + return $output; +} + + +sub submit_row { + my ($title,$cmd,$submit_text,$css_class) = @_; + my $output = &row_title($title,$css_class,'LC_pick_box_submit'); + $output .= qq| + <br /> + <input type="hidden" name="command" value="$cmd" /> + <input type="submit" value="$submit_text"/> + <br /><br /> + \n|; + return $output; +} + +sub course_custom_roles { + my ($cdom,$cnum) = @_; + my %returnhash=(); + my %coursepersonnel=&Apache::lonnet::dump('nohist_userroles',$cdom,$cnum); + foreach my $person (sort(keys(%coursepersonnel))) { + my ($role) = ($person =~ /^([^:]+):/); + my ($end,$start) = split(/:/,$coursepersonnel{$person}); + if ($end == -1 && $start == -1) { + next; + } + if ($role =~ m|^cr/[^/]+/[^/]+/[^/]|) { + $returnhash{$role} ++; + } } - if ($lasttime == 1) { - $lasttime = '('.$lasttime.' '.&mt('second for').' '.$extraInfo.')'; + return %returnhash; +} + + +sub resource_info_box { + my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp)=@_; + my $return=''; + if ($stuvcurrent ne '') { + $return = '<div class="LC_left_float">'; + } + if ($symb) { + $return.=&Apache::loncommon::start_data_table(); + my ($map,$id,$resource)=&Apache::lonnet::decode_symb($symb); + my $folder=&Apache::lonnet::gettitle($map); + $return.=&Apache::loncommon::start_data_table_row(). + '<th align="left">'.&mt('Folder:').'</th><td>'.$folder.'</td>'. + &Apache::loncommon::end_data_table_row(); + unless ($onlyfolderflag) { + $return.=&Apache::loncommon::start_data_table_row(). + '<th align="left">'.&mt('Resource:').'</th><td>'.&Apache::lonnet::gettitle($symb).'</td>'. + &Apache::loncommon::end_data_table_row(); + } + if ($stuvcurrent ne '') { + $return .= &Apache::loncommon::start_data_table_row(). + '<th align="left">'.&mt("Student's current version:").'</th><td>'.$stuvcurrent.'</td>'. + &Apache::loncommon::end_data_table_row(); + } + if ($stuvdisp ne '') { + $return .= &Apache::loncommon::start_data_table_row(). + '<th align="left">'.&mt("Student's version displayed:").'</th><td>'.$stuvdisp.'</td>'. + &Apache::loncommon::end_data_table_row(); + } + $return.=&Apache::loncommon::end_data_table(); } else { - $lasttime = '('.$lasttime.' '.&mt('seconds for').' '.$extraInfo.')'; + $return='<p><span class="LC_error">'.&mt('No context provided.').'</span></p>'; } - # - my $user_browser = $ENV{'browser.type'} if (exists($ENV{'browser.type'})); - my $user_os = $ENV{'browser.os'} if (exists($ENV{'browser.os'})); - if (! defined($user_browser) || ! defined($user_os)) { - (undef,$user_browser,undef,undef,undef,$user_os) = - &Apache::loncommon::decode_user_agent(); + if ($stuvcurrent ne '') { + $return .= '</div>'; } - if ($user_browser eq 'explorer' && $user_os =~ 'mac') { - $lasttime = ''; + return $return; +} + +############################################## +############################################## + +# topic_bar +# +# Generates a div containing an (optional) number with a white background followed by a +# title with a background color defined in the corresponding CSS: LC_topic_bar +# Inputs: +# 1. number to display. +# If input for number is empty only the title will be displayed. +# 2. title text to display. +# Outputs - a scalar containing html mark-up for the div. + +sub topic_bar { + my ($num,$title) = @_; + my $number = ''; + if ($num ne '') { + $number = '<span>'.$num.'</span>'; } - $r->print('<script>popwin.document.popremain.remaining.value="'. - $$prog_state{'done'}.'/'.$$prog_state{'max'}. - ': '.$time_est.' '.&mt('remaining').' '.$lasttime.'";'.'</script>'); - $$prog_state{'laststart'}=&Time::HiRes::time(); - $r->rflush(); + return '<div class="LC_topic_bar">'.$number.$title.'</div>'; } -# close Progress Line -sub Close_PrgWin { - my ($r,$prog_state)=@_; - $r->print('<script>popwin.close()</script>'."\n"); - undef(%$prog_state); - $r->rflush(); +############################################## +############################################## +# echo_form_input +# +# Generates html markup to add form elements from the referrer page +# as hidden form elements (values encoded) in the new page. +# +# Intended to support two types of use +# (a) to allow backing up to earlier pages in a multi-page +# form submission process using a breadcrumb trail. +# +# (b) to allow the current page to be reloaded with form elements +# set on previous page to remain unchanged. An example would +# be where the a page containing a dynamically-built table of data is +# is to be redisplayed, with only the sort order of the data changed. +# +# Inputs: +# 1. Reference to array of form elements in the submitted form on +# the referrer page which are to be excluded from the echoed elements. +# +# 2. Reference to array of regular expressions, which if matched in the +# name of the form element n the referrer page will be omitted from echo. +# +# Outputs: A scalar containing the html markup for the echoed form +# elements (all as hidden elements, with values encoded). + + +sub echo_form_input { + my ($excluded,$regexps) = @_; + my $output = ''; + foreach my $key (keys(%env)) { + if ($key =~ /^form\.(.+)$/) { + my $name = $1; + my $match = 0; + if (ref($excluded) eq 'ARRAY') { + next if (grep(/^\Q$name\E$/,@{$excluded})); + } + if (ref($regexps) eq 'ARRAY') { + if (@{$regexps} > 0) { + foreach my $regexp (@{$regexps}) { + if ($name =~ /$regexp/) { + $match = 1; + last; + } + } + } + } + next if ($match); + if (ref($env{$key}) eq 'ARRAY') { + foreach my $value (@{$env{$key}}) { + $value = &HTML::Entities::encode($value,'<>&"'); + $output .= '<input type="hidden" name="'.$name. + '" value="'.$value.'" />'."\n"; + } + } else { + my $value = &HTML::Entities::encode($env{$key},'<>&"'); + $output .= '<input type="hidden" name="'.$name. + '" value="'.$value.'" />'."\n"; + } + } + } + return $output; } +############################################## +############################################## +# set_form_elements +# +# Generates javascript to set form elements to values based on +# corresponding values for the same form elements when the page was +# previously submitted. +# +# Last submission values are read from hidden form elements in referring +# page which have the same name, i.e., generated by &echo_form_input(). +# +# Intended to be called by onload event. +# +# Inputs: +# (a) Reference to hash of echoed form elements to be set. +# +# In the hash, keys are the form element names, and the values are the +# element type (selectbox, radio, checkbox or text -for textbox, textarea or +# hidden). +# +# (b) Optional reference to hash of stored elements to be set. +# +# If the page being displayed is a page which permits modification of +# previously stored data, e.g., the first page in a multi-page submission, +# then if stored is supplied, form elements will be set to the last stored +# values. If user supplied values are also available for the same elements +# these will replace the stored values. +# +# Output: +# +# javascript function - set_form_elements() which sets form elements, +# expects an argument: formname - the name of the form according to +# the DOM, e.g., document.compose + +sub set_form_elements { + my ($elements,$stored) = @_; + my %values; + my $output .= 'function setFormElements(courseForm) { +'; + if (defined($stored)) { + foreach my $name (keys(%{$stored})) { + if (exists($$elements{$name})) { + if (ref($$stored{$name}) eq 'ARRAY') { + $values{$name} = $$stored{$name}; + } else { + @{$values{$name}} = ($$stored{$name}); + } + } + } + } + + foreach my $key (keys(%env)) { + if ($key =~ /^form\.(.+)$/) { + my $name = $1; + if (exists($$elements{$name})) { + @{$values{$name}} = &Apache::loncommon::get_env_multiple($key); + } + } + } -# ------------------------------------------------------- Puts directory header + foreach my $name (keys(%values)) { + for (my $i=0; $i<@{$values{$name}}; $i++) { + $values{$name}[$i] = &HTML::Entities::decode($values{$name}[$i],'<>&"'); + $values{$name}[$i] =~ s/([\r\n\f]+)/\\n/g; + $values{$name}[$i] =~ s/"/\\"/g; + } + if (($$elements{$name} eq 'text') || ($$elements{$name} eq 'hidden')) { + my $numvalues = @{$values{$name}}; + if ($numvalues > 1) { + my $valuestring = join('","',@{$values{$name}}); + $output .= qq| + var textvalues = new Array ("$valuestring"); + var total = courseForm.elements['$name'].length; + if (total > $numvalues) { + total = $numvalues; + } + for (var i=0; i<total; i++) { + courseForm.elements['$name']\[i].value = textvalues[i]; + } +|; + } else { + $output .= qq| + courseForm.elements['$name'].value = "$values{$name}[0]"; +|; + } + } else { + $output .= qq| + var elementLength = courseForm.elements['$name'].length; + if (elementLength==undefined) { +|; + foreach my $value (@{$values{$name}}) { + if ($$elements{$name} eq 'selectbox') { + $output .= qq| + if (courseForm.elements['$name'].options[0].value == "$value") { + courseForm.elements['$name'].options[0].selected = true; + }|; + } elsif (($$elements{$name} eq 'radio') || + ($$elements{$name} eq 'checkbox')) { + $output .= qq| + if (courseForm.elements['$name'].value == "$value") { + courseForm.elements['$name'].checked = true; + } else { + courseForm.elements['$name'].checked = false; + }|; + } + } + $output .= qq| + } + else { + for (var i=0; i<courseForm.elements['$name'].length; i++) { +|; + if ($$elements{$name} eq 'selectbox') { + $output .= qq| + courseForm.elements['$name'].options[i].selected = false;|; + } elsif (($$elements{$name} eq 'radio') || + ($$elements{$name} eq 'checkbox')) { + $output .= qq| + courseForm.elements['$name']\[i].checked = false;|; + } + $output .= qq| + } + for (var j=0; j<courseForm.elements['$name'].length; j++) { +|; + foreach my $value (@{$values{$name}}) { + if ($$elements{$name} eq 'selectbox') { + $output .= qq| + if (courseForm.elements['$name'].options[j].value == "$value") { + courseForm.elements['$name'].options[j].selected = true; + }|; + } elsif (($$elements{$name} eq 'radio') || + ($$elements{$name} eq 'checkbox')) { + $output .= qq| + if (courseForm.elements['$name']\[j].value == "$value") { + courseForm.elements['$name']\[j].checked = true; + }|; + } + } + $output .= qq| + } + } +|; + } + } + $output .= " + return; +}\n"; + return $output; +} -sub crumbs { - my ($uri,$target,$prefix,$form)=@_; - my $output='<br /><tt><b><font size="+2">'.$prefix.'/'; - if ($ENV{'user.adv'}) { - my $path=$prefix; - foreach (split('/',$uri)) { - unless ($_) { next; } - $path.='/'.$_; - my $linkpath=$path; - if ($form) { - $linkpath="javascript:$form.action='$path';$form.submit();"; - } - $output.='<a href="'.$linkpath.'"'.($target?' target="'.$target.'"':'').'>'.$_.'</a>/'; - } +############################################## +############################################## + +sub file_submissionchk_js { + my ($turninpaths,$multiples) = @_; + my $overwritewarn = &mt('File(s) you uploaded for your submission will overwrite existing file(s) submitted for this item').'\\n'. + &mt('Continue submission and overwrite the file(s)?'); + my $delfilewarn = &mt('You have indicated you wish to remove some files previously included in your submission.').'\\n'. + &mt('Continue submission with these files removed?'); + my ($turninpathtext,$multtext,$arrayindexofjs); + if (ref($turninpaths) eq 'HASH') { + foreach my $key (sort(keys(%{$turninpaths}))) { + $turninpathtext .= " if (prefix == '$key') {\n". + " return '$turninpaths->{$key}';\n". + " }\n"; + } + } + $turninpathtext .= " return '';\n"; + if (ref($multiples) eq 'HASH') { + foreach my $key (sort(keys(%{$multiples}))) { + $multtext .= " if (prefix == '$key') {\n". + " return '$multiples->{$key}';\n". + " }\n"; + } + } + $multtext .= " return '';\n"; + + $arrayindexofjs = &Apache::loncommon::javascript_array_indexof(); + return <<"ENDSCRIPT"; +<script type="text/javascript"> +// <![CDATA[ + +function file_submission_check(formname,path,multiresp) { + var elemnum = formname.elements.length; + if (elemnum == 0) { + return true; + } + var alloverwrites = []; + var alldelconfirm = []; + var result = []; + var submitter; + var subprefix; + var allsub = getIndexByName(formname,'all_submit'); + if (allsub == -1) { + var idx = getIndexByName(formname,'submitted'); + if (idx != -1) { + var subval = String(formname.elements[idx].value); + submitter = subval.replace(/^part_/,''); + result = overwritten_check(formname,path,multiresp,submitter); + alloverwrites.push.apply(alloverwrites,result['overwrite']); + alldelconfirm.push.apply(alldelconfirm,result['delete']); + } + } else { + if (formname.elements[allsub].type == 'submit') { + var partsub = /^\\d+\\.\\d+_submit_.+\$/; + var allprefixes = []; + var allparts = []; + for (var i=0; i<formname.elements.length; i++) { + if (formname.elements[i].type == 'submit') { + var elemname = formname.elements[i].name; + var subname = String(elemname); + var savesub = String(elemname); + if (partsub.test(subname)) { + var prefix = subname.replace(/_submit_.+\$/,''); + if (allprefixes.indexOf(prefix) == -1) { + allprefixes.push(prefix); + allparts[prefix] = []; + } + var part = savesub.replace(/^\\d+\\.\\d+_submit_/,''); + allparts[prefix].push(part); + } + } + } + for (var k=0; k<allprefixes.length; k++) { + var idx = getIndexByName(formname,allprefixes[k]+'_submitted'); + if (idx > -1) { + if (formname.elements[idx].value != 'yes') { + submitterval = formname.elements[idx].value; + submitter = submitterval.replace(/^part_/,''); + subprefix = allprefixes[k]; + result = overwritten_check(formname,path,multiresp,submitter,subprefix); + alloverwrites.push.apply(alloverwrites,result['overwrite']); + alldelconfirm.push.apply(alldelconfirm,result['delete']); + break; + } + } + } + if (submitter == '' || submitter == undefined) { + for (var m=0; m<allprefixes.length; m++) { + for (var n=0; n<allparts[allprefixes[m]].length; n++) { + var result = overwritten_check(formname,path,multiresp,allparts[allprefixes[m]][n],allprefixes[m]); + alloverwrites.push.apply(alloverwrites,result['overwrite']); + alldelconfirm.push.apply(alldelconfirm,result['delete']); + } + } + } + } + } + if (alloverwrites.length > 0) { + if (!confirm("$overwritewarn")) { + for (var n=0; n<alloverwrites.length; n++) { + formname.elements[alloverwrites[n]].value = ""; + } + return false; + } + } + if (alldelconfirm.length > 0) { + if (!confirm("$delfilewarn")) { + for (var p=0; p<alldelconfirm.length; p++) { + formname.elements[alldelconfirm[p]].checked = false; + } + return false; + } + } + return true; +} + +function getIndexByName(formname,item) { + for (var i=0;i<formname.elements.length;i++) { + if (formname.elements[i].name == item) { + return i; + } + } + return -1; +} + +function overwritten_check(formname,path,multiresp,part,prefix) { + var result = []; + result['overwrite'] = []; + result['delete'] = []; + var elemnum = formname.elements.length; + if (elemnum == 0) { + return result; + } + var uploadstr; + var deletestr; + if ((prefix != undefined) && (prefix != '')) { + var prepend = prefix+'_'; + uploadstr = new RegExp("^"+prepend+"HWFILE"+part+".+\$"); + deletestr = new RegExp("^"+prepend+"HWFILE"+part+".+_\\\\d+_delete\$"); + multiresp = check_for_multiples(prepend); + path = check_for_turninpath(prepend); } else { - $output.=$uri; + uploadstr = new RegExp("^HWFILE"+part+".+\$"); + deletestr = new RegExp("^HWFILE"+part+".+_\\\\d+_delete\$"); + } + var alluploads = []; + var allchecked = []; + var allskipdel = []; + var fnametrim = /[^\\/\\\\]+\$/; + for (var i=0; i<formname.elements.length; i++) { + var id = formname.elements[i].id; + if (id != '') { + if (uploadstr.test(id)) { + if (formname.elements[i].type == 'file') { + alluploads.push(id); + } else { + if (deletestr.test(id)) { + if (formname.elements[i].type == 'checkbox') { + if (formname.elements[i].checked) { + allchecked.push(id); + } + } + } + } + } + } + } + for (var j=0; j<alluploads.length; j++) { + var delstr = new RegExp("^"+alluploads[j]+"_\\\\d+_delete\$"); + var delboxes = []; + for (var k=0; k<formname.elements.length; k++) { + var id = formname.elements[k].id; + if ((id != '') && (id != undefined)) { + if (delstr.test(id)) { + if (formname.elements[k].type == 'checkbox') { + delboxes.push(id); + } + } + } + } + if (delboxes.length > 0) { + if ((formname.elements[alluploads[j]].value != undefined) && + (formname.elements[alluploads[j]].value != '')) { + var filepath = formname.elements[alluploads[j]].value; + var newfilename = fnametrim.exec(filepath); + if (newfilename != null) { + var filename = String(newfilename); + var nospaces = filename.replace(/\\s+/g,'_'); + var nospecials = nospaces.replace(/[^\\/\\w\\.\\-]/g,''); + var cleanfilename = nospecials.replace(/\\.(\\d+\\.)/g,"_\$1"); + if (cleanfilename != '') { + var fullpath = path+"/"+cleanfilename; + if (multiresp == 1) { + var partid = String(alluploads[i]); + var subdir = partid.replace(/^\\d*.?\\d*_?HWFILE/,''); + if (subdir != "" && subdir != undefined) { + fullpath = path+"/"+subdir+"/"+cleanfilename; + } + } + for (var m=0; m<delboxes.length; m++) { + if (fullpath == formname.elements[delboxes[m]].value) { + if (formname.elements[delboxes[m]].checked) { + allskipdel.push(delboxes[m]); + } else { + result['overwrite'].push(alluploads[j]); + } + break; + } + } + } + } + } + } + } + if (allchecked.length > 0) { + if (allskipdel.length > 0) { + for (var n=0; n<allchecked.length; n++) { + if (allskipdel.indexOf(allchecked[n]) == -1) { + result['delete'].push(allchecked[n]); + } + } + } else { + result['delete'].push.apply(result['delete'],allchecked); + } + } + return result; +} + +function check_for_multiples(prefix) { +$multtext +} + +function check_for_turninpath(prefix) { +$turninpathtext +} + +// ]]> +</script> + +$arrayindexofjs + +ENDSCRIPT +} + +############################################## +############################################## + +# javascript_valid_email +# +# Generates javascript to validate an e-mail address. +# Returns a javascript function which accetps a form field as argumnent, and +# returns false if field.value does not satisfy two regular expression matches +# for a valid e-mail address. Backwards compatible with old browsers without +# support for javascript RegExp (just checks for @ in field.value in this case). + +sub javascript_valid_email { + my $scripttag .= <<'END'; +function validmail(field) { + var str = field.value; + if (window.RegExp) { + var reg1str = "(@.*@)|(\\.\\.)|(@\\.)|(\\.@)|(^\\.)"; + var reg2str = "^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$"; //" + var reg1 = new RegExp(reg1str); + var reg2 = new RegExp(reg2str); + if (!reg1.test(str) && reg2.test(str)) { + return true; + } + return false; + } + else + { + if(str.indexOf("@") >= 0) { + return true; + } + return false; } - unless ($uri=~/\/$/) { $output=~s/\/$//; } - return $output.'</font></b></tt><br />'; } +END + return $scripttag; +} + + +# USAGE: htmltag(element, content, {attribute => value,...}); +# +# EXAMPLES: +# - htmltag('a', 'this is an anchor', {href => 'www.example.com', +# title => 'this is a title'}) +# +# - You might want to set up needed tags like: +# +# my $h3 = sub { return htmltag( "h3", @_ ) }; +# +# ... and use them: $h3->("This is a headline") +# +# - To set up a couple of tags, see sub inittags +# +# NOTES: +# - Empty elements, such as <br/> are correctly terminated, +# i.e. htmltag('br') returns <br/> +# - Empty attributes (title="") are filtered out. +# - The function will not check for deprecated attributes. +# +# OUTPUT: content enclosed in xhtml conform tags +sub htmltag{ + return + qq|<$_[0]| + . join( '', map { qq| $_="${$_[2]}{$_}"| if ${$_[2]}{$_} } keys %{ $_[2] } ) + . ($_[1] ? qq|>$_[1]</$_[0]>| : qq|/>|). "\n"; +}; + + +# USAGE: inittags(@tags); +# +# EXAMPLES: +# - my ($h1, $h2, $h3) = inittags( qw( h1 h2 h3 ) ) +# $h1->("This is a headline") #Returns: <h1>This is a headline</h1> +# +# NOTES: See sub htmltag for further information. +# +# OUTPUT: List of subroutines. +sub inittags { + my @tags = @_; + return map { my $tag = $_; + sub { return htmltag( $tag, @_ ) } + } @tags; +} + + +# USAGE: scripttag(scriptcode, [start|end|both]); +# +# EXAMPLES: +# - scripttag("alert('Hello World!')", 'both') +# returns: +# <script type="text/javascript"> +# // BEGIN LON-CAPA Internal +# alert(Hello World!') +# // END LON-CAPA Internal +# </script> +# +# NOTES: +# - works currently only for javascripts +# +# OUTPUT: +# Scriptcode properly enclosed in <script> and CDATA tags (and LC +# Internal markers if 2nd argument is given) +sub scripttag { + my ( $content, $marker ) = @_; + return unless defined $content; + + my $begin = "\n// BEGIN LON-CAPA Internal\n"; + my $end = "\n// END LON-CAPA Internal\n"; + + if ($marker) { + $content = $begin . $content if $marker eq 'start' or $marker eq 'both'; + $content .= $end if $marker eq 'end' or $marker eq 'both'; + } + + $content = "\n// <![CDATA[\n$content\n// ]]>\n"; + + return htmltag('script', $content, {type => 'text/javascript'}); +}; + +=pod + +=item &list_from_array( \@array, { listattr =>{}, itemattr =>{} } ) + +Constructs a XHTML list from \@array. + +input: + +=over + +=item \@array + +A reference to the array containing text that will be wrapped in <li></li> tags. + +=item { listattr => {}, itemattr =>{} } + +Attributes for <ul> and <li> passed in as hash references. +See htmltag() for more details. + +=back + +returns: XHTML list as String. + +=cut + +# \@items, {listattr => { class => 'abc', id => 'xyx' }, itemattr => {class => 'abc', id => 'xyx'}} +sub list_from_array { + my ($items, $args) = @_; + return unless (ref($items) eq 'ARRAY'); + return unless scalar @$items; + my ($ul, $li) = inittags( qw(ul li) ); + my $listitems = join '', map { $li->($_, $args->{itemattr}) } @$items; + return $ul->( $listitems, $args->{listattr} ); +} + + +############################################## +############################################## + +# generate_menu +# +# Generates html markup for a menu. +# +# Inputs: +# An array of following structure: +# ({ categorytitle => 'Categorytitle', +# items => [ +# { +# linktext => 'Text to be displayed', +# url => 'URL the link is pointing to, i.e. /adm/site?action=dosomething', +# permission => 'Contains permissions as returned from lonnet::allowed(), +# must evaluate to true in order to activate the link', +# icon => 'icon filename', +# alttext => 'alt text for the icon', +# help => 'Name of the corresponding helpfile', +# linktitle => 'Description of the link (used for title tag)' +# }, +# ... +# ] +# }, +# ... +# ) +# +# Outputs: A scalar containing the html markup for the menu. + +sub generate_menu { + my @menu = @_; + # subs for specific html elements + my ($h3, $div, $ul, $li, $a, $img) = inittags( qw(h3 div ul li a img) ); + + my @categories; # each element represents the entire markup for a category + + foreach my $category (@menu) { + my @links; # contains the links for the current $category + foreach my $link (@{$$category{items}}) { + next unless $$link{permission}; + + # create the markup for the current $link and push it into @links. + # each entry consists of an image and a text optionally followed + # by a help link. + my $src; + if ($$link{icon} ne '') { + $src = '/res/adm/pages/'.$$link{icon}; + } + push(@links,$li->( + $a->( + $img->("", { + class => "LC_noBorder LC_middle", + src => $src, + alt => mt(defined($$link{alttext}) ? + $$link{alttext} : $$link{linktext}) + }), { + href => $$link{url}, + title => mt($$link{linktitle}), + class => 'LC_menubuttons_link' + }). + $a->(mt($$link{linktext}), { + href => $$link{url}, + title => mt($$link{linktitle}), + class => "LC_menubuttons_link" + }). + (defined($$link{help}) ? + Apache::loncommon::help_open_topic($$link{help}) : ''), + {class => "LC_menubuttons_inline_text"})); + } + + # wrap categorytitle in <h3>, concatenate with + # joined and in <ul> tags wrapped @links + # and wrap everything in an enclosing <div> and push it into + # @categories + # such that each element looks like: + # <div><h3>title</h3><ul><li>...</li>...</ul></div> + # the category won't be added if there aren't any links + push(@categories, + $div->($h3->(mt($$category{categorytitle}), {class=>"LC_hcell"}). + $ul->(join('' ,@links), {class =>"LC_ListStyleNormal" }), + {class=>"LC_Box LC_400Box"})) if scalar(@links); + } + + # wrap the joined @categories in another <div> (column layout) + return $div->(join('', @categories), {class => "LC_columnSection"}); +} + +############################################## +############################################## + +=pod + +=item &start_funclist() + +Start list of available functions + +Typically used to offer a simple list of available functions +at top or bottom of page. +All available functions/actions for the current page +should be included in this list. + +If the optional headline text is not provided, a default text will be used. + + +Related routines: +=over 4 +add_item_funclist +end_funclist +=back + + +Inputs: (optional) headline text + +Returns: HTML code with function list start + +=cut + +############################################## +############################################## + +sub start_funclist { + my($legendtext)=@_; + $legendtext=&mt('Functions') if !$legendtext; + return '<ul class="LC_funclist"><li style="font-weight:bold; margin-left:0.8em;">'.$legendtext.'</li>'."\n"; +} + + +############################################## +############################################## + +=pod + +=item &add_item_funclist() + +Adds an item to the list of available functions + +Related routines: +=over 4 +start_funclist +end_funclist +=back + +Inputs: content item with text and link to function + +Returns: HTML code with list item for funclist + +=cut + +############################################## +############################################## + +sub add_item_funclist { + my($content) = @_; + return '<li>'.$content.'</li>'."\n"; +} + +=pod + +=item &end_funclist() + +End list of available functions + +Related routines: +=over 4 +start_funclist +add_item_funclist +=back + +Inputs: ./. + +Returns: HTML code with function list end +=cut + +sub end_funclist { + return "</ul>\n"; +} + +=pod + +=item &funclist_from_array( \@array, {legend => 'text for legend'} ) + +Constructs a XHTML list from \@array with the first item being visually +highlighted and set to the value of legend or 'Functions' if legend is +empty. + +=over + +=item \@array + +A reference to the array containing text that will be wrapped in <li></li> tags. + +=item { legend => 'text' } + +A string that's used as visually highlighted first item. 'Functions' is used if +it's value evaluates to false. + +=back + +returns: XHTML list as string. + +=back + +=cut +sub funclist_from_array { + my ($items, $args) = @_; + return unless(ref($items) eq 'ARRAY'); + $args->{legend} ||= mt('Functions'); + return list_from_array( [$args->{legend}, @$items], + { listattr => {class => 'LC_funclist'} }); +} 1;