# The LearningOnline Network
# Syllabus
#
# $Id: lonsyllabus.pm,v 1.106.2.2 2010/02/15 16:05:32 faziophi 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::lonannounce;
use Apache::lonlocal;
use Apache::lonhtmlcommon;
use Apache::lonspeller();
use HTML::Entities();
use Digest::MD5 qw(md5_hex);
use Storable qw(freeze thaw);
# These are strings representing types of fields
# that will used to parse/display a field correctly
use constant {
TYPE_TEXT_HTML => 'html', #<-- default
TYPE_TEXT_PLAIN => 'text',
TYPE_URL_INCLUDE => 'include-url',
};
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'},['delete', 'rename', 'field', 'forcestudent','register','forceedit','forceflush','wrapperdisplay']);
# ----------------------------------------------------- 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;
}
# ------------------------------------- There is such a course, get environment
my %courseenv=&Apache::lonnet::dump('environment',$cdom,$cnum);
# ------------------------------------------------------------ Print the screen
if ($target eq 'tex') {
$r->print(&Apache::lonprintout::print_latex_header($env{'form.latex_type'}));
}
# -------------------------------------------------- Let's see who handles this
my $externalsyllabus=$courseenv{'externalsyllabus'};
if ($externalsyllabus=~/\w/) {
if ($env{'form.wrapperdisplay'} eq 'menu') {
$r->print(&Apache::lonwrapper::simple_menu());
} else {
$r->print(&Apache::lonwrapper::wrapper("/public/$cdom/$cnum/syllabus?wrapperdisplay=menu",
$externalsyllabus));
}
return OK;
}
# --------------------------------------------------------- The old 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');
# --------------------------------------------------------------- Force Student
my $forcestudent='';
if ($env{'form.forcestudent'}) { $forcestudent='student'; };
my $forceedit='';
if ($env{'form.forceedit'}) { $forceedit='edit'; }
# ----------------------------------------------------------------- Make header
if ($target ne 'tex') {
my $rss_link = &Apache::lonrss::rss_link($cnum,$cdom);
my $js;
if ($env{'form.backto'} eq 'coursecatalog') {
$js .= <<"ENDSCRIPT";
ENDSCRIPT
}
my $start_page =
&Apache::loncommon::start_page("Syllabus", $rss_link.$js,
{'function' => undef,
'domain' => $cdom,
'force_register' =>
$env{'form.register'},});
$r->print($start_page);
if ($env{'form.backto'} eq 'coursecatalog') {
&Apache::lonhtmlcommon::clear_breadcrumbs();
&Apache::lonhtmlcommon::add_breadcrumb
({href=>"javascript:ToCatalog()",
text=>"Course/Community Catalog"});
if ($env{'form.coursenum'} ne '') {
&Apache::lonhtmlcommon::add_breadcrumb
({href=>"javascript:ToCatalog('details')",
text=>"Course details"});
}
&Apache::lonhtmlcommon::add_breadcrumb
({href=>$r->uri,
text=>"Course syllabus"});
$r->print(&Apache::lonhtmlcommon::breadcrumbs());
}
}
# ---------------------------------------------------------- Load syllabus info
my %syllabus=&Apache::lonnet::dump('syllabus',$cdom,$cnum); # load db
my $allowed=0; # can we edit this page?
my $privileged=0;
my %data;
if ($env{'form.forceflush'}) {
delete $syllabus{'data.fields'};
&Apache::lonnet::del('syllabus', ['data.fields'], $cdom, $cnum);
delete $syllabus{'properties.v2_conflict'};
&Apache::lonnet::del('syllabus', ['properties.v2_conflict'], $cdom, $cnum);
delete $syllabus{'properties.v2_conflict_fail'};
&Apache::lonnet::del('syllabus', ['properties.v2_conflict_fail'], $cdom, $cnum);
delete $syllabus{'properties.last_modified'};
&Apache::lonnet::del('syllabus', ['properties.last_modified'], $cdom, $cnum);
delete $syllabus{'properties.v2_converted'};
&Apache::lonnet::del('syllabus', ['properties.v2_converted'], $cdom, $cnum);
delete $syllabus{'data.old_new_map'};
&Apache::lonnet::del('syllabus', ['data.old_new_map'], $cdom, $cnum);
delete $syllabus{'data.deleted_fields'};
&Apache::lonnet::del('syllabus', ['data.deleted_fields'], $cdom, $cnum);
%syllabus=&Apache::lonnet::dump('syllabus',$cdom,$cnum); # load db
$r->print("Flushed syllabus DB file.
");
$r->print("Syllabus conflict: ".$syllabus{'properties.v2_conflict'}."
");
}
$r->print("Existing fields: ".$syllabus{'data.fields'}."
");
$r->print("Old-new map: ".$syllabus{'data.old_new_map'}."
");
$r->print("Deleted fields: ".$syllabus{'data.deleted_fields'}."
");
if (!exists($syllabus{'data.fields'})) {
# convert existing 2.x data to new DB fields
# which become new primary data source for document
%data = %{convert_from_v2($r, \%syllabus, \%syllabusfields, 0)};
$r->print("New fields order: ".$data{'data.fields'}."
");
&Apache::lonnet::put('syllabus',\%data,$cdom,$cnum);
} elsif ( !exists($syllabus{'properties.v2_converted'}) &&
exists($syllabus{'uploaded.lastmodified'}) &&
exists($syllabus{'properties.last_modified'}) &&
($syllabus{'uploaded.lastmodified'} !=
$syllabus{'properties.last_modified'})) {
# if the document has been saved in 3.x and later edited in
# 2.x, reconvert the existing document, with extra warning
%data = %{convert_from_v2($r, \%syllabus, \%syllabusfields, 1)};
delete $data{'properties.v2_converted'};
&Apache::lonnet::del('syllabus', ['properties.v2_converted'], $cdom, $cnum);
$data{'properties.v2_conflict'} = 1;
&Apache::lonnet::put('syllabus',\%data,$cdom,$cnum);
} else {
%data = %syllabus;
}
# ----------------------------------------------------- Only if not public call
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'});
$privileged=$allowed;
if (($data{'uploaded.lastmodified'}) && (!$forceedit)) {
$forcestudent='student';
}
if ($forcestudent or $target eq 'tex') { $allowed=0; }
}
#store what the user typed in
my @fields = @{thaw($data{'data.fields'})};
if (($allowed) && ($env{'form.delete'})) {
my $field = $env{'form.delete'};
chomp($field);
#allow only numbers, underscores
$field=~s/[^0-9_]//g;
#check if the field exists
#do not delete if file in v2 conversion mode
if (exists($data{'data.field.'.$field}) &&
!exists($data{'properties.v2_converted'})) {
$r->print("Field '$field' can be deleted.
");
# linearly parse "data.fields" and remove it
for (my $i = 0; $i < length(@fields); $i++) {
if ($fields[$i] eq $field) {
splice(@fields, $i, 1);
$r->print("Removed entry $i from 'data.fields'
");
}
}
# if "data.deleted" does not exist, create it
my @deleted;
if (!exists($data{'data.deleted_fields'})) {
@deleted = [];
} else {
@deleted = @{thaw($data{'data.deleted_fields'})};
}
# only if deleted does not exist in 'data.deleted', push it
my $push = 1;
for (my $i = 0; $i < length(@deleted); $i++) {
if ($deleted[$i] eq $field) {
$push = 0;
last;
}
}
unless (!$push) {
push(@deleted, $field);
}
$data{'data.fields'} = freeze(\@fields);
$data{'data.deleted_fields'} = freeze(\@deleted);
&Apache::lonnet::put('syllabus',\%data,$cdom,$cnum);
}
}
if (($allowed) && ($env{'form.rename'})) {
my $field = $env{'form.rename'};
my $new_title = "Hello, World!";
chomp($field);
$field=~s/[^0-9_]//g;
#check if the field exists
#do not delete if file in v2 conversion mode
if (exists($data{'data.field.'.$field}) &&
!exists($data{'properties.v2_converted'})) {
#sanitize HTML content
$r->print("Rename -- field found.
");
my %db_entry = %{thaw($data{'data.field.'.$field})};
$new_title = &Apache::lonfeedback::clear_out_html($new_title, 1);
$db_entry{title} = $new_title;
$data{'data.field.'.$field} = freeze(\%db_entry);
&Apache::lonnet::put('syllabus',\%data,$cdom,$cnum);
}
}
if (($allowed) && ($env{'form.storesyl'})) {
foreach my $syl_field (@fields) {
my $field=$env{'form.'.$syl_field};
my $type;
my %field_hash;
# only update a field if it already exists!
if (exists($data{'data.field.'.$syl_field})) {
$r->print("Creating/updated field ".$syl_field."
");
%field_hash = exists($data{'data.field.'.$syl_field}) ?
%{thaw($data{'data.field.'.$syl_field})} : ();
$type = exists($field_hash{type}) ? $field_hash{type} : TYPE_TEXT_HTML;
chomp($field);
$field=~s/\s+$//s;
$field=~s/^\s+//s;
$field=~s/\
$//s;
$field=&Apache::lonfeedback::clear_out_html($field,1);
$field_hash{content}=$field;
if ($type eq TYPE_URL_INCLUDE) { # clean up included URLs
my $field='';
foreach my $value (split(/\n/,$field_hash{content})) {
my $url=$value;
# get rid of leading and trailing spaces
$url=~s/^\s+//;
$url=~s/\s+$//;
if ($url=~m|^https?\://([^/]+)/(.+)$|) {
my $host = $1;
my $remainder=$2;
# remove the hostname from internal URLs
my $hostname = &Apache::lonnet::hostname($host);
my %all_hostnames = &Apache::lonnet::all_hostnames();
foreach my $possible_host (keys(%all_hostnames)) {
if ($possible_host =~ /\Q$hostname\E/i) {
$url=$remainder;
}
}
}
# norm internal URLs
unless ($url=~/^https?\:/) {
$url=&Apache::lonnet::clutter($url);
}
# re-assemble field
if ($url) {
$field.=$url."\n";
}
}
$field_hash{content}=$field;
$field_hash{type}=TYPE_URL_INCLUDE;
}
$data{'data.field.'.$syl_field} = freeze(\%field_hash);
}
}
$data{'uploaded.domain'}=$env{'user.domain'};
$data{'uploaded.name'}=$env{'user.name'};
my $time = $^T;
$data{'uploaded.lastmodified'}=$time;
$data{'properties.last_modified'}=$time;
delete $data{'properties.v2_converted'};
delete $data{'properties.v2_conflict'};
delete $data{'properties.v2_conflict_fail'};
&Apache::lonnet::del('syllabus', ['properties.v2_converted',
'properties.v2_conflict', 'properties.v2_conflict_fail'], $cdom, $cnum);
#2.x compatibility: write to old fields with new mapped fields
my %old_new_map = %{thaw($data{'data.old_new_map'})};
foreach my $old_field (keys(%old_new_map)) {
$r->print("Looking for: ".$old_field." at ".$old_new_map{$old_field}."
");
if (exists($data{'data.field.'.$old_new_map{$old_field}})) {
$r->print("updating old field ".$old_field."
");
my %new_field = %{thaw($data{'data.field.'.$old_new_map{$old_field}})};
$data{$old_field} = $new_field{content};
}
}
&Apache::lonnet::put('syllabus',\%data,$cdom,$cnum);
}
}
#--------Functions
if( ($allowed || $privileged) && $target ne 'tex') {
my $functions=&Apache::lonhtmlcommon::start_funclist();
if ($allowed) {
#if you have the register flag, keep it
if($env{'form.register'} == 1) {
$functions.=&Apache::lonhtmlcommon::add_item_funclist(
''
.&mt('Show Public View').''
.&Apache::loncommon::help_open_topic(
'Uploaded_Templates_PublicView'));
} else {
$functions.=&Apache::lonhtmlcommon::add_item_funclist(
''
.&mt('Show Public View').''
.&Apache::loncommon::help_open_topic(
'Uploaded_Templates_PublicView'));
}
} elsif ($privileged) {
if($env{'form.register'} == 1) {
$functions.=&Apache::lonhtmlcommon::add_item_funclist(
''
.&mt('Edit').'');
} else {
$functions.=&Apache::lonhtmlcommon::add_item_funclist(
''
.&mt('Edit').'');
}
}
$functions.=&Apache::lonhtmlcommon::end_funclist();
$r->print(&Apache::loncommon::head_subbox($functions));
}
#---------------------Print External URL Syllabus Info and Help Text
if( ($allowed) && ($target ne 'tex') ) {
my $protocol = $Apache::lonnet::protocol{$homeserver};
$protocol = 'http' if ($protocol ne 'https');
$r->print('
' .&mt('This syllabus can be publicly viewed at [_1]' ,''.$protocol.'://'.&Apache::lonnet::hostname($homeserver).$r->uri.'') .' '.&Apache::loncommon::help_open_topic('Syllabus_ExtLink') .'
' .'' .&mt('Instead of using this template you can specify an external URL as Syllabus in the [_1]Course Configuration[_2].' ,'','') .'
' ); #-Print Help Text $r->print(&Apache::loncommon::help_open_topic( 'Uploaded_Templates_TextBoxes', &mt('Help with filling in text boxes'))); } #----------Print last update my $lastmod=$syllabus{'uploaded.lastmodified'}; $lastmod=($lastmod?&Apache::lonlocal::locallocaltime($lastmod):&mt('never')); my $who = &Apache::loncommon::aboutmewrapper( &Apache::loncommon::plainname($syllabus{'uploaded.name'}, $syllabus{'uploaded.domain'}),$syllabus{'uploaded.name'}, $syllabus{'uploaded.domain'}); if ($target ne 'tex') { $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_template_new_fields { my ($r, $data_ref, $target, $allowed, $default_rich_text, $custom_handlers_ref, $group) = @_; my @html_ids = (); my %data = %{$data_ref}; my @fields = @{thaw($data{'data.fields'})}; my %custom_handlers = %{$custom_handlers_ref}; foreach my $key (@fields) { my %field = %{thaw($data{'data.field.'.$key})}; my $title = $field{title}; my $raw_message = $field{content}; my $type = $field{type}; my $message = $raw_message if (($raw_message=~/\w/) || ($allowed)); if ((%custom_handlers) && exists($custom_handlers{$type})) { #$custom_handlers{$type}->($r, $field, $json_ref, $group, $target, $allowed); } else { if (($raw_message=~/\w/) || ($allowed)) { if (!&Apache::lonfeedback::contains_block_html($message)) { &Apache::lonfeedback::newline_to_br(\$message); } else { $message = &Apache::lonfeedback::tidy_html($message); } $message=&Apache::lonhtmlcommon::raw_href_to_link($message); if ($allowed) { $message=&Apache::lonspeller::markeduptext($message); } $message=&Apache::lontexconvert::msgtexconverted($message); if ($target ne 'tex') { #output of syllabusfields will be generated here. &Apache::lontemplate::print_start_template($r,$title,'LC_Box'); $r->print($message); if ($allowed) { $r->print("