--- loncom/interface/lonprintout.pm 2011/11/29 11:57:33 1.610 +++ loncom/interface/lonprintout.pm 2012/11/20 12:03:06 1.622 @@ -1,8 +1,7 @@ - # The LearningOnline Network # Printout # -# $Id: lonprintout.pm,v 1.610 2011/11/29 11:57:33 foxr Exp $ +# $Id: lonprintout.pm,v 1.622 2012/11/20 12:03:06 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -28,6 +27,7 @@ # package Apache::lonprintout; use strict; +use POSIX; use Apache::Constants qw(:common :http); use Apache::lonxml; use Apache::lonnet; @@ -41,12 +41,13 @@ use Apache::admannotations; use Apache::lonenc; 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 POSIX qw(ctime); use Apache::lonlocal; use Carp; use LONCAPA; @@ -76,6 +77,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. @@ -434,6 +487,191 @@ 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 retunred 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 (defined($earliest_close) && defined($latest_open) + && ($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 retunred 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 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); + + push(@open_dates, $partopen); + push(@close_dates, $partclose); + } + } + + ($open_date, $close_date) = &compute_open_window(\@open_dates, \@close_dates); + + if ($open_date) { + $open_date = POSIX::strftime('%D', localtime($open_date)); + } + if ($close_date) { + $close_date = POSIX::strftime('%D', localtime($close_date)); + } + + 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. @@ -660,7 +898,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. @@ -976,6 +1236,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: @@ -2329,6 +2605,7 @@ sub print_construction_sequence { # For item 100, filtering was done at the helper level. sub output_data { + my ($r,$helper,$rparmhash) = @_; my %parmhash = %$rparmhash; $ssi_error = 0; # This will be set nonzero by failing ssi's. @@ -2602,7 +2879,6 @@ ENDPART ($print_type eq 'select_sequences') or ($print_type eq 'map_incomplete_problems_seq') ) { - #-- produce an output string if (($print_type eq 'map_problems') or @@ -2745,7 +3021,6 @@ ENDPART $assignment, $courseidinfo, $name); - if ($numberofcolumns eq '1') { $result .='\newpage \noindent\parbox{\minipagewidth}{\noindent\\lhead{'.$header_text.'}} \vskip 5 mm '; } else { @@ -2846,6 +3121,8 @@ ENDPART if (($helper->{'VARS'}->{'student_sort'} eq 1) && ($helper->{'VARS'}->{'SPLIT_PDFS'} ne "sections")) { @students = sort compare_names @students; + } else { + @students = sort compare_sections @students; } &adjust_number_to_print($helper); @@ -2878,7 +3155,7 @@ ENDPART ($helper->{'VARS'}->{'PRINT_ANNOTATIONS'} eq 'yes')) { $moreenv{'problem_split'}='yes'; } - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Print Status','Class Print Status',$#students+1,'inline','75'); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$#students+1); my $student_counter=-1; my $i = 0; my $last_section = (split(/:/,$students[0]))[2]; @@ -2993,7 +3270,7 @@ ENDPART $number_per_page=$num_todo > 0 ? $num_todo : 1; } my $flag_latex_header_remove = 'NO'; - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Print Status','Class Print Status',$num_todo,'inline','75'); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$num_todo); my $count=0; foreach my $code (sort(@allcodes)) { my $file_num=int($count/$number_per_page); @@ -3122,6 +3399,11 @@ ENDPART $result = set_font_size($result); + # Insert any babel headers required. + + $result = &collect_languages($result); + + #-- writing .tex file in prtspool my $temp_file; my $identifier = &Apache::loncommon::get_cgi_id(); @@ -3250,6 +3532,7 @@ sub print_resources { my $fullname = &get_name($username,$userdomain); my $namepostfix = "\\\\"; # Both anon and not anon should get the same vspace. + # # Figure out if we need to filter the output by # the incomplete problems for that person @@ -3261,7 +3544,7 @@ sub print_resources { $print_incomplete = 1; } if ($person eq 'anonymous') { - $namepostfix .="Name: "; + $namepostfix .=&mt('Name:')." "; $fullname = "CODE - ".$moreenv->{'CODE'}; } @@ -3288,6 +3571,10 @@ sub print_resources { # my $syllabus_first = 0; + my $current_assignment = ""; + my $assignment; + my $courseidinfo = &get_course(); + foreach my $curresline (@{$master_seq}) { if (defined $page_breaks{$curresline}) { if($i != 0) { @@ -3296,9 +3583,12 @@ sub print_resources { } $current_output .= &get_extra_vspaces($helper, $curresline); $i++; + my ($map,$id,$res_url) = &Apache::lonnet::decode_symb($curresline); + + # See if we need to emit a new header: + if ( !($type eq 'problems' && ($curresline!~ m/$LONCAPA::assess_page_re/)) ) { - my ($map,$id,$res_url) = &Apache::lonnet::decode_symb($curresline); if ($print_incomplete && !&incomplete($username, $userdomain, $res_url)) { next; } @@ -3402,6 +3692,19 @@ sub print_resources { } $remove_latex_header = 'YES'; } + $assignment = &Apache::lonxml::latex_special_symbols( + &Apache::lonnet::gettitle($map), 'header'); + if (($assignment ne $current_assignment) && ($assignment ne "")) { + my $header_line = &format_page_header($LaTeXwidth, $parmhash{'print_header_format'}, + $assignment, $courseidinfo, + $fullname, $usersection); + my $header_start = ($columns_in_format == 1) ? '\lhead' + : '\fancyhead[LO]'; + $header_line = $header_start.'{'.$header_line.'}'; + $current_output = $current_output . $header_line; + $current_assignment = $assignment; + } + if (&Apache::loncommon::connection_aborted($r)) { last; } } # If we are printing incomplete it's possible we don't have @@ -3422,24 +3725,32 @@ sub print_resources { if ($syllabus_first) { $current_output =~ s/\\\\ Last updated:/Last updated:/ } - my $courseidinfo = &get_course(); - my $currentassignment=&Apache::lonxml::latex_special_symbols($helper->{VARS}->{'assignment'},'header'); - my $header_line = - &format_page_header($LaTeXwidth, $parmhash{'print_header_format'}, - $currentassignment, $courseidinfo, $fullname, $usersection); - my $header_start = ($columns_in_format == 1) ? '\lhead' - : '\fancyhead[LO]'; - $header_line = $header_start.'{'.$header_line.'}'; + if (0) { + my $currentassignment=&Apache::lonxml::latex_special_symbols($helper->{VARS}->{'assignment'},'header'); + my $header_line = + &format_page_header($LaTeXwidth, $parmhash{'print_header_format'}, + $currentassignment, $courseidinfo, $fullname, $usersection); + my $header_start = ($columns_in_format == 1) ? '\lhead' + : '\fancyhead[LO]'; + $header_line = $header_start.'{'.$header_line.'}'; + } if ($current_output=~/\\documentclass/) { - $current_output =~ s/\\begin{document}/\\setlength{\\topmargin}{1cm} \\begin{document}\\noindent\\parbox{\\minipagewidth}{\\noindent$header_line$namepostfix}\\vskip 5 mm /; +# $current_output =~ s/\\begin{document}/\\setlength{\\topmargin}{1cm} \\begin{document}\\noindent\\parbox{\\minipagewidth}{\\noindent$header_line$namepostfix}\\vskip 5 mm /; + $current_output =~ s/\\begin{document}/\\setlength{\\topmargin}{1cm} \\begin{document}\\noindent\\parbox{\\minipagewidth}{\\noindent$namepostfix}\\vskip 5 mm /; + } else { my $blankpages = '\clearpage\strut\clearpage'x$helper->{'VARS'}->{'EMPTY_PAGES'}; - + +# $current_output = '\strut\vspace*{-6 mm}\\newline'. +# ©right_line().' \newpage '.$blankpages.$end_of_student. +# '\setcounter{page}{1}\noindent\parbox{\minipagewidth}{\noindent'. +# $header_line.$namepostfix. '} \vskip 5 mm '.$current_output; $current_output = '\strut\vspace*{-6 mm}\\newline'. ©right_line().' \newpage '.$blankpages.$end_of_student. - '\setcounter{page}{1}\noindent\parbox{\minipagewidth}{\noindent'. - $header_line.$namepostfix.'} \vskip 5 mm '.$current_output; + '\setcounter{page}{1}\noindent\parbox{\minipagewidth}{\noindent' + .$namepostfix. '} \vskip 5 mm '.$current_output; + } # # Close the student bracketing. @@ -3448,12 +3759,46 @@ sub print_resources { } +sub printing_blocked { + my ($r,$blocktext) = @_; + my $title = &mt('Preparing Printout'); + &Apache::lonhtmlcommon::clear_breadcrumbs(); + &Apache::lonhtmlcommon::add_breadcrumb({href=>'/adm/printout', + text=> $title}); + my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs($title); + &Apache::loncommon::content_type($r,'text/html'); + &Apache::loncommon::no_cache($r); + $r->send_http_header; + $r->print(&Apache::loncommon::start_page('Preparing Printout'). + $breadcrumbs. + $blocktext. + &Apache::loncommon::end_page()); + return; +} + sub handler { my $r = shift; + + if ($env{'request.course.id'}) { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my ($blocked,$blocktext) = + &Apache::loncommon::blocking_status('printout',$cnum,$cdom); + if ($blocked) { + my $checkrole = "cm./$cdom/$cnum"; + if ($env{'request.course.sec'} ne '') { + $checkrole .= "/$env{'request.course.sec'}"; + } + unless ((&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) && + ($env{'request.role'} !~ m{^st\./$cdom/$cnum})) { + &printing_blocked($r,$blocktext); + return OK; + } + } + } &init_perm(); - my $helper = printHelper($r); if (!ref($helper)) { return $helper; @@ -3477,7 +3822,7 @@ sub handler { &output_data($r,$helper,\%parmhash); return OK; -} +} use Apache::lonhelper; @@ -3651,6 +3996,12 @@ sub printHelper { my $is_published=0; # True when printing from resource space. my $res_printable = 1; # By default the current resource is printable. my $userCanPrint = ($perm{'pav'} || $perm{'pfo'}); + my $res_printstartdate; + my $res_printenddate; + my $map_open = 0; + my $map_close = 0xffffffff; + my $course_open = 0; + my $course_close = 0xffffffff; # Get the resource name from construction space if ($helper->{VARS}->{'construction'}) { @@ -3667,7 +4018,11 @@ sub printHelper { &Apache::lonenc::check_encrypt(&Apache::lonnet::clutter($url)); my $navmap = Apache::lonnavmaps::navmap->new(); my $res = $navmap->getBySymb($symb); - $res_printable = $res->resprintable() || $userCanPrint; #printability in course context + $res_printable = $res->resprintable() | $userCanPrint; #printability in course context + ($res_printstartdate, $res_printenddate) = &get_print_dates($res); + ($course_open, $course_close) = &course_print_dates($res); + ($map_open, $map_close) = &map_print_dates($res); + } else { # Resource space. @@ -3686,6 +4041,7 @@ sub printHelper { if (!$helper->{VARS}->{'curseed'} && $env{'form.curseed'}) { $helper->{VARS}->{'curseed'}=$env{'form.curseed'}; } + if (!$helper->{VARS}->{'probstatus'} && $env{'form.problemtype'}) { $helper->{VARS}->{'probstatus'}=$env{'form.problemstatus'}; } @@ -3716,8 +4072,7 @@ sub printHelper { if ($resourceTitle && $res_printable) { push @{$printChoices}, ["$resourceTitle (".&mt('the resource you just saw on the screen').")", 'current_document', 'PAGESIZE']; - } - + } # Useful filter strings @@ -3822,7 +4177,7 @@ sub printHelper { my $nextState = 'CHOOSE_INCOMPLETE_SEQ'; my $textSuffix = ''; - if ($userCanPrint) { + if ($userCanPrint) { $printSelector = 'map_incomplete_problems_people_seq'; $nextState = 'CHOOSE_INCOMPLETE_PEOPLE_SEQ'; $textSuffix = ' for selected students'; @@ -3830,51 +4185,59 @@ sub printHelper { &create_incomplete_folder_selstud_helper($helper, $map); &Apache::lonxml::xmlparse($r, 'helper', $helperStates); } else { - my $helperStates = &create_incomplete_folder_helper($helper, $map); # Create needed states for student. - &Apache::lonxml::xmlparse($r, 'helper', $helperStates); + if (&printable($map_open, $map_close)) { + my $helperStates = &create_incomplete_folder_helper($helper, $map); # Create needed states for student. + &Apache::lonxml::xmlparse($r, 'helper', $helperStates); + } else { + # TODO: Figure out how to break the news...this folder is not printable. + } } - push(@{$printChoices}, - [&mt('Selected [_1]Incomplete Problems[_2] from folder [_3]' . $textSuffix, - '', '', - ''. $sequenceTitle . ''), - $printSelector, - $nextState]); - + if ($userCanPrint || &printable($map_open, $map_close)) { + push(@{$printChoices}, + [&mt('Selected [_1]Incomplete Problems[_2] from folder [_3]' . $textSuffix, + '', '', + ''. $sequenceTitle . ''), + $printSelector, + $nextState]); + } # Allow problems from sequence - push @{$printChoices}, + if ($userCanPrint || &printable($map_open, $map_close)) { + push @{$printChoices}, [&mt('Selected [_1]Problems[_2] from folder [_3]','','',''.$sequenceTitle.''), 'map_problems', 'CHOOSE_PROBLEMS']; - # Allow all resources from sequence - push @{$printChoices}, [&mt('Selected [_1]Resources[_2] from folder [_3]','','',''.$sequenceTitle.''), - 'map_problems_pages', - 'CHOOSE_PROBLEMS_HTML']; - my $helperFragment = &generate_resource_chooser('CHOOSE_PROBLEMS', - 'Select Problem(s) to print', - 'multichoice="1" toponly="1" addstatus="1" closeallpages="1"', - 'RESOURCES', - 'PAGESIZE', - $map, - $isProblem, '', - $symbFilter, - $start_new_option); - $helperFragment .= &generate_resource_chooser('CHOOSE_PROBLEMS_HTML', - 'Select Resource(s) to print', - 'multichoice="1" toponly="1" addstatus="1" closeallpages="1"', - 'RESOURCES', - 'PAGESIZE', - $map, - $isNotMap, '', - $symbFilter, - $start_new_option); - - &Apache::lonxml::xmlparse($r, 'helper', $helperFragment); + # Allow all resources from sequence + push @{$printChoices}, [&mt('Selected [_1]Resources[_2] from folder [_3]','','',''.$sequenceTitle.''), + 'map_problems_pages', + 'CHOOSE_PROBLEMS_HTML']; + my $helperFragment = &generate_resource_chooser('CHOOSE_PROBLEMS', + 'Select Problem(s) to print', + 'multichoice="1" toponly="1" addstatus="1" closeallpages="1"', + 'RESOURCES', + 'PAGESIZE', + $map, + $isProblem, '', + $symbFilter, + $start_new_option); + $helperFragment .= &generate_resource_chooser('CHOOSE_PROBLEMS_HTML', + 'Select Resource(s) to print', + 'multichoice="1" toponly="1" addstatus="1" closeallpages="1"', + 'RESOURCES', + 'PAGESIZE', + $map, + $isNotMap, '', + $symbFilter, + $start_new_option); + + &Apache::lonxml::xmlparse($r, 'helper', $helperFragment); + } else { + # TODO: Figure out how to tell them the folder is not printable. + } } - - # If the user has pfo (print for others) allow them to print all - # problems and resources in the entire course, optionally for selected students - my $post_data = $helper->{VARS}->{'postdata'}; + # If the user has pfo (print for others) allow them to print all + # problems and resources in the entire course, optionally for selected students + my $post_data = $helper->{VARS}->{'postdata'}; if ($perm{'pfo'} && !$is_published && ($post_data=~/\/res\// || $post_data =~/\/(syllabus|smppg|aboutme|bulletinboard)$/)) { @@ -4237,7 +4600,7 @@ CHOOSE_FROM_SUBDIR Select the sequence to print resources from: CHOOSE_FROM_ANY_SEQUENCE - return \$res->is_sequence; + return &Apache::lonprintout::printable_sequence(\$res); return $urlValue; return \$res->hasResource(\$res,sub { return !\$_[0]->is_sequence() },0,0); @@ -4259,6 +4622,14 @@ CHOOSE_FROM_ANY_SEQUENCE # Generate the first state, to select which resources get printed. Apache::lonhelper::state->new("START", "Select Printing Options:"); + if (!$res_printable) { + $paramHash = Apache::lonhelper::getParamHash(); + $paramHash->{MESSAGE_TEXT} = + &mt('

Printing for current resource is only possible between [_1] and [_1]

', + $res_printstartdate, $res_printenddate); + Apache::lonhelper::message->new(); + } + $paramHash = Apache::lonhelper::getParamHash(); $paramHash = Apache::lonhelper::getParamHash(); $paramHash->{MESSAGE_TEXT} = ""; Apache::lonhelper::message->new(); @@ -4901,7 +5272,5 @@ sub postprocess { } } - - __END__