1
# See bottom of file for license and copyright information
5
---+ package Foswiki::Attach
7
A singleton object of this class is used to deal with attachments to topics.
11
# Note: Meta-data is stored in topics fo performance. You need to use the
12
# official API to manipulate attachments. Dropping files into the
13
# attachments directory works only if the {AutoAttachPubFiles} configure
16
package Foswiki::Attach;
23
---++ ClassMethod new($session)
30
my ( $class, $session ) = @_;
31
my $this = bless( { session => $session }, $class );
38
---++ ObjectMethod finish()
39
Break circular references.
43
# Note to developers; please undef *all* fields in the object explicitly,
44
# whether they are references or not. That way this method is "golden
45
# documentation" of the live fields in the object.
48
undef $this->{session};
53
---++ ObjectMethod renderMetaData( $web, $topic, $meta, $args ) -> $text
55
Generate a table of attachments suitable for the bottom of a topic
56
view, using templates for the header, footer and each row.
59
* =$meta= meta-data hash for the topic
60
* =$args= hash of attachment arguments
65
my ( $this, $web, $topic, $meta, $attrs ) = @_;
67
my $showAll = $attrs->{all};
68
my $showAttr = $showAll ? 'h' : '';
69
my $A = ($showAttr) ? ':A' : '';
70
my $title = $attrs->{title} || '';
71
my $tmplname = $attrs->{template} || 'attachtables';
73
my @attachments = $meta->find('FILEATTACHMENT');
74
return '' unless @attachments;
76
my $templates = $this->{session}->templates;
77
$templates->readTemplate($tmplname);
80
my $row = $templates->expandTemplate( 'ATTACH:files:row' . $A );
82
my $attachment ( sort { ( $a->{name} || '' ) cmp( $b->{name} || '' ) }
85
my $attrAttr = $attachment->{attr};
87
if ( !$attrAttr || ( $showAttr && $attrAttr =~ /^[$showAttr]*$/ ) ) {
88
$rows .= _formatRow( $this, $web, $topic, $attachment, $row );
94
if ( $showAll || $rows ne '' ) {
95
my $header = $templates->expandTemplate( 'ATTACH:files:header' . $A );
96
my $footer = $templates->expandTemplate( 'ATTACH:files:footer' . $A );
98
$text = $header . $rows . $footer;
100
return $title . $text;
105
---++ ObjectMethod formatVersions ( $web, $topic, $attrs ) -> $text
107
Generate a version history table for a single attachment
109
* =$topic= - the topic
110
* =$attrs= - Hash of meta-data attributes
115
my ( $this, $web, $topic, %attrs ) = @_;
117
my $store = $this->{session}->{store};
118
my $users = $this->{session}->{users};
119
my $latestRev = $store->getRevisionNumber( $web, $topic, $attrs{name} );
121
my $templates = $this->{session}->templates;
122
$templates->readTemplate('attachtables');
124
my $header = $templates->expandTemplate('ATTACH:versions:header');
125
my $footer = $templates->expandTemplate('ATTACH:versions:footer');
126
my $row = $templates->expandTemplate('ATTACH:versions:row');
130
for ( my $rev = $latestRev ; $rev >= 1 ; $rev-- ) {
131
my ( $date, $user, $minorRev, $comment ) =
132
$store->getRevisionInfo( $web, $topic, $rev, $attrs{name} );
137
name => $attrs{name},
142
attr => $attrs{attr},
149
return "$header$rows$footer";
152
#Format a single row in an attachment table by expanding a template.
153
#| =$web= | the web |
154
#| =$topic= | the topic |
155
#| =$info= | hash containing fields name, user (user (not wikiname) who uploaded this revision), date (date of _this revision_ of the attachment), command and version (the required revision; required to be a full (major.minor) revision number) |
156
#| =$tmpl= | The template of a row |
158
my ( $this, $web, $topic, $info, $tmpl ) = @_;
162
$row =~ s/%A_(\w+)%/_expandAttrs( $this,$1,$web,$topic,$info)/ge;
163
$row =~ s/$Foswiki::TranslationToken/%/go;
169
my ( $this, $attr, $web, $topic, $info ) = @_;
170
my $file = $info->{name} || '';
171
my $users = $this->{session}->{users};
173
require Foswiki::Time;
175
if ( $attr eq 'REV' ) {
176
return $info->{version};
178
elsif ( $attr eq 'ICON' ) {
179
my $picked = $this->{session}->mapToIconFileName($file);
180
if (!defined($picked) || ($picked eq '')) {
183
my $url = $this->{session}->getIconUrl( 0, $picked );
190
alt => $picked || '',
195
elsif ( $attr eq 'EXT' ) {
197
# $fileExtension is used to map the attachment to its MIME type
198
# only grab the last extension in case of multiple extensions
199
$file =~ m/\.([^.]*)$/;
202
elsif ( $attr eq 'URL' ) {
203
return $this->{session}->getScriptUrl(
204
0, 'viewfile', $web, $topic,
205
rev => $info->{version} || undef,
209
elsif ( $attr eq 'SIZE' ) {
210
my $attrSize = $info->{size};
211
$attrSize = 100 if ( !$attrSize || $attrSize < 100 );
212
return sprintf( "%1.1f K", $attrSize / 1024 );
214
elsif ( $attr eq 'COMMENT' ) {
215
my $comment = $info->{comment};
217
$comment =~ s/\|/|/g;
224
elsif ( $attr eq 'ATTRS' ) {
225
return $info->{attr} or " ";
227
elsif ( $attr eq 'FILE' ) {
230
elsif ( $attr eq 'EFILE' ) {
232
# Really aggressive URL encoding, required to protect wikiwords
233
# See Bugs:Item3289, Bugs:Item3623
234
$file =~ s/([^A-Za-z0-9])/'%'.sprintf('%02x',ord($1))/ge;
237
elsif ( $attr eq 'DATE' ) {
238
return Foswiki::Time::formatTime( $info->{date} || 0 );
240
elsif ( $attr eq 'USER' ) {
241
my $user = $info->{user} || 'UnknownUser';
244
$cUID = $users->getCanonicalUserID($user);
247
# Not a login name or a wiki name. Is it a valid cUID?
248
my $ln = $users->getLoginName($user);
249
$cUID = $user if defined $ln && $ln ne 'unknown';
253
return $users->webDotWikiName($cUID);
256
return $Foswiki::TranslationToken . 'A_' . $attr
257
. $Foswiki::TranslationToken;
263
---++ ObjectMethod getAttachmentLink( $user, $web, $topic, $name, $meta ) -> $html
265
* =$user= - User doing the reading
266
* =$web= - Name of the web
267
* =$topic= - Name of the topic
268
* =$name= - Name of the attachment
269
* =$meta= - Meta object that contains the meta info
271
Build a link to the attachment, suitable for insertion in the topic.
275
sub getAttachmentLink {
276
my ( $this, $user, $web, $topic, $attName, $meta ) = @_;
278
my $att = $meta->get( 'FILEATTACHMENT', $attName );
279
my $fileComment = $att->{comment};
280
$fileComment = $attName unless ($fileComment);
284
my $prefs = $this->{session}->{prefs};
285
my $store = $this->{session}->{store};
287
# I18N: URL-encode the attachment filename
288
my $fileURL = Foswiki::urlEncodeAttachment($attName);
290
if ( $attName =~ /\.(gif|jpg|jpeg|png)$/i ) {
294
# The pixel size calculation is done for performance reasons
295
# Some browsers wait with rendering a page until the size of
296
# embedded images is known, e.g. after all images of a page are
297
# downloaded. When you upload an image to Foswiki and checkmark
298
# the link checkbox, Foswiki will generate the width and height
299
# img parameters, speeding up the page rendering.
301
$store->getAttachmentStream( $user, $web, $topic, $attName );
302
my ( $nx, $ny ) = &_imgsize( $stream, $attName );
305
if ( $nx > 0 && $ny > 0 ) {
306
push( @attrs, width => $nx, height => $ny );
307
$imgSize = "width='$nx' height='$ny'";
310
$fileLink = $prefs->getPreferencesValue('ATTACHEDIMAGEFORMAT');
312
push( @attrs, src => "%ATTACHURLPATH%/$fileURL" );
313
push( @attrs, alt => $attName );
314
return " * $fileComment: " . CGI::br() . CGI::img( {@attrs} );
319
# normal attached file
320
$fileLink = $prefs->getPreferencesValue('ATTACHEDFILELINKFORMAT');
322
return " * [[%ATTACHURL%/$fileURL][$attName]]: $fileComment";
326
# I18N: Site specified %ATTACHEDIMAGEFORMAT% or %ATTACHEDFILELINKFORMAT%,
327
# ensure that filename is URL encoded - first $name must be URL.
328
$fileLink =~ s/\$name/$fileURL/;
329
$fileLink =~ s/\$name/$attName/;
331
# Expand \t and \n early (only in the format, not in the comment) - Bugs:Item4581
332
$fileLink =~ s/\\t/\t/go;
333
$fileLink =~ s/\\n/\n/go;
334
$fileLink =~ s/\$comment/$fileComment/g;
335
$fileLink =~ s/\$size/$imgSize/g;
336
$fileLink =~ s/([^\n])$/$1\n/;
341
# code fragment to extract pixel size from images
342
# taken from http://www.tardis.ed.ac.uk/~ark/wwwis/
343
# subroutines: _imgsize, _gifsize, _OLDgifsize, _gif_blockskip,
344
# _NEWgifsize, _jpegsize
347
my ( $file, $att ) = @_;
348
my ( $x, $y ) = ( 0, 0 );
350
if ( defined($file) ) {
351
binmode($file); # For Windows
353
return ( 0, 0 ) unless ( read( $file, $s, 4 ) == 4 );
355
if ( $s eq 'GIF8' ) {
358
( $x, $y ) = _gifsize($file);
361
my ( $a, $b, $c, $d ) = unpack( 'C4', $s );
369
( $x, $y ) = _pngsize($file);
378
( $x, $y ) = _jpegsize($file);
389
return &_NEWgifsize($GIF);
392
return &_OLDgifsize($GIF);
398
my ( $type, $a, $b, $c, $d, $s ) = ( 0, 0, 0, 0, 0, 0 );
401
&& read( $GIF, $type, 6 )
402
&& $type =~ /GIF8[7,9]a/
403
&& read( $GIF, $s, 4 ) == 4 )
405
( $a, $b, $c, $d ) = unpack( 'C' x 4, $s );
406
return ( $b << 8 | $a, $d << 8 | $c );
411
# part of _NEWgifsize
413
my ( $GIF, $skip, $type ) = @_;
417
read( $GIF, $dummy, $skip ); # Skip header (if any)
421
#warn "Invalid/Corrupted GIF (at EOF in GIF $type)\n";
424
read( $GIF, $s, 1 ); # Block size
425
last if ord($s) == 0; # Block terminator
426
read( $GIF, $dummy, ord($s) ); # Skip data
430
# this code by "Daniel V. Klein" <dvk@lonewolf.com>
433
my ( $cmapsize, $a, $b, $c, $d, $e ) = 0;
434
my ( $type, $s ) = ( 0, 0 );
435
my ( $x, $y ) = ( 0, 0 );
438
return ( $x, $y ) if ( !defined $GIF );
440
read( $GIF, $type, 6 );
441
if ( $type !~ /GIF8[7,9]a/ || read( $GIF, $s, 7 ) != 7 ) {
443
#warn "Invalid/Corrupted GIF (bad header)\n";
446
($e) = unpack( "x4 C", $s );
448
$cmapsize = 3 * 2**( ( $e & 0x07 ) + 1 );
449
if ( !read( $GIF, $dummy, $cmapsize ) ) {
451
#warn "Invalid/Corrupted GIF (global color map too small?)\n";
459
#warn "Invalid/Corrupted GIF (at EOF w/o Image Descriptors)\n";
463
($e) = unpack( 'C', $s );
464
if ( $e == 0x2c ) { # Image Descriptor (GIF87a, GIF89a 20.c.i)
465
if ( read( $GIF, $s, 8 ) != 8 ) {
467
#warn "Invalid/Corrupted GIF (missing image header?)\n";
470
( $a, $b, $c, $d ) = unpack( "x4 C4", $s );
475
if ( $type eq 'GIF89a' ) {
476
if ( $e == 0x21 ) { # Extension Introducer (GIF89a 23.c.i)
478
($e) = unpack( 'C', $s );
479
if ( $e == 0xF9 ) { # Graphic Control Extension (GIF89a 23.c.ii)
480
read( $GIF, $dummy, 6 ); # Skip it
481
next FINDIMAGE; # Look again for Image Descriptor
483
elsif ( $e == 0xFE ) { # Comment Extension (GIF89a 24.c.ii)
484
&_gif_blockskip( $GIF, 0, 'Comment' );
485
next FINDIMAGE; # Look again for Image Descriptor
487
elsif ( $e == 0x01 ) { # Plain Text Label (GIF89a 25.c.ii)
488
&_gif_blockskip( $GIF, 12, 'text data' );
489
next FINDIMAGE; # Look again for Image Descriptor
492
{ # Application Extension Label (GIF89a 26.c.ii)
493
&_gif_blockskip( $GIF, 11, 'application data' );
494
next FINDIMAGE; # Look again for Image Descriptor
498
#printf STDERR "Invalid/Corrupted GIF (Unknown extension %#x)\n", $e;
504
#printf STDERR "Invalid/Corrupted GIF (Unknown code %#x)\n", $e;
510
#warn "Invalid/Corrupted GIF (missing GIF87a Image Descriptor)\n";
516
# _jpegsize : gets the width and height (in pixels) of a jpeg file
517
# Andrew Tong, werdna@ugcs.caltech.edu February 14, 1995
518
# modified slightly by alex@ed.ac.uk
522
my ( $c1, $c2, $ch, $s, $length, $dummy ) = ( 0, 0, 0, 0, 0, 0 );
523
my ( $a, $b, $c, $d );
526
&& read( $JPEG, $c1, 1 )
527
&& read( $JPEG, $c2, 1 )
529
&& ord($c2) == 0xD8 )
531
while ( ord($ch) != 0xDA && !$done ) {
533
# Find next marker (JPEG markers begin with 0xFF)
534
# This can hang the program!!
535
while ( ord($ch) != 0xFF ) {
536
return ( 0, 0 ) unless read( $JPEG, $ch, 1 );
539
# JPEG markers can be padded with unlimited 0xFF's
540
while ( ord($ch) == 0xFF ) {
541
return ( 0, 0 ) unless read( $JPEG, $ch, 1 );
544
# Now, $ch contains the value of the marker.
545
if ( ( ord($ch) >= 0xC0 ) && ( ord($ch) <= 0xC3 ) ) {
546
return ( 0, 0 ) unless read( $JPEG, $dummy, 3 );
547
return ( 0, 0 ) unless read( $JPEG, $s, 4 );
548
( $a, $b, $c, $d ) = unpack( 'C' x 4, $s );
549
return ( $c << 8 | $d, $a << 8 | $b );
553
# We **MUST** skip variables, since FF's within variable
554
# names are NOT valid JPEG markers
555
return ( 0, 0 ) unless read( $JPEG, $s, 2 );
556
( $c1, $c2 ) = unpack( 'C' x 2, $s );
557
$length = $c1 << 8 | $c2;
558
last if ( !defined($length) || $length < 2 );
559
read( $JPEG, $dummy, $length - 2 );
566
# _pngsize : gets the width & height (in pixels) of a png file
567
# source: http://www.la-grange.net/2000/05/04-png.html
571
my ( $a, $b, $c, $d, $e, $f, $g, $h ) = 0;
573
&& read( $PNG, $head, 8 ) == 8
574
&& $head eq "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"
575
&& read( $PNG, $head, 4 ) == 4
576
&& read( $PNG, $head, 4 ) == 4
578
&& read( $PNG, $head, 8 ) == 8 )
580
( $a, $b, $c, $d, $e, $f, $g, $h ) = unpack( 'C' x 8, $head );
582
$a << 24 | $b << 16 | $c << 8 | $d,
583
$e << 24 | $f << 16 | $g << 8 | $h
591
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
593
# Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
594
# are listed in the AUTHORS file in the root of this distribution.
595
# NOTE: Please extend that file, not this notice.
597
# Additional copyrights apply to some or all of the code in this
600
# Copyright (C) 2001-2007 Peter Thoeny, peter@thoeny.org
601
# and TWiki Contributors. All Rights Reserved. TWiki Contributors
602
# are listed in the AUTHORS file in the root of this distribution.
604
# This program is free software; you can redistribute it and/or
605
# modify it under the terms of the GNU General Public License
606
# as published by the Free Software Foundation; either version 2
607
# of the License, or (at your option) any later version. For
608
# more details read LICENSE in the root of this distribution.
610
# This program is distributed in the hope that it will be useful,
611
# but WITHOUT ANY WARRANTY; without even the implied warranty of
612
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
614
# As per the GPL, removal of this notice is prohibited.