# The LearningOnline Network # Syllabus # # $Id: lonsyllabus.pm,v 1.155 2024/01/25 22:23:32 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # package Apache::lonsyllabus; use strict; use Apache::lontemplate; use Apache::Constants qw(:common); use Apache::loncommon; use Apache::lonnet; use Apache::lontexconvert; use Apache::lonfeedback; use Apache::lonhtmlgateway; use Apache::lonannounce; use Apache::lonlocal; use Apache::lonhtmlcommon; use Apache::lonspeller(); use Apache::lonwrapper(); use HTML::Entities(); sub handler { my $r = shift; &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; return OK if $r->header_only; my $target=$env{'form.grade_target'}; # --------------------------------------------------- Get course info from URL my (undef,undef,$cdom,$cnum)=split(/\//,$r->uri); # ------------------------------------------------------------ Get query string &Apache::loncommon::get_unprocessed_cgi ($ENV{'QUERY_STRING'},['register','forceedit','todocs', 'folderpath','title','only_body']); # ----------------------------------------------------- Is this even a course? my $homeserver=&Apache::lonnet::homeserver($cnum,$cdom); if ($homeserver eq 'no_host') { &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; &Apache::loncommon::simple_error_page($r,'No syllabus available', 'No syllabus available'); return OK; } elsif (!&Apache::lonnet::is_course($cdom,$cnum)) { &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; &Apache::loncommon::simple_error_page($r,'No syllabus available', 'The course/community for which the syllabus was requested does not exist.'); return OK; } # ------------------------------------- There is such a course, get environment my %courseenv=&Apache::lonnet::dump('environment',$cdom,$cnum); my $crstype = &Apache::loncommon::course_type(); # --------------------------------------------------------------- Force Student my ($forceedit,$forcestudent); if ($env{'form.forceedit'}) { $forceedit=1; } if (!$forceedit) { $forcestudent=1; } # --------------------------------------------------------------- Check Privileges my $allowed = 0; if ($env{'user.environment'}) { # does this user have privileges to post, etc? if ($env{'request.course.id'} && $cdom eq $env{'course.'.$env{'request.course.id'}.'.domain'} && $cnum eq $env{'course.'.$env{'request.course.id'}.'.num'}) { $allowed=&Apache::lonnet::allowed('mdc',$env{'request.course.id'}); if ($forcestudent or $target eq 'tex') { $allowed=0; } } } # -------------------------------------------------- Let's see who handles this my $external=$courseenv{'externalsyllabus'}; my $uploaded=$courseenv{'uploadedsyllabus'}; my $minimal=$courseenv{'minimalsyllabus'}; if (($minimal =~/\w/) || ($uploaded =~/\w/)) { my ($item,$is_pdf); if ($minimal =~/\w/) { if ($external =~ m{\Q$minimal\E$}) { undef($external); } $item = $minimal; } elsif ($uploaded =~/\w/) { if ($external =~ m{\Q$uploaded\E$}) { undef($external); } $item = $uploaded; if ($item =~ /\.pdf$/i) { $is_pdf = 1; } } unless ($allowed && $forceedit) { my $file=&Apache::lonnet::filelocation("",$item); if ($file =~ /\.(tex|x?html?)$/) { my $filetype = $1; my $filecontents=&Apache::lonnet::getfile($file); if ($filecontents eq -1) { $r->print(&mt('Syllabus file unavailable')); } elsif ($filetype eq 'tex') { if ($target eq 'tex') { $r->print($filecontents); } else { my $texengine = $env{'form.texengine'}; if ($texengine eq '') { $texengine = 'tth'; } elsif (lc($texengine) eq 'jsmath') { $texengine = 'MathJax'; } my $result = &Apache::lontexconvert::converted(\$filecontents,$texengine); my %args; &get_breadcrumbs($cdom,$cnum,$crstype,\%args); if ($env{'form.only_body'}) { $args{'only_body'} = 1; } if ($env{'request.use_absolute'}) { $args{'use_absolute'} = $env{'request.use_absolute'}; } $r->print(&Apache::loncommon::start_page("Syllabus",undef,\%args). $result. &Apache::loncommon::end_page()); } } else { my %mystyle; unless ($target eq 'tex') { $target = 'web'; } &Apache::structuretags::reset_problem_globals(); my $oldfile = $env{'request.filename'}; $env{'request.filename'} = $item; my $oldinhibit; if ($env{'form.only_body'}) { $oldinhibit = $env{'form.inhibitmenu'}; $env{'form.inhibitmenu'} = 'yes'; } my $result = &Apache::lonxml::xmlparse($r,$target,$filecontents, '',%mystyle); if ($env{'form.only_body'}) { if ($oldinhibit ne '') { $env{'form.inhibitmenu'} = $oldinhibit; } else { delete($env{'form.inhibitmenu'}); } } &Apache::structuretags::reset_problem_globals(); &Apache::lonhomework::finished_parsing(); $env{'request.filename'} = $oldfile; &Apache::lonxml::add_messages(\$result); $r->print($result); } } else { if ($target eq 'tex') { $r->print(&Apache::lonprintout::print_latex_header($env{'form.latex_type'}). ' \strut \\\\ \textbf{'.&mt('Syllabus').'} \strut \\\\ '. &mt('Unsupported file type.').' \strut \\\\ '. &mt('Print the syllabus directly from your web browser'). '\end{document}'); } else { my $brcrum; if ($env{'form.folderpath'} =~ /^supplemental/) { &Apache::loncommon::validate_folderpath(1,'',$cnum,$cdom); my $title = $env{'form.title'}; if ($title eq '') { $title = &mt('Syllabus'); } $brcrum = &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); } $r->print(&Apache::lonwrapper::wrapper($r,$item,$brcrum,$env{'request.use_absolute'}, undef,$is_pdf,undef,'','',&mt('Syllabus'))); } } return OK; } } elsif ($external=~/\w/) { unless ($allowed && $forceedit) { if (($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public') && ($ENV{'SERVER_PORT'} == 443) && ($external =~ m{^http://}) && !($env{'form.usehttp'})) { my $hostname = $r->hostname(); unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) { &redirect_to_http($r); return OK; } } if ($target eq 'tex') { $r->print(&Apache::lonprintout::print_latex_header($env{'form.latex_type'}). ' \strut \\\\ \textbf{'.&mt('Syllabus').'} \strut \\\\ '.$external.' '. ' \strut \\\\ '.&mt('Print the syllabus directly from your web browser'). '\end{document}'); } else { my $is_ext = 1; my ($is_pdf,$brcrum); if ($external =~ /\.pdf$/i) { $is_pdf = 1; } if ($env{'form.folderpath'} =~ /^supplemental/) { &Apache::loncommon::validate_folderpath(1,'',$cnum,$cdom); my $title = $env{'form.title'}; if ($title eq '') { $title = &mt('Syllabus'); } $title = &HTML::Entities::encode($title,'\'"<>&'); $brcrum = &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); } $r->print(&Apache::lonwrapper::wrapper($r,$external,$brcrum,$env{'request.use_absolute'}, $is_ext,$is_pdf,undef,'','',&mt('Syllabus'))); } return OK; } } # ------------------------------------------------------------ Print the screen if ($target eq 'tex') { $r->print(&Apache::lonprintout::print_latex_header($env{'form.latex_type'})); } # ------------------------------ The buck stops here: internal syllabus display # --------------------------------------------------------- The syllabus fields my %syllabusfields=&Apache::lonlocal::texthash( 'aaa_instructorinfo' => 'Instructor Information', 'bbb_description' => 'Course Description', 'ccc_prereq' => 'Prerequisites', 'cdc_classhours' => 'Class Hours', 'ddd_officehours' => 'Office Hours', 'eee_helproom' => 'Helproom Hours', 'efe_projectinfo' => 'Project Information', 'fff_examinfo' => 'Exam Information', 'fgf_deadlines' => 'Deadlines', 'ggg_grading' => 'Grading Information', 'hhh_readings' => 'Readings', 'iii_coursepack' => 'Coursepack', 'jjj_weblinks' => 'Web Links', 'kkk_textbook' => 'Textbook', 'lll_includeurl' => 'URLs To Include in Syllabus'); # ---------------------------------------------------------- Load syllabus info my %syllabus=&Apache::lonnet::dump('syllabus',$cdom,$cnum); my ($output,%displayfields,%noshow); # This handler might be called anonymously ... # ----------------------------------------------------- Only if not public call if ($allowed) { if (($env{'form.choice'} =~ /^(template|minimal|url|file)$/) || ($env{'form.phase'} =~ /^(upload|check)_embedded$/)) { my $earlyout; ($earlyout,$uploaded,$external,$minimal,$output) = &save_changes($cnum,$cdom,$uploaded,$external,$minimal, \%syllabus,\%syllabusfields,\%courseenv); if (($env{'form.choice'} eq 'minimal') && ($minimal eq "/uploaded/$cdom/$cnum/portfolio/syllabus/loncapa.html")) { delete($env{'form.symb'}); delete($env{'request.symb'}); $r->internal_redirect("$minimal?editmode=1&forceedit=1"); return OK; } if ($earlyout) { if ($target ne 'tex') { &print_header($r,$cnum,$cdom,$crstype,$allowed,$forceedit, \%syllabus,\%syllabusfields); $r->print($output. &Apache::loncommon::end_page()); } return OK; } } } if ($target ne 'tex') { &print_header($r,$cnum,$cdom,$crstype,$allowed,$forceedit,\%syllabus, \%syllabusfields); $r->print($output); } # -------------------------------------------- Determine which fields are shown if ($syllabus{'uploaded.fields'}) { if ($syllabus{'uploaded.fields'} eq 'none') { foreach my $field (keys(%syllabusfields)) { $displayfields{$field} = ' style="display:none;"'; $noshow{$field} = 1; } } else { my %included; map { $included{$_} = 1; } split(/,/,$syllabus{'uploaded.fields'}); foreach my $field (keys(%syllabusfields)) { my ($prefix) = split(/_/,$field); if ($included{$prefix}) { $displayfields{$field} = ' style="display:block;"'; } else { $displayfields{$field} = ' style="display:none;"'; $noshow{$field} = 1; } } } } else { foreach my $field (keys(%syllabusfields)) { if ($syllabus{$field} ne '') { $displayfields{$field} = ' style="display:block;"'; } else { $displayfields{$field} = ' style="display:none;"'; } } } if ($allowed) { #---------------------------------- Print External URL Syllabus Info if editing if ($target ne 'tex') { my $link = &Apache::lonnet::url_prefix($r,$cdom,$homeserver,'web'). $r->uri; $r->print('
'. &mt('All feeds currently hidden'). $msg. '
'); } else { $r->print(''. &mt('No current feeds'). $msg. '
'); } $rsslinktext = &mt('Manage Course RSS Feeds/Blogs'); if ($crstype eq 'Community') { $rsslinktext = &mt('Manage Community RSS Feeds/Blogs'); } } my $editurl= &Apache::lonnet::absolute_url().'/adm/'.$cdom.'/'.$cnum.'/_rss.html'; $r->print( ''.$rsslinktext.''); &Apache::lontemplate::print_end_template($r); } else { unless ($hidefeeds) { my $feeds = &Apache::lonrss::advertisefeeds($cnum,$cdom,$forceedit); if ($feeds ne '') { &Apache::lontemplate::print_start_template($r,&mt('RSS Feeds and Blogs'),'LC_Box'); $r->print($feeds); &Apache::lontemplate::print_end_template($r); } } } } else { $r->print(&Apache::lonxml::xmlparse($r,'tex',$day)); } # ---------------------------------------------------------------- Get syllabus if (($syllabus{'uploaded.lastmodified'}) || ($allowed)) { my $url_include_handler = sub { my ($r, $field, $message, $group, $data_ref, $fields_ref, $target, $allowed, $display) = @_; my %data = %{$data_ref}; my %fields = %{$fields_ref}; my $urls=$message; $message=''; foreach my $filelink (split(/\n/,$urls)) { my $output=''; # embed style? my ($curfext)=($filelink=~/\.([^\.]+)$/); my $embstyle=&Apache::loncommon::fileembstyle($curfext); if (($embstyle eq 'ssi') || ($curfext=~/\/$/)) {# make ssi call and remove everything but the body contents $output=&Apache::lonnet::ssi_body($filelink); } elsif ($embstyle eq 'img') {# embed as an image $output=''; } if ($output ne '') { if ($target ne 'tex') { $message.=''.$output.'
'; } else { $message.=' '.&Apache::lonxml::xmlparse($r,'tex',''.$output.'
').' '; } } } if ($allowed) { &Apache::lonfeedback::newline_to_br(\$urls); &Apache::lontemplate::print_start_template($r,$fields{$field}. &Apache::loncommon::help_open_topic('Syllabus_URLs'),'LC_Box', 'box_'.$field,$display); $r->print($urls); $r->print("');} else {$r->print('\par ');} $r->print(&mt('No syllabus information provided.')); if ($target ne 'tex') {$r->print('
');} } if ($target ne 'tex') { if ($env{'form.backto'} eq 'coursecatalog') { $r->print(''); } $r->print(&Apache::loncommon::end_page()); } else { $r->print('\end{document}'); } return OK; } sub print_header { my ($r,$cnum,$cdom,$crstype,$allowed,$forceedit,$syllabus,$syllabusfields) = @_; return unless ((ref($syllabus) eq 'HASH') || (ref($syllabusfields) eq 'HASH')); # ----------------------------------------------------------------- Make header my $rss_link = &Apache::lonrss::rss_link($cnum,$cdom); my $js; if ($env{'form.backto'} eq 'coursecatalog') { $js .= <<"ENDSCRIPT"; ENDSCRIPT } if ($allowed && $forceedit) { my $check_uncheck = &Apache::loncommon::check_uncheck_jscript(); my @fieldnames = sort(keys(%{$syllabusfields})); unshift(@fieldnames,'000_showpeople','111_showrssfeeds'); my (@checked,@unchecked); if ($syllabus->{'uploaded.fields'} eq 'none') { my $lastidx = scalar(@fieldnames)-1; @unchecked = (0..$lastidx); } elsif ($syllabus->{'uploaded.fields'}) { my %included; map { $included{$_} = 1; } split(/,/,$syllabus->{'uploaded.fields'}); for (my $i=0; $i<@fieldnames; $i++) { my ($prefix) = split(/_/,$fieldnames[$i]); if ($included{$prefix}) { push(@checked,$i); } else { push(@unchecked,$i); } } } else { @checked = (0,1); for (my $i=2; $i<@fieldnames; $i++) { if ($syllabus->{$fieldnames[$i]}) { push(@checked,$i); } else { push(@unchecked,$i); } } } my $fieldstr = "var fields = new Array('".join("','",@fieldnames)."');"; my $checkedstr = "var include = new Array('".join("','",@checked)."');"; my $uncheckedstr = "var exclude = new Array('".join("','",@unchecked)."');"; my $invurl = &mt('Invalid URL'); &js_escape(\$invurl); my $urlregexp = <<'ENDREGEXP'; /^([a-z]([a-z]|\d|\+|-|\.)*):(\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?((\[(|(v[\da-f]{1,}\.(([a-z]|\d|-|\.|_|~)|[!\$&'\(\)\*\+,;=]|:)+))\])|((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=])*)(:\d*)?)(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*|(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)|((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)|((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)){0})(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i ENDREGEXP $js .= <<"ENDSCRIPT"; ENDSCRIPT } my $args = {'function' => undef, 'domain' => $cdom}; my $forcereg; if ($env{'form.register'}) { $forcereg = 1; $args->{'force_register'} = $forcereg; } if ($env{'form.backto'} eq 'coursecatalog') { &Apache::lonhtmlcommon::clear_breadcrumbs(); my $brcrum = [{href=>"javascript:ToCatalog();", text=>&mt('Course/Community Catalog'), no_mt=>1} ]; if ($env{'form.coursenum'} ne '') { push(@{$brcrum}, {href=>"javascript:ToCatalog('details')", text=>"Course details"}); } push(@{$brcrum}, {href=>$r->uri, text=>"Course syllabus"}); $args->{'bread_crumbs'} = $brcrum; } else { &get_breadcrumbs($cdom,$cnum,$crstype,$args); } if ($allowed) { my %loaditem = ( onload => 'setTemplateBoxes();', ); $args->{'add_entries'} = \%loaditem; } else { if ($env{'request.use_absolute'}) { $args->{'use_absolute'} = $env{'request.use_absolute'}; } } if ($env{'form.only_body'}) { $args->{'only_body'} = 1; } $args->{'hostname'} = $r->hostname(); my $start_page = &Apache::loncommon::start_page("Syllabus", $rss_link.$js,$args); if ($start_page) { $r->print($start_page); } } sub get_breadcrumbs{ my ($cdom,$cnum,$crstype,$args) = @_; return unless (ref($args) eq 'HASH'); if ($env{'form.folderpath'} =~ /^supplemental/) { &Apache::loncommon::validate_folderpath(1,'',$cnum,$cdom); my $title = $env{'form.title'}; if ($title eq '') { $title = &mt('Syllabus'); } my $brcrum = &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); if (ref($brcrum) eq 'ARRAY') { $args->{'bread_crumbs'} = $brcrum; $args->{'bread_crumbs_nomenu'} = 1; } } else { if ((&Apache::lonnet::is_on_map("public/$cdom/$cnum/syllabus")) && (($env{'form.symb'}) || ($env{'form.register'}))) { &Apache::lonhtmlcommon::clear_breadcrumbs(); } else { $args->{'bread_crumbs'} = [ {'href' => "/public/$cdom/$cnum/syllabus", 'text' => 'Syllabus'}, ]; } } return; } sub chooser { my ($r,$external,$uploaded,$minimal,$cdom,$cnum,$lonhost,$fields,$values) = @_; my %lt = &Apache::lonlocal::texthash( 'type' => 'Syllabus Type', 'url' => 'External URL', 'file' => 'Uploaded file', 'minimal' => 'Minimal template', 'template' => 'Standard template', 'templateboxes' => 'Choose template items ... ', 'curr' => 'Current:', 'rep' => 'Replace:', 'upl' => 'Upload:', 'pr' => 'Preview', 'save' => 'Save', 'sved' => 'Save and Edit', 'chourl' => 'External URL', 'chofile' => 'Uploaded syllabus file', 'parse' => 'Upload embedded images/multimedia files if HTML file', ); my %checked = ( file => '', minimal => '', url => '', template => '', ); my %display = ( file => 'none', minimal => 'none', url => 'none', templatebox => 'none', ); my $check = ' checked="checked" '; if ($uploaded) { $checked{'file'} = $check; $display{'file'} = 'block'; } elsif ($external) { $checked{'url'} = $check; $display{'url'} = 'block'; } elsif ($minimal) { $checked{'minimal'} = $check; $display{'minimal'} = 'block'; } else { $checked{'template'} = $check; $checked{'templatebox'} = $check; $display{'templatebox'} = 'block'; } my $protocol = ($ENV{'SERVER_PORT'} == 443?'https':'http'); my $output = '