File Coverage

blib/lib/Kwiki/Attachments.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Kwiki::Attachments;
2 1     1   34562 use strict;
  1         2  
  1         128  
3 1     1   6 use warnings;
  1         1  
  1         39  
4 1     1   587 use Kwiki::Plugin '-Base';
  0            
  0            
5             use Kwiki::Installer '-base';
6             use File::Basename;
7             our $VERSION = '0.21';
8              
9             const class_id => 'attachments';
10             const class_title => 'File Attachments';
11             const cgi_class => 'Kwiki::Attachments::CGI';
12             const config_file => 'attachments.yaml';
13             const css_file => 'attachments.css';
14              
15             field 'display_msg';
16             field files => [];
17              
18             sub register {
19             my $registry = shift;
20             $registry->add( action => 'attachments');
21             $registry->add( action => 'attachments_upload');
22             $registry->add( action => 'attachments_delete');
23             $registry->add( toolbar => 'attachments_button',
24             template => 'attachments_button.html',
25             show_for => ['display'],
26             );
27             $registry->add( wafl => file => 'Kwiki::Attachments::Wafl');
28             $registry->add( wafl => img => 'Kwiki::Attachments::Wafl');
29             $registry->add( wafl => thumb => 'Kwiki::Attachments::Wafl');
30             $registry->add( preload => 'attachments' );
31             $registry->add(widget => 'attachments_widget',
32             template => 'attachments_widget.html',
33             show_for => [ 'display', 'edit' ],
34             );
35             }
36              
37             sub update_metadata {
38             # Updates the modification time of the page.
39             my $page = $self->pages->current;
40             $page->metadata->update->store;
41             }
42              
43             sub attachments {
44             $self->get_attachments( $self->pages->current_id );
45             $self->render_screen();
46             }
47              
48             sub attachments_upload {
49             my $page_id = $self->pages->current_id;
50             my $skip_like = $self->config->attachments_skip;
51             my $client_file = CGI::param('uploaded_file');
52             my $file = basename($client_file);
53             $file =~ tr/a-zA-Z0-9.&+-/_/cs;
54             unless ($file){
55             $self->display_msg("Please specify a file to upload.");
56             }
57             else {
58             if ($file =~ /$skip_like/i) {
59             $self->display_msg("The selected file, $client_file,
60             was not uploaded because its name matches the
61             pattern of file names excluded by this wiki.");
62             }
63             else {
64             my $newfile = io->catfile($self->plugin_directory, $page_id, $file);
65             local $/;
66             my $fh = CGI::upload('uploaded_file');
67              
68             if ( $fh ) {
69             binmode($fh);
70             $newfile->assert->print(<$fh>);
71             $newfile->close();
72             $self->update_metadata();
73             if ($self->config->make_thumbnails !~ /off/i) {
74             $self->make_thumbnail($newfile);
75             }
76             }
77             }
78             }
79             $self->get_attachments( $page_id );
80             $self->render_screen;
81             }
82              
83             sub make_thumbnail {
84              
85             my $file = shift;
86             my ($fname, $fpath, $ftype) = fileparse($file, qr(\..*$));
87             my $thumb = io->catfile($fpath, "thumb_$fname$ftype");
88             my $override = ($self->config->im_override =~ /on/i) ? 0 : 1;
89            
90             if (eval { require Imager } && !$override) {
91             # Imager.pm naively (IMO) depends on (and believes) file
92             # extensions in order to determine the file type.
93             my $supported = 0;
94             $ftype = 'jpeg' if $ftype =~ /jpg/i;
95             for (keys %Imager::formats) {
96             if ($ftype =~ /$_/i) {
97             $supported = 1;
98             last;
99             }
100             }
101             if ($supported) {
102             my $image = Imager->new;
103             return unless ref($image);
104             if ($image->read(file=>$file)) {
105             my $thumb_img = $image->scale(xpixels=>80,ypixels=>80);
106             $thumb_img->write(file=>$thumb);
107             }
108             }
109             } elsif (eval { require Image::Magick }) {
110             # Image::Magick does the right thing with image files regardless
111             # of whether the file extension looks like ".jpg", ".png" etc.
112             my $image = Image::Magick->new;
113             return unless ref($image);
114             if (!$image->Read($file)) {
115             if (!$image->Scale(geometry=>'80x80')) {
116             if (!$image->Contrast(sharpen=>"true")) {
117             $image->Write($thumb);
118             }
119             }
120             }
121             }
122             return;
123             }
124              
125             sub attachments_delete {
126             # Remove the attachment and thumbnail, if one exists
127             my $page_id = $self->hub->pages->current_id;
128             my $base_dir = $self->plugin_directory;
129             foreach my $file ( $self->cgi->delete_these_files ) {
130             # my $f = $base_dir . '/' . $page_id . '/' . $file;
131             my $f = io->catfile($base_dir, $page_id, $file)->pathname;
132             if ( -f $f ) {
133             unlink $f or die "Unable To Delete: $f";
134             }
135             my $thumb = io->catfile($base_dir, $page_id, "thumb_$file")->pathname;
136             if ( -f $thumb ) {
137             unlink $thumb;
138             }
139             }
140             $self->get_attachments( $page_id );
141             $self->update_metadata();
142             $self->render_screen();
143             }
144              
145             sub get_attachments {
146             my $page_id = shift;
147             my $skip_like = $self->config->attachments_skip;
148             # my @files;
149             my $page_dir = $self->plugin_directory . '/' . $page_id;
150             my $count = 0;
151             @{$self->{files}} = ();
152             if ( opendir( DIR, $page_dir) ) {
153             my $file_name;
154             while ( $file_name = readdir(DIR) ) {
155             next if $file_name =~ /$skip_like/i;
156             my $full_name = $page_dir . '/' . $file_name;
157             my $thumb = $page_dir . '/' . "thumb_$file_name";
158             next unless -f $full_name;
159             ( undef, undef, undef, undef, undef, undef, undef,
160             my $size,
161             undef,
162             my $mtime,
163             undef, undef, undef ) = stat($full_name);
164             push @{$self->{files}}, Kwiki::Attachments::File->new( $file_name,
165             $full_name,
166             $size,
167             $mtime,
168             $thumb);
169             $count++;
170             }
171             closedir DIR;
172             }
173             return $count;
174             }
175              
176             sub file_count {
177             return $self->{files} ? (0 + @{$self->{files}}) : 0;
178             }
179              
180             package Kwiki::Attachments::CGI;
181             use base 'Kwiki::CGI';
182              
183             cgi 'delete_these_files';
184             cgi 'uploaded_file';
185              
186             package Kwiki::Attachments::File;
187             use Spiffy qw(-Base -dumper);
188              
189             use POSIX qw( strftime );
190              
191             field 'name';
192             field 'href';
193             field 'bytes';
194             field 'time';
195             field 'thumb';
196              
197             sub new() {
198             my $class = shift;
199             my $self = bless {}, $class;
200              
201             my $name = shift;
202             my $href = shift;
203             my $bytes = shift;
204             my $time = shift;
205             my $thumb = shift;
206              
207             $name = Spoon::Base->html_escape($name);
208             $href = Spoon::Base->html_escape($href);
209              
210             $self->name( $name );
211             $self->href( $href );
212             $self->bytes( $bytes );
213             $self->time( $time );
214             $self->thumb( $thumb );
215              
216             return $self;
217             }
218              
219             sub date {
220             return Kwiki::Page->format_time($self->time);
221             }
222              
223             sub size {
224             my $size = $self->bytes;
225             my $scale = 'B';
226             if ( $size > 1024 ) { $size >>= 10; $scale = 'K'; }
227             if ( $size > 1024 ) { $size >>= 10; $scale = 'MB'; }
228             if ( $size > 1024 ) { $size >>= 10; $scale = 'GB'; }
229             return $size . $scale;
230             }
231              
232             sub thumbnail {
233             # returns nothing if no thumbnail file
234             return $self->thumb if (-f $self->thumb)
235             }
236              
237             package Kwiki::Attachments::Wafl;
238             use base 'Spoon::Formatter::WaflPhrase';
239              
240             sub html {
241             my @args = split( / +/, $self->arguments );
242             my $file_name = shift( @args );
243             my $desc = @args ? join( ' ', @args ) : $file_name;
244             my $base_dir = $self->hub->attachments->plugin_directory;
245             my $loc = $base_dir . '/';
246             if ( $file_name !~ /\// ) {
247             # file is on the current page
248             my $page_id = $self->hub->pages->current_id;
249             $loc .= $page_id . '/';
250             }
251             $loc .= $file_name;
252             my $thumb = $loc;
253             $thumb =~ s|/(?=[^/]*$)|/thumb_|;
254             return join('', $self->html_escape($desc), ' (file not found)')
255             unless -f $loc;
256             if ( $self->method eq "img" ) {
257             return join '',
258             '<img src="', $loc, '" alt="',
259             $self->html_escape($desc),
260             '" />';
261             } elsif ( $self->method eq "file" ) {
262             return join '',
263             '<a href="', $loc, '">',
264             $self->html_escape($desc),
265             '</a>';
266             } else {
267             return join '',
268             '<a href="', $loc, '">',
269             '<img border="0" src="', $thumb, '"',
270             ' alt="', $self->html_escape($desc), '"',
271             ' />',
272             '</a>';
273             }
274             }
275              
276             1;
277              
278             package Kwiki::Attachments;
279              
280             __DATA__
281              
282             =head1 NAME
283              
284             Kwiki::Attachments - Kwiki Page Attachments Plugin
285              
286             =head1 SYNOPSIS
287              
288             =over 4
289              
290             =item 1.
291             Install Kwiki::Attachments
292              
293             =item 2.
294             Run 'kwiki -add Kwiki::Attachments'
295              
296             =back
297              
298             =head1 DESCRIPTION
299              
300             =head3 B<General>
301              
302             Kwiki::Attachments gives a Kwiki wiki the ability to upload, store and manage
303             file attachments on any page. By default, if you have an image creation module
304             such as Imager or Image::Magick installed, then a thumbnail will be created for
305             every supported image file type that is uploaded. Thumbnails are displayed on
306             the attachments page, and can also be displayed on wiki pages via the wafl
307             directives described in the next paragraph. The thumbnail files have "thumb_"
308             prepended to the original filename and are not displayed separately in the
309             attachment page or widget. For this reason, you cannot upload files beginning
310             with "thumb_".
311              
312             =head3 B<WAFL>
313              
314             This module provides 3 wafl tags which can be used to link to or display
315             attachments in a Kwiki page.
316              
317             =over 4
318              
319             =item *
320             C<{file:[page/]filename}> creates a link to attachment I<filename>.
321              
322             =item *
323             C<{img:[page/]filename}> displays attachment I<filename>.
324              
325             =item *
326             C<{thumb:[page/]filename}> displays the thumbnail for attachment I<filename>.
327              
328             =back
329              
330             =head3 B<Configuration Options>
331              
332             [kwiki_base_dir]/config/attachments.yaml
333              
334             =over 4
335              
336             =item *
337             attachments_skip: [regular_expression]
338              
339             Kwiki::Attachments may be configured to reject the upload of files with names
340             matched by the given regular expression. By default, it is set to reject files
341             beginning with "thumb_" or "." and those ending with "~" or ".bak".
342              
343             =item * make_thumbnails: [on|off]
344              
345             This flag controls whether thumbnails are created from uploaded image files.
346             It is set to 'on' by default.
347              
348             =item * im_override: [on|off]
349              
350             If both Imager.pm and Image::Magick.pm are available, Kwiki::Attachments uses
351             Imager, unless im_override is set to 'on'. This parameter has no effect if only
352             one of the two image manipulation modules is installed. It is set to 'off' by
353             default.
354              
355             =head1 AUTHOR
356              
357             Sue Spence <sue_cpan@pennine.com>
358              
359             This module is based almost entirely on work by
360             Eric Lowry <eric@clubyo.com> and Brian Ingerson <INGY@cpan.org>
361              
362             =head1 COPYRIGHT
363              
364             Copyright (c) 2005. Sue Spence. All rights reserved.
365              
366             This program is free software; you can redistribute it and/or modify it
367             under the same terms as Perl itself.
368              
369             See http://www.perl.com/perl/misc/Artistic.html
370              
371             =cut
372             __config/attachments.yaml__
373             # Kwiki::Attachments configuration options
374              
375             # attachments_skip: regex describing file names to be excluded
376             attachments_skip: (^\.)|(^thumb_)|(~$)|(\.bak$)
377              
378             # make_thumbnails: on|off
379             make_thumbnails: on
380              
381             # 'im_override' overrides the preference for Imager for
382             # thumbnail creation if both Imager & Image::Magick are installed.
383             # im_override: on|off
384             im_override: off
385              
386             __css/attachments.css__
387             table.attachments {
388             color: #999;
389             }
390             th.delete { text-align: left;width: 15% }
391             th.file { text-align: left;width: 35% }
392             th.size { text-align: left;width: 15% }
393             th.date { text-align: left; }
394             th.thumb { text-align: left; }
395             td.delete { text-align: left;width: 15% }
396             td.file { text-align: left;width: 35% }
397             td.size { text-align: left;width: 15% }
398             td.thumb { text-align: left; }
399              
400             __template/tt2/attachments_button.html__
401             <!-- BEGIN attachments_button.html -->
402             <a href="[% script_name %]?action=attachments&page_name=[% page_uri %]" accesskey="f" title="File Attachments">
403             [% INCLUDE attachments_button_icon.html %]
404             </a>
405             <!-- END attachments_button.html -->
406             __template/tt2/attachments_button_icon.html__
407             <!-- BEGIN attachments_button_icon.html -->
408             Attachments
409             <!-- END attachments_button_icon.html -->
410             __icons/gnome/template/attachments_button_icon.html__
411             <!-- BEGIN attachments_button_icon.html -->
412             <img src="icons/gnome/image/attachments.png" alt="Attachments" />
413             <!-- END attachments_button_icon.html -->
414             __template/tt2/attachments_content.html__
415             <!-- BEGIN attachments_content.html -->
416              
417             File Attachments For:
418             <a href="[% script_name %]?[% page_name %]">[% page_uri %]</a>
419             <br />
420              
421             [% IF self.file_count > 0 %]
422             <br />
423             <form method="post"
424             action="[% script_name %]" >
425             <input type="hidden"
426             name="action"
427             value="attachments_delete">
428             <input type="hidden"
429             name="page_name"
430             value="[% page_uri %]">
431             <table class="attachments">
432             <tr>
433             <th class="delete">Delete?</th>
434             <th class="file">File</th>
435             <th class="size">Size</th>
436             <th class="date">Uploaded On</th>
437             </tr>
438             [% FOR file = self.files %]
439             <tr>
440             <td class="delete"><input type="checkbox" name="delete_these_files" value="[% file.name %]"></td>
441             <td class="file"><a href="[% file.href %]">[% file.name %]</a></td>
442             <td class="size">[% file.size %]</td>
443             <td class="date">[% file.date %]</td>
444             [% IF file.thumbnail %]
445             <td class="thumb"><a href="[% file.href %]"><img src="[% file.thumbnail %]" border="0" width="80" height="80" alt="[% file.name %]"></a></td>
446             [% END %]
447             </tr>
448             [% END %]
449             <tr>
450             <td colspan="99">
451             <input type="submit" name="Delete" value="Delete">
452             </td>
453             </tr>
454             </table>
455             </form>
456             [% END %]
457             <br />
458             <form method="post"
459             action="[% script_name %]"
460             enctype="multipart/form-data" >
461             <input type="hidden"
462             name="action"
463             value="attachments_upload">
464             <input type="hidden" name="page_name" value="[% page_uri %]">
465             File
466             <input type="file"
467             name="uploaded_file"
468             size="40"
469             maxlength="80" />
470             <input type="submit"
471             name="Upload"
472             value="Upload">
473             </form>
474             <p style="color: red;font-size: larger "> [% self.display_msg %]</p>
475             <!-- END attachments_content.html -->
476             __template/tt2/attachments_widget.html__
477             <!-- BEGIN attachments_widget.html -->
478             [% count = hub.attachments.get_attachments( page_uri ) -%]
479             [% IF count %]
480             <table class="attachments_widget">
481             <tr><th colspan="2" style="text-align: left">Attachments</th></td>
482             [% FOR file = hub.attachments.files %]
483             <tr>
484             <td><a href="[% file.href %]" title="[% file.size %] - [% file.name %]">[% file.name %]</a></td>
485             <td>[% file.size %]</td>
486             </tr>
487             [% END -%]
488             </table>
489             [% END -%]
490             <!-- END attachments_widgets.html -->
491             __icons/gnome/image/attachments.png__
492             iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABLUlE
493             QVR42rWSPW6EMBCFX3KEVDnHlmlpKKjpqVNEOca2nIAmFVus0BYRSpEWpBRuaSzRUJgDWPzMSxOs
494             hcDuKlIsjeQZz3v+PDLwD8sDwEW8bjXfr9Q+ANydxTOARwBvP2b+NQKe7X0AX1EUUUR4PB65ON80
495             cMJhGDiOI0WEWmsGQXCRwp/e3XUdu65j3/cUEeZ5fpHiCcDe8zwCoLWWIkIRIQAqpVyutWYYhr8o
496             8jiOnUBEqJSiUsrlU22L4r0oitUbp9wYM6NYzuIhCAInaNuWIsKqqmYEdV0zTVMOw7BNcTqdCMCJ
497             D4eDw2+ahn3fU2u9auAoJpMkSWiMYVmWtNY6YRRF3PpUbhbGGBZFwaqqbhLOKOq6dsO6VTijyLLs
498             T0JHAeATAHe73cu15m88QWrCNHlDUQAAAABJRU5ErkJggg==
499