implements a mail-based queue and does some other clean-up

This commit is contained in:
myk%mozilla.org 2004-04-27 20:52:54 +00:00
parent bf6483c50f
commit 12eb13c634
5 changed files with 388 additions and 440 deletions

View File

@ -56,6 +56,8 @@ my $template = Template->new(
{
# Colon-separated list of directories containing templates.
INCLUDE_PATH => "templates" ,
PRE_CHOMP => 1,
POST_CHOMP => 1,
FILTERS =>
{
@ -89,46 +91,16 @@ use AppConfig qw(:expand :argcount);
# in the configuration file.
my $config = AppConfig->new(
{
CREATE => 1 ,
GLOBAL => {
CASE => 1,
CREATE => 1 ,
GLOBAL => {
ARGCOUNT => ARGCOUNT_ONE ,
}
}
);
$config->file("doctor.conf");
# Write parameters into global variables.
my $TEMP_DIR = $config->get('TEMP_DIR');
my $READ_CVS_SERVER = $config->get('READ_CVS_SERVER');
my $READ_CVS_USERNAME = $config->get('READ_CVS_USERNAME');
my $READ_CVS_PASSWORD = $config->get('READ_CVS_PASSWORD');
my $WRITE_CVS_SERVER = $config->get('WRITE_CVS_SERVER');
my $WEB_BASE_URI = $config->get('WEB_BASE_URI');
my $WEB_BASE_URI_PATTERN = $config->get('WEB_BASE_URI_PATTERN');
my $WEB_BASE_PATH = $config->get('WEB_BASE_PATH');
my $ADMIN_EMAIL = $config->get('ADMIN_EMAIL');
# !!! Switch use of the global variables above to this hash!
my $CONFIG = {
TEMP_DIR => $TEMP_DIR ,
READ_CVS_SERVER => $READ_CVS_SERVER ,
READ_CVS_USERNAME => $READ_CVS_USERNAME ,
READ_CVS_PASSWORD => $READ_CVS_PASSWORD ,
WRITE_CVS_SERVER => $WRITE_CVS_SERVER ,
WEB_BASE_URI => $WEB_BASE_URI ,
WEB_BASE_URI_PATTERN => $WEB_BASE_URI_PATTERN ,
WEB_BASE_PATH => $WEB_BASE_PATH ,
ADMIN_EMAIL => $ADMIN_EMAIL ,
DOCTOR_EMAIL => $config->get('DOCTOR_EMAIL') ,
EDITOR_EMAIL => $config->get('EDITOR_EMAIL') ,
DB_HOST => $config->get('DB_HOST') ,
DB_PORT => $config->get('DB_PORT') ,
DB_NAME => $config->get('DB_NAME') ,
DB_USERNAME => $config->get('DB_USERNAME') ,
DB_PASSWORD => $config->get('DB_PASSWORD') ,
};
$vars->{'CONFIG'} = $CONFIG;
my %CONFIG = $config->varlist(".*");
$vars->{'config'} = \%CONFIG;
# Store the home directory so we can get back to it after changing directories
# in certain places in the code.
@ -139,27 +111,55 @@ my $HOME = cwd;
# Main Body Execution
################################################################################
# Note: All calls to this script should contain an "action" variable whose
# value determines what the user wants to do. The code below checks the value
# All calls to this script should contain an "action" variable whose value
# determines what the user wants to do. The code below checks the value
# of that variable and runs the appropriate code.
# Determine whether to use the action specified by the user or the default,
# and if the user wants to edit a file, but they haven't specified the name
# of the file, prompt them for it.
my $action =
lc($request->param('action')) || ($request->param('file') ? "edit" : "choose");
my $action = lc($request->param('action'));
if (!$action) {
$action = $request->param('file') ? "edit" : "choose";
}
# Displays a form for choosing the page you want to edit.
if ($action eq "choose") { choose() }
# Displays a page with UI for editing the page online, downloading it
# for local editing, viewing the original and the user's modified versions,
# getting a diff of the user's changes, and committing the user's changes
# or submitting them to the editors for review. This is the principle
# interface to Doctor functionality, and many other actions are called
# from links, forms, and JavaScript on this page.
elsif ($action eq "edit") { edit() }
elsif ($action eq "review") { review() }
elsif ($action eq "commit") { commit() }
elsif ($action eq "create") { create() }
elsif ($action eq "download") { download() }
elsif ($action eq "display") { display() }
# Retrieves a page from CVS and returns it. Called by the "download the page"
# link on the Edit page for users who want to download and edit pages locally
# instead of via the embedded textarea. Also called by the View Original panel
# of the Edit page to show the original version of the page being edited.
elsif ($action eq "download" || $action eq "display")
{ retrieve() }
# Generates and returns a diff of changes between the submitted version
# of a page and the version in CVS. Called by the Show Diff panel of the Edit page.
elsif ($action eq "diff") { diff() }
elsif ($action eq "queue") { queue() }
elsif ($action eq "list") { list() }
# Returns the content that was submitted to it. Useful for displaying
# the modified version of a page the user downloaded and edited locally,
# since for security reasons there's no way to get access to a file
# in a file upload control on the client side. Called by JavaScript
# when the user focuses the View Edited panel on the Edit page after entering
# a filename into the file upload control.
elsif ($action eq "regurgitate") { regurgitate() }
# Submits a change to the editors for review. Requires the EDITOR_EMAIL config
# parameter to be set to the editors' email addresses.
elsif ($action eq "queue") { queue() }
# Adds and commits a new page to the repository.
elsif ($action eq "create") { create() }
# Commits changes to an existing page to the repository.
elsif ($action eq "commit") { commit() }
else
{
ThrowCodeError("couldn't recognize the value of the action parameter", "Unknown Action");
@ -180,64 +180,27 @@ sub choose
sub edit
{
if ($request->param('patch_id')) {
# Get the patch and metadata from the database.
my $id = ValidateID($request->param('patch_id'));
my $dbh = ConnectToDatabase();
my $get_patch_sth =
$dbh->prepare("SELECT file, version, patch FROM patch WHERE id = ?");
$get_patch_sth->execute($id);
my ($file, $oldversion, $patch) = $get_patch_sth->fetchrow_array();
$get_patch_sth->finish();
$dbh->disconnect();
# Check out the file from the repository.
CreateTempDir();
my $newversion = CheckOutFile($file);
ValidateVersions($oldversion, $newversion);
# Apply the patch.
open(PATCH, "|/usr/bin/patch $file 2>&1 > /dev/null");
print PATCH $patch;
close(PATCH);
# Get the file with the patch applied.
open(NEWFILE, "< $file");
undef $/;
my $content = <NEWFILE>;
close(NEWFILE);
ValidateFile();
my $file = $request->param('file');
# Delete the temporary directory into which we checked out the file.
DeleteTempDir();
my $line_endings = "unix";
if ($content =~ /\r\n/s) { $line_endings = "windows" }
elsif ($content =~ /\r[^\n]/s) { $line_endings = "mac" }
# Remove the base path from the beginning of the file to save space
# when displaying it.
$file =~ /^\Q$CONFIG{WEB_BASE_PATH}\E(.*)$/;
$vars->{'short_file'} = "/$1";
$vars->{'file'} = $file;
$vars->{'content'} = $content;
$vars->{'version'} = $newversion;
$vars->{'line_endings'} = $line_endings;
$vars->{'patch_id'} = $id;
$vars->{'file'} = $file;
if ($request->param('content'))
{
ValidateContent();
$vars->{'content'} = $request->param('content');
$vars->{'version'} = $request->param('version');
$vars->{'line_endings'} = $request->param('line_endings');
}
else {
ValidateFile();
$vars->{'file'} = $request->param('file');
if ($request->param('content'))
{
ValidateContent();
$vars->{'content'} = $request->param('content');
$vars->{'version'} = $request->param('version');
$vars->{'line_endings'} = $request->param('line_endings');
}
else
{
($vars->{'content'}, $vars->{'version'}, $vars->{'line_endings'})
= RetrieveFile($request->param('file'));
}
else
{
($vars->{'content'}, $vars->{'version'}, $vars->{'line_endings'})
= RetrieveFile($request->param('file'));
}
print $request->header;
@ -245,7 +208,7 @@ sub edit
|| ThrowCodeError($template->error(), "Template Processing Failed");
}
sub download
sub retrieve
{
ValidateFile();
@ -258,55 +221,16 @@ sub download
my ($content) = RetrieveFile($file);
my $filesize = length($content);
print $request->header(-type=>"text/plain; name=\"$filename\"",
-content_disposition=> "attachment; filename=\"$filename\"",
-content_length => $filesize);
print $content;
}
sub display
{
ValidateFile();
my $file = $request->param('file');
# Separate the name of the file from its path.
$file =~ /^(.*)\/([^\/]+)$/;
my $path = $1;
my $filename = $2;
my ($content) = RetrieveFile($file);
my $filesize = length($content);
my $disposition = $action eq "download" ? "attachment" : "inline";
print $request->header(-type=>"text/html; name=\"$filename\"",
-content_disposition=> "inline; filename=\"$filename\"",
-content_disposition=> "$disposition; filename=\"$filename\"",
-content_length => $filesize);
print $content;
}
sub regurgitate
{
ValidateFile();
my $content = GetUploadedContent() || $request->param('content');
my $file = $request->param('file');
# Separate the name of the file from its path.
$file =~ /^(.*)\/([^\/]+)$/;
my $path = $1;
my $filename = $2;
my $filesize = length($content);
print $request->header(-type=>"text/html; name=\"$filename\"",
-content_disposition=> "inline; filename=\"$filename\"",
-content_length => $filesize);
print $content;
}
sub review
sub diff
{
# Displays a diff between the version of a file in the repository
# and the version submitted by the user so the user can review the
@ -357,208 +281,111 @@ sub review
|| ThrowCodeError("Template Process Failed", $template->error());
}
sub commit
sub regurgitate
{
# Commit a file to the repository. Checks out the file via cvs,
# applies the changes, generates a diff, and then checks the file
# back in. It would be easier if we could commit a file by piping
# it into standard input, but cvs does not provide for this.
# Returns the content that was submitted to it. Useful for displaying
# the modified version of a document the user downloaded and edited locally,
# since for security reasons there's no way to get access to the file
# until the user uploads it to the server. When the user clicks
# on the "modified" tab, client-side JS checks to see if there's a value
# in the file upload control, and if so it adds an iframe to the "modified"
# panel and posts the file to it with action=regurgitate, which then returns
# the content of the file to the user, who can then see his changes.
ValidateFile();
ValidateContent();
ValidateUsername();
ValidatePassword();
ValidateComment();
my $patch_id;
if ($request->param('patch_id')) {
$patch_id = ValidateID($request->param('patch_id'));
}
# Create and change to a temporary sub-directory into which
# we will check out the file being committed.
CreateTempDir();
# Check out the file from the repository.
my $content = GetUploadedContent() || $request->param('content');
my $file = $request->param('file');
my $oldversion = $request->param('version');
my $newversion = CheckOutFile($file);
# Throw an error if the version of the file that was edited
# does not match the version in the repository. In the future
# we should try to merge the user's changes if possible.
if ($oldversion && $newversion && $oldversion != $newversion)
{
ThrowCodeError("You edited version <em>$oldversion</em> of the file,
but version <em>$newversion</em> is in the repository. Reload the edit
page and make your changes again (and bother the authors of this script
to implement change merging
(<a href=\"http://bugzilla.mozilla.org/show_bug.cgi?id=164342\">bug 164342</a>).");
}
# Replace the checked out file with the edited version, generate
# a diff between the edited file and the version in the repository,
# and check in the edited file.
ReplaceFile($file);
$vars->{'diff'} = DiffFile($file);
$vars->{'checkin_results'} = CheckInFile($file);
# Delete the temporary directory into which we checked out the file.
DeleteTempDir();
# Delete the patch from the patches queue.
if ($patch_id) {
my $dbh = ConnectToDatabase();
$dbh->do("DELETE FROM patch WHERE id = $patch_id");
$dbh->disconnect;
}
$vars->{'file'} = $file;
print $request->header;
$template->process("committed.tmpl", $vars)
|| ThrowCodeError($template->error(), "Template Processing Failed");
# Separate the name of the file from its path.
$file =~ /^(.*)\/([^\/]+)$/;
my $path = $1;
my $filename = $2;
my $filesize = length($content);
print $request->header(-type=>"text/html; name=\"$filename\"",
-content_disposition=> "inline; filename=\"$filename\"",
-content_length => $filesize);
print $content;
}
sub queue
{
# Queue the file for review.
# Checks out the file via cvs, applies the changes, generates a diff,
# and then saves the diff with meta-data to a file for retrieval
# by people with CVS access.
# Sends the diff or new file to an editors mailing list for review.
ThrowCodeError("The administrator has not enabled submission of patches for review.",
"Review Not Enabled")
unless $CONFIG{EDITOR_EMAIL};
ValidateFile();
ValidateContent();
# XXX validate the version as well?
# XXX validate the user's email address
my $email = $request->param('email');
# Create and change to a temporary sub-directory into which we will check out
# the file being committed.
CreateTempDir();
# Check out the file from the repository.
my $file = $request->param('file');
my $comment = $request->param('queue_comment');
my $oldversion = $request->param('version');
my $newversion = CheckOutFile($file);
# Throw an error if the version of the file that was edited
# does not match the version in the repository. In the future
# we should try to merge the user's changes if possible.
if ($oldversion && $newversion && $oldversion != $newversion)
{
ThrowCodeError("You edited version <em>$oldversion</em> of the file,
but version <em>$newversion</em> is in the repository. Reload the edit
page and make your changes again (and bother the authors of this script
to implement change merging
(<a href=\"http://bugzilla.mozilla.org/show_bug.cgi?id=164342\">bug 164342</a>).");
my $comment = $request->param('comment');
my ($patch, $oldversion, $newversion);
if ($request->param('is_new')) {
$patch = $request->param('content');
$oldversion = "-new";
}
else {
# Create and change to a temporary sub-directory into which we will check out
# the file being committed.
CreateTempDir();
# Check out the file from the repository.
$oldversion = $request->param('version');
$newversion = CheckOutFile($file);
# Throw an error if the version of the file that was edited
# does not match the version in the repository. In the future
# we should try to merge the user's changes if possible.
if ($oldversion && $newversion && $oldversion != $newversion)
{
ThrowCodeError("You edited version <em>$oldversion</em> of the file,
but version <em>$newversion</em> is in the repository. Reload the edit
page and make your changes again (and bother the authors of this script
to implement change merging
(<a href=\"http://bugzilla.mozilla.org/show_bug.cgi?id=164342\">bug 164342</a>).");
}
# Replace the checked out file with the edited version, and generate a patch
# that patches the checked out file to make it look like the edited version.
ReplaceFile($file);
$patch = DiffFile($file);
# Delete the temporary directory into which we checked out the file.
DeleteTempDir();
}
# Replace the checked out file with the edited version, and generate a patch
# that patches the checked out file to make it look like the edited version.
ReplaceFile($file);
my $patch = DiffFile($file);
# Delete the temporary directory into which we checked out the file.
DeleteTempDir();
eval {
use Mail::Mailer;
# Generate a random string to serve as an attachment boundary.
my @chars = (0..9);
my $dashes = "--------------";
my $boundary = "${dashes}394857292719284728394756";
my $i = 0;
while ($patch =~ /\Q$boundary\E/ && ++$i < 100) {
$boundary = $dashes . join("", @chars[ map {rand @chars} (1..24) ]);
}
if ($patch =~ /\Q$boundary\E/ && $i == 100) {
die "can't find a unique string that isn't in the patch file: $boundary";
}
my $mailer = new Mail::Mailer;
$mailer->open( { From => $email,
To => $CONFIG->{EDITOR_EMAIL},
Subject => "patch: $file v$oldversion",
"MIME-Version" => "1.0",
"Content-Type" => "multipart/mixed; boundary=\"$boundary\"" });
print $mailer "This is a multi-part message in MIME format.\n";
print $mailer "$boundary\n";
print $mailer "Content-Type: text/plain; charset=us-ascii; format=flowed\n";
print $mailer "Content-Transfer-Encoding: 7bit\n";
print $mailer "\n";
print $mailer "$comment\n\n";
print $mailer "$boundary\n";
print $mailer "Content-Type: text/plain\n";
print $mailer " name=\"patch.txt\"\n";
print $mailer "Content-Transfer-Encoding: 7bit\n";
print $mailer "Content-Disposition: inline\n";
print $mailer " filename=\"patch.txt\"\n";
print $mailer "\n";
print $mailer $patch;
print $mailer "$boundary\n";
$mailer->close;
use MIME::Entity;
my $mail = MIME::Entity->build(Type =>"multipart/mixed",
From => $email,
To => $CONFIG{EDITOR_EMAIL},
Subject => "patch: $file v$oldversion");
$mail->attach(Data =>$comment,
Encoding => "quoted-printable");
$mail->attach(Data => $patch,
Encoding => "quoted-printable",
Disposition => "inline",
Filename => "patch.txt");
$mail->send();
};
if ($@) {
ThrowCodeError($@, "Mailing Patch to Editors Failure");
ThrowCodeError($@, "Mail Failure");
}
# my $dbh = ConnectToDatabase();
# my $insert_patch_sth =
# $dbh->prepare("INSERT INTO patch (id, submitter, submitted, file, version,
# patch, comment)
# VALUES (?, ?, NOW(), ?, ?, ?, ?)");
# $dbh->do("START TRANSACTION");
# my ($patch_id) = $dbh->selectrow_array("SELECT MAX(id) FROM patch");
# $patch_id = ($patch_id || 0) + 1;
# $insert_patch_sth->execute($patch_id, $request->param('email'), $file,
# $oldversion, $patch, $comment);
# $dbh->commit;
# $dbh->disconnect;
print $request->header;
$template->process("queued.tmpl", $vars)
|| ThrowCodeError($template->error(), "Template Processing Failed");
}
sub list
{
my $dbh = ConnectToDatabase();
my $get_patches_sth =
$dbh->prepare("SELECT id, submitter, submitted, file, version, comment FROM patch");
$get_patches_sth->execute();
my ($patch, @patches);
while ( $patch = $get_patches_sth->fetchrow_hashref ) {
push(@patches, $patch);
}
$vars->{patches} = \@patches;
$dbh->disconnect;
print $request->header;
$template->process("list.tmpl", $vars)
|| ThrowCodeError($template->error(), "Template Processing Failed");
}
sub ConnectToDatabase
{
use DBI;
# Establish a database connection.
my $dsn = "DBI:mysql:host=$CONFIG->{DB_HOST};database=$CONFIG->{DB_NAME};port=$CONFIG->{DB_PORT}";
my $dbh = DBI->connect($dsn,
$CONFIG->{DB_USERNAME},
$CONFIG->{DB_PASSWORD},
{ RaiseError => 1,
PrintError => 0,
ShowErrorStatement => 1,
AutoCommit => 0 } );
return $dbh;
}
sub create
{
# Creates a new file in the repository.
@ -632,6 +459,61 @@ sub create
|| ThrowCodeError($template->error(), "Template Processing Failed");
}
sub commit
{
# Commit a file to the repository. Checks out the file via cvs,
# applies the changes, generates a diff, and then checks the file
# back in. It would be easier if we could commit a file by piping
# it into standard input, but cvs does not provide for this.
ValidateFile();
ValidateContent();
ValidateUsername();
ValidatePassword();
ValidateComment();
my $patch_id;
if ($request->param('patch_id')) {
$patch_id = ValidateID($request->param('patch_id'));
}
# Create and change to a temporary sub-directory into which
# we will check out the file being committed.
CreateTempDir();
# Check out the file from the repository.
my $file = $request->param('file');
my $oldversion = $request->param('version');
my $newversion = CheckOutFile($file);
# Throw an error if the version of the file that was edited
# does not match the version in the repository. In the future
# we should try to merge the user's changes if possible.
if ($oldversion && $newversion && $oldversion != $newversion)
{
ThrowCodeError("You edited version <em>$oldversion</em> of the file,
but version <em>$newversion</em> is in the repository. Reload the edit
page and make your changes again (and bother the authors of this script
to implement change merging
(<a href=\"http://bugzilla.mozilla.org/show_bug.cgi?id=164342\">bug 164342</a>).");
}
# Replace the checked out file with the edited version, generate
# a diff between the edited file and the version in the repository,
# and check in the edited file.
ReplaceFile($file);
$vars->{'diff'} = DiffFile($file);
$vars->{'checkin_results'} = CheckInFile($file);
# Delete the temporary directory into which we checked out the file.
DeleteTempDir();
$vars->{'file'} = $file;
print $request->header;
$template->process("committed.tmpl", $vars)
|| ThrowCodeError($template->error(), "Template Processing Failed");
}
################################################################################
# Input Validation
################################################################################
@ -648,8 +530,8 @@ sub ValidateFile
# Remove the absolute URI for files on the web site (if any)
# from the beginning of the path.
if ($WEB_BASE_URI_PATTERN) { $file =~ s/^$WEB_BASE_URI_PATTERN//i }
else { $file =~ s/^\Q$WEB_BASE_URI\E//i }
if ($CONFIG{WEB_BASE_URI_PATTERN}) { $file =~ s/^$CONFIG{WEB_BASE_URI_PATTERN}//i }
else { $file =~ s/^\Q$CONFIG{WEB_BASE_URI}\E//i }
# Entire Path Issues
@ -665,7 +547,7 @@ sub ValidateFile
# Add the base path of the file in the cvs repository if necessary.
# (i.e. if the user entered a URL or a path based on the URL).
if ($file !~ /^\Q$WEB_BASE_PATH\E/) { $file = $WEB_BASE_PATH . $file }
if ($file !~ /^\Q$CONFIG{WEB_BASE_PATH}\E/) { $file = $CONFIG{WEB_BASE_PATH} . $file }
# End of Path Issues
@ -687,7 +569,7 @@ sub ValidateFile
# Construct a URL to the file if possible.
my $url = $file;
if ($url =~ s/^\Q$WEB_BASE_PATH\E(.*)$/$1/i) { $vars->{'file_url'} = $WEB_BASE_URI . $url }
if ($url =~ s/^\Q$CONFIG{WEB_BASE_PATH}\E(.*)$/$1/i) { $vars->{'file_url'} = $CONFIG{WEB_BASE_URI} . $url }
}
@ -779,7 +661,7 @@ sub RetrieveFile
my ($name) = @_;
my @args = ("-d",
":pserver:$READ_CVS_USERNAME:$READ_CVS_PASSWORD\@$READ_CVS_SERVER",
":pserver:$CONFIG{READ_CVS_USERNAME}:$CONFIG{READ_CVS_PASSWORD}\@$CONFIG{READ_CVS_SERVER}",
"checkout",
"-p", # write to STDOUT
$name);
@ -800,8 +682,8 @@ sub RetrieveFile
{
# Include the command in the error message (but hide the username/password).
my $command = join(" ", "cvs", @args);
$command =~ s/\Q$READ_CVS_PASSWORD\E/[password]/g;
$errors =~ s/\Q$READ_CVS_PASSWORD\E/[password]/g;
$command =~ s/\Q$CONFIG{READ_CVS_PASSWORD}\E/[password]/g;
$errors =~ s/\Q$CONFIG{READ_CVS_PASSWORD}\E/[password]/g;
ThrowUserError("Doctor could not check out the file from the repository.",
undef,
@ -836,7 +718,7 @@ sub CheckOutFile
my @args = ("-t", # debugging messages from which to extract the version
"-d",
":pserver:$READ_CVS_USERNAME:$READ_CVS_PASSWORD\@$READ_CVS_SERVER",
":pserver:$CONFIG{READ_CVS_USERNAME}:$CONFIG{READ_CVS_PASSWORD}\@$CONFIG{READ_CVS_SERVER}",
"checkout",
$file);
@ -852,8 +734,8 @@ sub CheckOutFile
{
# Include the command in the error message (but hide the password).
my $command = join(" ", "cvs", @args);
$command =~ s/\Q$READ_CVS_PASSWORD\E/[password]/g;
$errors =~ s/\Q$READ_CVS_PASSWORD\E/[password]/g;
$command =~ s/\Q$CONFIG{READ_CVS_PASSWORD}\E/[password]/g;
$errors =~ s/\Q$CONFIG{READ_CVS_PASSWORD}\E/[password]/g;
ThrowUserError("Doctor could not check out the file from the repository.",
undef,
@ -876,7 +758,7 @@ sub DiffFile
my ($file) = @_;
my @args = ("-d",
":pserver:$READ_CVS_USERNAME:$READ_CVS_PASSWORD\@$READ_CVS_SERVER",
":pserver:$CONFIG{READ_CVS_USERNAME}:$CONFIG{READ_CVS_PASSWORD}\@$CONFIG{READ_CVS_SERVER}",
"diff",
"-u",
$file);
@ -896,8 +778,8 @@ sub DiffFile
{
# Include the command in the error message (but hide the password).
my $command = join(" ", "cvs", @args);
$command =~ s/\Q$READ_CVS_PASSWORD\E/[password]/g;
$errors =~ s/\Q$READ_CVS_PASSWORD\E/[password]/g;
$command =~ s/\Q$CONFIG{READ_CVS_PASSWORD}\E/[password]/g;
$errors =~ s/\Q$CONFIG{READ_CVS_PASSWORD}\E/[password]/g;
ThrowUserError("Doctor could not diff your version of the file
against the version in the repository.",
@ -923,7 +805,7 @@ sub CheckInFile
my $comment = $request->param('comment');
my @args = ("-d" ,
":pserver:$username:$password\@$WRITE_CVS_SERVER" ,
":pserver:$username:$password\@$CONFIG{WRITE_CVS_SERVER}" ,
"commit" ,
"-m" ,
$comment ,
@ -977,7 +859,7 @@ sub ThrowUserError
# working directory (if any; generally all user errors should be caught
# before we do anything requiring the creation of a temporary directory).
chdir($HOME);
rmtree("$TEMP_DIR/$PID", 0, 1) if -e "$TEMP_DIR/$PID";
rmtree("$CONFIG{TEMP_DIR}/$PID", 0, 1) if -e "$CONFIG{TEMP_DIR}/$PID";
print $request->header;
$template->process("user-error.tmpl", $vars)
@ -998,10 +880,10 @@ sub ThrowCodeError
# Return to the original working directory and delete the temporary
# working directory.
(chdir($HOME) && (-e "$TEMP_DIR/$PID" ? rmtree("$TEMP_DIR/$PID", 0, 1) : 1))
(chdir($HOME) && (-e "$CONFIG{TEMP_DIR}/$PID" ? rmtree("$CONFIG{TEMP_DIR}/$PID", 0, 1) : 1))
|| ($vars->{'message'} =
("couldn't return to original working directory '$HOME' " .
"and delete temporary working directory '$TEMP_DIR/$PID': $!" .
"and delete temporary working directory '$CONFIG{TEMP_DIR}/$PID': $!" .
"; error occurred while trying to display error message: " .
($vars->{'title'} ? "$vars->{'title'}: ": "") . $vars->{'message'}));
@ -1014,7 +896,7 @@ sub ThrowCodeError
provided below. Please forward this information along with any
other information that would help diagnose and fix this problem
to the system administrator at
<a href=\"mailto:$CONFIG->{'ADMIN_EMAIL'}\">$CONFIG->{'ADMIN_EMAIL'}</a>.
<a href=\"mailto:$CONFIG{ADMIN_EMAIL}\">$CONFIG{ADMIN_EMAIL}</a>.
</p>
<p>
couldn't process error.tmpl template: " . $template->error() .
@ -1068,25 +950,25 @@ sub CreateTempDir
# Creates and changes to a temporary sub-directory unique to this process
# that can be used for CVS activities requiring the manipulation of files.
if (!-e $TEMP_DIR and !mkdir($TEMP_DIR))
if (!-e $CONFIG{TEMP_DIR} and !mkdir($CONFIG{TEMP_DIR}))
{
ThrowCodeError("couldn't create the temporary directory
<em>$TEMP_DIR</em>: $!");
<em>$CONFIG{TEMP_DIR}</em>: $!");
}
elsif (!-d $TEMP_DIR)
elsif (!-d $CONFIG{TEMP_DIR})
{
ThrowCodeError("couldn't use the temporary directory <em>$TEMP_DIR</em>
ThrowCodeError("couldn't use the temporary directory <em>$CONFIG{TEMP_DIR}</em>
because it isn't a directory.");
}
# Create and change to a unique sub-directory in the temporary directory.
mkdir("$TEMP_DIR/$PID")
mkdir("$CONFIG{TEMP_DIR}/$PID")
|| ThrowCodeError("couldn't create a temporary sub-directory
<em>$TEMP_DIR/$PID</em>: $!");
<em>$CONFIG{TEMP_DIR}/$PID</em>: $!");
chdir("$TEMP_DIR/$PID")
chdir("$CONFIG{TEMP_DIR}/$PID")
|| ThrowCodeError("couldn't change to the temporary sub-directory
<em>$TEMP_DIR/$PID</em>: $!");
<em>$CONFIG{TEMP_DIR}/$PID</em>: $!");
}
sub DeleteTempDir
@ -1094,7 +976,7 @@ sub DeleteTempDir
# Changes back to the home directory and deletes the temporary directory.
chdir($HOME);
rmtree("$TEMP_DIR/$PID", 0, 1)
rmtree("$CONFIG{TEMP_DIR}/$PID", 0, 1)
|| ThrowCodeError("couldn't delete the temporary sub-directory
<em>$TEMP_DIR/$PID</em>: $!");
<em>$CONFIG{TEMP_DIR}/$PID</em>: $!");
}

View File

@ -1,27 +1,16 @@
#panels { width: 98%; height: 84%; }
.panel {
border: 2px inset black;
position: absolute;
width: 98%;
height: 84%;
visibility: hidden;
overflow: scroll;
}
#editPanel { overflow: visible; }
#modifiedPanel { padding: 8px; }
#savePanel { overflow: visible; border: 0px; }
#content { width: 100%; height: 100%; }
/*
* Tab styles
* Banner and Navigation Bar
*/
/* Hide the "Edit" tab unless the page can be edited in WYSIWYG mode. */
button.tab#editTab { display: none; }
button.tab.wysiwyg#editTab { display: inline; }
#bannernav {
margin-bottom: 5px;
font-weight: bold;
}
/*
* Tabs
*/
button.tab {
background-color: #fofofo;
@ -44,3 +33,70 @@ button.tab.active, button.tab[disabled].active, button.tab:hover.active {
background-color: #b0b0b0;
color: #000000;
}
/* Hide the "Edit" tab unless the page can be edited in WYSIWYG mode. */
button.tab#editTab {
display: none;
}
button.tab.wysiwyg#editTab {
display: inline;
}
/* Hide the "View Original" and "View Diff" tabs if a new page is being created. */
body.new button.tab#originalTab, body.new button.tab#diffTab {
display: none;
}
/*
* Panels
*/
#panels {
width: 98%;
height: 84%;
}
.panel {
border: 2px inset black;
position: absolute;
width: 98%;
height: 84%;
visibility: hidden;
overflow: scroll;
}
#editPanel {
overflow: visible;
}
#modifiedPanel {
padding: 8px;
}
#savePanel {
overflow: visible;
border: 0px;
padding-top: 50px;
}
#content {
width: 100%;
height: 100%;
}
/*
* Form
*/
label {
font-weight: bold;
}
table#submit-changes {
float: left;
margin-right: 20px;
}
table#submit-changes th {
text-align: right;
}

View File

@ -55,7 +55,7 @@ function switchToTab(newTab) {
var modifiedPanel = document.getElementById('modifiedPanel');
var content_file = document.getElementById('content_file');
if (content_file.value) {
modifiedPanel.innerHTML = '<iframe name="modified"></iframe>';
modifiedPanel.innerHTML = '<iframe id="modified" name="modified"></iframe>';
submitForm("modified", "regurgitate");
}
else {
@ -64,7 +64,7 @@ function switchToTab(newTab) {
}
else if (newTab == "diff") {
synchronizeEditModes();
submitForm("diffPanel", "review", { raw: "1" });
submitForm("diffPanel", "diff", { raw: "1" });
}
else if (newTab == "save") {
synchronizeEditModes();

View File

@ -42,3 +42,8 @@ WEB_BASE_PATH = mozilla-org/html/
# The email address of the administrator for this installation.
# Change this to your email address.
ADMIN_EMAIL = nobody@mozilla.org
# The email address to which Doctor should send patches submitted
# by users who don't have CVS write access. If empty, users won't
# be able to submit patches without CVS write access.
EDITOR_EMAIL = nobody@mozilla.org

View File

@ -25,7 +25,7 @@
<html>
<head>
<title>Doctor - [% is_new ? "create" : "edit" %] [% (file_url ? file_url : file) FILTER html %]</title>
<title>Doctor - [% is_new ? "create" : "edit" %] [%+ (file_url ? file_url : file) FILTER html %]</title>
[% IF wysiwyg %]
<script type="text/javascript">
_editor_url = "/htmlarea/";
@ -45,77 +45,82 @@
<link rel="stylesheet" href="doctor.css" type="text/css"></link>
</head>
<body bgcolor="white" color="black" onload="onLoad();">
<body bgcolor="white" color="black" onload="onLoad();" class="[% "new" IF is_new %]">
<span style="float: right;">
<a href="doctor.cgi?action=list">Review Queue</a>
</span>
<big><b>Doctor - [% is_new ? "create" : "edit" %] <em>
[% IF file_url %]
<a href="[% file_url FILTER html %]">[% file_url FILTER html %]</a>
[% ELSE %]
[% file FILTER html %]
[% END %]
</em></b></big>
<form id="form" method="post" action="doctor.cgi" enctype="multipart/form-data">
<input id="file" type="hidden" name="file" value="[% file FILTER html %]">
<input id="version" type="hidden" name="version" value="[% version %]">
<input id="line_endings" type="hidden" name="line_endings" value="[% line_endings %]">
<input id="is_new" type="hidden" name="is_new" value="[% is_new %]">
<div id="bannernav">
Doctor - [% is_new ? "create" : "edit" %]
[% IF file_url %]
<a href="[% file_url FILTER html %]">[% short_file FILTER html %]</a>
[% ELSE %]
[% short_file FILTER html %]
[% END %]
<form id="form" method="post" action="doctor.cgi" enctype="multipart/form-data">
[% IF is_new %]
Username: <input type="text" name="username" size="10">
Password: <input type="password" name="password" size="10">
Check-in Comment: <input type="text" name="comment" size="50">
<button type="submit" name="action" value="create">Create</button>
[% ELSE %]
Edit using the form below or
<a href="doctor.cgi?file=[% file | uri | html %]&amp;action=download">download the page</a>,
edit it locally, then upload it: <input id="content_file" type="file" name="content_file">
[% END %]
<br>
<input id="file" type="hidden" name="file" value="[% file FILTER html %]">
<input id="version" type="hidden" name="version" value="[% version %]">
<input id="line_endings" type="hidden" name="line_endings" value="[% line_endings %]">
[% IF patch_id %]
<input id="patch_id" type="hidden" name="patch_id" value="[% patch_id %]">
[% END %]
[% IF !is_new %]
<button id="editTab" class="tab [% "wysiwyg" IF wysiwyg %]"
type="button" onclick="switchToTab('edit');" disabled="disabled">Edit</button>
<button id="hackTab" class="tab" type="button" onclick="switchToTab('hack');" disabled="disabled">Edit Source</button>
<button id="originalTab" class="tab" type="button" onclick="switchToTab('original');" disabled="disabled">View Original</button>
<button id="modifiedTab" class="tab" type="button" onclick="switchToTab('modified');" disabled="disabled">View Edited</button>
<button id="diffTab" class="tab" type="button" onclick="switchToTab('diff');" disabled="disabled">Show Diff</button>
<button id="saveTab" class="tab" type="button" onclick="switchToTab('save');" disabled="disabled">Save Changes</button>
<br>
[% END %]
<div id="panels">
<div id="editPanel" class="panel">
<textarea id="content" name="content" rows="16" cols="100">[% content FILTER html %]</textarea><br>
[% IF !is_new %]
| <a href="doctor.cgi?file=[% file | uri | html %]&amp;action=download">download</a>
[% END %]
| <label for="content_file">upload:</label>
<input id="content_file" type="file" name="content_file">
</div>
<iframe id="originalPanel" class="panel" src="doctor.cgi?file=[% file | uri | html %]&amp;action=display"></iframe>
<div id="modifiedPanel" class="panel"></div>
<iframe id="diffPanel" class="panel" name="diffPanel"></iframe>
<div id="savePanel" class="panel">
<p>
Username: <input type="text" name="username" size="20">
Password: <input type="password" name="password" size="20"><br>
Change Comment:<br><textarea rows="5" cols="80" name="comment"></textarea><br>
<button type="submit" name="action" value="commit">Commit Changes</button>
</p>
<p>
Don't have commit privileges?
</p>
<p>
Email Address: <input type="text" name="email" size="30"><br>
Change Comment:<br><textarea rows="5" cols="80" name="queue_comment"></textarea><br>
<button type="submit" name="action" value="queue">Submit Changes for Review</button>
</p>
<div id="tabs">
<button id="editTab" class="tab [% "wysiwyg" IF wysiwyg %]"
type="button" onclick="switchToTab('edit');" disabled="disabled">Edit</button>
<button id="hackTab" class="tab" type="button" onclick="switchToTab('hack');" disabled="disabled">Edit Source</button>
<button id="originalTab" class="tab" type="button" onclick="switchToTab('original');" disabled="disabled">View Original</button>
<button id="modifiedTab" class="tab" type="button" onclick="switchToTab('modified');" disabled="disabled">View Edited</button>
<button id="diffTab" class="tab" type="button" onclick="switchToTab('diff');" disabled="disabled">Show Diff</button>
<button id="saveTab" class="tab" type="button" onclick="switchToTab('save');" disabled="disabled">Save</button>
</div>
</div>
</form>
<div id="panels">
<div id="editPanel" class="panel">
<textarea id="content" name="content" rows="16" cols="100">[% content FILTER html %]</textarea><br>
</div>
<iframe id="originalPanel" class="panel" src="doctor.cgi?file=[% file | uri | html %]&amp;action=display"></iframe>
<div id="modifiedPanel" class="panel"></div>
<iframe id="diffPanel" class="panel" name="diffPanel"></iframe>
<div id="savePanel" class="panel">
<label for="comment">Comment:</label><br>
<textarea rows="5" cols="80" name="comment"></textarea>
<table id="submit-changes">
<tr>
<th><label for="username">Username:</label></th>
<td><input type="text" name="username" size="20"></td>
</tr>
<tr>
<th><label for="password">Password:</label></th>
<td><input type="password" name="password" size="20"></td>
</tr>
<tr>
<th>&nbsp;</th>
<td><button type="submit" name="action" value="[% is_new ? "create" : "commit" %]">Commit</button></td>
</tr>
</table>
[% IF config.EDITOR_EMAIL %]
<table id="submit-review">
<tr>
<td colspan="2">Don't have commit privileges?</td>
</tr>
<tr>
<th><label for="email">Your Email:</label></th>
<td><input type="text" name="email" size="30"></td>
</tr>
<tr>
<th>&nbsp;</th>
<td><button type="submit" name="action" value="queue">Submit for Review</button></td>
</tr>
</table>
[% END %]
</div>
</div>
</form>
</body>