File:  [LON-CAPA] / rat / map.pm
Revision 1.16: download - view: text, annotated - select for diffs
Sat Oct 22 17:24:55 2022 UTC (19 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, version_2_11_4_msu, HEAD
- Bug 6975
  Code to trigger updates of cached Supplemental Content information for a
  course moved from londocs.pm to lonnet.pm to facilitate reuse, and called
  after uploading file and/or extracting archive file using Course Editor.

# The LearningOnline Network with CAPA
# routines for modyfing .sequence and .page files
#
# $Id: map.pm,v 1.16 2022/10/22 17:24:55 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

package LONCAPA::map;
use strict;
use HTML::TokeParser;
use HTML::Entities();
use Apache::lonnet;
use Apache::lonlocal;
use File::Copy;
use LONCAPA;

use vars qw(@order @resources @resparms @zombies);

# Mapread read maps into global arrays @links and @resources, determines status
# sets @order - pointer to resources in right order
# sets @resources - array with the resources with correct idx
#
sub mapread {
    my ($fn)= @_;

    my @links;

    @resources=('');
    @order=();
    @resparms=();
    @zombies=();

    my ($outtext,$errtext)=&loadmap($fn,'');
    if ($errtext) { return ($errtext,2); }

# -------------------------------------------------------------------- Read map
    foreach (split(/\<\&\>/,$outtext)) {
	my ($command,$number,$content)=split(/\<\:\>/,$_);
        if ($command eq 'objcont') {
	    my ($title,$src,$ext,$type)=split(/\:/,$content);
	    if ($ext eq 'cond') { next; }
	    if ($type ne 'zombie') {
		$resources[$number]=$content;
	    } else {
		$zombies[$number]=$content;
	    }
        }
        if ($command eq 'objlinks') {
            $links[$number]=$content;
        }
        if ($command eq 'objparms') {
	    if ($resparms[$number]) {
		$resparms[$number].='&&&'.$content;
	    } else {
		$resparms[$number]=$content;
	    }
        }
    }
# ------------------------------------------------------- Is this a linear map?
    my @starters;
    my @endings;

    foreach (@links) {
        if (defined($_)) {
	    my ($start,$end,$cond)=split(/\:/,$_);
            if ((defined($starters[$start])) || (defined($endings[$end]))) { 
		return
		    (&mt('Map has branchings. Use advanced editor.'),1);
            }
	    $starters[$start]=1;
	    $endings[$end]=1;
	    if ($cond) {
		return
		    (&mt('Map has conditions. Use advanced editor.'),1);
            }
	}
    }

    for (my $i=1; $i<=$#resources; $i++) {
        if (defined($resources[$i])) {
	    unless (($starters[$i]) || ($endings[$i])) {
                return
		    (&mt('Map has unconnected resources. Use advanced editor.'),1);
            }
        }
    }
# ---------------------------------------------- Did we just read an empty map?
    if ($#resources<1) {
        undef $resources[0];
	$resources[1]=':::start';
        $resources[2]=':::finish';
    }
# -------------------------------------------------- This is a linear map, sort

    my $startidx=0;
    my $endidx=0;
    for (my $i=0; $i<=$#resources; $i++) {
        if (defined($resources[$i])) {
            my ($title,$url,$ext,$type)=split(/\:/,$resources[$i]);
	    if ($type eq 'start') { $startidx=$i; }
            if ($type eq 'finish') { $endidx=$i; }
        }
    }
    my $k=0;
    my $currentidx=$startidx;
    $order[$k]=$currentidx;
    for (my $i=0; $i<=$#resources; $i++) {
        foreach (@links) {
	    my ($start,$end)=split(/\:/,$_);
            if ($start==$currentidx) {
		$currentidx=$end;
                $k++;
                $order[$k]=$currentidx;
                last;
            }
        }
        if ($currentidx==$endidx) { last; }
    }
    return $errtext;
}

# ---------------------------------------------- Read a map as well as possible
# Also used by the sequence handler
# Call lonsequence::attemptread to read from resource space
#
sub attemptread {
    my ($fn,$unsorted)=@_;

    my @links;
    my @theseres;

    my ($outtext,$errtext)=&loadmap($fn,'');
    if ($errtext) { return @theseres }

# -------------------------------------------------------------------- Read map
    my ($start,$finish);
    foreach (split(/\<\&\>/,$outtext)) {
	my ($command,$number,$content)=split(/\<\:\>/,$_);
        if ($command eq 'objcont') {
	    my ($title,$src,$ext,$type)=split(/\:/,$content);	    
	    if ($type ne 'zombie' && $ext ne 'cond') {
		$theseres[$number]=$content;
	    }
	    if ($type eq 'start') {
		$start = $number;
	    }
	    if ($type eq 'finish') {
		$finish = $number;
	    }
        }
        if ($command eq 'objlinks') {
            $links[$number]=$content;
        }
    }
    if ($unsorted) {
	return @theseres;
    }

# ---------------------------- attempt to flatten the map into a 'sorted' order

    my %path_length = ($start => 0);
    my @todo = @links;

    while (@todo) {
	my $link = shift(@todo);
	next if (!defined($link));
	my ($from,$to) = split(':',$link);
	if (!exists($path_length{$from})) {
	    # don't know how long it takes to get to this link,
	    # save away to retry
	    push(@todo,$link);
	    next;
	}
	# already have a length, keep it
	next if (exists($path_length{$to}));
	$path_length{$to}=$path_length{$from}+1;
    }
    # invert hash so we have the ids in depth order now
    my @by_depth;
    while (my ($key,$value) = each(%path_length)) {
	push(@{$by_depth[$value]},$key);
    }
    # reorder resources
    my @outres;
    foreach my $ids_at_depth (@by_depth) {
	foreach my $id (sort(@{$ids_at_depth})) {
	    # skip the finish resource
	    next if ($id == $finish);
	    push(@outres, $theseres[$id]);
	}
    }
    # make sure finish is last (in case there are cycles or bypass routes
    # finish can end up with a rather short possible path)
    push(@outres, $theseres[$finish]);
    return @outres;
}

# ------------------------------------- Revive zombie idx or get unused number

sub getresidx {
    my ($url,$residx)= @_;
    my $max=1+($#resources>$#zombies?$#resources:$#zombies);
    unless ($url) { return $max; }
    for (my $i=0; $i<=$#zombies; $i++) {
	my ($title,$src,$ext,$type)=split(/\:/,$zombies[$i]);
	if ($src eq $url) {
	    if ($residx) {
		if ($i == $residx) {
		    undef($zombies[$i]);
		    return $i;
		}
	    } else {
		undef($zombies[$i]);
		return $i;
	    }
	}
    }
    return $max;
}

# --------------------------------------------------------------- Make a zombie

sub makezombie {
    my $idx=shift;
    my ($name,$url,$ext)=split(/\:/,$resources[$idx]);
    my $now=time;
    $zombies[$idx]=$name.
	' [('.$now.','.$env{'user.name'}.','.$env{'user.domain'}.')]:'.
	$url.':'.$ext.':zombie';
}

# ----------------------------------------------------------- Paste into target
# modifies @order, @resources

sub pastetarget {
    my ($after,@which)=@_;
    my @insertorder=();
    foreach (@which) {
        if (defined($_)) {
	    my ($name,$url,$residx)=split(/\=/,$_);
            $name=&unescape($name);
            $url=&unescape($url);
            if ($url) {
		my $idx=&getresidx($url,$residx);
		$insertorder[$#insertorder+1]=$idx;
		my $ext='false';
		if ($url=~/^https?\:\/\//) { $ext='true'; }
		$url=~s/\:/\&colon;/g;
		$name=~s/\:/\&colon;/g;
		$resources[$idx]=$name.':'.$url.':'.$ext.':normal:res';
	    }
        }
    }
    my @oldorder=splice(@order,$after);
    @order=(@order,@insertorder,@oldorder);
}

# ------------------------------------------------ Get start and finish correct
# modifies @resources

sub startfinish {
# Remove all start and finish
    foreach (@order) {
	my ($name,$url,$ext)=split(/\:/,$resources[$_]);
        if ($url=~/https?\&colon\:\/\//) { $ext='true'; }
        $resources[$_]=$name.':'.$url.':'.$ext.':normal:res';
    }
# Garbage collection
    my $stillchange=1;
    while (($#order>1) && ($stillchange)) {
	$stillchange=0;
	for (my $i=0;$i<=$#order;$i++) {
	    my ($name,$url,$ext)=split(/\:/,$resources[$order[$i]]);
	    unless ($url) {
# Take out empty resource
		for (my $j=$i+1;$j<=$#order;$j++) {
		    $order[$j-1]=$order[$j];
		}
		$#order--;
		$stillchange=1;
		last;
	    }
	}
    }
# Make sure this has at least start and finish
    if ($#order==-1) {
        $resources[&getresidx()]='::false';
        $order[0]=$#resources;
    }
# Put in a start resource
    my ($name,$url,$ext)=split(/\:/,$resources[$order[0]]);
    $resources[$order[0]]=$name.':'.$url.':'.$ext.':start:res';
    if ($#order==0) {
	$resources[&getresidx()]='::false';
	$order[1]=$#resources;
    }
# Make the last one a finish resource
    ($name,$url,$ext)=split(/\:/,$resources[$order[$#order]]);
    $resources[$order[$#order]]=$name.':'.$url.':'.$ext.':finish:res';
}

# ------------------------------------------------------------------- Store map

sub storemap {
    my ($realfn,$useorig,$dotimeupdate) = @_;
    my $fn=$realfn;
# unless this is forced to work from the original file, use a temporary file
# instead
    unless ($useorig) {
	$fn=$realfn.'.tmp';
	unless (-e $fn) {
	    copy($realfn,$fn);
	}
    }
# store data either into tmp or real file
    &startfinish();
    my $output='graphdef<:>no';
    my $k=1;
    for (my $i=0; $i<=$#order; $i++) {
        if (defined($resources[$order[$i]])) {
	    $output.='<&>objcont<:>'.$order[$i].'<:>'.$resources[$order[$i]];
        }
	if (defined($resparms[$order[$i]])) {
	    foreach (split('&&&',$resparms[$order[$i]])) {
		if ($_) {
		    $output.='<&>objparms<:>'.$order[$i].'<:>'.$_;
		}
	    }
	}
        if (defined($order[$i+1])) {
	    if (defined($resources[$order[$i+1]])) {
		$output.='<&>objlinks<:>'.$k.'<:>'.
		    $order[$i].':'.$order[$i+1].':0';
		$k++;
            }
        }
    }
    for (my $i=0; $i<=$#zombies; $i++) {
        if (defined($zombies[$i])) {
	    $output.='<&>objcont<:>'.$i.'<:>'.$zombies[$i];
        }
    }
    $output=~s/http\&colon\;\/\///g;
    $env{'form.output'}=$output;
    return &loadmap($fn,&savemap($fn,'',$dotimeupdate));
}

# ------------------------------------------ Store and get parameters in global

sub storeparameter {
    my ($to,$name,$value,$ptype)=@_;
    my $newentry='';
    my $nametype='';
    foreach (split('&&&',$resparms[$to])) {
	my ($thistype,$thisname,$thisvalue)=split('___',$_);
	if ($thisname) {
	    unless ($thisname eq $name) {
		$newentry.=$_.'&&&';
	    } else {
		$nametype=$thistype;
	    }
	}
    }
    unless ($ptype) { $ptype=$nametype; }
    unless ($ptype) { $ptype='string'; }
    $newentry.=$ptype.'___'.$name.'___'.$value;
    $resparms[$to]=$newentry;
}

sub delparameter {
    my ($to,$name)=@_;
    my $newentry='';
    my $nametype='';
    foreach (split('&&&',$resparms[$to])) {
	my ($thistype,$thisname,$thisvalue)=split('___',$_);
	if ($thisname) {
	    unless ($thisname eq $name) {
		$newentry.=$_.'&&&';
	    }
	}
    }
    $resparms[$to]=$newentry;
}

sub getparameter {
    my ($to,$name)=@_;
    my $value=undef;
    my $ptype=undef;
    foreach (split('&&&',$resparms[$to])) {
	my ($thistype,$thisname,$thisvalue)=split('___',$_);
	if ($thisname eq $name) {
	    $value=$thisvalue;
	    $ptype=$thistype;
	}
    }
    return ($value,$ptype);
}

# ------------------------------------------------------------- From RAT to XML

sub qtescape {
    my $str=shift;
    $str=~s/\&colon;/\:/g;
    $str=~s/\&\#58\;/\:/g;
    $str=~s/\&\#39\;/\'/g;
    $str=~s/\&\#44\;/\,/g;
    $str=~s/\&\#34\;/\"/g;
    return $str;
}

# ------------------------------------------------------------- From XML to RAT

sub qtunescape {
    my $str=shift;
    $str=~s/\:/\&colon\;/g;
    $str=~s/\'/\&\#39\;/g;
    $str=~s/\,/\&\#44\;/g;
    $str=~s/\"/\&\#34\;/g;
    return $str;
}

# --------------------------------------------------------- Loads map from disk

sub loadmap {
    my ($fn,$errtext,$infotext)=@_;
    if ($errtext) { return('',$errtext); }
    my $outstr='';
    my @obj=();
    my @links=();
    my $instr='';
    if ($fn=~/^\/*uploaded\//) {
        $instr=&Apache::lonnet::getfile($fn);
    } elsif (-e $fn) {
        my @content=();
        {
	    open(my $fh,"<$fn");
            @content=<$fh>;
        }
        $instr=join('',@content);
    }
    if ($instr eq -2) {
        $errtext.='Map not loaded: An error occurred while trying to load the map.';
    } elsif ($instr eq '-1') {
	# Map doesn't exist 
    } elsif ($instr) {
        my $parser = HTML::TokeParser->new(\$instr);
        my $token;
        my $graphmode=0;

        $fn=~/\.(\w+)$/;
        $outstr="mode<:>$1";

        while ($token = $parser->get_token) {
	    if ($token->[0] eq 'S') {
                if ($token->[1] eq 'map') {
		    $graphmode=($token->[2]->{'mode'} eq 'rat/graphical');
                } elsif ($token->[1] eq 'resource') {
# -------------------------------------------------------------------- Resource
                    $outstr.='<&>objcont';
                    if (defined($token->[2]->{'id'})) {
			$outstr.='<:>'.$token->[2]->{'id'};
                        if ($obj[$token->[2]->{'id'}]==1) {
			    $errtext.='Error: multiple use of ID '.
				$token->[2]->{'id'}.'. ';
                        }
                        $obj[$token->[2]->{'id'}]=1; 
                    } else {
                        my $i=1;
                        while (($i<=$#obj) && ($obj[$i]!=0)) { $i++; }
                        $outstr.='<:>'.$i;
                        $obj[$i]=1;
                    }
                    $outstr.='<:>';
                    $outstr.=qtunescape($token->[2]->{'title'}).":";
                    $outstr.=qtunescape($token->[2]->{'src'}).":";
                    if ($token->[2]->{'external'} eq 'true') {
                        $outstr.='true:';
                    } else {
                        $outstr.='false:';
                    }
                    if (defined($token->[2]->{'type'})) {
			$outstr.=$token->[2]->{'type'}.':';
                    }  else {
                        $outstr.='normal:';
                    }
		    if ($token->[2]->{'type'} ne 'zombie') {
			$outstr.='res';
		    } else {
                        $outstr.='zombie';
		    }
                } elsif ($token->[1] eq 'condition') {
# ------------------------------------------------------------------- Condition
                    $outstr.='<&>objcont';
                    if (defined($token->[2]->{'id'})) {
			$outstr.='<:>'.$token->[2]->{'id'};
                        if ($obj[$token->[2]->{'id'}]==1) {
			    $errtext.='Error: multiple use of ID '.
				$token->[2]->{'id'}.'. ';
                        }
                        $obj[$token->[2]->{'id'}]=1; 
                    } else {
                        my $i=1;
                        while (($i<=$#obj) && ($obj[$i]!=0)) { $i++; }
                        $outstr.='<:>'.$i;
                        $obj[$i]=1;
                    }
                    $outstr.='<:>';
                    $outstr.=qtunescape($token->[2]->{'value'}).':';
                    if (defined($token->[2]->{'type'})) {
			$outstr.=$token->[2]->{'type'}.':';
                    } else {
                        $outstr.='normal:';
                    }
                    $outstr.='cond';
                } elsif ($token->[1] eq 'link') {
# ----------------------------------------------------------------------- Links
                    $outstr.='<&>objlinks';
		    
		    if (defined($token->[2]->{'index'})) {
			if ($links[$token->[2]->{'index'}]) {
			    $errtext.='Error: multiple use of link index '.
				$token->[2]->{'index'}.'. ';
			}
			$outstr.='<:>'.$token->[2]->{'index'};
			$links[$token->[2]->{'index'}]=1;
		    } else {
			my $i=1;
			while (($i<=$#links) && ($links[$i]==1)) { $i++; }
			$outstr.='<:>'.$i;
			$links[$i]=1;
		    }
		    
                    $outstr.='<:>'.$token->[2]->{'from'}.
			':'.$token->[2]->{'to'};
                    if (defined($token->[2]->{'condition'})) {
			$outstr.=':'.$token->[2]->{'condition'};
                    } else {
 			$outstr.=':0';
                    }
# ------------------------------------------------------------------- Parameter
                } elsif ($token->[1] eq 'param') {
                    $outstr.='<&>objparms<:>'.$token->[2]->{'to'}.'<:>'.
			$token->[2]->{'type'}.'___'.$token->[2]->{'name'}.
			'___'.$token->[2]->{'value'};
                } elsif ($graphmode) {
# --------------------------------------------- All other tags (graphical only)
                    $outstr.='<&>'.$token->[1];
                    if (defined($token->[2]->{'index'})) {
			$outstr.='<:>'.$token->[2]->{'index'};
                        if ($token->[1] eq 'obj') {
			    $obj[$token->[2]->{'index'}]=2;
                        }
                    }
                    $outstr.='<:>'.$token->[2]->{'value'};
                }
            }
        }

    } else {
        $errtext.='Map not loaded: The file does not exist. ';
    }
    return($outstr,$errtext,$infotext);
}


# ----------------------------------------------------------- Saves map to disk

sub savemap {
    my ($fn,$errtext,$dotimeupdate)=@_;
    my $infotext='';
    my %alltypes;
    my %allvalues;
    if (($fn=~/\.sequence(\.tmp)*$/) ||
        ($fn=~/\.page(\.tmp)*$/)) {
	
# ------------------------------------------------------------- Deal with input
        my @tags=split(/<&>/,$env{'form.output'});
        my $outstr='';
        my $graphdef=0;
        if ($tags[0] eq 'graphdef<:>yes') {
	    $outstr='<map mode="rat/graphical">'."\n";
            $graphdef=1;
        } else {
            $outstr="<map>\n";
        }
        foreach (@tags) {
	    my @parts=split(/<:>/,$_);
	    if ($parts[0] eq 'objcont') {
		my @comp=split(/:/,$parts[$#parts]);
# --------------------------------------------------------------- Logical input
		if (($comp[$#comp] eq 'res') || ($comp[$#comp] eq 'zombie')) {
		    $comp[0]=qtescape($comp[0]);
		    $comp[0] = &HTML::Entities::encode($comp[0],'&<>"');
		    
		    $comp[1]=qtescape($comp[1]);
		    if ($comp[2] eq 'true') {
			if ($comp[1]!~/^http\:\/\//) {
			    $comp[1]='http://'.$comp[1];
			}
			$comp[1].='" external="true';
		    } else {
			if ($comp[1]=~/^http\:\/\//) {
			    $comp[1]=~s/^http\:\/\/[^\/]*\//\//;
			}
		    }
		    $outstr.='<resource id="'.$parts[1].'" src="'
			.$comp[1].'"';
		    
		    if (($comp[3] ne '') && ($comp[3] ne 'normal')) {
			$outstr.=' type="'.$comp[3].'"';
		    }
		    if ($comp[0] ne '') {
			$outstr.=' title="'.$comp[0].'"';
		    }
		    $outstr.=" />\n";
		} elsif ($comp[$#comp] eq 'cond') {
		    $outstr.='<condition id="'.$parts[1].'"';
		    if (($comp[1] ne '') && ($comp[1] ne 'normal')) {
			$outstr.=' type="'.$comp[1].'"';
		    }
		    $outstr.=' value="'.qtescape($comp[0]).'"';
		    $outstr.=" />\n";
		}
	    } elsif ($parts[0] eq 'objlinks') {
		my @comp=split(/:/,$parts[$#parts]);
		$outstr.='<link';
		$outstr.=' from="'.$comp[0].'"';
		$outstr.=' to="'.$comp[1].'"';
		if (($comp[2] ne '') && ($comp[2]!=0)) {
		    $outstr.=' condition="'.$comp[2].'"';
		}
		$outstr.=' index="'.$parts[1].'"';
		$outstr.=" />\n";
	    } elsif ($parts[0] eq 'objparms') {
		undef %alltypes;
		undef %allvalues;
		foreach (split(/:/,$parts[$#parts])) {
		    my ($type,$name,$value)=split(/\_\_\_/,$_);
		    $alltypes{$name}=$type;
		    $allvalues{$name}=$value;
		}
		foreach (keys %allvalues) {
		    if ($allvalues{$_} ne '') {
			$outstr.='<param to="'.$parts[1].'" type="'
			    .$alltypes{$_}.'" name="'.$_
			    .'" value="'.$allvalues{$_}.'" />'
			    ."\n";
		    }
		}
	    } elsif (($parts[0] ne '') && ($graphdef)) {
# ------------------------------------------------------------- Graphical input
		$outstr.='<'.$parts[0];
		if ($#parts==2) {
		    $outstr.=' index="'.$parts[1].'"';
		}
		$outstr.=' value="'.qtescape($parts[$#parts]).'" />'."\n";
	    }
        }
        $outstr.="</map>\n";
        my ($cdom,$cnum,$mapname);
	if ($fn=~m{^/*uploaded/($LONCAPA::domain_re)/($LONCAPA::courseid_re)/(.*)$}) {
            ($cdom,$cnum,$mapname) = ($1,$2,$3);
	    $env{'form.output'}=$outstr;
            my $result=&Apache::lonnet::finishuserfileupload($cnum,$cdom,
							     'output',$mapname);
	    if ($result != m|^/uploaded/|) {
		$errtext.='Map not saved: A network error occurred when trying to save the map. ';
	    }
        } else {
	    if (open(my $fh,">$fn")) {
		print $fh $outstr;
		$infotext.="Map saved as $fn. ";
	    } else {
		$errtext.='Could not write file '.$fn.'.  Map not saved. ';
	    }
        }
        if ($dotimeupdate) {
            unless ($errtext) {
                if ($env{'request.course.id'}) {
                    if (($cdom eq $env{'course.'.$env{'request.course.id'}.'.domain'}) &&
                        ($cnum eq $env{'course.'.$env{'request.course.id'}.'.num'}) &&
                        ($mapname =~ /^supplemental(|_\d+)\.sequence$/)) {
                        &Apache::lonnet::update_supp_caches($cdom,$cnum);
                    } else {
                        my $now = time;
                        &Apache::lonnet::put('environment',{'internal.contentchange' => $now},
                                             $env{'course.'.$env{'request.course.id'}.'.domain'},
                                             $env{'course.'.$env{'request.course.id'}.'.num'});
                        &Apache::lonnet::appenv(
                            {'course.'.$env{'request.course.id'}.'.internal.contentchange' => $now});
                        &Apache::lonnet::do_cache_new('crschange',$env{'request.course.id'},$now,600);
                    }
                }
            }
        }
    } else {
# -------------------------------------------- Cannot write to that file, error
        $errtext.='Map not saved: The specified path does not exist. ';
    }
    return ($errtext,$infotext);
}

1;
__END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.