]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Fix for bug 84338: initial implementation of attachment tracker, which lets users...
authormyk%mozilla.org <>
Fri, 31 Aug 2001 10:54:25 +0000 (10:54 +0000)
committermyk%mozilla.org <>
Fri, 31 Aug 2001 10:54:25 +0000 (10:54 +0000)
Patch by Myk Melez <myk@mozilla.org>
r=justdave@syndicomm.com

15 files changed:
CGI.pl
bug_form.pl
buglist.cgi
checksetup.pl
defparams.pl
processmail
template/default/attachment/edit.atml [new file with mode: 0755]
template/default/attachment/list.atml [new file with mode: 0755]
template/default/attachment/updated.atml [new file with mode: 0755]
template/default/attachment/viewall.atml [new file with mode: 0755]
template/default/attachstatus/create.atml [new file with mode: 0755]
template/default/attachstatus/edit.atml [new file with mode: 0755]
template/default/attachstatus/list.atml [new file with mode: 0755]
template/default/global/footer [new file with mode: 0755]
template/default/global/header [new file with mode: 0755]

diff --git a/CGI.pl b/CGI.pl
index 2cf6ffeca8a846198d9f7578a8db07a6dfd97fe1..6bcb1ba02285e4599e2725c6b6f69b5b69aad117 100644 (file)
--- a/CGI.pl
+++ b/CGI.pl
@@ -1194,7 +1194,8 @@ sub DumpBugActivity {
         $datepart = "and bugs_activity.bug_when >= $starttime";
     }
     my $query = "
-        SELECT IFNULL(fielddefs.name, bugs_activity.fieldid),
+        SELECT IFNULL(fielddefs.description, bugs_activity.fieldid),
+                bugs_activity.attach_id,
                 bugs_activity.bug_when,
                 bugs_activity.removed, bugs_activity.added,
                 profiles.login_name
@@ -1219,7 +1220,9 @@ sub DumpBugActivity {
     my @row;
     my $incomplete_data = 0;
     while (@row = FetchSQLData()) {
-        my ($field,$when,$removed,$added,$who) = (@row);
+        my ($field,$attachid,$when,$removed,$added,$who) = (@row);
+        $field =~ s/^Attachment/<a href="attachment.cgi?id=$attachid&action=view">Attachment #$attachid<\/a>/ 
+          if (Param('useattachmenttracker') && $attachid);
         $removed = html_quote($removed);
         $added = html_quote($added);
         $removed = "&nbsp;" if $removed eq "";
@@ -1289,6 +1292,8 @@ Actions:
         }
         if (UserInGroup("editcomponents")) {
             $html .= ", <a href=editproducts.cgi>components</a>";
+            $html .= ", <a href=editattachstatuses.cgi><NOBR>attachment statuses</NOBR></a>"
+              if Param('useattachmenttracker');
         }
         if (UserInGroup("creategroups")) {
             $html .= ", <a href=editgroups.cgi>groups</a>";
index c554cb0b079ce33705e6f996dcf019341684c739..d2f6cc3f4525db162b33be3866d5c2bc130c26ee 100644 (file)
@@ -283,18 +283,26 @@ if (@::legal_keywords) {
 };
 }
 
-print "<tr><td align=right><B>Attachments:</b></td>\n";
-SendSQL("select attach_id, creation_ts, mimetype, description from attachments where bug_id = $id");
-while (MoreSQLData()) {
-    my ($attachid, $date, $mimetype, $desc) = (FetchSQLData());
-    if ($date =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
-        $date = "$3/$4/$2 $5:$6";
+# 2001-05-16 myk@mozilla.org: use the attachment tracker to display attachments
+# if this installation has enabled use of the attachment tracker.
+if (Param('useattachmenttracker')) {
+    print "</table>\n";
+    use Attachment;
+    &Attachment::list($id);
+} else {
+    print "<tr><td align=right><B>Attachments:</b></td>\n";
+    SendSQL("select attach_id, creation_ts, mimetype, description from attachments where bug_id = $id");
+    while (MoreSQLData()) {
+        my ($attachid, $date, $mimetype, $desc) = (FetchSQLData());
+        if ($date =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
+            $date = "$3/$4/$2 $5:$6";
+        }
+        my $link = "showattachment.cgi?attach_id=$attachid";
+        $desc = value_quote($desc);
+        print qq{<td><a href="$link">$date</a></td><td colspan=6>$desc&nbsp;&nbsp;&nbsp;($mimetype)</td></tr><tr><td></td>};
     }
-    my $link = "showattachment.cgi?attach_id=$attachid";
-    $desc = value_quote($desc);
-    print qq{<td><a href="$link">$date</a></td><td colspan=6>$desc&nbsp;&nbsp;&nbsp;($mimetype)</td></tr><tr><td></td>};
+    print "<td colspan=7><a href=\"createattachment.cgi?id=$id\">Create a new attachment</a> (proposed patch, testcase, etc.)</td></tr></table>\n";
 }
-print "<td colspan=7><a href=\"createattachment.cgi?id=$id\">Create a new attachment</a> (proposed patch, testcase, etc.)</td></tr></table>\n";
 
 
 sub EmitDependList {
index 2b2a394bf73f4b0193680da38c81c02d60e1ab57..830b66599f57ce06140b55d9383d83836c9eab6c 100755 (executable)
@@ -415,6 +415,22 @@ sub GenerateSQL {
              }
              $f = "$table.$field";
          },
+         # 2001-05-16 myk@mozilla.org: enable querying against attachment status
+         # if this installation has enabled use of the attachment manager.
+         "^attachstatusdefs.name," => sub {
+             my $attachtable = "attachments_$chartid";
+             my $statustable = "attachstatuses_$chartid";
+             my $statusdefstable = "attachstatusdefs_$chartid";
+             push(@supptables, "attachments $attachtable");
+             push(@supptables, "attachstatuses $statustable");
+             push(@supptables, "attachstatusdefs $statusdefstable");
+             push(@wherepart, "bugs.bug_id = $attachtable.bug_id");
+             push(@wherepart, "$attachtable.attach_id = $statustable.attach_id");
+             push(@wherepart, "$statustable.statusid = $statusdefstable.id");
+             my $table = $statusdefstable;
+             my $field = "name";
+             $f = "$table.$field";
+         },
          "^changedin," => sub {
              $f = "(to_days(now()) - to_days(bugs.delta_ts))";
          },
index 4c3ab81d3cec8b4ab9427a135d990b1ce9659b1a..7c165b9ebdffa8809bfcd92f43791dc32a33f5be 100755 (executable)
@@ -192,6 +192,7 @@ unless (have_vers("DBI","1.13"))          { push @missing,"DBI" }
 unless (have_vers("Data::Dumper",0))      { push @missing,"Data::Dumper" }
 unless (have_vers("DBD::mysql","1.2209")) { push @missing,"DBD::mysql" }
 unless (have_vers("Date::Parse",0))       { push @missing,"Date::Parse" }
+unless (have_vers("Template","2.01"))       { push @missing,"Template" }
 
 # If CGI::Carp was loaded successfully for version checking, it changes the
 # die and warn handlers, we don't want them changed, so we need to stash the
@@ -838,6 +839,7 @@ my %table;
 
 $table{bugs_activity} = 
    'bug_id mediumint not null,
+    attach_id mediumint null,
     who mediumint not null,
     bug_when datetime not null,
     fieldid mediumint not null,
@@ -859,10 +861,33 @@ $table{attachments} =
     filename mediumtext not null,
     thedata longblob not null,
     submitter_id mediumint not null,
+    isobsolete tinyint not null default 0, 
 
     index(bug_id),
     index(creation_ts)';
 
+# 2001-05-05 myk@mozilla.org: Tables to support the attachment tracker.
+# "attachstatuses" stores one record for each status on each attachment.
+# "attachstatusdefs" defines the statuses that can be set on attachments.
+# Note: These tables are only used if the parameter "useattachmenttracker"
+# is turned on via editparameters.cgi.
+
+$table{attachstatuses} =
+   '
+     attach_id    MEDIUMINT    NOT NULL , 
+     statusid     SMALLINT     NOT NULL , 
+     PRIMARY KEY(attach_id, statusid) 
+   ';
+
+$table{attachstatusdefs} =
+   '
+     id           SMALLINT     NOT NULL  PRIMARY KEY , 
+     name         VARCHAR(50)  NOT NULL , 
+     description  MEDIUMTEXT   NULL , 
+     sortkey      SMALLINT     NOT NULL  DEFAULT 0 , 
+     product      VARCHAR(64)  NOT NULL 
+   ';
+
 #
 # Apostrophe's are not supportied in the enum types.
 # See http://bugzilla.mozilla.org/show_bug.cgi?id=27309
@@ -1294,6 +1319,8 @@ AddFDef("attachments.description", "Attachment description", 0);
 AddFDef("attachments.thedata", "Attachment data", 0);
 AddFDef("attachments.mimetype", "Attachment mime type", 0);
 AddFDef("attachments.ispatch", "Attachment is patch", 0);
+AddFDef("attachments.isobsolete", "Attachment is obsolete", 0);
+AddFDef("attachstatusdefs.name", "Attachment Status", 0);
 AddFDef("target_milestone", "Target Milestone", 0);
 AddFDef("delta_ts", "Last changed date", 0);
 AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed",
@@ -2322,6 +2349,12 @@ unless (-d 'data/duplicates') {
 #
 AddField('groups', 'isactive', 'tinyint not null default 1');
 
+#
+# 2001-06-15 myk@mozilla.org:
+# isobsolete determines whether or not an attachment is pertinent/relevant/valid.
+#
+AddField('attachments', 'isobsolete', 'tinyint not null default 0');
+
 # 2001-04-29 jake@acutex.net - Remove oldemailtech
 #   http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
 if (-d 'shadow') {
@@ -2470,6 +2503,11 @@ AddField("bugs", "assignee_accessible", "tinyint not null default 1");
 AddField("bugs", "qacontact_accessible", "tinyint not null default 1");
 AddField("bugs", "cclist_accessible", "tinyint not null default 1");
 
+# 2001-08-21 myk@mozilla.org bug84338:
+# Add a field for the attachment ID to the bugs_activity table, so installations
+# using the attachment manager can record changes to attachments.
+AddField("bugs_activity", "attach_id", "mediumint null");
+
 # If you had to change the --TABLE-- definition in any way, then add your
 # differential change code *** A B O V E *** this comment.
 #
index 46a00b78f149b2470ac055e691fbb780bea14929..31bd57d4f9ad0e9e81db2fe31f967c0353a30d50 100644 (file)
@@ -669,4 +669,9 @@ DefParam("moved-default-component",
          "t",
          '');
 
+DefParam("useattachmenttracker",
+         "Whether or not to use the attachment tracker that adds additional features for tracking bug attachments.",
+         "b",
+         0);
+
 1;
index b82c80aca5799f7a5190e2d355c9c4f91b7fdfe7..45f5aa31016c52dc1dd2099965cabfc7d3701a4d 100755 (executable)
@@ -135,7 +135,7 @@ sub ProcessOneBug {
 
 
     SendSQL("SELECT profiles.login_name, fielddefs.description, " .
-            "       bug_when, removed, added " .
+            "       bug_when, removed, added, attach_id " .
             "FROM bugs_activity, fielddefs, profiles " .
             "WHERE bug_id = $id " .
             "  AND fielddefs.fieldid = bugs_activity.fieldid " .
@@ -153,13 +153,14 @@ sub ProcessOneBug {
     my $difftext = "";
     my $lastwho = "";
     foreach my $ref (@diffs) {
-        my ($who, $what, $when, $old, $new) = (@$ref);
+        my ($who, $what, $when, $old, $new, $attachid) = (@$ref);
         if ($who ne $lastwho) {
             $lastwho = $who;
             $difftext .= "\n$who" . Param('emailsuffix') . " changed:\n\n";
             $difftext .= FormatTriple("What    ", "Removed", "Added");
             $difftext .= ('-' x 76) . "\n";
         }
+        $what =~ s/^Attachment/Attachment #$attachid/ if (Param('useattachmenttracker') && $attachid);
         $difftext .= FormatTriple($what, $old, $new);
     }
 
diff --git a/template/default/attachment/edit.atml b/template/default/attachment/edit.atml
new file mode 100755 (executable)
index 0000000..7428223
--- /dev/null
@@ -0,0 +1,192 @@
+[% INCLUDE global/header 
+  title = "Edit Attachment #$attachid for Bug #$bugid"
+  h1 = "Edit Attachment #$attachid for <a href=\"show_bug.cgi?id=$bugid\">Bug #$bugid</a>"
+  h2 = bugsummary
+  style = "
+    th { text-align: right; vertical-align: top; }
+    td { text-align: left; vertical-align: top; }
+    td#info { text-align: right; vertical-align: top; }
+    td#actions { text-align: right; vertical-align: bottom; }
+    td#noview { text-align: left; vertical-align: center; }
+  "
+%]
+
+<form method="post" action="attachment.cgi" onsubmit="normalizeComments();">
+  <input type="hidden" name="id" value="[% attachid %]">
+  <input type="hidden" name="action" value="update">
+
+  <table width="100%">
+
+    <tr>
+      <td width="25%">
+        <small>
+        <b>Description:</b><br>
+          <textarea rows="3" cols="25" name="description">[% description %]</textarea><br>
+
+        <b>MIME Type:</b><br>
+          <input type="text" size="20" name="mimetype" value="[% mimetype %]"><br>
+
+        <b>Flags:</b><br>
+          <input type="checkbox" name="ispatch" value="1"[% " checked" IF ispatch %]>patch
+          <input type="checkbox" name="isobsolete" value="1"[% " checked" IF isobsolete %]>obsolete<br>
+
+        [% IF statusdefs.size %]
+          <b>Status:</b><br>
+            [% FOREACH def = statusdefs %]
+                <input type="checkbox" name="status" value="[% def.id %]"[% " checked" IF statuses.${def.id} %]>[% def.name %]<br>
+            [% END %]
+        [% END %]
+
+        <div id="smallCommentFrame">
+          <b>Comment (on the bug):</b><br>
+            <textarea name="comment" rows="5" cols="25"></textarea><br>
+        </div>
+
+        <input type="submit" value="Submit">
+
+        </small>
+      </td>
+
+      [% IF isviewable %]
+        <td width="75%">
+          <textarea id="editFrame" name="comment" style="height: 400px; width: 100%; display: none;"></textarea>
+          <iframe id="viewFrame" src="attachment.cgi?id=[% attachid %]&action=view" style="height: 400px; width: 100%;">
+            <b>You cannot view the attachment while editing it because your browser does not support IFRAMEs.
+            <a href="attachment.cgi?id=[% attachid %]&action=view">View the attachment on a separate page</a>.</b>
+          </iframe>
+          <button id="editButton" onclick="editAsComment();">Edit Attachment As Comment</button>
+          <button id="undoEditButton" onclick="undoEditAsComment();" style="display: none;">Undo Edit As Comment</button>
+          <button id="redoEditButton" onclick="redoEditAsComment();" style="display: none;">Redo Edit As Comment</button>
+        </td>
+      [% ELSE %]
+        <td id="noview" width="50%">
+          <p><b>
+            Attachment cannot be viewed because its MIME type is not either text/*, image/*, or application/vnd.mozilla.*.
+            <a href="attachment.cgi?id=[% attachid %]&action=view">Download the attachment instead</a>.
+          </b></p>
+        </td>
+      [% END %]
+
+    </tr>
+
+  </table>
+
+  Attachments on this Bug:
+  [% FOREACH a = attachments %]
+    [% IF a == attachid %]
+      #[% a %]
+    [% ELSE %]
+      <a href="attachment.cgi?id=[% a %]&action=edit">#[% a %]</a>
+    [% END %]
+    [% "|" UNLESS loop.last() %]
+  [% END %]
+
+</form>
+
+<script type="application/x-javascript" language="JavaScript">
+  function editAsComment()
+    {
+      // Get the content of the document as a string.
+      var viewFrame = document.getElementById('viewFrame');
+      var aSerializer = new XMLSerializer();
+      var contentDocument = viewFrame.contentDocument;
+      var theContent = aSerializer.serializeToString(contentDocument);
+
+      // If this is a plaintext document, remove cruft that Mozilla adds
+      // because it treats it as an HTML document with a big PRE section.
+      // http://bugzilla.mozilla.org/show_bug.cgi?id=86012
+      var mimeType = '[% mimetype %]';
+      if ( mimeType == 'text/plain' )
+        {
+          theContent = theContent.replace( /^<html><head\/><body><pre>/ , "" );
+          theContent = theContent.replace( /<\/pre><\/body><\/html>$/ , "" );
+          theContent = theContent.replace( /&lt;/gi , "<" );
+          theContent = theContent.replace( /&gt;/gi , ">" );
+          theContent = theContent.replace( /&amp;/gi , "&" );
+        }
+
+      // Add mail-style quote indicators (>) to the beginning of each line.
+      // ".*\n" matches lines that end with a newline, while ".+" matches
+      // the rare situation in which the last line of a file does not end
+      // with a newline.
+      theContent = theContent.replace( /(.*\n|.+)/g , ">$1" );
+
+      hideElementById('viewFrame');
+      hideElementById('editButton');
+      hideElementById('smallCommentFrame');
+
+      showElementById('undoEditButton');
+
+      // Show the TEXTAREA that will contain the editable attachment
+      // and copy the content of the attachment into it.
+      showElementById('editFrame');
+
+      var editFrame = document.getElementById('editFrame');
+      editFrame.value = theContent;
+      editFrame.value += "\n\n";
+    }
+  function undoEditAsComment()
+    {
+      // Hide the "edit attachment as comment" TEXTAREA and the "undo" button.
+      hideElementById('undoEditButton');
+      hideElementById('editFrame');
+
+      // Show the "view attachment" IFRAME, the "redo" button that allows the user
+      // to go back to editing the attachment as a comment, and the small comment field.
+      showElementById('viewFrame');
+      showElementById('redoEditButton');
+      showElementById('smallCommentFrame');
+
+    }
+  function redoEditAsComment()
+    {
+      // Hide the "view attachment" IFRAME, the "redo" button that allows the user
+      // to go back to editing the attachment as a comment, and the small comment field.
+      hideElementById('viewFrame');
+      hideElementById('redoEditButton');
+      hideElementById('smallCommentFrame');
+
+      // Show the "edit attachment as comment" TEXTAREA and the "undo" button.
+      showElementById('undoEditButton');
+      showElementById('editFrame');
+    }
+
+  function hideElementById(id)
+  {
+    var elm = document.getElementById(id);
+    if (!elm) return;
+
+    elm.style.setProperty('display', 'none', '');
+  }
+
+  function showElementById(id, val)
+  {
+    var elm = document.getElementById(id);
+    if (!elm) return;
+
+    if (!val) val = 'inline';
+    elm.style.setProperty('display', val, '');
+  }
+
+  function normalizeComments()
+  {
+    // Remove the unused comment field from the document so its contents
+    // do not get transmitted back to the server.
+
+    var small = document.getElementById('smallCommentFrame');
+    var big = document.getElementById('editFrame');
+    if ( small.style.getProperty('display') == 'none' )
+    {
+      small.parentNode.removeChild(small);
+    }
+    if ( big.style.getProperty('display') == 'none' )
+    {
+      big.parentNode.removeChild(big);
+    }
+    
+  }
+</script>
+
+<br>
+
+[% INCLUDE global/footer %]
diff --git a/template/default/attachment/list.atml b/template/default/attachment/list.atml
new file mode 100755 (executable)
index 0000000..3086c8d
--- /dev/null
@@ -0,0 +1,58 @@
+<br>
+<table cellspacing="0" cellpadding="4" border="1">
+  <tr>
+    <th bgcolor="#cccccc" align="left">Attachment</th>
+    <th bgcolor="#cccccc" align="left">Type</th>
+    <th bgcolor="#cccccc" align="left">Modified</th>
+    <th bgcolor="#cccccc" align="left">Status</th>
+    <th bgcolor="#cccccc" align="left">Actions</th>
+  </tr>
+
+  [% FOREACH attachment = attachments %]
+    <tr>
+      <td valign="top">
+        [% IF attachment.isobsolete %]
+          <strike><a href="attachment.cgi?id=[% attachment.attachid %]&action=view">[% attachment.description %]</a></strike>
+        [% ELSE %]
+          <a href="attachment.cgi?id=[% attachment.attachid %]&action=view">[% attachment.description %]</a> 
+        [% END %]
+      </td>
+
+      <td valign="top">
+        [% IF attachment.ispatch %]
+          <i>patch</i>
+        [% ELSE %]
+          [% attachment.mimetype %]
+        [% END %]
+      </td>
+
+      <td valign="top">[% attachment.date %]</td>
+
+      <td valign="top">
+        <nobr>
+        [% IF attachment.statuses.size == 0 %]
+          <i>none</i>
+        [% ELSE %]
+          [% FOREACH s = attachment.statuses %]
+            [% s %]<br>
+          [% END %]
+        [% END %]
+        </nobr>
+      </td>
+
+      <td valign="top">
+        <a href="attachment.cgi?id=[% attachment.attachid %]&action=edit">Edit</a> 
+      </td>
+    </tr>
+  [% END %]
+
+  <tr>
+    <td colspan="4">
+      <a href="createattachment.cgi?id=[% bugid %]">Create a new attachment</a> (proposed patch, testcase, etc.)
+    </td>
+    <td colspan="1">
+      <a href="attachment.cgi?bugid=[% bugid %]&action=viewall">View All</a>
+    </td>
+  </tr>
+</table>
+<br>
diff --git a/template/default/attachment/updated.atml b/template/default/attachment/updated.atml
new file mode 100755 (executable)
index 0000000..7b52dee
--- /dev/null
@@ -0,0 +1,16 @@
+[% INCLUDE global/header 
+  title = "Changes Submitted"
+  style = "th { text-align: left; }"
+%]
+
+<p>
+  Your changes have been submitted for 
+  <a href="attachment.cgi?id=[% attachid %]&action=edit">attachment #[% attachid %]</a>
+  of <a href="show_bug.cgi?id=[% bugid %]">bug #[% bugid %]</a>.
+</p>
+
+[% mailresults %]
+
+<br>
+
+[% INCLUDE global/footer %]
diff --git a/template/default/attachment/viewall.atml b/template/default/attachment/viewall.atml
new file mode 100755 (executable)
index 0000000..9a22b71
--- /dev/null
@@ -0,0 +1,78 @@
+[% INCLUDE global/header 
+  title = "View All Attachments for Bug #$bugid"
+  h1 = "View All Attachments for <a href=\"show_bug.cgi?id=$bugid\">Bug #$bugid</a>"
+  h2 = bugsummary
+  style = "
+    th { text-align: right; vertical-align: top; }
+    td { text-align: left; vertical-align: top; }
+    td#info { text-align: right; vertical-align: top; }
+    td#actions { text-align: right; vertical-align: bottom; }
+  "
+%]
+
+<br>
+
+[% FOREACH a = attachments %]
+
+  <div align="center">
+  <table cellspacing="0" cellpadding="4" border="1" width="75%">
+    <tr>
+      <td valign="top" bgcolor="#cccccc" colspan="5">
+        <big><b>Attachment #[% a.attachid %]</b></big>
+      </td>
+    </tr>
+    <tr>
+      <td valign="top">
+        [% IF a.isobsolete %]
+          <strike>[% a.description %]</strike>
+        [% ELSE %]
+          [% a.description %]
+        [% END %]
+      </td>
+
+      <td valign="top">
+        [% IF a.ispatch %]
+          <i>patch</i>
+        [% ELSE %]
+          [% a.mimetype %]
+        [% END %]
+      </td>
+
+      <td valign="top">[% a.date %]</td>
+
+      <td valign="top">
+        <nobr>
+        [% IF a.statuses.size == 0 %]
+          <i>none</i>
+        [% ELSE %]
+          [% FOREACH s = a.statuses %]
+            [% s %]<br>
+          [% END %]
+        [% END %]
+        </nobr>
+      </td>
+
+      <td valign="top">
+        <a href="attachment.cgi?id=[% a.attachid %]&action=edit">Edit</a> 
+      </td>
+    </tr>
+  </table>
+
+  [% IF a.isviewable %]
+    <iframe src="attachment.cgi?id=[% a.attachid %]&action=view" width="75%" height="350">
+      <b>You cannot view the attachment on this page because your browser does not support IFRAMEs.
+      <a href="attachment.cgi?id=[% a.attachid %]&action=view">View the attachment on a separate page</a>.</b>
+    </iframe>
+  [% ELSE %]
+    <p><b>
+      Attachment cannot be viewed because its MIME type is not text/*, image/*, or application/vnd.mozilla.*.
+      <a href="attachment.cgi?id=[% a.attachid %]&action=view">Download the attachment instead</a>.
+    </b></p>
+  [% END %]
+  </div>
+
+  <br><br>
+
+[% END %]
+
+[% INCLUDE global/footer %]
diff --git a/template/default/attachstatus/create.atml b/template/default/attachstatus/create.atml
new file mode 100755 (executable)
index 0000000..602dab9
--- /dev/null
@@ -0,0 +1,56 @@
+[% INCLUDE global/header 
+  title = "Create Attachment Status"
+  style = "
+    th { text-align: right; vertical-align: top; }
+    td { text-align: left; vertical-align: top; }
+  "
+%]
+
+<form method="post" action="editattachstatuses.cgi">
+  <input type="hidden" name="action" value="insert">
+  <input type="hidden" name="id" value="[% id %]">
+
+  <table cellspacing="0" cellpadding="4" border="0">
+    <tr>
+      <th>Name:</th>
+      <td>
+        <input type="text" name="name" size="50" maxlength="50">
+      </td>
+    </tr>
+
+    <tr>
+      <th>Description:</th>
+      <td>
+        <textarea name="desc" rows="4" cols="50"></textarea>
+      </td>
+    </tr>
+
+    <tr>
+      <th>Sort Key:</th>
+      <td>
+        <input type="text" name="sortkey" size="5" maxlength="5">
+      </td>
+    </tr>
+
+    <tr>
+      <th>Product:</th>
+      <td>
+        <select name="product">
+          [% FOREACH item = products %]
+            <option value="[% item %]">[% item %]</option>
+          [% END %]
+        </select>
+      </td>
+    </tr>
+
+    <tr>
+      <th></th>
+      <td>
+        <input type="submit" value="Add">
+      </td>
+    </tr>
+
+  </table>
+</form>
+
+[% INCLUDE global/footer %]
diff --git a/template/default/attachstatus/edit.atml b/template/default/attachstatus/edit.atml
new file mode 100755 (executable)
index 0000000..646e0f0
--- /dev/null
@@ -0,0 +1,52 @@
+[% INCLUDE global/header 
+  title = "Edit Attachment Status"
+  style = "
+    th { text-align: right; vertical-align: top; }
+    td { text-align: left; vertical-align: top; }
+  "
+%]
+
+<form method="post" action="editattachstatuses.cgi">
+  <input type="hidden" name="action" value="update">
+  <input type="hidden" name="id" value="[% id %]">
+
+  <table cellspacing="0" cellpadding="4" border="0">
+    <tr>
+      <th>Name:</th>
+      <td>
+        <input type="text" name="name" value="[% name %]" size="50" maxlength="50">
+      </td>
+    </tr>
+
+    <tr>
+      <th>Description:</th>
+      <td>
+        <textarea name="desc" rows="4" cols="50">[% desc %]</textarea>
+      </td>
+    </tr>
+
+    <tr>
+      <th>Sort Key:</th>
+      <td>
+        <input type="text" name="sortkey" value="[% sortkey %]" size="5" maxlength="5">
+      </td>
+    </tr>
+
+    <tr>
+      <th>Product:</th>
+      <td>
+        [% product %]
+      </td>
+    </tr>
+
+    <tr>
+      <th></th>
+      <td>
+        <input type="submit" value="Update">
+      </td>
+    </tr>
+
+  </table>
+</form>
+
+[% INCLUDE global/footer %]
diff --git a/template/default/attachstatus/list.atml b/template/default/attachstatus/list.atml
new file mode 100755 (executable)
index 0000000..acae5cd
--- /dev/null
@@ -0,0 +1,52 @@
+[% INCLUDE global/header 
+  title = 'Administer Attachment Statuses'
+  message = message
+  style = "
+    th { text-align: left; }
+  "
+%]
+
+<table cellspacing="0" cellpadding="4" border="1">
+
+  <tr>
+    <th>Name</th>
+    <th>Description</th>
+    <th>Sort Key</th>
+    <th>Product</th>
+    <th>Action(s)</th>
+  </tr>
+
+  [% FOREACH statusdef = statusdefs %]
+
+    <tr>
+      <td>[% statusdef.name FILTER html %]</td>
+      <td>[% statusdef.description FILTER html %]</td>
+      <td>[% statusdef.sortkey %]</td>
+      <td>[% statusdef.product %]</td>
+      <td>
+        <a href="editattachstatuses.cgi?action=edit&id=[% statusdef.id %]">Edit</a>
+        <a href="editattachstatuses.cgi?action=delete&id=[% statusdef.id %]" onclick="return confirmDelete();">Delete</a>
+      </td>
+    </tr>
+
+  [% END %]
+
+  <tr>
+    <td colspan="4"></td>
+    <td>
+      <a href="editattachstatuses.cgi?action=create">Create</a>
+    </td>
+  </tr>
+
+</table>
+
+<script language="JavaScript">
+  function confirmDelete()
+  {
+    return confirm('Are you sure you want to permanently delete ' +
+                   'this attachment status?  All attachments ' + 
+                   'with this status will have it unset.');
+  }
+</script>
+
+[% INCLUDE global/footer %]
diff --git a/template/default/global/footer b/template/default/global/footer
new file mode 100755 (executable)
index 0000000..e06d3d8
--- /dev/null
@@ -0,0 +1,6 @@
+
+[% PerformSubsts(Param('footerhtml')) %]
+
+</body>
+</html>
+
diff --git a/template/default/global/header b/template/default/global/header
new file mode 100755 (executable)
index 0000000..676b7dd
--- /dev/null
@@ -0,0 +1,49 @@
+[% DEFAULT
+  title = ""
+  h1 = title
+  h2 = ""
+  extra = ""
+  jscript = ""
+  style = ""
+  message = ""
+%]
+
+<html>
+  <head>
+    <title>[% title %]</title>
+    [% Param('headerhtml') %]
+    [% jscript %]
+    <style>
+      [% style %]
+    </style>
+  </head>
+  <body [% Param('bodyhtml') %] [% extra %]>
+
+  [% PerformSubsts(Param('bannerhtml')) %]
+    
+[% IF h1 || h2 %]
+    <table border="0" cellspacing="0" width="100%">
+      <tr>
+        <td width="10%" valign="top" align="left">
+          <table border="0" cellpadding="0" cellspacing="2">
+            <tr>
+              <td valign="top" align="left" nowrap>
+                <font size="+1"><b>[% h1 %]</b></font>
+              </td>
+            </tr>
+          </table>
+        </td>
+        <td valign="center">&nbsp;</td>
+        <td valign="center" align="left">[% h2 %]</td>
+      </tr>
+    </table>
+[% END %]
+
+    [% IF message %]
+      <table width="100%" cellspacing="0" cellpadding="5" border="1"><tr><td>
+        <font color="green">[% message %]</font>
+      </td></tr></table>
+    [% END %]
+
+    [% Param('shutdownhtml') %]
+