'%Y.%m.%d %H:%i') . " AS attached",
'attachments.filename AS filename',
'attachments.ispatch AS ispatch',
+ 'attachments.isurl AS isurl',
'attachments.isobsolete AS isobsolete',
'attachments.isprivate AS isprivate'
);
sub get_param_list {
my $class = shift;
my @param_list = (
+ {
+ name => 'allow_attach_url',
+ type => 'b',
+ default => 0
+ },
{
name => 'maxpatchsize',
type => 't',
DEFAULT => 'FALSE'},
isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'},
+ isurl => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
],
INDEXES => [
attachments_bug_id_idx => ['bug_id'],
ValidateBugID($bugid);
validateCanChangeBug($bugid);
ValidateComment(scalar $cgi->param('comment'));
- my $filename = validateFilename();
+ my $attachurl = $cgi->param('attachurl') || '';
+ my $data;
+ my $filename;
+ my $contenttype;
+ my $isurl;
validateIsPatch();
validateDescription();
- # need to validate content type before data as
- # we now check the content type for image/bmp in validateData()
- validateContentType() unless $cgi->param('ispatch');
- my $data = validateData();
+
+ if (($attachurl =~ /^(http|https|ftp):\/\/\S+/)
+ && !(defined $cgi->upload('data'))) {
+ $filename = '';
+ $data = $attachurl;
+ $isurl = 1;
+ $contenttype = SqlQuote('text/plain');
+ $cgi->param('ispatch', 0);
+ $cgi->delete('bigfile');
+ } else {
+ $filename = validateFilename();
+ # need to validate content type before data as
+ # we now check the content type for image/bmp in validateData()
+ validateContentType() unless $cgi->param('ispatch');
+ $data = validateData();
+ $contenttype = SqlQuote($cgi->param('contenttype'));
+ $isurl = 0;
+ }
my @obsolete_ids = ();
@obsolete_ids = validateObsolete() if $cgi->param('obsolete');
# Escape characters in strings that will be used in SQL statements.
my $sql_filename = SqlQuote($filename);
my $description = SqlQuote($cgi->param('description'));
- my $contenttype = SqlQuote($cgi->param('contenttype'));
my $isprivate = $cgi->param('isprivate') ? 1 : 0;
# Figure out when the changes were made.
# Insert the attachment into the database.
my $sth = $dbh->prepare("INSERT INTO attachments
(bug_id, creation_ts, filename, description,
- mimetype, ispatch, isprivate, submitter_id)
+ mimetype, ispatch, isurl, isprivate, submitter_id)
VALUES ($bugid, $sql_timestamp, $sql_filename,
$description, $contenttype, " . $cgi->param('ispatch') . ",
- $isprivate, $userid)");
+ $isurl, $isprivate, $userid)");
$sth->execute();
# Retrieve the ID of the newly created attachment record.
my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
my ($attach_id) = validateID();
# Retrieve the attachment from the database.
- SendSQL("SELECT description, mimetype, filename, bug_id, ispatch, isobsolete, isprivate, LENGTH(thedata)
+ SendSQL("SELECT description, mimetype, filename, bug_id, ispatch, isurl,
+ isobsolete, isprivate, LENGTH(thedata)
FROM attachments
INNER JOIN attach_data
ON id = attach_id
WHERE attach_id = $attach_id");
- my ($description, $contenttype, $filename, $bugid, $ispatch, $isobsolete, $isprivate, $datasize) = FetchSQLData();
+ my ($description, $contenttype, $filename, $bugid, $ispatch, $isurl, $isobsolete, $isprivate, $datasize) = FetchSQLData();
- my $isviewable = isViewable($contenttype);
+ my $isviewable = !$isurl && isViewable($contenttype);
+ my $thedata;
+ if ($isurl) {
+ SendSQL("SELECT thedata FROM attach_data WHERE id = $attach_id");
+ ($thedata) = FetchSQLData();
+ }
# 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.
$vars->{'bugid'} = $bugid;
$vars->{'bugsummary'} = $bugsummary;
$vars->{'ispatch'} = $ispatch;
+ $vars->{'isurl'} = $isurl;
$vars->{'isobsolete'} = $isobsolete;
$vars->{'isprivate'} = $isprivate;
$vars->{'datasize'} = $datasize;
+ $vars->{'thedata'} = $thedata;
$vars->{'isviewable'} = $isviewable;
$vars->{'attachments'} = \@bugattachments;
$vars->{'GetBugLink'} = \&GetBugLink;
$dbh->do("DELETE FROM fielddefs WHERE name='attachments.thedata'");
AddFDef("attach_data.thedata", "Attachment data", 0);
+AddFDef("attachments.isurl", "Attachment is a URL", 0);
###########################################################################
# Detect changed local settings
$dbh->bz_rename_column('series', 'public', 'is_public');
}
+# 2005-09-28 bugreport@peshkin.net Bug 149504
+$dbh->bz_add_column('attachments', 'isurl',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
# If you had to change the --TABLE-- definition in any way, then add your
# differential change code *** A B O V E *** this comment.
%]
[% param_descs = {
+ allow_attach_url => "If this option is on, it will be possible to " _
+ "specify a URL when creating an attachment and " _
+ "treat the URL itself as if it were an attachment.",
maxpatchsize => "The maximum size (in kilobytes) of patches. $terms.Bugzilla will not " _
"accept patches greater than this number of kilobytes in size. " _
"To accept patches of any size (subject to the limitations of " _
convert_uncompressed_images => "If this option is on, attachments with content type image/bmp " _
"will be converted to image/png and compressed before uploading to " _
"the database to conserve disk space." }
-%]
\ No newline at end of file
+%]
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Erik Stambaugh <erik@dasbistro.com>
#%]
[% PROCESS global/variables.none.tmpl %]
onload="setContentTypeDisabledState();"
%]
+[% IF Param("allow_attach_url") %]
+ <script type="text/javascript">
+
+ function URLFieldHandler() {
+ var field_attachurl = document.getElementById("attachurl");
+ var greyfields = new Array("data", "ispatch", "autodetect",
+ "list", "manual", "bigfile",
+ "contenttypeselection",
+ "contenttypeentry");
+ var i;
+ if (field_attachurl.value.match(/^\s*$/)) {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.removeAttribute("disabled");
+ }
+ }
+ } else {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.setAttribute("disabled", "disabled");
+ }
+ }
+ }
+ }
+
+ function DataFieldHandler() {
+ var field_data = document.getElementById("data");
+ var greyfields = new Array("attachurl");
+ if (field_data.value.match(/^\s*$/)) {
+ var i;
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.removeAttribute("disabled");
+ }
+ }
+ } else {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.setAttribute("disabled", "disabled");
+ }
+ }
+ }
+ }
+
+ </script>
+[% END %]
+
+
<form name="entryform" method="post" action="attachment.cgi" enctype="multipart/form-data">
<input type="hidden" name="bugid" value="[% bugid %]">
<input type="hidden" name="action" value="insert">
<th><label for="data">File:</label></th>
<td>
<em>Enter the path to the file on your computer.</em><br>
- <input type="file" id="data" name="data" size="50">
+ <input type="file" id="data" name="data" size="50"
+ [% IF Param("allow_attach_url") %]
+ onchange="DataFieldHandler()"
+ [% END %]
+ >
</td>
</tr>
[% IF Param("maxlocalattachment") %]
</td>
</tr>
[% END %]
+ [% IF Param("allow_attach_url") %]
+ <tr>
+ <th><label for="attachurl">AttachURL:</label></th>
+ <td>
+ <em>URL to be attached instead.</em><br>
+ <input type="text" id="attachurl" name="attachurl" size="60"
+ maxlength="2000"
+ onkeyup="URLFieldHandler()" onblur="URLFieldHandler()">
+ </td>
+ </tr>
+ [% END %]
<tr>
<th><label for="description">Description:</label></th>
<td>
<input type="radio" id="list"
name="contenttypemethod" value="list">
<label for="list">select from list:</label>
- <select name="contenttypeselection"
+ <select name="contenttypeselection" id="contenttypeselection"
onchange="this.form.contenttypemethod[1].checked = true;">
[% PROCESS "attachment/content-types.html.tmpl" %]
</select><br>
<input type="radio" id="manual"
name="contenttypemethod" value="manual">
<label for="manual">enter manually:</label>
- <input type="text" name="contenttypeentry" size="30" maxlength="200"
+ <input type="text" name="contenttypeentry" id="contenttypeentry"
+ size="30" maxlength="200"
onchange="if (this.value) this.form.contenttypemethod[2].checked = true;">
</td>
</tr>
<b>Description:</b><br>
<textarea rows="3" cols="25" name="description" wrap="soft">[% description FILTER html %]</textarea><br>
- <b>Filename:</b><br>
- <input type="text" size="20" name="filename" value="[% filename FILTER html %]"><br>
- <b>Size: </b>[% datasize FILTER unitconvert %]<br>
+ [% IF isurl %]
+ <input type="hidden" name="filename" value="[% filename FILTER html %]"><br>
+ <input type="hidden" name="contenttypeentry" value="[% contenttype FILTER html %]"><br>
+ [% ELSE %]
+ <b>Filename:</b><br>
+ <input type="text" size="20" name="filename" value="[% filename FILTER html %]"><br>
+ <b>Size: </b>[% datasize FILTER unitconvert %]<br>
- <b>MIME Type:</b><br>
- <input type="text" size="20" name="contenttypeentry" value="[% contenttype FILTER html %]"><br>
+ <b>MIME Type:</b><br>
+ <input type="text" size="20" name="contenttypeentry" value="[% contenttype FILTER html %]"><br>
<input type="checkbox" id="ispatch" name="ispatch" value="1"
[% 'checked="checked"' IF ispatch %]>
<label for="ispatch">patch</label>
+ [% END %]
<input type="checkbox" id="isobsolete" name="isobsolete" value="1"
[% 'checked="checked"' IF isobsolete %]>
<label for="isobsolete">obsolete</label><br>
</div>
<input type="submit" value="Submit"><br><br>
- <strong>Actions:</strong> <a href="attachment.cgi?id=[% attachid %]">View</a>
+ <strong>Actions:</strong>
+ <a href="attachment.cgi?id=[% attachid %]">View</a>
[% IF ispatch && patchviewerinstalled %]
| <a href="attachment.cgi?id=[% attachid %]&action=diff">Diff</a>
[% END %]
//-->
</script>
</td>
+ [% ELSIF isurl %]
+ <td width="75%">
+ <a href="[% thedata FILTER html %]">
+ [% IF datasize < 120 %]
+ [% thedata FILTER html %]
+ [% ELSE %]
+ [% thedata FILTER truncate(80) FILTER html %]
+ ...
+ [% thedata.match(".*(.{20})$").0 FILTER html %]
+ [% END %]
+ </a>
+ </td>
[% ELSE %]
<td id="noview" width="50%">
<p><b>
<td valign="top">
[% IF attachment.ispatch %]
<i>patch</i>
+ [% ELSIF attachment.isurl %]
+ <i>url</i>
[% ELSE %]
[% attachment.contenttype FILTER html %]
[% END %]