--- loncom/interface/lonprintout.pm 2008/04/04 16:36:54 1.528 +++ loncom/interface/lonprintout.pm 2020/03/05 18:33:35 1.627.2.30 @@ -1,7 +1,7 @@ # The LearningOnline Network # Printout # -# $Id: lonprintout.pm,v 1.528 2008/04/04 16:36:54 raeburn Exp $ +# $Id: lonprintout.pm,v 1.627.2.30 2020/03/05 18:33:35 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,7 +26,6 @@ # # package Apache::lonprintout; - use strict; use Apache::Constants qw(:common :http); use Apache::lonxml; @@ -39,14 +38,19 @@ use Apache::File(); use Apache::lonnavmaps; use Apache::admannotations; use Apache::lonenc; -use HTTP::Response; +use Apache::entities; +use Apache::londefdef; +# use Apache::structurelags; # for language management. +use File::Basename; + +use HTTP::Response; use LONCAPA::map(); -use POSIX qw(strftime); use Apache::lonlocal; use Carp; use LONCAPA; + my %perm; my %parmhash; my $resources_printed; @@ -65,6 +69,723 @@ my $ssi_last_error; # The error text fr my $ssi_retry_count = 5; # Some arbitrary value. +# Font size: + +my $font_size = 'normalsize'; # Default is normalsize... + +#---------------------------- Helper helpers. ------------------------- + +## +# Filter function to determine if a resource is a printable sequence. +# +# @param $res -Resource to check. +# +# @return 1 - printable and a resource +# 0 - either notm a sequence or not printable. +# +sub printable_sequence { + my $res = shift; + + # Non-sequences are not listed: + + if (!$res->is_sequence()) { + return 0; + } + + # Person with pav or pfo can always print: + + if ($perm{'pav'} || $perm{'pfo'}) { + return 1; + } + + if ($res->is_sequence()) { + my $symb = $res->symb(); + my $navmap = $res->{NAV_MAP}; + + # Find the first resource in the map: + + my $iterator = $navmap->getIterator($res, undef, undef, 1, 1); + my $first = $iterator->next(); + + while (1) { + if ($first == $iterator->END_ITERATOR) { last; } + if (ref($first) && ! $first->is_sequence()) {last; } + $first = $iterator->next(); + } + + + # Might be an empty map: + + if (!ref($first)) { + return 0; + } + my $partsref = $first->parts(); + my @parts = @$partsref; + my ($open, $close) = $navmap->map_printdates($first, $parts[0]); + return &printable($open, $close); + } + return 0; +} + +# BZ5209: +# Create the states needed to run the helper for incomplete problems from +# the current folder for selected students. +# This includes: +# - A resource selector limited to problems (incompleteness must be +# calculated on a student per student basis. +# - A student selector. +# - Tie in to the FORMAT of the print job. +# +# States: +# CHOOSE_INCOMPLETE_PEOPLE_SEQ - Resource selection. +# CHOOSE_STUDENTS_INCOMPLETE - Student selection. +# CHOOSE_STUDENTS_INCOMPLETE_FORMAT - Format selection +# Parameters: +# helper - the helper which already contains info about the current folder we can +# purloin. +# url - Top url of the sequence +# Return: +# XML that can be parsed by the helper to drive the state machine. +# +sub create_incomplete_folder_selstud_helper { + my ($helper, $map) = @_; + + + my $symbFilter = '$res->shown_symb()'; + my $selFilter = '$res->is_problem()'; + + + my $resource_chooser = &generate_resource_chooser('CHOOSE_INCOMPLETE_PEOPLE_SEQ', + 'Select problem(s) to print', + 'multichoice="1" toponly="1" addstatus="1" closeallpages="1" modallink="1"', + 'RESOURCES', + 'CHOOSE_STUDENTS_INCOMPLETE', + $map, + $selFilter, + '', + $symbFilter, + ''); + + my $student_chooser = &generate_student_chooser('CHOOSE_STUDENTS_INCOMPLETE', + 'student_sort', + 'STUDENTS', + 'CHOOSE_STUDENTS_INCOMPLETE_FORMAT'); + + my $format_chooser = &generate_format_selector($helper, + 'Format of the print job', + 'CHOOSE_STUDENTS_INCOMPLETE_FORMAT'); # end state. + + return $resource_chooser . $student_chooser . $format_chooser; +} + + +# BZ 5209 +# Create the states needed to run the helper for incomplete problems from +# the current folder for selected students. +# This includes: +# - A resource selector limited to problems. (incompleteness must be calculated +# on a student per student basis. +# - A student selector. +# - Tie in to format for the print job. +# States: +# INCOMPLETE_PROBLEMS_COURSE_RESOURCES - Resource selector. +# INCOMPLETE_PROBLEMS_COURSE_STUDENTS - Student selector. +# INCOMPLETE_PROBLEMS_COURSE_FORMAT - Format selection. +# +# Parameters: +# helper - Helper we are creating states for. +# Returns: +# Text that can be parsed by the helper. +# + +sub create_incomplete_course_helper { + my $helper = shift; + + my $filter = '$res->is_problem() || $res->contains_problem() || $res->is_sequence() || $res->is_practice())'; + my $symbfilter = '$res->shown_symb()'; + + my $resource_chooser = &generate_resource_chooser('INCOMPLETE_PROBLEMS_COURSE_RESOURCES', + 'Select problem(s) to print', + 'multichoice = "1" suppressEmptySequences="0" addstatus="1" closeallpagtes="1" modallink="1"', + 'RESOURCES', + 'INCOMPLETE_PROBLEMS_COURSE_STUDENTS', + '', + $filter, + '', + $symbfilter, + ''); + + my $people_chooser = &generate_student_chooser('INCOMPLETE_PROBLEMS_COURSE_STUDENTS', + 'student_sort', + 'STUDENTS', + 'INCOMPLETE_PROBLEMS_COURSE_FORMAT'); + + my $format = &generate_format_selector($helper, + 'Format of the print job', + 'INCOMPLETE_PROBLEMS_COURSE_FORMAT'); # end state. + + return $resource_chooser . $people_chooser . $format; + + +} + +# BZ5209 +# Creates the states needed to run the print helper for a student +# that wants to print his incomplete problems from the current folder. +# Parameters: +# $helper - helper we are generating states for. +# $map - The map for which the student wants incomplete problems. +# Returns: +# XML that defines the helper states being created. +# +# States: +# CHOOSE_INCOMPLETE_SEQ - Resource selector. +# +sub create_incomplete_folder_helper { + my ($helper, $map) = @_; + + my $filter = '$res->is_problem()'; + $filter .= ' && $res->resprintable() '; + $filter .= ' && $res->is_incomplete() '; + + my $symfilter = '$res->shown_symb()'; + + my $resource_chooser = &generate_resource_chooser('CHOOSE_INCOMPLETE_SEQ', + 'Select problem(s) to print', + 'multichoice="1", toponly ="1", addstatus="1", closeallpages="1" modallink="1"', + 'RESOURCES', + 'PAGESIZE', + $map, + $filter, '', + $symfilter, + ''); + + return $resource_chooser; +} + + +# Returns the text neded for a student chooser. +# that text must still be parsed by the helper xml parser. +# Parameters: +# this_state - State name of the chooser. +# sort_choice - variable to hold the sorting choice. +# variable - Name of variable to hold students. +# next_state - State after chooser. + + +sub generate_student_chooser { + my ($this_state, + $sort_choice, + $variable, + $next_state) = @_; + my $result = < + Select sorting order of printout + + + Sort by section then student + Sort by students across sections. + + +


+ + + +CHOOSE_STUDENTS + + return $result; +} + +# Generate the text needed for a resource chooser given the top level of +# the sequence/page +# +# Parameters: +# this_state - State name of the chooser. +# prompt_text - Text to use to prompt user. +# resource_options - Resource tag options e.g. +# "multichoice='1', toponly='1', addstatus='1', +# modallink='1'" +# that control the selection and appearance of the +# resource selector. +# variable - Name of the variable to hold the choice +# next_state - Name of the next state the helper should transition +# to +# top_url - Top level URL within which to make the selector. +# If empty the top level sequence is shown. +# filter - How to filter the resources. +# value_func - function. +# choice_func - If not empty generates a with this function. +# start_new_option +# - Fragment appended after valuefunc. +# +# +sub generate_resource_chooser { + my ($this_state, + $prompt_text, + $resource_options, + $variable, + $next_state, + $top_url, + $filter, + $choice_func, + $value_func, + $start_new_option) = @_; + + my $result = < + + $next_state + return $filter; +CHOOSE_RESOURCES + if ($choice_func ne '') { + $result .= "return $choice_func;"; + } + if ($top_url ne '') { + $result .= "$top_url"; + } + $result .= <return $value_func; + $start_new_option + + +CHOOSE_RESOURCES + return $result; +} +# +# Generate the helper XML for a code choice helper dialog: +# +# Paramters: +# $helper - Reference to the helper. +# $state - Name of the state for the chooser. +# $next_state - Name fo the state to follow the chooser. +# $bubble_types - Populates the bubble sheet type dropt down. +# $code_selections - Provides set of code choices that have been used +# $saved_codes - Provides the list of saved codes. +# +# Returns; +# The Xml of the code chooser. +# +sub generate_code_selector { + my ($helper, + $state, + $next_state, + $bubble_types, + $code_selections, + $saved_codes) = @_; # Unpack the parameters. + + my $result = < + $next_state +

Fill out one of the forms below

+


+

Generate new CODEd Assignments

+
Number of CODEd assignments to print: + + + if (((\$helper->{'VARS'}{'NUMBER_TO_PRINT_TOTAL'}+0) < 1) && + !\$helper->{'VARS'}{'REUSE_OLD_CODES'} && + !\$helper->{'VARS'}{'SINGLE_CODE'} && + !\$helper->{'VARS'}{'CODE_SELECTED_FROM_LIST'} ) { + + return "You need to specify the number of assignments to print"; + } + if (((\$helper->{'VARS'}{'NUMBER_TO_PRINT_TOTAL'}+0) >= 1) && + (\$helper->{'VARS'}{'SINGLE_CODE'} ne '') ) { + return 'Specifying number of codes to print and a specific code is not compatible'; + } + return undef; + + +
+ Names to save the CODEs under for later: + + +
+ Bubblesheet type: + + + $bubble_types + +
+
+

Print a Specific CODE


+
Enter a CODE to print: + + + if(!\$helper->{'VARS'}{'NUMBER_TO_PRINT_TOTAL'} && + !\$helper->{'VARS'}{'REUSE_OLD_CODES'} && + !\$helper->{'VARS'}{'CODE_SELECTED_FROM_LIST'}) { + return &Apache::lonprintout::is_code_valid(\$helper->{'VARS'}{'SINGLE_CODE'}, + \$helper->{'VARS'}{'CODE_OPTION'}); + } elsif (\$helper->{'VARS'}{'SINGLE_CODE'} ne ''){ + return 'Specifying a code name is incompatible with specifying number of codes.'; + } else { + return undef; # Other forces control us. + } + + +
+ $code_selections +
+

Reprint a Set of Saved CODEs

+ Select saved CODEs: + + + $saved_codes + +
+ +CHOOSE_ANON1 + + return $result; +} + +# Returns the XML for choosing how assignments are to be formatted +# that text must still be parsed by the helper xml parser. +# Parameters: 3 (required) + +# helper - The helper; $helper->{'VARS'}->{'PRINT_TYPE'} used +# to check if splitting PDFs by section can be offered. +# title - Title for the current state. +# this_state - State name of the chooser. + +sub generate_format_selector { + my ($helper,$title,$this_state) = @_; + my $secpdfoption; + unless (($helper->{'VARS'}->{'PRINT_TYPE'} eq 'problems_for_anon') || + ($helper->{'VARS'}->{'PRINT_TYPE'} eq 'problems_for_anon_page') || + ($helper->{'VARS'}->{'PRINT_TYPE'} eq 'resources_for_anon') ) { + $secpdfoption = 'Each PDF contains exactly one section'; + } + return < +
How should the results be printed?
+ + Start each student\'s assignment on a new page/column (add a pagefeed after each assignment) + Add one empty page/column after each student\'s assignment + Add two empty pages/column after each student\'s assignment + Add three empty pages/column after each student\'s assignment + + PAGESIZE +
How do you want assignments split into PDF files?
+ + All assignments in a single PDF file + $secpdfoption + Each PDF contains exactly one assignment + + Specify the number of assignments per PDF: + + +RESOURCE_SELECTOR +} + +#----------------------------------------------------------------------- + +# Computes an open and close date from a list of open/close dates for a resource's +# parts. +# +# @param \@opens - reference to an array of open dates. +# @param \@closes - reference to an array of close dates. +# +# @return ($open, $close) +# +# @note If open/close dates are not defined they will be returned as undef +# @note It is possible for there to be no overlap in which case -1,-1 +# will be returned. +# @note The algorithm used is to take the latest open date and the earliest end date. +# +sub compute_open_window { + my ($opensref, $closesref) = @_; + + my @opens = @$opensref; + my @closes = @$closesref; + + # latest open date: + my $latest_open; + + foreach my $open (@opens) { + if (!defined($latest_open) || ($open > $latest_open)) { + $latest_open = $open; + } + } + # Earliest close: + + my $earliest_close; + foreach my $close (@closes) { + if (!defined($earliest_close) || ($close < $earliest_close)) { + $earliest_close = $close; + } + } + + # If no overlap...both are -1 as promised. + + if (($earliest_close ne '') && ($latest_open ne '') + && ($earliest_close < $latest_open)) { + $latest_open = -1; + $earliest_close = -1; + } + + return ($latest_open, $earliest_close); + +} + +## +# Determines if 'now' is within the set of printable dates. +# +# @param $open_date - Starting date/timestamp. +# @param $close_date - Ending date/timestamp. +# +# @return 0 - Not open. +# @return 1 - open. +# +sub printable { + my ($open_date, $close_date) = @_; + + + my $now = time(); + + # Have to do a bit of fancy footwork around undefined open/close dates: + + if ($open_date && ($open_date > $now)) { + return 0; + } + + if ($close_date && ($close_date < $now)) { + return 0; + } + + return 1; + +} + +## +# Returns the innermost print start/print end dates for a resource. +# This is done by looking at the start/end dates for its parts and choosing +# the intersection of those dates. +# +# @param res - lonnvamaps::resource object that represents the resource. +# +# @return (opendate, closedate) +# +# @note If open/close dates are not defined they will be returned as undef +# @note It is possible for there to be no overlap in which case -1,-1 +# will be returned. +# @note The algorithm used is to take the latest open date and the earliest end date. +# For consistency with &printable() in lonnavmaps.pm determination of start +# date for printing checks printstartdate param first, then, if not set, +# opendate param, then, if not set, contentopen param. + + +sub get_print_dates { + my $res = shift; + my $partsref = $res->parts(); + my @parts; + if (ref($partsref) eq 'ARRAY') { + @parts = @{$partsref}; + } + my $open_date; + my $close_date; + my @open_dates; + my @close_dates; + + + if (@parts) { + foreach my $part (@parts) { + my $partopen = $res->parmval('printstartdate', $part); + my $partclose = $res->parmval('printenddate', $part); + if (!$partopen) { + $partopen = $res->parmval('opendate',$part); + } + if (!$partopen) { + $partopen = $res->parmval('contentopen',$part); + } + if ($partopen) { + push(@open_dates, $partopen); + } + if ($partclose) { + push(@close_dates, $partclose); + } + push(@open_dates, $partopen); + push(@close_dates, $partclose); + } + } + + ($open_date, $close_date) = &compute_open_window(\@open_dates, \@close_dates); + + return ($open_date, $close_date); +} + +## +# Get the dates for which a course says a resource can be printed. This is like +# get_print_dates but namvaps::course_print_dates are gotten...and not converted +# to times either. +# +# @param $res - Reference to a resource has from lonnvampas::resource. +# +# @return (opendate, closedate) +# +sub course_print_dates { + my $res = shift; + my $partsref = $res->parts(); + my @parts = @$partsref; + my $open_date; + my $close_date; + my @open_dates; + my @close_dates; + my $navmap = $res->{NAV_MAP}; # Slightly OO dirty. + + # Don't bother looping over undefined or empty parts arraY; + + if (@parts) { + foreach my $part (@parts) { + my ($partopen, $partclose) = $navmap->course_printdates($res, $part); + push(@open_dates, $partopen); + push(@close_dates, $partclose); + } + ($open_date, $close_date) = &compute_open_window(\@open_dates, \@close_dates); + } + return ($open_date, $close_date); +} +## +# Same as above but for the enclosing map: +# +sub map_print_dates { + my $res = shift; + my $partsref = $res->parts(); + my @parts = @$partsref; + my $open_date; + my $close_date; + my @open_dates; + my @close_dates; + my $navmap = $res->{NAV_MAP}; # slightly OO dirty. + + + # Don't bother looping over undefined or empty parts arraY; + + if (@parts) { + foreach my $part (@parts) { + my ($partopen, $partclose) = $navmap->map_printdates($res, $part); + push(@open_dates, $partopen); + push(@close_dates, $partclose); + } + ($open_date, $close_date) = &compute_open_window(\@open_dates, \@close_dates); + } + return ($open_date, $close_date); +} + +# Determine if a resource is incomplete given the map: +# Parameters: +# $username - Name of user for whom we are checking. +# $domain - Domain of user we are checking. +# $map - map name. +# Returns: +# 0 - map is not incomplete. +# 1 - map is incomplete. +# +sub incomplete { + my ($username, $domain, $map) = @_; + + + my $navmap = Apache::lonnavmaps::navmap->new($username, $domain); + + + if (defined($navmap)) { + my $res = $navmap->getResourceByUrl($map); + my $result = $res->is_incomplete(); + return $result; + } else { + return 1; + } +} +# +# When printing for students, the resources and order of the +# resources may need to be altered if there are folders with +# random selectiopn or random ordering (or both) enabled. +# This sub computes the set of resources to print for a student +# modified both by random ordering and selection and filtered +# to only those that are in the original set selected to be printed. +# +# Parameters: +# $map - The URL of the folder being printed. +# Used to determine which startResource and finishResource +# to use when using the navmap's getIterator method. +# $seq - The original set of resources to print. +# (really an array of resource names (array of symb's). +# $who - Student/domain for whome the sequence will be generated. +# $code - CODE being printed when printing Problems/Resources +# from folder for CODEd assignments +# $nohidemap - If true, parameter in map for hiddenresource will be +# ignored. The user calling the routine should have +# both the pav and vgr privileges if this is set to true). +# +# Implicit inputs: +# $ +# Returns: +# reference to an array of resources that can be passed to +# print_resources. +# +sub master_seq_to_person_seq { + my ($map, $seq, $who, $code, $nohidemap) = @_; + + + my ($username, $userdomain, $usersection) = split(/:/, $who); + + # Toss the sequence up into a hash so that we have O(1) lookup time. + # on the items that come out of the user's list of resources. + # + + my %seq_hash = map {$_ => 1} @$seq; + my @output_seq; + + my $unhidden; + if ($nohidemap) { + $unhidden = &Apache::lonnet::clutter($map); + } + + my $navmap = Apache::lonnavmaps::navmap->new($username, $userdomain, + $code,$unhidden); + my ($start,$finish); + + if ($map) { + my $mapres = $navmap->getResourceByUrl($map); + if ($mapres->is_map()) { + $start = $mapres->map_start(); + $finish = $mapres->map_finish(); + } + } + unless ($start && $finish) { + $start = $navmap->firstResource(); + $finish = $navmap->finishResource(); + } + + my $iterator = $navmap->getIterator($start,$finish,{},1); + + # Iterate on the resource..select the items that are randomly selected + # and that are in the seq_has. Presumably the iterator will take care + # of the random ordering part of the deal. + my $curres; + while ($curres = $iterator->next()) { + # + # Only process resources..that are not removed by randomout... + # and are selected for printint as well. + # + + if (ref($curres) && ! $curres->randomout()) { + my $currsymb = $curres->symb(); + if (exists($seq_hash{$currsymb})) { + push(@output_seq, $currsymb); + } + } + } + + return \@output_seq; # for now. + +} + # Fetch the contents of a resource, uninterpreted. # This is used here to fetch a latex file to be included @@ -95,7 +816,7 @@ sub fetch_raw_resource { sub annotate { my ($symb) = @_; - my $annotation_text = &Apache::admannotations::get_annotation($symb, 1); + my $annotation_text = &Apache::loncommon::get_annotation($symb, 1); my $result = ""; @@ -109,9 +830,116 @@ sub annotate { return $result; } +# +# Set a global document font size: +# This is done by replacing \begin{document} +# with \begin{document}{\some-font-directive +# and \end{document} with +# }\end{document +# +sub set_font_size { + + my ($text) = @_; + + # There appear to be cases where the font directive is empty.. in which + # case the first substituion would insert a spurious \ oh happy day. + # as this has been the cause of much mystery and hair pulling _sigh_ + + if ($font_size ne '') { + + $text =~ s/\\begin\{document}/\\begin{document}{\\$font_size/; + $text =~ s/\\end\{document}/}\\end{document}/; + } + return $text; + + +} + +# include_pdf - PDF files are included into the +# output as follows: +# - The PDF, if necessary, is replicated. +# - The PDF is added to the list of files to convert to postscript (along with the images). +# - The LaTeX is added to include the final converted postscript in the file as an included +# job. The assumption is that the includedpsheader.ps header will be included. +# +# Parameters: +# pdf_uri - URI of the PDF file to include. +# +# Returns: +# The LaTeX to include. +# +# Assumptions: +# The uri is actually a PDF file +# The postscript will have the includepsheader.ps included. +# +# +sub include_pdf { + my ($pdf_uri) = @_; + + # Where is the file? If not local we'll need to repcopy it:' + + my $file = &Apache::lonnet::filelocation('', $pdf_uri); + if (! -e $file) { + &Apache::lonnet::repcopy($file); + $file = &Apache::lonnet::filelocation('',$pdf_uri); + } + + # The file isn ow replicated locally.. or it did not exist in the first place + # (unlikely). If it did exist, add the pdf to the set of files/images that + # need tob e converted for this print job: + + my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'}; + $file =~ s{(.*)/res/}{$londocroot/res/}; + + open(FILE,">>","$Apache::lonnet::perlvar{'lonPrtDir'}/$env{'user.name'}_$env{'user.domain'}_printout.dat"); + print FILE ("$file\n"); + close (FILE); + + # Construct the special to put out. To do this we need to get the + # resulting filename after conversion. The file will have the same name + # but will be in the user's spool directory with converted images. + + my $dirname = "/home/httpd/prtspool/$env{'user.name'}/"; + my ( $base, $path, $ext) = &fileparse($file, '.pdf'); +# my $destname = $dirname.'/'.$base.'.eps'; # Not really an eps but easier in printout.pl + $base =~ s/ /\_/g; + + + my $output = &print_latex_header(); + $output .= '\special{ps: _begin_job_ (' + .$base.'.pdf.eps'. + ')run _end_job_}'; + return $output; + + +} +## +# Collect the various \select_language{language_name} +# latex tags to build a \usepackage[lang-list]{babel} which will +# appear just prior to the \begin{document} at the front of the concatenated +# set of resources: +# @param doc - The string of latex to search/replace. +# @return string +# @retval - the modified document stringt. # -# ssi_with_retries - Does the server side include of a resource. +sub collect_languages { + my $doc = shift; + my %languages; + while ($doc =~ /\\selectlanguage\{(\w+)}/mg) { + $languages{$1} = 1; # allows us to request each language exactly once. + } + my @lang_list = (keys(%languages)); # List of unique languages + if (scalar @lang_list) { + my $babel_header = '\usepackage[' . join(',', @lang_list) .']{babel}'. "\n"; + $doc =~ s/\\begin\{document}/$babel_header\\begin{document}/; + } + return $doc; +} +#------------------------------------------------------------------- + +# +# ssi_with_retries- Does the server side include of a resource. # if the ssi call returns an error we'll retry it up to # the number of times requested by the caller. # If we still have a proble, no text is appended to the @@ -144,6 +972,10 @@ sub annotate { sub ssi_with_retries { my ($resource, $retries, %form) = @_; + my $target = $form{'grade_target'}; + my $aom = $form{'answer_output_mode'}; + + my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form); if (!$response->is_success) { @@ -151,7 +983,6 @@ sub ssi_with_retries { $ssi_last_error_resource = $resource; $ssi_last_error = $response->code . " " . $response->message; $content='\section*{!!! An error occurred !!!}'; - &Apache::lonnet::logthis("Error in SSI resource: $resource Error: $ssi_last_error"); } return $content; @@ -167,7 +998,6 @@ sub get_student_view_with_retries { $ssi_last_error_resource = $curresline.' for user '.$username.':'.$userdomain; $ssi_last_error = $response->code . " " . $response->message; $content='\section*{!!! An error occurred !!!}'; - &Apache::lonnet::logthis("Error in SSI (student view) resource: $curresline Error: $ssi_last_error User: $username:$userdomain"); } return $content; @@ -227,11 +1057,16 @@ sub printf_style_subst { # %a - Assignment name. # %c - Course name. # %n - Student name. +# %s - The section if it is supplied. # sub format_page_header { my ($width, $format, $assignment, $course, $student) = @_; - + + + $width = &recalcto_mm($width); # Get width in mm. + my $chars_per_line = int($width/2); # Character/textline. + # Default format? if ($format eq '') { @@ -245,40 +1080,60 @@ sub format_page_header { # but only truncate the course. # - Allow the assignment to be 2 lines (wrapped). # - my $chars_per_line = $width/2; # Character/textline. + my $firstline = "$student $course"; + if (length($firstline) > $chars_per_line) { + my $lastchar = $chars_per_line - length($student) - 1; + if ($lastchar > 0) { + $course = substr($course, 0, $lastchar); + } else { # Nothing left of course: + $course = ''; + } + } + if (length($assignment) > $chars_per_line) { + $assignment = substr($assignment, 0, $chars_per_line); + } - my $firstline = "$student $course"; - if (length($firstline) > $chars_per_line) { - my $lastchar = $chars_per_line - length($student) - 1; - if ($lastchar > 0) { - $course = substr($course, 0, $lastchar); - } else { # Nothing left of course: - $course = ''; - } - } - if (length($assignment) > $chars_per_line) { - $assignment = substr($assignment, 0, $chars_per_line); - } - - $format = "\\textbf{$student} $course \\hfill \\thepage \\\\ \\textit{$assignment}"; - - } else { - # An open question is how to handle long user formatted page headers... - # A possible future is to support e.g. %na so that the user can control - # the truncation of the elements that can appear in the header. - # - $format = &printf_style_subst("a", $format, $assignment); - $format = &printf_style_subst("c", $format, $course); - $format = &printf_style_subst("n", $format, $student); - - # If the user put %'s in the format string, they must be escaped - # to \% else LaTeX will think they are comments and terminate - # the line.. which is bad!!! + $format = "\\textbf{$student} $course \\hfill \\thepage \\\\ \\textit{$assignment}"; + } else { + # An open question is how to handle long user formatted page headers... + # A possible future is to support e.g. %na so that the user can control + # the truncation of the elements that can appear in the header. + # + $format = &printf_style_subst("a", $format, $assignment); + $format = &printf_style_subst("c", $format, $course); + $format = &printf_style_subst("n", $format, $student); + + # If the user put %'s in the format string, they must be escaped + # to \% else LaTeX will think they are comments and terminate + # the line.. which is bad!!! } + + return $format; + + # If the user has role author, $course and $assignment are empty so + # there is '\\ \\ ' in the page header. That's cause a error in LaTeX + if($format =~ /\\\\\s\\\\\s/) { + #TODO find sensible caption for page header + my $testPrintout = '\\\\'.&mt('Authoring Space').' \\\\'.&mt('Test-Printout '); + $format =~ s/\\\\\s\\\\\s/$testPrintout/; + } + # + # We're going to trust LaTeX to break lines appropriately, but + # we'll truncate anything that's more than 3 lines worth of + # text. This is also assuming (which will probably end badly) + # nobody's going to embed LaTeX control sequences in the title + # header or rather that those control sequences won't get broken + # by the stuff below. + # + my $total_length = 3*$chars_per_line; + if (length($format) > $total_length) { + $format = substr($format, 0, $total_length); + } + return $format; @@ -383,8 +1238,9 @@ sub is_valid_alpha_code { sub is_code_valid { my ($code_value, $code_option) = @_; my ($code_type, $code_length) = ('letter', 6); # defaults. - open(FG, $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); - foreach my $line () { + my @lines = &Apache::lonnet::get_scantronformat_file(); + foreach my $line (@lines) { + next if (($line =~ /^\#/) || ($line eq '')); my ($name, $type, $length) = (split(/:/, $line))[0,2,4]; if($name eq $code_option) { $code_length = $length; @@ -401,6 +1257,22 @@ sub is_code_valid { } } +# +# Compare two students by section (Used to sort by section). +# +# Implicit inputs, +# $a - The first one +# $b - The second one. +# +# Returns: +# a-section cmp b-section +# +sub compare_sections { + my ($u1, $d1, $s1, $n1, $stat1) = split(/:/, $a); + my ($u2, $d2, $s2, $n2, $stat2) = split(/:/, $b); + + return $s1 cmp $s2; +} # Compare two students by name. The students are in the form # returned by the helper: @@ -462,8 +1334,8 @@ sub compare_names { sub latex_header_footer_remove { my $text = shift; - $text =~ s/\\end{document}//; - $text =~ s/\\documentclass([^&]*)\\begin{document}//; + $text =~ s/\\end\{document}//; + $text =~ s/\\documentclass([^&]*)\\begin\{document}//; return $text; } # @@ -502,12 +1374,19 @@ sub adjust_number_to_print { # Unmodified. } else { # Error!!!! - + croak "bad SPLIT_PDFS: $split_pdf in lonprintout::adjust_number_to_print"; + } } + sub character_chart { + my $result = shift; + return &Apache::entities::replace_entities($result); +} + +sub old_character_chart { my $result = shift; $result =~ s/&\#0?0?(7|9);//g; $result =~ s/&\#0?(10|13);//g; @@ -629,7 +1508,7 @@ sub character_chart { $result =~ s/&(\#165|yen);/\\textyen /g; $result =~ s/&(\#166|brvbar);/\\textbrokenbar /g; $result =~ s/&(\#167|sect);/\\textsection /g; - $result =~ s/&(\#168|uml);/\\texthighdieresis /g; + $result =~ s/&(\#168|uml);/\\"\{\} /g; $result =~ s/&(\#169|copy);/\\copyright /g; $result =~ s/&(\#170|ordf);/\\textordfeminine /g; $result =~ s/&(\#172|not);/\\ensuremath\{\\neg\}/g; @@ -640,7 +1519,7 @@ sub character_chart { $result =~ s/&(\#177|plusmn);/\\ensuremath\{\\pm\}/g; $result =~ s/&(\#178|sup2);/\\ensuremath\{^2\}/g; $result =~ s/&(\#179|sup3);/\\ensuremath\{^3\}/g; - $result =~ s/&(\#180|acute);/\\textacute /g; + $result =~ s/&(\#180|acute);/\\'\{\} /g; $result =~ s/&(\#181|micro);/\\ensuremath\{\\mu\}/g; $result =~ s/&(\#182|para);/\\P/g; $result =~ s/&(\#183|middot);/\\ensuremath\{\\cdot\}/g; @@ -844,6 +1723,7 @@ sub character_chart { $result =~ s/&(rdquo|#8221);/\'\'/g; + return $result; } @@ -979,9 +1859,9 @@ sub page_format_transformation { if ($selectionmade eq '4') { if ($choice eq 'all_problems') { - $assignment='Problems from the Whole Course'; + $assignment=&mt('Problems from the Whole Course'); } else { - $assignment='Resources from the Whole Course'; + $assignment=&mt('Resources from the Whole Course'); } } else { $assignment=&Apache::lonxml::latex_special_symbols($assignment,'header'); @@ -1004,21 +1884,27 @@ sub page_format_transformation { $fancypagestatement="\\rhead{}\\chead{}\\lhead{$header_text}"; } if ($layout eq 'album') { - $text =~ s/\\begin{document}/\\setlength{\\oddsidemargin}{$oddoffset}\\setlength{\\evensidemargin}{$evenoffset}$topmargintoinsert\n\\setlength{\\textwidth}{$textwidth}\\setlength{\\textheight}{$textheight}\\setlength{\\textfloatsep}{8pt plus 2\.0pt minus 4\.0pt}\n\\newlength{\\minipagewidth}\\setlength{\\minipagewidth}{\\textwidth\/\$number_of_columns-0\.2cm}\\usepackage{fancyhdr}\\addtolength{\\headheight}{\\baselineskip}\n\\pagestyle{fancy}$fancypagestatement\\begin{document}\\voffset=-0\.8 cm\\setcounter{page}{1}\n /; + $text =~ s/\\begin\{document}/\\setlength{\\oddsidemargin}{$oddoffset}\\setlength{\\evensidemargin}{$evenoffset}$topmargintoinsert\n\\setlength{\\textwidth}{$textwidth}\\setlength{\\textheight}{$textheight}\\setlength{\\textfloatsep}{8pt plus 2\.0pt minus 4\.0pt}\n\\newlength{\\minipagewidth}\\setlength{\\minipagewidth}{\\textwidth\/\$number_of_columns-0\.2cm}\\usepackage{fancyhdr}\\addtolength{\\headheight}{\\baselineskip}\n\\pagestyle{fancy}$fancypagestatement\\usepackage{booktabs}\\begin{document}\\voffset=-0\.8 cm\\setcounter{page}{1}\n /; } elsif ($layout eq 'book') { if ($choice ne 'All class print') { - $text =~ s/\\begin{document}/\\textheight $textheight\\oddsidemargin = $evenoffset\\evensidemargin = $evenoffset $topmargintoinsert\n\\textwidth= $textwidth\\newlength{\\minipagewidth}\\setlength{\\minipagewidth}{\\textwidth\/\$number_of_columns-0\.2cm}\n\\renewcommand{\\ref}{\\keephidden\}\\usepackage{fancyhdr}\\addtolength{\\headheight}{\\baselineskip}\\pagestyle{fancy}$fancypagestatement\\begin{document}\n\\voffset=-0\.8 cm\\setcounter{page}{1}\n/; + $text =~ s/\\begin\{document}/\\textheight $textheight\\oddsidemargin = $evenoffset\\evensidemargin = $evenoffset $topmargintoinsert\n\\textwidth= $textwidth\\newlength{\\minipagewidth}\\setlength{\\minipagewidth}{\\textwidth\/\$number_of_columns-0\.2cm}\n\\renewcommand{\\ref}{\\keephidden\}\\usepackage{fancyhdr}\\addtolength{\\headheight}{\\baselineskip}\\pagestyle{fancy}$fancypagestatement\\usepackage{booktabs}\\begin{document}\n\\voffset=-0\.8 cm\\setcounter{page}{1}\n/; } else { - $text =~ s/\\pagestyle{fancy}\\rhead{}\\chead{}\s*\\begin{document}/\\textheight = $textheight\\oddsidemargin = $evenoffset\n\\evensidemargin = $evenoffset $topmargintoinsert\\textwidth= $textwidth\\newlength{\\minipagewidth}\n\\setlength{\\minipagewidth}{\\textwidth\/\$number_of_columns-0\.2cm}\\renewcommand{\\ref}{\\keephidden\}\\pagestyle{fancy}\\rhead{}\\chead{}\\begin{document}\\voffset=-0\.8cm\n\\setcounter{page}{1} \\vskip 5 mm\n /; + $text =~ s/\\pagestyle\{fancy}\\rhead\{}\\chead\{}\s*\\begin\{document}/\\textheight = $textheight\\oddsidemargin = $evenoffset\n\\evensidemargin = $evenoffset $topmargintoinsert\\textwidth= $textwidth\\newlength{\\minipagewidth}\n\\setlength{\\minipagewidth}{\\textwidth\/\$number_of_columns-0\.2cm}\\renewcommand{\\ref}{\\keephidden\}\\pagestyle{fancy}\\rhead{}\\chead{}\\usepackage{booktabs}\\begin{document}\\voffset=-0\.8cm\n\\setcounter{page}{1} \\vskip 5 mm\n /; } if ($papersize eq 'a4') { - $text =~ s/(\\begin{document})/$1\\special{papersize=210mm,297mm}/; + my $papersize_text; + if ($perm{'pav'}) { + $papersize_text = '\\special{papersize=210mm,297mm}'; + } else { + $papersize_text = '\special{papersize=210mm,297mm}'; + } + $text =~ s/(\\begin\{document})/$1$papersize_text/; } } if ($tableofcontents eq 'yes') {$text=~s/(\\setcounter\{page\}\{1\})/$1 \\tableofcontents\\newpage /;} if ($indexlist eq 'yes') { - $text=~s/(\\begin{document})/\\makeindex $1/; - $text=~s/(\\end{document})/\\strut\\\\\\strut\\printindex $1/; + $text=~s/(\\begin\{document})/\\makeindex $1/; + $text=~s/(\\end\{document})/\\strut\\\\\\strut\\printindex $1/; } return $text; } @@ -1027,12 +1913,12 @@ sub page_format_transformation { sub page_cleanup { my $result = shift; - $result =~ m/\\end{document}(\d*)$/; + $result =~ m/\\end\{document}(\d*)$/; my $number_of_columns = $1; my $insert = '{'; for (my $id=1;$id<=$number_of_columns;$id++) { $insert .='l'; } $insert .= '}'; - $result =~ s/(\\begin{longtable})INSERTTHEHEADOFLONGTABLE\\endfirsthead\\endhead/$1$insert/g; + $result =~ s/(\\begin\{longtable})INSERTTHEHEADOFLONGTABLE\\endfirsthead\\endhead/$1$insert/g; $result =~ s/&\s*REMOVETHEHEADOFLONGTABLE\\\\/\\\\/g; return $result,$number_of_columns; } @@ -1068,16 +1954,16 @@ my $end_of_student = "\n".'\special{ps:E sub latex_corrections { my ($number_of_columns,$result,$selectionmade,$answer_mode) = @_; -# $result =~ s/\\includegraphics{/\\includegraphics\[width=\\minipagewidth\]{/g; +# $result =~ s/\\includegraphics\{/\\includegraphics\[width=\\minipagewidth\]{/g; my $copyright = ©right_line(); if ($selectionmade eq '1' || $answer_mode eq 'only') { - $result =~ s/(\\end{document})/\\strut\\vskip 0 mm $copyright $end_of_student $1/; + $result =~ s/(\\end\{document})/\\strut\\vskip 0 mm $copyright $end_of_student $1/; } else { - $result =~ s/(\\end{document})/\\strut\\vspace\*{-4 mm}\\newline $copyright $end_of_student $1/; + $result =~ s/(\\end\{document})/\\strut\\vspace\*{-4 mm}\\newline $copyright $end_of_student $1/; } $result =~ s/\$number_of_columns/$number_of_columns/g; - $result =~ s/(\\end{longtable}\s*)(\\strut\\newline\\noindent\\makebox\[\\textwidth\/$number_of_columns\]\[b\]{\\hrulefill})/$2$1/g; - $result =~ s/(\\end{longtable}\s*)\\strut\\newline/$1/g; + $result =~ s/(\\end\{longtable}\s*)(\\strut\\newline\\noindent\\makebox\[\\textwidth\/$number_of_columns\]\[b\]\{\\hrulefill})/$2$1/g; + $result =~ s/(\\end\{longtable}\s*)\\strut\\newline/$1/g; #-- LaTeX corrections my $first_comment = index($result,' - -ELEMENTHTML + my %lt = &Apache::lonlocal::texthash( + 'format' => 'How should each column be formatted?', + 'width' => 'Width', + 'height' => 'Height', + 'margin' => 'Left Margin' + ); + + $result .= '

'.$lt{'format'}.'

' + .&Apache::lonhtmlcommon::start_pick_box() + .&Apache::lonhtmlcommon::row_title($lt{'width'}) + .'' + .'' + .&Apache::lonhtmlcommon::row_closure() + .&Apache::lonhtmlcommon::row_title($lt{'height'}) + .'' + .'' + .&Apache::lonhtmlcommon::row_closure() + .&Apache::lonhtmlcommon::row_title($lt{'margin'}) + .'' + .'' + .&Apache::lonhtmlcommon::row_closure(1) + .&Apache::lonhtmlcommon::end_pick_box(); + #

Hint: Some instructors like to leave scratch space for the student by + # making the width much smaller than the width of the page.

return $result; } @@ -3584,11 +5355,8 @@ sub preprocess { ($papersize) = split(/ /, $papersize); - if ($laystyle eq 'L') { - $laystyle = 'album'; - } else { - $laystyle = 'book'; - } + $laystyle = &Apache::lonprintout::map_laystyle($laystyle); + # Figure out some good defaults for the print out and set them: my %size; @@ -3658,7 +5426,5 @@ sub postprocess { } } - - __END__