--- loncom/interface/lonprintout.pm 2011/11/07 20:05:56 1.609 +++ loncom/interface/lonprintout.pm 2017/01/29 23:53:55 1.655 @@ -1,8 +1,7 @@ - # The LearningOnline Network # Printout # -# $Id: lonprintout.pm,v 1.609 2011/11/07 20:05:56 www Exp $ +# $Id: lonprintout.pm,v 1.655 2017/01/29 23:53:55 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -41,12 +40,13 @@ use Apache::admannotations; use Apache::lonenc; use Apache::entities; use Apache::londefdef; +use LONCAPA; +# 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; @@ -76,6 +76,58 @@ my $font_size = 'normalsize'; # Default #---------------------------- 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. @@ -96,8 +148,7 @@ my $font_size = 'normalsize'; # Default # Return: # XML that can be parsed by the helper to drive the state machine. # -sub create_incomplete_folder_selstud_helper($helper) -{ +sub create_incomplete_folder_selstud_helper { my ($helper, $map) = @_; @@ -302,7 +353,6 @@ CHOOSE_RESOURCES CHOOSE_RESOURCES - return $result; } # @@ -435,6 +485,197 @@ 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. @@ -460,19 +701,25 @@ sub incomplete { } } # -# When printing for students, the resoures and order of the +# 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 selcted to be printed. +# to only those that are in the original set selected to be printed. # # Parameters: -# $helper - The helper we need $helper->{'VARS'}->{'symb'} -# to construct the navmap and the iteration. -# $seq - The original set of resources to print +# $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: # $ @@ -481,33 +728,40 @@ sub incomplete { # print_resources. # sub master_seq_to_person_seq { - my ($helper, $seq, $who) = @_; + 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 ($map, $id, $url) = &Apache::lonnet::decode_symb($helper->{VARS}->{'symb'}); - my $navmap = Apache::lonnavmaps::navmap->new($username, $userdomain); - my $iterator = $navmap->getIterator($navmap->firstResource(), - $navmap->finishResource(), - {}, 1); - my %nonResourceItems = ( - $iterator->BEGIN_MAP => 1, - $iterator->BEGIN_BRANCH => 1, - $iterator->END_BRANCH => 1, - $iterator->END_MAP => 1, - $iterator->FORWARD => 1, - $iterator->BACKWARD => 1 + 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(); + } - ); # These items are not resources but appear in the midst of iteration. + 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 @@ -519,14 +773,13 @@ sub master_seq_to_person_seq { # Only process resources..that are not removed by randomout... # and are selected for printint as well. # - if (! exists $nonResourceItems{$curres} && ! $curres->randomout()) { - my $symb = $curres->symb(); - if (exists $seq_hash{$symb}) { - push(@output_seq, $symb); + if (ref($curres) && ! $curres->randomout()) { + my $currsymb = $curres->symb(); + if (exists($seq_hash{$currsymb})) { + push(@output_seq, $currsymb); } } } - return \@output_seq; # for now. @@ -593,9 +846,9 @@ sub set_font_size { if ($font_size ne '') { - $text =~ s/\\begin{document}/\\begin{document}{\\$font_size/; + $text =~ s/\\begin\{document}/\\begin{document}{\\$font_size/; } - $text =~ s/\\end{document}/}\\end{document}/; + $text =~ s/\\end\{document}/}\\end{document}/; return $text; @@ -660,7 +913,29 @@ sub include_pdf { } - +## +# 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. +# +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. @@ -838,7 +1113,7 @@ sub format_page_header { # 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('Construction Space').' \\\\'.&mt('Test-Printout '); + my $testPrintout = '\\\\'.&mt('Authoring Space').' \\\\'.&mt('Test-Printout '); $format =~ s/\\\\\s\\\\\s/$testPrintout/; } # @@ -976,6 +1251,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: @@ -1037,8 +1328,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; } # @@ -1588,12 +1879,12 @@ 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\\usepackage{booktabs}\\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\\usepackage{booktabs}\\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{}\\usepackage{booktabs}\\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') { my $papersize_text; @@ -1602,13 +1893,13 @@ sub page_format_transformation { } else { $papersize_text = '\special{papersize=210mm,297mm}'; } - $text =~ s/(\\begin{document})/$1$papersize_text/; + $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; } @@ -1617,12 +1908,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; } @@ -1658,16 +1949,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,'