[LON-CAPA-cvs] cvs: loncom /auth lonacc.pm /interface loncommon.pm loncourserespicker.pm londocs.pm lonprintout.pm /misc cleanup_file_caches.pl /node.js/axe lonaxe.pl

raeburn raeburn at source.lon-capa.org
Mon Jan 12 22:18:26 EST 2026


raeburn		Tue Jan 13 03:18:26 2026 EDT

  Modified files:              
    /loncom/interface	londocs.pm loncommon.pm lonprintout.pm 
                     	loncourserespicker.pm 
    /loncom/node.js/axe	lonaxe.pl 
    /loncom/misc	cleanup_file_caches.pl 
    /loncom/auth	lonacc.pm 
  Log:
  - Accessibility testing using axe-core and puppeteer (node.js).
    - Choose Accessibility level to test: 2.0, 2.1, 2.2 and A, AA, AAA
    - Summary results written to .csv file for download.
    - Details for any violations written to .txt file for download.
    - Recently generated files are listed.
    - Nightly run of cleanup_file_caches.pl removes files more than 1 day old.
    - Warning not to change roles or logout while accessibility checking active
    - Lock file is written in user's session to discourage a user from 
      simultaneously running more than 1 instance of the accessibility checker.
  
  
-------------- next part --------------
Index: loncom/interface/londocs.pm
diff -u loncom/interface/londocs.pm:1.735 loncom/interface/londocs.pm:1.736
--- loncom/interface/londocs.pm:1.735	Wed Dec 31 23:44:27 2025
+++ loncom/interface/londocs.pm	Tue Jan 13 03:18:22 2026
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Documents
 #
-# $Id: londocs.pm,v 1.735 2025/12/31 23:44:27 raeburn Exp $
+# $Id: londocs.pm,v 1.736 2026/01/13 03:18:22 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -6283,9 +6283,16 @@
     my ($r,$canedit,$cnum,$cdom) = @_;
     my $crstype = &Apache::loncommon::course_type();
     my $formname = 'wcagcheck';
-    $r->print(&Apache::loncommon::start_page('Choose Resources for Accessibility checking'));
+    $r->print(&Apache::loncommon::start_page('Choose Resources for Accessibility Checking'));
     $r->print(&Apache::lonhtmlcommon::breadcrumbs('Accessibility'));
     $r->print(&startContentScreen('tools'));
+    my $axedir = $Apache::lonnet::perlvar{'lonAxeDir'};
+    my $warning = &lock_warning('Checking Accessibility');
+    if ($warning) {
+        $r->print($warning
+                 .&endContentScreen());
+        return;
+    }
     my ($navmap,$errormsg) =
         &Apache::loncourserespicker::get_navmap_object($crstype,'wcag');
     my (%maps,%resources,%titles);
@@ -6303,7 +6310,8 @@
         $r->print(&Apache::loncourserespicker::create_picker($navmap,'wcagcheck',$formname,$crstype,undef,
                                                              undef,undef,undef,undef,undef,undef,undef,$readonly));
     }
-    $r->print(&endContentScreen());
+    $r->print(&Apache::loncommon::recently_generated('axespool')
+             .&endContentScreen());
 }
 
 sub wcag_check_results {
@@ -6315,6 +6323,12 @@
     $r->print(&Apache::loncommon::start_page('Accessibility Results'));
     $r->print(&Apache::lonhtmlcommon::breadcrumbs('Accessibility'));
     $r->print(&startContentScreen('tools'));
+    my $warning = &lock_warning('Checking Accessibility');
+    if ($warning) {
+        $r->print($warning
+                 .&endContentScreen());
+        return;
+    }
     if ((ref($checkmain) eq 'ARRAY') && (ref($checksupp) eq 'ARRAY')) {
         if (@{$checkmain} > 0) {
             my ($navmap,$errormsg) =
@@ -6348,31 +6362,84 @@
         }
     }
     if ($filelist) {
-        my $identifier = &Apache::loncommon::get_cgi_id();
-        my $filename = "/home/httpd/axespool/$env{'user.name'}_$env{'user.domain'}_axe_$identifier.txt";
-        if (open(my $fh,'>',$filename)) {
-            print $fh $filelist;
-            close($fh);
-            &Apache::lonnet::appenv({'cgi.'.$identifier.'.file' => $filename,
-                                     'cgi.'.$identifier.'.user' => $env{'user.name'},
-                                     'cgi.'.$identifier.'.domain' => $env{'user.domain'},
-                                     'cgi.'.$identifier.'.cnum' => $cnum,
-                                     'cgi.'.$identifier.'.cdom' => $cdom,
-                                     'cgi.'.$identifier.'.main' => $nummain,
-                                     'cgi.'.$identifier.'.supp' => $numsupp,
-                                     'cgi.'.$identifier.'.crstype' => $crstype});
+        my $wcag;
+        if ($env{'form.standard'} =~ /^2(0|1|2)$/) {
+            $wcag = $env{'form.standard'};
+        } else {
+            $wcag = '22';
+        }
+        if ($env{'form.compliance'} =~ /^a{1,3}$/) {
+            $wcag .= $env{'form.compliance'};
+        } else {
+            $wcag .= 'aa';
         }
-        my $continue_text = &mt('Continue');
-        $r->print(<<END);
+        my $identifier = &Apache::loncommon::get_cgi_id();
+        my $axedir = $Apache::lonnet::perlvar{'lonAxeDir'};
+        if ($axedir) {
+            my $filename = "$axedir/$env{'user.name'}_$env{'user.domain'}_axe_$identifier.dat";
+            if (open(my $fh,'>',$filename)) {
+                print $fh $filelist;
+                close($fh);
+                &Apache::lonnet::appenv({'cgi.'.$identifier.'.file' => $filename,
+                                         'cgi.'.$identifier.'.wcag' => $wcag,
+                                         'cgi.'.$identifier.'.user' => $env{'user.name'},
+                                         'cgi.'.$identifier.'.domain' => $env{'user.domain'},
+                                         'cgi.'.$identifier.'.cnum' => $cnum,
+                                         'cgi.'.$identifier.'.cdom' => $cdom,
+                                         'cgi.'.$identifier.'.main' => $nummain,
+                                         'cgi.'.$identifier.'.supp' => $numsupp,
+                                         'cgi.'.$identifier.'.crstype' => $crstype});
+                my $continue_text = &mt('Continue');
+                my $loadmsg = &mt('Please be patient while the Accessibility Testing environment loads');
+                my $preamble = '<div id="LC_a11y" class="LC_info">'.
+                               '<br />'.
+                               $loadmsg.
+                               '<br /></div>'.
+                               '<div style="padding:0;clear:both;margin:0;border:0"></div>';
+                my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($r,undef,$preamble);
+                &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Loading Accessibility Checker ...'));
+                $r->print(<<END);
 <br />
 <meta http-equiv="Refresh" content="0; url=/cgi-bin/lonaxe.pl?$identifier" />
-<a href="/cgi-bin/lonaxe.pl?$identifier">$continue_text</a>
+<!-- <a href="/cgi-bin/lonaxe.pl?$identifier">$continue_text</a> -->
 END
+                $r->rflush();
+            } else {
+                $r->print('Could not open file to store list of resources')
+            }
+        } else {
+            $r->print('Could not determine directory where list of resources is stored');
+        }
+    } else {
+        $r->print('No resources selected. Nothing to do');
     }
     $r->print(&endContentScreen());
     return;
 }
 
+sub lock_warning {
+    my ($locktext) = @_;
+    if ($locktext ne '') {
+        my $lockid;
+        my ($numlocks,%locks)=&Apache::lonnet::get_locks();
+        if ($numlocks) {
+            foreach my $id (keys(%locks)) {
+                if ($locks{$id} =~ /^\Q$locktext\E/) {
+                    $lockid = $id;
+                    last;
+                }
+            }
+            if ($lockid) {
+                return '<h2 class="LC_heading_2">'.&mt('Accessibility checking in progress').'</h2>'
+                      .&mt('When an Accessibility check is started a lock set in your session remains in place until completion.')
+                      .'<br />'.&mt('An existing lock in your current session is preventing another check being started.')
+                      .'<p>'.&mt("If the lock should have been removed, but wasn't, you can clear it by attempting to [_1]logout[_2] of LON-CAPA and pushing the 'Override' button.",'<a href="/adm/logout">','</a>').'</p>';
+            }
+        }
+    }
+    return;
+}
+
 sub contentverifyform {
     my ($r) = @_;
     my $crstype = &Apache::loncommon::course_type();
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1498 loncom/interface/loncommon.pm:1.1499
--- loncom/interface/loncommon.pm:1.1498	Tue Jan 13 02:33:37 2026
+++ loncom/interface/loncommon.pm	Tue Jan 13 03:18:22 2026
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1498 2026/01/13 02:33:37 raeburn Exp $
+# $Id: loncommon.pm,v 1.1499 2026/01/13 03:18:22 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -9011,6 +9011,7 @@
 }
 
 fieldset.LC_grace,
+fieldset.LC_wcag,
 fieldset.LC_autoaddsdrops,
 fieldset#LC_additionalrecips {
   display:inline;
@@ -20799,67 +20800,91 @@
 #
 # Recently generated files in /home/httpd/prtspool or /home/httpd/axespool 
 #
-#
-# List of recently generated print files
-#
 sub recently_generated {
-    my ($prtspool) = @_;
+    my ($spooltype) = @_;
+    return unless (($spooltype eq 'prtspool') || ($spooltype eq 'axespool'));
+    my ($spooldir,$frag, at okexts,$regexp);
+    if ($spooltype eq 'axespool') {
+        $spooldir = $Apache::lonnet::perlvar{'lonAxeDir'};
+        $frag = 'axe';
+        @okexts = qw(csv txt);
+    } else {
+        $spooldir = $Apache::lonnet::perlvar{'lonPrtDir'};
+        $frag = 'printout';
+        @okexts = qw(zip pdf);
+    }
+    $regexp = join('|', at okexts);
+    my %lt = &Apache::lonlocal::texthash (
+        pdf => 'PDF File',
+        zip => 'Zip File',
+        csv => 'CSV File',
+        txt => 'Text File',
+    );
+    my %heading = &Apache::lonlocal::texthash (
+        zip => 'Recently generated printout zip files',
+        pdf => 'Recently generated printouts',
+        csv => 'Recently generated accessibility reports',
+        txt => 'Recently generated accessibility data',
+    );
     my $output;
-    my $zip_result;
-    my $pdf_result;
-    opendir(DIR,$prtspool);
-
-    my @files =
-        grep(/^$env{'user.name'}_$env{'user.domain'}_printout_(\d+)_.*\.(pdf|zip)$/,readdir(DIR));
-    closedir(DIR);
-
-    @files = sort {
-        my ($actime) = (stat($prtspool.'/'.$a))[10];
-        my ($bctime) = (stat($prtspool.'/'.$b))[10];
-        return $bctime <=> $actime;
-    } (@files);
-
-    foreach my $filename (@files) {
-        my ($ext) = ($filename =~ m/(pdf|zip)$/);
-        my ($cdev,$cino,$cmode,$cnlink,
-            $cuid,$cgid,$crdev,$csize,
-            $catime,$cmtime,$cctime,
-            $cblksize,$cblocks)=stat($prtspool.'/'.$filename);
-        my $ext_text = ($ext eq 'pdf') ? &mt('PDF File'):&mt('Zip File');
-        my $result=&start_data_table_row()
-                  .'<td>'
-                  .'<a href="/prtspool/'.$filename.'">'.$ext_text.'</a>'
-                  .'</td>'
-                  .'<td>'.&Apache::lonlocal::locallocaltime($cctime).'</td>'
-                  .'<td align="right">'.$csize.'</td>'
-                  .&end_data_table_row();
-        if ($ext eq 'pdf') { $pdf_result .= $result; }
-        if ($ext eq 'zip') { $zip_result .= $result; }
-    }
-    if ($zip_result || $pdf_result) {
-        $output ='<hr />';
-    }
-    if ($zip_result) {
-        $output .='<h3>'.&mt('Recently generated printout zip files')."</h3>\n"
-                  .&start_data_table()
-                  .&start_data_table_header_row()
-                  .'<th>'.&mt('Download').'</th>'
-                  .'<th>'.&mt('Creation Date').'</th>'
-                  .'<th>'.&mt('File Size (Bytes)').'</th>'
-                  .&end_data_table_header_row()
-                  .$zip_result
-                  .&end_data_table();
-    }
-    if ($pdf_result) {
-        $output .='<h3>'.&mt('Recently generated printouts')."</h3>\n"
-                  .&start_data_table()
-                  .&start_data_table_header_row()
-                  .'<th>'.&mt('Download').'</th>'
-                  .'<th>'.&mt('Creation Date').'</th>'
-                  .'<th>'.&mt('File Size (Bytes)').'</th>'
-                  .&end_data_table_header_row()
-                  .$pdf_result
-                  .&end_data_table();
+    if ($spooldir) {
+        my %result;
+        if (opendir(my $dh,$spooldir)) {
+            my @files =
+                grep(/^\Q$env{'user.name'}_$env{'user.domain'}_$frag\E_(\d+)_.*\.($regexp)$/,readdir($dh));
+            closedir($dh);
+            if (@files) {
+                @files = sort {
+                    my ($actime) = (stat($spooldir.'/'.$a))[10];
+                    my ($bctime) = (stat($spooldir.'/'.$b))[10];
+                    return $bctime <=> $actime;
+                } (@files);
+                my %count;
+                foreach my $filename (@files) {
+                    my ($ext) = ($filename =~ m/($regexp)$/);
+                    unless ($count{$ext}) {
+                        $count{$ext} = 0;
+                        $result{$ext} =
+                            '<h3>'.$heading{$ext}."</h3>\n"
+                           .'<table class="LC_data_table">'
+                           .'<tr class="LC_header_row">'
+                           .'<th>'.&mt('Download').'</th>'
+                           .'<th>'.&mt('Creation Date').'</th>'
+                           .'<th>'.&mt('File Size (Bytes)').'</th>'
+                           .'</tr>'."\n";
+                    }
+                    my ($cdev,$cino,$cmode,$cnlink,
+                        $cuid,$cgid,$crdev,$csize,
+                        $catime,$cmtime,$cctime,
+                        $cblksize,$cblocks)=stat($spooldir.'/'.$filename);
+                    my $css_class = ($count{$ext} % 2)?'LC_odd_row':'LC_even_row';
+                    $result{$ext} .=
+                        '<tr class="'.$css_class.'">'
+                       .'<td>'
+                       .'<a href="/'.$spooltype.'/'.$filename.'">'.$lt{$ext}.'</a>'
+                       .'</td>'
+                       .'<td>'.&Apache::lonlocal::locallocaltime($cctime).'</td>'
+                       .'<td align="right">'.$csize.'</td>'
+                       .'</tr>'."\n";
+                    $count{$ext} ++;
+                }
+                if ($spooltype eq 'prtspool') {
+                    if ($result{'zip'} || $result{'pdf'}) {
+                        $output ='<hr />';
+                    }
+                } elsif ($spooltype eq 'axespool') {
+                    if ($result{'csv'} || $result{'txt'}) {
+                        $output ='<hr />';
+                    }
+                }
+                foreach my $ext (@okexts) {
+                    if ($result{$ext}) {
+                        $output .= $result{$ext}
+                                  .'</table>'."\n";
+                    }
+                }
+            }
+        }
     }
     return $output;
 }
Index: loncom/interface/lonprintout.pm
diff -u loncom/interface/lonprintout.pm:1.709 loncom/interface/lonprintout.pm:1.710
--- loncom/interface/lonprintout.pm:1.709	Tue Jan 13 02:33:37 2026
+++ loncom/interface/lonprintout.pm	Tue Jan 13 03:18:22 2026
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Printout
 #
-# $Id: lonprintout.pm,v 1.709 2026/01/13 02:33:37 raeburn Exp $
+# $Id: lonprintout.pm,v 1.710 2026/01/13 03:18:22 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -4995,8 +4995,7 @@
         $r->print(&Apache::loncommon::start_page('Printing Helper').
                   '<h2>'.&mt('Unable to determine print context').'</h2>'.
                   '<p>'.&mt('Please display a resource, and then click the "Print" button/icon').'</p>');
-        my $prtspool=$r->dir_config('lonPrtDir');
-        my $footer = &Apache::loncommon::recently_generated($prtspool);
+        my $footer = &Apache::loncommon::recently_generated('prtspool');
         $r->print($footer.&Apache::loncommon::end_page());
         return OK;
     }
@@ -5297,8 +5296,7 @@
 
     my $footer;
     if ($helper->{STATE} eq 'START') {
-        my $prtspool=$r->dir_config('lonPrtDir');
-        $footer = &Apache::loncommon::recently_generated($prtspool);
+        $footer = &Apache::loncommon::recently_generated('prtspool');
     }
     $r->print($helper->display($footer));
     &Apache::lonhelper::unregisterHelperTags();
Index: loncom/interface/loncourserespicker.pm
diff -u loncom/interface/loncourserespicker.pm:1.25 loncom/interface/loncourserespicker.pm:1.26
--- loncom/interface/loncourserespicker.pm:1.25	Wed Dec 31 23:44:27 2025
+++ loncom/interface/loncourserespicker.pm	Tue Jan 13 03:18:22 2026
@@ -1,6 +1,6 @@
 # The LearningOnline Network
 #
-# $Id: loncourserespicker.pm,v 1.25 2025/12/31 23:44:27 raeburn Exp $
+# $Id: loncourserespicker.pm,v 1.26 2026/01/13 03:18:22 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -741,7 +741,26 @@
             if ($supp) {
                 $supp .= '</fieldset></div>';
             }
-            $display .= $supp.'<div style="padding:0;clear:both;margin:0;border:0"></div>'.
+            $display .= $supp.
+                        '<div style="padding:0;clear:both;margin:0;border:0"></div>'.
+                        '<div>'."\n".
+                        '<h2 class="LC_heading_2">'.&mt('Accessibility level').'</h2>'."\n".
+                        '<fieldset class="LC_wcag"><legend>'.&mt('WCAG standard').'</legend>'.
+                        '<span class="LC_nobreak">'.
+                        '<label><input name="standard" type="radio" value="20" />2.0</label>'.
+                        (' 'x2).
+                        '<label><input name="standard" type="radio" value="21" />2.1</label>'.
+                        (' 'x2).
+                        '<label><input name="standard" type="radio" value="22" checked="checked" />2.2</label>'.
+                        '</span></fieldset>'.
+                        (' 'x2).
+                        '<fieldset class="LC_wcag"><legend>'.&mt('Compliance level').'</legend>'.
+                        '<span class="LC_nobreak"><label><input name="compliance" type="radio" value ="a" />A</label>'.
+                        (' 'x2).
+                        '<label><input name="compliance" type="radio" value="aa" checked="checked" />AA</label>'.
+                        (' 'x2).
+                        '<label><input name="compliance" type="radio" value="aaa" />AAA</label>'.
+                        '</span></fieldset></div>'.
                         '<div>'.
                         '<input type="submit" name="wcagcheck" value="'.&mt('Check Accessibility').'" />'.
                         '</div>';
Index: loncom/node.js/axe/lonaxe.pl
diff -u loncom/node.js/axe/lonaxe.pl:1.2 loncom/node.js/axe/lonaxe.pl:1.3
--- loncom/node.js/axe/lonaxe.pl:1.2	Sat Jan  3 20:03:47 2026
+++ loncom/node.js/axe/lonaxe.pl	Tue Jan 13 03:18:24 2026
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 $|=1;
-# $Id: lonaxe.pl,v 1.2 2026/01/03 20:03:47 raeburn Exp $
+# $Id: lonaxe.pl,v 1.3 2026/01/13 03:18:24 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -50,7 +50,12 @@
 }
 
 my $identifier = $ENV{'QUERY_STRING'};
+my $wcag = $env{'cgi.'.$identifier.'.wcag'};
 my $filename = $env{'cgi.'.$identifier.'.file'};
+my $outfile = $filename;
+$outfile =~s/\.dat$/.txt/;
+my $csvfile = $outfile;
+$csvfile =~s/\.txt/.csv/;
 my $nummain = $env{'cgi.'.$identifier.'.main'};
 my $numsupp = $env{ 'cgi.'.$identifier.'.supp'};
 my $crstype = $env{ 'cgi.'.$identifier.'.crstype'};
@@ -68,7 +73,7 @@
 # Breadcrumbs
 my $brcrum = [{'href' => '/adm/coursedocs?tools=1',
                'text' => "$crstype Editor"},
-              {'href' => '',
+              {'href' => 'javascript:document.wcagresults.submit();',
                'text' => "Choose Resources"},
               {'href' => '',
                'text' => 'Accessibility Results'}];
@@ -76,14 +81,55 @@
 print &Apache::loncommon::start_page('Accessibility Checking',
                                      undef,
                                      {'bread_crumbs' => $brcrum,});
+print <<ENDFORM;
+<div class="LC_landmark" role="main" id="LC_main_content" enctype="application/x-www-form-urlencoded">
+<form name="wcagresults" action="/adm/coursedocs" method="post">
+<input type="hidden" name="wcagcheck" value="1">
+<input type="hidden" name="tools" value="1">
+</form>
+ENDFORM
 
 my $linked_id = $env{'user.linkedenv'};
 my $sessionfile = $env{'user.environment'};
 my $lonidsdir = $perlvar{'lonIDsDir'};
 my $lonhost = $perlvar{'lonHostID'};
+my $spooldir = $perlvar{'lonAxeDir'};
 my $hostname = &Apache::lonnet::hostname($lonhost);
-my $wcag = '22aa';
-my $handle;
+my ($handle,$csvlink,$detailslink,$showlevel,$lock);
+if ($wcag =~ /^2(0|1|2)(a{1,3})$/) {
+    $showlevel = "WCAG 2.$1 ".uc($2);
+} else {
+    $wcag = '22aa';
+    $showlevel = 'WCAG 2.2 AA';
+}
+
+my %lt = &Apache::lonlocal::texthash(
+             ple => 'Please so not switch roles, logout, or quit the browser until accessibility check is complete.',
+             res => 'Resource',
+             sta => 'Status',
+             vio => 'Violations',
+             cno => 'Could not open files for output',
+             nor => 'No resources found to check',
+             com => 'Number of resources in compliance',
+             not => 'Number of resources not in compliance',
+             sum => 'Results summary (CSV format)',
+             det => 'Details for non-compliant resources (plain text)',
+             err => 'Errors during accessibility checking',
+             lau => 'Launch of Accessibility checking failed',
+             pass => 'pass',
+             fail => 'fail',
+             invalid => 'Invalid compliance level or invalid URL',
+             nolistener => 'No listener available to receive URL',
+);
+
+if ($filename =~ m{^\Q$spooldir\E/($env{'user.name'}_$env{'user.domain'}_axe_\d+_.*)\.dat$}) {
+    my $stem = $1;
+    $csvlink = "/axespool/$stem.csv";
+    $detailslink = "/axespool/$stem.txt";
+} else {
+    print &Apache::loncommon::end_page();
+    exit;
+}
 
 if ($sessionfile =~ m{^\Q$lonidsdir\E/([^/]+)\.id$}) {
     $handle = $1;
@@ -95,10 +141,18 @@
         $cookieid = 'lonID';
         $protocol = 'http';
     }
+    my $now = time;
+    $lock=&Apache::lonnet::set_lock("Checking Accessibility ($now)");
     if (&LONCAPA::AxeRunner::launch($cookieid,$handle,$linked_id,$hostname,$wcag) eq 'ok') {
         my $number_of_files = $nummain + $numsupp;
         if ($number_of_files) {
-            my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin('',$number_of_files);
+            my $preamble = '<div id="LC_wcag_axe" class="LC_info">'.
+                           '<br />'.
+                           $lt{'ple'}.
+                           '<br /></div>'.
+                           '<div style="padding:0;clear:both;margin:0;border:0"></div>';
+            my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin('',$number_of_files,$preamble);
+            my (%errors, at passed, at failed);
             print "<br />";
             if (open(my $fh,'<',$filename)) {
                 my @items;
@@ -107,41 +161,90 @@
                     push(@items,$line);
                 }
                 close($fh);
-                foreach my $item (@items) {
-                    my ($type,$dest,$title) = split(/\0/,$item);
-                    my $querystr = '?inhibitmenu=yes';
-                    my $url = $dest;
-                    if ($type eq 'M') {
-                        $querystr .= '&symb='.$dest;
-                        $url = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($dest))[2]);
-                    }
-                    if ($url ne '') { 
-                        my ($violations,$details) =
-                            &LONCAPA::AxeRunner::checkcompliance($protocol.'://'.$hostname.$url.$querystr);
-                        &Apache::lonhtmlcommon::Increment_PrgWin('',\%prog_state,'last resource');
-                        if ($violations eq -1) {
-                            print "Invalid compliance level or invalid URL\n";
-                        } elsif ($violations eq '') {
-                            print "No listener available to receive URL\n";
-                        } elsif ($violations == 0) {
-                            print "No violations for $url\n";
-                        } else {
-                            print "$violations violations for $url\n".'<br />'.$details."\n";
+                if (@items) {
+                    my ($outfh,$csvfh);
+                    if ((open($outfh,'>',$outfile)) && (open($csvfh,'>',$csvfile))) {
+                        print $csvfh "\"$lt{'res'}\",\"$lt{'sta'}\",\"$lt{'vio'}\"\n";
+                        foreach my $item (@items) {
+                            my ($type,$dest,$title) = split(/\0/,$item);
+                            my $querystr = '?inhibitmenu=yes';
+                            my $url = $dest;
+                            if ($type eq 'M') {
+                                $querystr .= '&symb='.$dest;
+                                $url = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($dest))[2]);
+                            }
+                            if ($url ne '') { 
+                                my ($violations,$details) =
+                                    &LONCAPA::AxeRunner::checkcompliance($protocol.'://'.$hostname.$url.$querystr);
+                                &Apache::lonhtmlcommon::Increment_PrgWin('',\%prog_state,'last resource');
+                                if ($violations eq -1) {
+                                    push(@{$errors{'invalid'}},$url);
+                                } elsif ($violations eq '') {
+                                    push(@{$errors{'nolistener'}},$url);
+                                } elsif ($violations == 0) {
+                                    push(@passed,$url);
+                                    print $csvfh "\"$url\",\"$lt{'pass'}\",\"0\"\n";
+                                } else {
+                                    push(@failed,$url);
+                                    print $outfh &mt('[quant,_1,violation] for[_2]',
+                                                     $violations,$url)."\n".$details."\n\n";
+                                    print $csvfh "\"$url\",\"$lt{'fail'}\",\"$violations\"\n";
+                                }
+                            }
                         }
+                        close($csvfh);
+                        close($outfh);
+                    } else {
+                        print '<span class="LC_error">'.$lt{'cno'}.'</span><br />'."\n";
                     }
+                } else {
+                    print '<span class="LC_warning">'.$lt{'nor'}.'</span><br />'."\n";
                 }
             }
             &Apache::lonhtmlcommon::Close_PrgWin('',\%prog_state);
-            print "<br />";
+            print <<ENDCLOSE;
+<script type="text/javascript">
+// <![CDATA[
+\$("#LC_wcag_axe").hide('slow');
+// ]]>
+</script>
+ENDCLOSE
+            if (@passed || @failed) {
+                print '<h2 class="LC_heading_2">'.
+                      &mt('Accessibility checking: compliance status (level [_1])',$showlevel).
+                      '</h2>'."\n".
+                      '<ul>'."\n".
+                      '<li>'.$lt{'com'}.': '.scalar(@passed).'</li>'."\n".
+                      '<li>'.$lt{'not'}.': '.scalar(@failed).'</li>'."\n".
+                      '</ul>'."\n".
+                      '<p><a href="'.$csvlink.'">'.$lt{'sum'}.'</a></p>';
+                if (@failed) {
+                    print '<p><a href="'.$detailslink.'">'.$lt{'det'}.'</a></p>';
+                }
+            }
+            if (keys(%errors)) {
+                print  '<h2 class="LC_heading_2">'.$lt{'err'}.'</h2>'."\n";
+                foreach my $posskey ('invalid','nolistener') {
+                    if ((exists($errors{$posskey})) && (ref($errors{$posskey}) eq 'ARRAY')) {
+                        print '<h3 class="LC_heading_3">'.$lt{$posskey}.'</h3>'.
+                              '<ul>';
+                        foreach my $url (@{$errors{$posskey}}) {
+                            print '<li>'.$url.'</li>';
+                        }
+                        print '</ul>';
+                    }
+                }
+            }
         }
     }
     unless (&LONCAPA::AxeRunner::cleanup() eq 'ok') {
-        print "Did not exit normally.\n";
+        &Apache::lonnet::logthis("lonaxe.pl -- AxeRunner::cleanup did not exit normally.");
     }
+    if ($lock) { &Apache::lonnet::remove_lock($lock); }
 } else {
-    print "Launch failed.\n";
+    print '<h2 class="LC_heading_2">'.$lt{'lau'}.'</h2>';
     unless (&LONCAPA::AxeRunner::cleanup() eq 'ok') {
-        print "Did not exit normally.\n";
+        &Apache::lonnet::logthis("lonaxe.pl -- AxeRunner::cleanup did not exit normally.");
     }
 }
 print &Apache::loncommon::end_page();
Index: loncom/misc/cleanup_file_caches.pl
diff -u loncom/misc/cleanup_file_caches.pl:1.5 loncom/misc/cleanup_file_caches.pl:1.6
--- loncom/misc/cleanup_file_caches.pl:1.5	Fri Apr 20 22:00:40 2007
+++ loncom/misc/cleanup_file_caches.pl	Tue Jan 13 03:18:25 2026
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 # The LearningOnline Network
 #
-# $Id: cleanup_file_caches.pl,v 1.5 2007/04/20 22:00:40 banghart Exp $
+# $Id: cleanup_file_caches.pl,v 1.6 2026/01/13 03:18:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -58,6 +58,7 @@
 my $killtime = $conf->{'lonExpire'};
 my $prt_spool_dir = $conf->{'lonPrtDir'};
 my $zip_spool_dir = $conf->{'lonZipDir'};
+my $axe_spool_dir = $conf->{'lonAxeDir'};
 my $userfile_dir = $conf->{'lonDocRoot'}.'/userfiles';
 sub kill_if_old {
     my $filename = $File::Find::name;
@@ -70,4 +71,5 @@
 
 find (\&kill_if_old,$prt_spool_dir);
 find (\&kill_if_old,$zip_spool_dir);
+find (\&kill_if_old,$axe_spool_dir);
 find (\&kill_if_old,$userfile_dir);
Index: loncom/auth/lonacc.pm
diff -u loncom/auth/lonacc.pm:1.210 loncom/auth/lonacc.pm:1.211
--- loncom/auth/lonacc.pm:1.210	Mon Aug 28 20:40:00 2023
+++ loncom/auth/lonacc.pm	Tue Jan 13 03:18:26 2026
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Cookie Based Access Handler
 #
-# $Id: lonacc.pm,v 1.210 2023/08/28 20:40:00 raeburn Exp $
+# $Id: lonacc.pm,v 1.211 2026/01/13 03:18:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -906,7 +906,7 @@
 # ---------------------------------------------------------------- Check access
 	my $now = time;
         my ($check_symb,$check_access,$check_block,$access,$poss_symb);
-	if ($requrl !~ m{^/(?:adm|public|(?:prt|zip)spool)/}
+	if ($requrl !~ m{^/(?:adm|public|(?:prt|zip|axe)spool)/}
 	    || $requrl =~ /^\/adm\/.*\/(smppg|bulletinboard)(\?|$ )/x) {
             $check_access = 1;
         }
@@ -1062,8 +1062,9 @@
                 }
 	    }
 	}
-	if ($requrl =~ m|^/prtspool/|) {
-	    my $start='/prtspool/'.$env{'user.name'}.'_'.
+	if ($requrl =~ m{^/(prt|axe)spool/}) {
+	    my $prefix = $1;
+	    my $start='/'.$prefix.'spool/'.$env{'user.name'}.'_'.
 		$env{'user.domain'};
 	    if ($requrl !~ /^\Q$start\E/) {
 		$env{'user.error.msg'}="$requrl:bre:1:1:Access Denied";


More information about the LON-CAPA-cvs mailing list