From 5d1bca95620daf504093e781e26bea10357c9850 Mon Sep 17 00:00:00 2001 From: "myk%mozilla.org" <> Date: Fri, 31 Aug 2001 11:19:32 +0000 Subject: Fix for bug 84338: initial implementation of attachment tracker, which lets users flag attachments with statuses. Patch by Myk Melez r=justdave@syndicomm.com --- attachment.cgi | 519 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100755 attachment.cgi (limited to 'attachment.cgi') diff --git a/attachment.cgi b/attachment.cgi new file mode 100755 index 000000000..e0b723734 --- /dev/null +++ b/attachment.cgi @@ -0,0 +1,519 @@ +#!/usr/bonsaitools/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Terry Weissman +# Myk Melez + +################################################################################ +# Script Initialization +################################################################################ + +# Make it harder for us to do dangerous things in Perl. +use diagnostics; +use strict; + +# Include the Bugzilla CGI and general utility library. +require "CGI.pl"; + +# Establish a connection to the database backend. +ConnectToDatabase(); + +# Use the template toolkit (http://www.template-toolkit.org/) to generate +# the user interface (HTML pages and mail messages) using templates in the +# "template/" subdirectory. +use Template; + +# Create the global template object that processes templates and specify +# configuration parameters that apply to all templates processed in this script. +my $template = Template->new( + { + # Colon-separated list of directories containing templates. + INCLUDE_PATH => "template/default" , + # Allow templates to be specified with relative paths. + RELATIVE => 1 + } +); + +# Define the global variables and functions that will be passed to the UI +# template. Individual functions add their own values to this hash before +# sending them to the templates they process. +my $vars = + { + # Function for retrieving global parameters. + 'Param' => \&Param , + + # Function for processing global parameters that contain references + # to other global parameters. + 'PerformSubsts' => \&PerformSubsts + }; + +# Check whether or not the user is logged in and, if so, set the $::userid +# and $::usergroupset variables. +quietly_check_login(); + +################################################################################ +# Main Body Execution +################################################################################ + +# 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. +my $action = $::FORM{'action'} || 'view'; + +if ($action eq "view") +{ + validateID(); + view(); +} +elsif ($action eq "viewall") +{ + ValidateBugID($::FORM{'bugid'}); + viewall(); +} +elsif ($action eq "edit") +{ + validateID(); + edit(); +} +elsif ($action eq "update") +{ + confirm_login(); + UserInGroup("editbugs") + || DisplayError("You are not authorized to edit attachments.") + && exit; + validateID(); + validateDescription(); + validateMIMEType(); + validateIsPatch(); + validateIsObsolete(); + validateStatuses(); + update(); +} +else +{ + DisplayError("I could not figure out what you wanted to do.") +} + +exit; + +################################################################################ +# Data Validation / Security Authorization +################################################################################ + +sub validateID +{ + # Validate the value of the "id" form field, which must contain a positive + # integer that is the ID of an existing attachment. + + $::FORM{'id'} =~ /^[1-9][0-9]*$/ + || DisplayError("You did not enter a valid attachment number.") + && exit; + + # Make sure the attachment exists in the database. + SendSQL("SELECT bug_id FROM attachments WHERE attach_id = $::FORM{'id'}"); + MoreSQLData() + || DisplayError("Attachment #$::FORM{'id'} does not exist.") + && exit; + + # Make sure the user is authorized to access this attachment's bug. + my ($bugid) = FetchSQLData(); + ValidateBugID($bugid); +} + +sub validateDescription +{ + $::FORM{'description'} + || DisplayError("You must enter a description for the attachment.") + && exit; +} + +sub validateMIMEType +{ + $::FORM{'mimetype'} =~ /^(application|audio|image|message|model|multipart|text|video)\/.+$/ + || DisplayError("You must enter a valid MIME type of the form foo/bar + where foo is either application, audio, image, message, + model, multipart, text, or video.") + && exit; +} + +sub validateIsPatch +{ + # Set the ispatch flag to zero if it is undefined, since the UI uses + # an HTML checkbox to represent this flag, and unchecked HTML checkboxes + # do not get sent in HTML requests. + $::FORM{'ispatch'} = $::FORM{'ispatch'} ? 1 : 0; +} + +sub validateIsObsolete +{ + # Set the isobsolete flag to zero if it is undefined, since the UI uses + # an HTML checkbox to represent this flag, and unchecked HTML checkboxes + # do not get sent in HTML requests. + $::FORM{'isobsolete'} = $::FORM{'isobsolete'} ? 1 : 0; +} + +sub validateStatuses +{ + # Get a list of attachment statuses that are valid for this attachment. + PushGlobalSQLState(); + SendSQL("SELECT attachstatusdefs.id + FROM attachments, bugs, attachstatusdefs + WHERE attachments.attach_id = $::FORM{'id'} + AND attachments.bug_id = bugs.bug_id + AND attachstatusdefs.product = bugs.product"); + my @statusdefs; + push(@statusdefs, FetchSQLData()) while MoreSQLData(); + PopGlobalSQLState(); + + foreach my $status (@{$::MFORM{'status'}}) + { + grep($_ == $status, @statusdefs) + || DisplayError("One of the statuses you entered is not a valid status + for this attachment.") + && exit; + } +} + +################################################################################ +# Functions +################################################################################ + +sub view +{ + # Display an attachment. + + # Retrieve the attachment content and its MIME type from the database. + SendSQL("SELECT mimetype, thedata FROM attachments WHERE attach_id = $::FORM{'id'}"); + my ($mimetype, $thedata) = FetchSQLData(); + + # Return the appropriate HTTP response headers. + print "Content-Type: $mimetype\n\n"; + + print $thedata; +} + + +sub viewall +{ + # Display all attachments for a given bug in a series of IFRAMEs within one HTML page. + + # Retrieve the attachments from the database and write them into an array + # of hashes where each hash represents one attachment. + SendSQL("SELECT attach_id, creation_ts, mimetype, description, ispatch, isobsolete + FROM attachments WHERE bug_id = $::FORM{'bugid'} ORDER BY attach_id"); + my @attachments; # the attachments array + while ( MoreSQLData() ) { + my %a; # the attachment hash + ($a{'attachid'}, $a{'date'}, $a{'mimetype'}, + $a{'description'}, $a{'ispatch'}, $a{'isobsolete'}) = FetchSQLData(); + + # Format the attachment's creation/modification date into something readable. + if ($a{'date'} =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) { + $a{'date'} = "$3/$4/$2 $5:$6"; + } + + # Quote HTML characters (&<>) in the description and MIME Type. + $a{'description'} = value_quote($a{'description'}); + $a{'mimetype'} = value_quote($a{'mimetype'}); + + # Flag attachments as to whether or not they can be viewed (as opposed to + # being downloaded). Currently I decide they are viewable if their MIME type + # is either text/*, image/*, or application/vnd.mozilla.*. + # !!! Yuck, what an ugly hack. Fix it! + $a{'isviewable'} = ( $a{'mimetype'} =~ /^(text|image|application\/vnd\.mozilla\.)/ ); + + # Retrieve a list of status flags that have been set on the attachment. + PushGlobalSQLState(); + SendSQL("SELECT name + FROM attachstatuses, attachstatusdefs + WHERE attach_id = $a{'attachid'} + AND attachstatuses.statusid = attachstatusdefs.id + ORDER BY sortkey"); + my @statuses; + push(@statuses, FetchSQLData()) while MoreSQLData(); + $a{'statuses'} = \@statuses; + PopGlobalSQLState(); + + # Add the hash representing the attachment to the array of attachments. + push @attachments, \%a; + } + + # Retrieve the bug summary for displaying on screen. + SendSQL("SELECT short_desc FROM bugs WHERE bug_id = $::FORM{'bugid'}"); + my ($bugsummary) = FetchSQLData(); + + # Define the variables and functions that will be passed to the UI template. + $vars->{'bugid'} = $::FORM{'bugid'}; + $vars->{'bugsummary'} = $bugsummary; + $vars->{'attachments'} = \@attachments; + + # Return the appropriate HTTP response headers. + print "Content-Type: text/html\n\n"; + + # Generate and return the UI (HTML page) from the appropriate template. + $template->process("attachment/viewall.atml", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; +} + + +sub edit +{ + # Edit an attachment record. Users with "editbugs" privileges can edit the + # attachment's description, MIME type, ispatch and isobsolete flags, and + # statuses, and they can also submit a comment that appears in the bug. + # Users cannot edit the content of the attachment itself. + + # Retrieve the attachment from the database. + SendSQL("SELECT description, mimetype, bug_id, ispatch, isobsolete + FROM attachments WHERE attach_id = $::FORM{'id'}"); + my ($description, $mimetype, $bugid, $ispatch, $isobsolete) = FetchSQLData(); + + # Flag attachment as to whether or not it can be viewed (as opposed to + # being downloaded). Currently I decide it is viewable if its MIME type + # is either text/.* or application/vnd.mozilla.*. + # !!! Yuck, what an ugly hack. Fix it! + my $isviewable = ( $mimetype =~ /^(text|image|application\/vnd\.mozilla\.)/ ); + + # Retrieve a list of status flags that have been set on the attachment. + my %statuses; + SendSQL("SELECT id, name + FROM attachstatuses JOIN attachstatusdefs + WHERE attachstatuses.statusid = attachstatusdefs.id + AND attach_id = $::FORM{'id'}"); + while ( my ($id, $name) = FetchSQLData() ) + { + $statuses{$id} = $name; + } + + # Retrieve a list of statuses for this bug's product, and build an array + # of hashes in which each hash is a status flag record. + # ???: Move this into versioncache or its own routine? + my @statusdefs; + SendSQL("SELECT id, name + FROM attachstatusdefs, bugs + WHERE bug_id = $bugid + AND attachstatusdefs.product = bugs.product + ORDER BY sortkey"); + while ( MoreSQLData() ) + { + my ($id, $name) = FetchSQLData(); + push @statusdefs, { 'id' => $id , 'name' => $name }; + } + + # Retrieve a list of attachments for this bug as well as a summary of the bug + # to use in a navigation bar across the top of the screen. + SendSQL("SELECT attach_id FROM attachments WHERE bug_id = $bugid ORDER BY attach_id"); + my @bugattachments; + push(@bugattachments, FetchSQLData()) while (MoreSQLData()); + SendSQL("SELECT short_desc FROM bugs WHERE bug_id = $bugid"); + my ($bugsummary) = FetchSQLData(); + + # Define the variables and functions that will be passed to the UI template. + $vars->{'attachid'} = $::FORM{'id'}; + $vars->{'description'} = $description; + $vars->{'mimetype'} = $mimetype; + $vars->{'bugid'} = $bugid; + $vars->{'bugsummary'} = $bugsummary; + $vars->{'ispatch'} = $ispatch; + $vars->{'isobsolete'} = $isobsolete; + $vars->{'isviewable'} = $isviewable; + $vars->{'statuses'} = \%statuses; + $vars->{'statusdefs'} = \@statusdefs; + $vars->{'attachments'} = \@bugattachments; + + # Return the appropriate HTTP response headers. + print "Content-Type: text/html\n\n"; + + # Generate and return the UI (HTML page) from the appropriate template. + $template->process("attachment/edit.atml", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; +} + + +sub update +{ + # Update an attachment record. + + # Get the bug ID for the bug to which this attachment is attached. + SendSQL("SELECT bug_id FROM attachments WHERE attach_id = $::FORM{'id'}"); + my $bugid = FetchSQLData() + || DisplayError("Cannot figure out bug number.") + && exit; + + # Lock database tables in preparation for updating the attachment. + SendSQL("LOCK TABLES attachments WRITE , attachstatuses WRITE , + attachstatusdefs READ , fielddefs READ , bugs_activity WRITE"); + + # Get a copy of the attachment record before we make changes + # so we can record those changes in the activity table. + SendSQL("SELECT description, mimetype, ispatch, isobsolete + FROM attachments WHERE attach_id = $::FORM{'id'}"); + my ($olddescription, $oldmimetype, $oldispatch, $oldisobsolete) = FetchSQLData(); + + # Get the list of old status flags. + SendSQL("SELECT attachstatusdefs.name + FROM attachments, attachstatuses, attachstatusdefs + WHERE attachments.attach_id = $::FORM{'id'} + AND attachments.attach_id = attachstatuses.attach_id + AND attachstatuses.statusid = attachstatusdefs.id + ORDER BY attachstatusdefs.sortkey + "); + my @oldstatuses; + while (MoreSQLData()) { + push(@oldstatuses, FetchSQLData()); + } + my $oldstatuslist = join(', ', @oldstatuses); + + # Update the database with the new status flags. + SendSQL("DELETE FROM attachstatuses WHERE attach_id = $::FORM{'id'}"); + foreach my $statusid (@{$::MFORM{'status'}}) + { + SendSQL("INSERT INTO attachstatuses (attach_id, statusid) VALUES ($::FORM{'id'}, $statusid)"); + } + + # Get the list of new status flags. + SendSQL("SELECT attachstatusdefs.name + FROM attachments, attachstatuses, attachstatusdefs + WHERE attachments.attach_id = $::FORM{'id'} + AND attachments.attach_id = attachstatuses.attach_id + AND attachstatuses.statusid = attachstatusdefs.id + ORDER BY attachstatusdefs.sortkey + "); + my @newstatuses; + while (MoreSQLData()) { + push(@newstatuses, FetchSQLData()); + } + my $newstatuslist = join(', ', @newstatuses); + + # Quote "description" and "mimetype" for use in the SQL UPDATE statement. + my $quoteddescription = SqlQuote($::FORM{'description'}); + my $quotedmimetype = SqlQuote($::FORM{'mimetype'}); + + # Update the attachment record in the database. + # Sets the creation timestamp to itself to avoid it being updated automatically. + SendSQL("UPDATE attachments + SET description = $quoteddescription , + mimetype = $quotedmimetype , + ispatch = $::FORM{'ispatch'} , + isobsolete = $::FORM{'isobsolete'} , + creation_ts = creation_ts + WHERE attach_id = $::FORM{'id'} + "); + + # Record changes in the activity table. + if ($olddescription ne $::FORM{'description'}) { + my $quotedolddescription = SqlQuote($olddescription); + my $fieldid = GetFieldID('attachments.description'); + SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) + VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $quotedolddescription, $quoteddescription)"); + } + if ($oldmimetype ne $::FORM{'mimetype'}) { + my $quotedoldmimetype = SqlQuote($oldmimetype); + my $fieldid = GetFieldID('attachments.mimetype'); + SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) + VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $quotedoldmimetype, $quotedmimetype)"); + } + if ($oldispatch ne $::FORM{'ispatch'}) { + my $fieldid = GetFieldID('attachments.ispatch'); + SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) + VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $oldispatch, $::FORM{'ispatch'})"); + } + if ($oldisobsolete ne $::FORM{'isobsolete'}) { + my $fieldid = GetFieldID('attachments.isobsolete'); + SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) + VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $oldisobsolete, $::FORM{'isobsolete'})"); + } + if ($oldstatuslist ne $newstatuslist) { + my ($removed, $added) = DiffStrings($oldstatuslist, $newstatuslist); + my $quotedremoved = SqlQuote($removed); + my $quotedadded = SqlQuote($added); + my $fieldid = GetFieldID('attachstatusdefs.name'); + SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) + VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $quotedremoved, $quotedadded)"); + } + + # Unlock all database tables now that we are finished updating the database. + SendSQL("UNLOCK TABLES"); + + # If this installation has enabled the request manager, let the manager know + # an attachment was updated so it can check for requests on that attachment + # and fulfill them. The request manager allows users to request database + # changes of other users and tracks the fulfillment of those requests. When + # an attachment record is updated and the request manager is called, it will + # fulfill those requests that were requested of the user performing the update + # which are requests for the attachment being updated. + #my $requests; + #if (Param('userequestmanager')) + #{ + # use Request; + # # Specify the fieldnames that have been updated. + # my @fieldnames = ('description', 'mimetype', 'status', 'ispatch', 'isobsolete'); + # # Fulfill pending requests. + # $requests = Request::fulfillRequest('attachment', $::FORM{'id'}, @fieldnames); + # $vars->{'requests'} = $requests; + #} + + # If the user submitted a comment while editing the attachment, + # add the comment to the bug. + if ( $::FORM{'comment'} ) + { + # Append a string to the comment to let users know that the comment came from + # the "edit attachment" screen. + my $comment = qq|(From update of attachment $::FORM{'id'})\n| . $::FORM{'comment'}; + + # Get the user's login name since the AppendComment function needs it. + my $who = DBID_to_name($::userid); + # Mention $::userid again so Perl doesn't give me a warning about it. + my $neverused = $::userid; + + # Append the comment to the list of comments in the database. + AppendComment($bugid, $who, $comment); + + } + + # Send mail to let people know the bug has changed. Uses a special syntax + # of the "open" and "exec" commands to capture the output of "processmail", + # which "system" doesn't allow, without running the command through a shell, + # which backticks (``) do. + #system ("./processmail", $bugid , $::userid); + #my $mailresults = `./processmail $bugid $::userid`; + my $mailresults = ''; + open(PMAIL, "-|") or exec('./processmail', $bugid, $::userid); + $mailresults .= $_ while ; + close(PMAIL); + + # Define the variables and functions that will be passed to the UI template. + $vars->{'attachid'} = $::FORM{'id'}; + $vars->{'bugid'} = $bugid; + $vars->{'mailresults'} = $mailresults; + + # Return the appropriate HTTP response headers. + print "Content-Type: text/html\n\n"; + + # Generate and return the UI (HTML page) from the appropriate template. + $template->process("attachment/updated.atml", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; + +} -- cgit v1.2.3-24-g4f1b