File Coverage

blib/lib/CGI/Uploader.pm
Criterion Covered Total %
statement 305 345 88.4
branch 60 102 58.8
condition 27 64 42.1
subroutine 28 30 93.3
pod 19 19 100.0
total 439 560 78.3


line stmt bran cond sub pod time code
1             package CGI::Uploader;
2              
3 9     9   715553 use 5.008;
  9         36  
  9         376  
4 9     9   52 use strict;
  9         17  
  9         276  
5 9     9   46 use Carp;
  9         46  
  9         577  
6 9     9   11015 use Params::Validate ':all';
  9         105665  
  9         2417  
7 9     9   84 use File::Path;
  9         25  
  9         539  
8 9     9   54 use File::Spec;
  9         15  
  9         230  
9 9     9   12164 use File::Temp 'tempfile';
  9         246021  
  9         642  
10 9     9   1604 use Carp::Assert;
  9         2363  
  9         79  
11 9     9   11685 use Image::Size;
  9         64255  
  9         26291  
12             require Exporter;
13              
14             our $VERSION = '2.18';
15              
16             =head1 NAME
17              
18             CGI::Uploader - Manage CGI uploads using SQL database
19              
20             =head1 Synopsis
21              
22             use CGI::Uploader::Transform::ImageMagick 'gen_thumb';
23              
24             my $u = CGI::Uploader->new(
25             spec => {
26             # Upload one image named from the form field 'img'
27             # and create one thumbnail for it.
28             img_1 => {
29             gen_files => {
30             'img_1_thmb_1' => gen_thumb({ w => 100, h => 100 }),
31             }
32             },
33             },
34              
35             updir_url => 'http://localhost/uploads',
36             updir_path => '/home/user/www/uploads',
37             temp_dir => '/home/user/www/uploads',
38              
39             dbh => $dbh,
40             query => $q, # defaults to CGI->new(),
41             );
42              
43             # ... now do something with $u
44              
45             =head1 Description
46              
47             This module is designed to help with the task of managing files uploaded
48             through a CGI application. The files are stored on the file system, and
49             the file attributes stored in a SQL database.
50              
51             =head1 Introduction and Recipes
52              
53             The L provides
54             a slightly more in depth introduction and recipes for a basic BREAD web
55             application. (Browse, Read, Edit, Add, Delete).
56              
57             =head1 Constructor
58              
59             =head2 new()
60              
61             my $u = CGI::Uploader->new(
62             spec => {
63             # The first image has 2 different sized thumbnails
64             img_1 => {
65             gen_files => {
66             'img_1_thmb_1' => gen_thumb({ w => 100, h => 100 }),
67             'img_1_thmb_2' => gen_thumb({ w => 50, h => 50 }),
68             }
69             },
70             },
71              
72             # Just upload it
73             img_2 => {},
74             # Downsize the large image to these maximum dimensions if it's larger
75             img_3 => {
76             # Besides generating dependent files
77             # We can also transform the file itself
78             # Here, we shrink the image to be wider than 380
79             transform_method => \&gen_thumb,
80             # demostrating the old-style param passing
81             params => [{ w => 380 }],
82             }
83             },
84              
85             updir_url => 'http://localhost/uploads',
86             updir_path => '/home/user/www/uploads',
87              
88             dbh => $dbh,
89             query => $q, # defaults to CGI->new(),
90              
91             up_table => 'uploads', # defaults to "uploads"
92             up_seq => 'upload_id_seq', # Required for Postgres
93             );
94              
95             =over 4
96              
97             =item spec [required]
98              
99             The specification described the examples above. The keys correspond to form
100             field names for upload fields.
101              
102             The values are hash references. The simplest case is an empty hash reference,
103             which means to just upload the image and apply no transformations.
104              
105             #####
106              
107             Each key in the hash is the corresponds to a file upload field. The values
108             are hash references used provide options for how to transform the file,
109             and possibly generate additional files based on it.
110              
111             Valid keys here are:
112              
113             =item transform_method
114              
115             This is a subroutine reference. This routine can be used to transform the
116             upload before it is stored. The first argument given to the routine will be the
117             CGI::Uploader object. The second will be a full path to a file name containing
118             the upload.
119              
120             Additional arguments can be passed to the subroutine using C, as in the
121             example above. But don't do that, it's ugly. If you need a custom transform
122             method, write a little closure for it like this:
123              
124             sub my_transformer {
125             my %args = @_;
126             return sub {
127             my ($self, $file) = shift;
128             # do something with $file and %args here...
129             return $path_to_new_file_i_made;
130             }
131              
132             Then in the spec you can put:
133              
134             transform_method => my_tranformer(%args),
135              
136             It must return a full path to a transformed file.
137              
138             }
139              
140             =item params (DEPRECATED)
141              
142             B Using a closure based interface provides a cleaner alternative to
143             using params. See L for an example.
144              
145             Used to pass additional arguments to C. See above.
146              
147             Each method used may have additional documentation about parameters
148             that can be passed to it.
149              
150              
151             =item gen_files
152              
153             A hash reference to describe files generated from a particular upload.
154             The keys are unique identifiers for the generated files. The values
155             are code references (usually closures) that prove a transformation
156             for the file. See L for an
157             an example.
158              
159             An older interface for C is deprecated. For that, the values are
160             hashrefs, containing keys named C and C, which work
161             as described above to generate a transformed version of the file.
162              
163             =item updir_url [required]
164              
165             URL to upload storage directory. Should not include a trailing slash.
166              
167             =item updir_path [required]
168              
169             File system path to upload storage directory. Should not include a trailing
170             slash.
171              
172             =item temp_dir
173              
174             Optional file system path to temporary directory. Default is File::Spec->tmpdir().
175             This temporary directory will also be used by gen_files during image transforms.
176              
177             =item dbh [required]
178              
179             DBI database handle. Required.
180              
181             =item query
182              
183             A CGI.pm-compatible object, used for the C and C functions.
184             Defaults to CGI->new() if omitted.
185              
186             =item up_table
187              
188             Name of the SQL table where uploads are stored. See example syntax above or one
189             of the creation scripts included in the distribution. Defaults to "uploads" if
190             omitted.
191              
192             =item up_table_map
193              
194             A hash reference which defines a mapping between the column names used in your
195             SQL table, and those that CGI::Uploader uses. The keys are the CGI::Uploader
196             default names. Values are the names that are actually used in your table.
197              
198             This is not required. It simply allows you to use custom column names.
199              
200             upload_id => 'upload_id',
201             mime_type => 'mime_type',
202             extension => 'extension',
203             width => 'width',
204             height => 'height',
205             gen_from_id => 'gen_from_id',
206             file_name => 'file_name',
207              
208             You may also define additional column names with a value of 'undef'. This feature
209             is only useful if you override the C method or pass in
210             C<$shared_meta> to store_uploads(). Values for these additional columns will
211             then be stored by C and retrieved with C.
212              
213             =item up_seq
214              
215             For Postgres only, the name of a sequence used to generate the upload_ids.
216             Defaults to C if omitted.
217              
218             =item file_scheme
219              
220             file_scheme => 'md5',
221              
222             C controls how file files are stored on the file system. The default
223             is C, which stores all the files in the same directory with names like
224             C<123.jpg>. Depending on your environment, this may be sufficient to store
225             10,000 or more files.
226              
227             As an alternative, you can specify C, which will create three levels
228             of directories based on the first three letters of the ID's md5 sum. The
229             result may look like this:
230              
231             2/0/2/123.jpg
232              
233             This should scale well to millions of files. If you want even more control,
234             consider overriding the C method, which is used to return the
235             stored file path.
236              
237             Note that specifying the file storage scheme for the file system is not related
238             to the C stored in the database, which is always the original uploaded
239             file name.
240              
241              
242             =back
243              
244             =cut
245              
246             sub new {
247 4     4 1 16176724 my $proto = shift;
248 4   33     131 my $class = ref($proto) || $proto;
249 4         2459 my %in = validate( @_, {
250             updir_url => { type => SCALAR },
251             updir_path => { type => SCALAR },
252             dbh => 1,
253             up_table => {
254             type => SCALAR,
255             default=> 'uploads',
256             },
257             temp_dir => {
258             type => SCALAR,
259             default => File::Spec->tmpdir()
260             },
261             up_table_map => {
262             type => HASHREF,
263             default => {
264             upload_id => 'upload_id',
265             mime_type => 'mime_type',
266             extension => 'extension',
267             width => 'width',
268             height => 'height',
269             gen_from_id => 'gen_from_id',
270             # bytes => 'bytes',
271             }
272             },
273             up_seq => { default => 'upload_id_seq'},
274             spec => { type => HASHREF },
275             query => { optional => 1 } ,
276             file_scheme => {
277             regex => qr/^simple|md5$/,
278             default => 'simple',
279             },
280              
281             });
282 4         336 $in{db_driver} = $in{dbh}->{Driver}->{Name};
283             # Support PostgreSQL via ODBC
284 4 50       147 $in{db_driver} = 'Pg' if $in{dbh}->get_info(17) eq 'PostgreSQL';
285 4 50 33     295 unless (($in{db_driver} eq 'mysql') or ($in{db_driver} eq 'Pg') or ($in{db_driver} eq 'SQLite')) {
      33        
286 0         0 croak "only mysql, Pg and SQLite drivers are supported at this time. You are trying to use $in{db_driver}.";
287             }
288              
289 4 50       28 unless ($in{query}) {
290 0         0 require CGI;
291 0         0 $in{query} = CGI->new;
292             }
293              
294             # Process the spec
295 4         35 for my $k (keys %{ $in{spec} }) {
  4         23  
296             # If the spec is an arrayref, that's a shorthand for specifying some gen_files.
297 4 100       27 if (ref $in{spec}->{$k} eq 'ARRAY') {
298 1         6 $in{spec}->{$k} = {
299             gen_files => $in{spec}->{$k},
300             };
301             }
302             }
303              
304             # Fill in missing map values
305 4         10 for (keys %{ $in{up_table_map} }) {
  4         25  
306 25 100       89 $in{up_table_map}{$_} = $_ unless defined $in{up_table_map}{$_};
307             }
308              
309             # keep pointer to input hash for easier re-use later
310 4         31 $in{input} =\%in;
311              
312 4         160 my $self = \%in;
313 4         30 bless ($self, $class);
314 4         23 return $self;
315             }
316              
317             =head1 Basic Methods
318              
319             These basic methods are all you need to know to make effective use of this
320             module.
321              
322             =head2 store_uploads()
323              
324             my $entity = $u->store_uploads($form_data);
325              
326             Stores uploaded files based on the definition given in C.
327              
328             Specifically, it does the following:
329              
330             =over
331              
332             =item o
333              
334             possibily transforms the original file according to C
335              
336             =item o
337              
338             possibly generates additional files based on those uploaded, according to
339             C.
340              
341             =item o
342              
343             stores all the files on the file system
344              
345             =item o
346              
347             inserts upload details into the database, including upload_id,
348             mime_type and extension. The columns 'width' and 'height' will be
349             populated if that meta data is available.
350              
351             =back
352              
353             As input, a hash reference of form data is expected. The simplest way
354             to get this is like this:
355              
356             use CGI;
357             my $q = new CGI;
358             $form_data = $q->Vars;
359              
360             However, I recommend that you validate your data with a module with
361             L, and use a hash reference
362             of validated data, instead of directly using the CGI form data.
363              
364             CGI::Uploader is designed to handle uploads that are included as a part
365             of an add/edit form for an entity stored in a database. So, C<$form_data>
366             is expected to contain additional fields for this entity as well
367             as the file upload fields.
368              
369             For this reason, the C method returns a hash reference of the
370             valid data with some transformations. File upload fields will be removed from
371             the hash, and corresponding "_id" fields will be added.
372              
373             So for a file upload field named 'img_field', the 'img_field' key
374             will be removed from the hash and 'img_field_id' will be added, with
375             the appropriate upload ID as the value.
376              
377             store_uploads takes an optional second argument as well:
378              
379             my $entity = $u->store_uploads($form_data,$shared_meta);
380              
381             This is a hash refeference of additional meta data that you want to store
382             for all of the images you storing. For example, you may wish to store
383             an "uploaded_user_id".
384              
385             The keys should be column names that exist in your C table. The values
386             should be appropriate data for the column. Only the key names defined by the
387             C in C will be used. Other values in the hash will be
388             ignored.
389              
390             =cut
391              
392             sub store_uploads {
393 2     2 1 3080 validate_pos(@_,1,1,0);
394 2         13 my $self = shift;
395 2         11 my $form_data = shift;
396 2         7 my $shared_meta = shift;
397 2         44 assert($form_data, 'store_uploads: input hashref missing');
398              
399 2         174 my $uploads = $self->{spec};
400              
401 2         6 my %entity_all_extra;
402 2         20 for my $file_field (keys %$uploads) {
403             # If we have an uploaded file for this
404 2         36 my ($tmp_filename,$uploaded_mt,$file_name) = $self->upload($file_field);
405 2 50       11 if ($tmp_filename) {
406 2         38 my $id_to_update = $form_data->{$file_field.'_id'};
407              
408 2         1454 my %entity_upload_extra = $self->store_upload(
409             file_field => $file_field,
410             src_file => $tmp_filename,
411             uploaded_mt => $uploaded_mt,
412             file_name => $file_name,
413             shared_meta => $shared_meta,
414             id_to_update => $id_to_update,
415             );
416              
417 2         204 %entity_all_extra = (%entity_all_extra, %entity_upload_extra);
418             }
419             }
420              
421             # Now add and delete as needed
422 2         119 my $entity = { %$form_data, %entity_all_extra };
423 2         1316 map { delete $entity->{$_} } keys %{ $self->{spec} };
  2         104  
  2         19  
424             # For good measure.
425 2         15 delete $entity->{''};
426              
427 2         39 File::Temp::cleanup();
428              
429 2         756 return $entity;
430             }
431              
432             =head2 delete_checked_uploads()
433              
434             my @fk_col_names = $u->delete_checked_uploads;
435              
436             This method deletes all uploads and any generated files
437             based on form input. Both files and meta data are removed.
438              
439             It looks through all the field names defined in C. For an upload named
440             I, a field named I is checked to see if it has a true
441             value.
442              
443             A list of the field names is returned, prepended with '_id', such as:
444              
445             img_1_id
446              
447             The expectation is that you have foreign keys with these names defined in
448             another table. Having the names is format allows you to easily
449             set these fields to NULL in a database update:
450              
451             map { $entity->{$_} = undef } @fk_names;
452              
453             B This method can not currently be used to delete a generated file by itself.
454              
455             =cut
456              
457             sub delete_checked_uploads {
458 2     2 1 8247 my $self = shift;
459 2         16 my $imgs = $self->{spec};
460              
461 2         8 my $q = $self->{query};
462 2         10 my $map = $self->{up_table_map};
463              
464 2 50       18 croak "missing gen_from_id in up_table_map" unless $map->{gen_from_id};
465              
466              
467 2         8 my @to_delete;
468              
469 2         17 for my $file_field (keys %$imgs) {
470 2 50       14 if ($q->param($file_field.'_delete') ) {
471 2   33     59 my $upload_id = $q->param($file_field.'_id') ||
472             croak "$file_field was selected to delete,
473             but ID was missing in '${file_field}_id' field";
474              
475 2         63 $self->delete_upload($upload_id);
476              
477             # Delete generated files as well.
478 2   50     241362 my $gen_file_ids = $self->{dbh}->selectcol_arrayref(
479             "SELECT $map->{upload_id}
480             FROM $self->{up_table}
481             WHERE $map->{gen_from_id} = ?",{},$upload_id) || [];
482              
483 2         821 for my $gen_file_id (@$gen_file_ids) {
484 2         16 $self->delete_upload($gen_file_id);
485             }
486              
487 2         48712 push @to_delete, map {$_.'_id'} $self->spec_names($file_field) ;
  4         20  
488             }
489              
490             }
491              
492 2         19 return @to_delete;
493             }
494              
495              
496             =head2 fk_meta()
497              
498             my $href = $u->fk_meta(
499             table => $table,
500             where => \%where,
501             prefixes => \@prefixes,
502              
503             Returns a hash reference of information about the file, useful for
504             passing to a templating system. Here's an example of what the contents
505             of C<$href> might look like:
506              
507             {
508             file_1_id => 523,
509             file_1_url => 'http://localhost/images/uploads/523.pdf',
510             }
511              
512             If the files happen to be images and have their width and height
513             defined in the database row, template variables will be made
514             for these as well.
515              
516             This is going to fetch the file information from the upload table for using the row
517             where news.item_id = 23 AND news.file_1_id = uploads.upload_id.
518              
519             This is going to fetch the file information from the upload table for using the row
520             where news.item_id = 23 AND news.file_1_id = uploads.upload_id.
521              
522             The C<%where> hash mentioned here is a L where clause. The
523             complete SQL that used to fetch the data will be built like this:
524              
525             SELECT upload_id as id,width,height,extension
526             FROM uploads, $table
527             WHERE (upload_id = ${prefix}_id AND (%where_clause_expanded here));
528              
529             =cut
530              
531             sub fk_meta {
532 2     2 1 42253 my $self = shift;
533 2         130 my %p = validate(@_,{
534             table => { type => SCALAR },
535             where => { type => HASHREF },
536             prefixes => { type => ARRAYREF },
537             prevent_browser_caching => { default => 1 }
538             });
539              
540              
541 2         20 my $table = $p{table};
542 2         11 my $where = $p{where};
543 2         5 my @file_fields = @{ $p{prefixes} };
  2         8  
544              
545 2         15 my $DBH = $self->{dbh};
546 2         5 my %fields;
547 2         25 require SQL::Abstract;
548 2         30 my $sql = SQL::Abstract->new;
549 2         126 my ($stmt,@bind) = $sql->where($where);
550              
551             # We don't want the 'WHERE' word that SQL::Abstract adds
552 2         452 $stmt =~ s/^\s?WHERE//;
553              
554             # XXX There is probably a more efficient way to get this data than using N selects
555              
556             # mysql uses non-standard quoting
557 2 50       53 my $qt = ($DBH->{Driver}->{Name} eq 'mysql') ? '`' : '"';
558              
559 2         11 my $map = $self->{up_table_map};
560              
561 2         11 for my $field (@file_fields) {
562 4         144 my $upload = $DBH->selectrow_hashref(qq!
563             SELECT *
564             FROM !.$self->{up_table}.qq!, $table AS t
565             WHERE ($self->{up_table}.$map->{upload_id} = t.${qt}${field}_id${qt} and ($stmt) )!,
566             {},@bind);
567              
568 4         1643 my %upload_fields = $self->transform_meta(
569             meta => $upload,
570             prevent_browser_caching => $p{prevent_browser_caching},
571             prefix => $field,
572             );
573 4         32 %fields = (%fields, %upload_fields);
574              
575             }
576              
577 2         14 return \%fields;
578             }
579              
580             =head1 Class Methods
581              
582             These are some handy class methods that you can use without the need to first create
583             an object using C.
584              
585             =head2 upload()
586              
587             # As a class method
588             ($tmp_filename,$uploaded_mt,$file_name) =
589             CGI::Uplooader->upload('file_field',$q);
590              
591             # As an object method
592             ($tmp_filename,$uploaded_mt,$file_name) =
593             $u->upload('file_field');
594              
595             The function is responsible for actually uploading the file.
596              
597             It can be called as a class method or an object method. As a class method, it's
598             necessary to provide a query object as the second argument. As an object
599             method, the query object given the constructor is used.
600              
601             Input:
602             - file field name
603              
604             Output:
605             - temporary file name
606             - Uploaded MIME Type
607             - Name of uploaded file (The value of the file form field)
608              
609             Currently CGI.pm, CGI::Simple and Apache::Request and are supported.
610              
611             =cut
612              
613             sub upload {
614 2     2 1 4 my $self = shift;
615 2         5 my $file_field = shift;
616 2   33     46 my $q = shift || $self->{query};
617              
618 2         3 my $fh;
619 2         18493 my $mt = '';
620 2         23 my $filename = $q->param($file_field);
621              
622 2 50       146 if ($q->isa('CGI::Simple') ) {
    50          
623 0         0 local $CGI::Simple::DISABLE_UPLOADS = 0; # Having uploads enabled is mandatory for this to work.
624 0         0 $fh = $q->upload($filename);
625 0         0 $mt = $q->upload_info($filename, 'mime' );
626              
627 0 0 0     0 if (!$fh && $q->cgi_error) {
628 0   0     0 warn $q->cgi_error && return undef;
629             }
630             }
631             elsif ( $q->isa('Apache::Request') ) {
632 0         0 my $upload = $q->upload($file_field);
633 0         0 $fh = $upload->fh;
634 0         0 $mt = $upload->type;
635             }
636             # default to CGI.pm behavior
637             else {
638 2         7 local $CGI::DISABLE_UPLOADS = 0; # Having uploads enabled is mandatory for this to work.
639 2         88 $fh = $q->upload($file_field);
640 2 50       620 $mt = $q->uploadInfo($fh)->{'Content-Type'} if $q->uploadInfo($fh);
641              
642 2 50 33     405 if (!$fh && $q->cgi_error) {
643 0   0     0 warn $q->cgi_error && return undef;
644             }
645             }
646              
647 2 50 33     484 return undef unless ($fh && $filename);
648              
649 2         235 my ($tmp_fh, $tmp_filename) = tempfile('CGIuploaderXXXXX', UNLINK => 1, DIR => $self->{'temp_dir'} );
650              
651 2         1818 binmode($fh);
652              
653 2         15818 require File::Copy;
654 2         8058 import File::Copy;
655 2 50       8 copy($fh,$tmp_filename) || croak "upload: unable to create tmp file: $!";
656              
657 2         9133 return ($tmp_filename,$mt,$filename);
658             }
659              
660             =head1 Upload Methods
661              
662             These methods are high level methods to manage the file and meta data parts of
663             an upload, as well its generated files. If you are doing something more
664             complex or customized you may want to call or overide one of the below methods.
665              
666             =head2 store_upload()
667              
668             my %entity_upload_extra = $u->store_upload(
669             file_field => $file_field,
670             src_file => $tmp_filename,
671             uploaded_mt => $uploaded_mt,
672             file_name => $file_name,
673             shared_meta => $shared_meta, # optional
674             id_to_update => $id_to_update, # optional
675             );
676              
677             Does all the processing for a single upload, after it has been uploaded
678             to a temp file already.
679              
680             It returns a hash of key/value pairs as described in L.
681              
682             =cut
683              
684             sub store_upload {
685 4     4 1 3703 my $self = shift;
686 4         233 my %p = validate(@_, {
687             file_field => { type => SCALAR },
688             src_file => { type => SCALAR },
689             uploaded_mt => { type => SCALAR },
690             file_name => { type => SCALAR | GLOBREF },
691             shared_meta => { type => HASHREF | UNDEF, default => {} },
692             id_to_update => { type => SCALAR | UNDEF, optional => 1 },
693             });
694              
695             my (
696 4         132 $file_field,
697             $tmp_filename,
698             $uploaded_mt,
699             $file_name,
700             $shared_meta,
701             $id_to_update,
702             ) = ($p{file_field},$p{src_file},$p{uploaded_mt},$p{file_name},$p{shared_meta},$p{id_to_update});
703              
704             # Transform file if needed
705 4 50       41 if (my $meth = $self->{spec}{$file_field}{transform_method}) {
706 0         0 $tmp_filename = $meth->( $self,
707             $tmp_filename,
708             $self->{spec}{$file_field}{params},
709             );
710             }
711              
712             # XXX The uploaded mime type may have nothing to do with
713             # the current mime-type after it's transformed
714 4         118 my $meta = $self->extract_meta($tmp_filename,$file_name,$uploaded_mt);
715              
716 4   100     66 $shared_meta ||= {};
717 4         59 my $all_meta = { %$meta, %$shared_meta };
718              
719 4         18 my $id;
720             # If it's an update
721 4 100       57 if ($id = $id_to_update) {
722             # delete old generated files before we create new ones
723 1         21 $self->delete_gen_files($id);
724              
725             # It's necessary to delete the old file when updating, because
726             # the new one may have a new extension.
727 1         19879 $self->delete_file($id);
728             }
729              
730             # insert or update will be performed as appropriate.
731 4         66 $id = $self->store_meta(
732             $file_field,
733             $all_meta,
734             $id );
735              
736 4         319 $self->store_file($file_field,$id,$meta->{extension},$tmp_filename);
737              
738 4         2393 my %ids = ();
739 4         131 %ids = $self->create_store_gen_files(
740             file_field => $file_field,
741             meta => $all_meta,
742             src_file => $tmp_filename,
743             gen_from_id => $id,
744             );
745              
746 4         289 return (%ids, $file_field.'_id' => $id);
747              
748             }
749              
750             =head2 create_store_gen_files()
751              
752             my %gen_file_ids = $u->create_store_gen_files(
753             file_field => $file_field,
754             meta => $meta_href,
755             src_file => $tmp_filename,
756             gen_from_id => $gen_from_id,
757             );
758              
759             This method is responsible for creating and storing
760             any needed thumbnails.
761              
762             Input:
763             - file_field: file field name
764             - meta: a hash ref of meta data, as C would produce
765             - src_file: path to temporary file of the file upload
766             - gen_from_id: ID of upload that generated files will be made from
767              
768             =cut
769              
770             sub create_store_gen_files {
771 4     4 1 11 my $self = shift;
772 4         352 my %p = validate(@_, {
773             file_field => { type => SCALAR },
774             src_file => { type => SCALAR },
775             meta => { type => HASHREF | UNDEF, default => {} },
776             gen_from_id => { regex => qr/^\d*$/, },
777             });
778 4         224 my ($file_field,
779             $meta,
780             $tmp_filename,
781             $gen_from_id) = ($p{file_field},$p{meta},$p{src_file},$p{gen_from_id});
782              
783 4   50     39 my $gen_fields_key = $self->{spec}{$file_field}{gen_files} || return undef;
784 4         11 my @gen_files = keys %{ $gen_fields_key };
  4         23  
785              
786 4         16 my $gen_files = $self->{spec}{$file_field}{gen_files};
787 4         12 my $q = $self->{query};
788 4         11 my %out;
789              
790 4         18 my ($w,$h) = ($meta->{width},$meta->{height});
791 4         20 for my $gen_file (@gen_files) {
792 4         10 my $gen_tmp_filename;
793              
794             # tranform as needed
795 4         20 my $gen_file_key = $self->{spec}{$file_field}{gen_files}{$gen_file};
796             # Recommended code ref API
797 4 100       157 if (ref $gen_file_key eq 'CODE') {
    50          
798             # It needed any params, they should already have been provided via closure.
799 2         188 $gen_tmp_filename = $gen_file_key->($self,$tmp_filename);
800             }
801             # Old, yucky hashref API
802             elsif (ref $gen_file_key eq 'HASH') {
803 2         9 my $meth = $gen_file_key->{transform_method};
804 2         3672 $gen_tmp_filename = $meth->(
805             $self,
806             $tmp_filename,
807             $gen_file_key->{params},
808             );
809             }
810             else {
811 0         0 croak "$gen_file for $file_field was not a hashref or code ref. Check spec syntax.";
812             }
813              
814             # inherit mime-type and extension from parent
815             # but merge in updated details for this file
816 4         1674 my $meta_from_gen_file = $self->extract_meta($gen_tmp_filename,$gen_file);
817 4   50     93 $meta_from_gen_file ||= {};
818 4         144 my %t_info = (%$meta, gen_from_id => $gen_from_id, %$meta_from_gen_file);
819              
820              
821              
822             # Try to get image dimensions (will fail safely for non-images)
823             #($t_info{width}, $t_info{height}) = imgsize($gen_tmp_filename);
824              
825             # Insert
826 4         80 my $id = $self->store_meta($gen_file, \%t_info );
827              
828             # Add to output hash
829 4         147 $out{$gen_file.'_id'} = $id;
830              
831 4         233 $self->store_file($gen_file,$id,$t_info{extension},$gen_tmp_filename);
832             }
833 4         2542 return %out;
834             }
835              
836             =head2 delete_upload()
837              
838             $u->delete_upload($upload_id);
839              
840             This method is used to delete the meta data and file associated with an upload.
841             Usually it's more convenient to use C than to call this
842             method directly.
843              
844             This method does not delete generated files for this upload.
845              
846             =cut
847              
848             sub delete_upload {
849 4     4 1 12 my $self = shift;
850 4         10 my ($id) = @_;
851              
852 4         34 $self->delete_file($id);
853 4         26 $self->delete_meta($id);
854              
855             }
856              
857             =head2 delete_gen_files()
858              
859             $self->delete_gen_files($id);
860              
861             Delete the generated files for a given file ID, from the file system and the database
862              
863             =cut
864              
865             sub delete_gen_files {
866 1     1 1 28 validate_pos(@_,1,1);
867 1         4 my ($self,$id) = @_;
868              
869 1         8 my $dbh = $self->{dbh};
870 1         8 my $map = $self->{up_table_map};
871              
872 1   50     82 my $gen_file_ids_aref = $dbh->selectcol_arrayref(
873             "SELECT $map->{upload_id}
874             FROM ".$self->{up_table}. "
875             WHERE $map->{gen_from_id} = ?",{},$id) || [];
876              
877 1         481 for my $gen_file_id (@$gen_file_ids_aref) {
878 1         18 $self->delete_file($gen_file_id);
879 1         22 $self->delete_meta($gen_file_id);
880             }
881              
882             }
883              
884             =head1 Meta-data Methods
885              
886             =head2 extract_meta()
887              
888             $meta = $self->extract_meta($tmp_filename,$file_name,$uploaded_mt);
889              
890             This method extracts and returns the meta data about a file and returns it.
891              
892             Input:
893              
894             - Path to file to extract meta data from
895             - the name of the file (as sent through the file upload file)
896             - The mime-type of the file, as supplied by the browser
897              
898             Returns: a hash reference of meta data, following this example:
899              
900             {
901             mime_type => 'image/gif',
902             extension => '.gif',
903             bytes => 60234,
904             file_name => 'happy.txt',
905              
906             # only for images
907             width => 50,
908             height => 50,
909             }
910              
911             =cut
912              
913             sub extract_meta {
914 8     8 1 196 validate_pos(@_,1,1,1,0);
915 8         23 my $self = shift;
916 8         20 my $tmp_filename = shift;
917 8         15 my $file_name = shift;
918 8   100     78 my $uploaded_mt = shift || '';
919              
920             # Determine and set the appropriate file system parsing routines for the
921             # uploaded path name based upon the HTTP client header information.
922 9     9   12114 use HTTP::BrowserDetect;
  9         126106  
  9         8359  
923 8         48 my $client_os = $^O;
924 8         182 my $browser = HTTP::BrowserDetect->new;
925 8 50       512 $client_os = 'MSWin32' if $browser->windows;
926 8 50       250 $client_os = 'MacOS' if $browser->mac;
927 8 50       191 $client_os = 'Unix' if $browser->macosx;
928 8         242 require File::Basename;
929 8         1435 File::Basename::fileparse_set_fstype($client_os);
930 8         701 $file_name = File::Basename::fileparse($file_name,[]);
931              
932              
933 8         4605 require File::MMagic;
934 8         55045 my $mm = File::MMagic->new;
935              
936             # If the uploaded mime_type was not provided calculate one from the file magic number
937             # if that does not exist, fall back on the file name
938 8         5406 my $fm_mt = $mm->checktype_magic($tmp_filename);
939 8 50 33     219484 $fm_mt = $mm->checktype_filename($tmp_filename) if (not defined $fm_mt or not length $fm_mt) ;
940              
941 8   66     67698 my $mt = ($uploaded_mt || $fm_mt);
942 8         182 assert($mt,'found mime type');
943              
944              
945 9     9   11749 use MIME::Types;
  9         74031  
  9         18070  
946 8         472 my $mimetypes = MIME::Types->new;
947 8         228218 my MIME::Type $t = $mimetypes->type($mt);
948 8 100       966 my @mt_exts = $t ? $t->extensions : undef;
949              
950 8         137 my $ext;
951              
952             # figure out an extension
953 8         85 my ($uploaded_ext) = ($file_name =~ m/\.([\w\d]*)?$/);
954              
955             # If there is at least one MIME-type found
956 8 100       22 if ($mt_exts[0]) {
957             # If the upload extension is one recognized by MIME::Type, use it.
958 7 100 66     55 if ((defined $uploaded_ext)
  25         150  
959             and (grep {/^$uploaded_ext$/} @mt_exts)) {
960 3         8 $ext = $uploaded_ext;
961             }
962             # otherwise, use one from MIME::Type, just to be safe
963             else {
964 4         14 $ext = $mt_exts[0];
965             }
966             }
967             else {
968             # If is a provided extension but no MIME::Type extension, use that.
969             # It's possible that there no extension uploaded or found)
970 1         5 $ext = $uploaded_ext;
971             }
972              
973 8 50       26 if ($ext) {
974 8 50       155 $ext = ".$ext" if $ext;
975             }
976             else {
977 0         0 croak "no extension found for file name: $file_name";
978             }
979              
980              
981             # Now get the image dimensions if it's an image
982 8         102 my ($width,$height) = imgsize($tmp_filename);
983              
984             return {
985 8         83011 file_name => $file_name,
986             mime_type => $mt,
987             extension => $ext,
988             bytes => (stat ($tmp_filename))[7],
989              
990             # only for images
991             width => $width,
992             height => $height,
993             };
994              
995              
996             }
997              
998             =head2 store_meta()
999              
1000             my $id = $self->store_meta($file_field,$meta);
1001              
1002             This function is used to store the meta data of a file upload.
1003              
1004             Input:
1005              
1006             - file field name
1007              
1008             - A hashref of key/value pairs to be stored. Only the key names defined by the
1009             C in C will be used. Other values in the hash will be
1010             ignored.
1011              
1012             - Optionally, an upload ID can be passed, causing an 'Update' to happen instead of an 'Insert'
1013              
1014             Output:
1015             - The id of the file stored. The id is generated by store_meta().
1016              
1017             =cut
1018              
1019             sub store_meta {
1020 8     8 1 185 validate_pos(@_,1,1,1,0);
1021 8         40 my $self = shift;
1022              
1023             # Right now we don't use the the file field name
1024             # It seems like a good idea to have in case you want to sub-class it, though.
1025 8         64 my $file_field = shift;
1026 8         24 my $href = shift;
1027 8         19 my $id = shift;
1028              
1029 8         46 my $DBH = $self->{dbh};
1030              
1031 8         5021 require SQL::Abstract;
1032 8         63646 my $sql = SQL::Abstract->new;
1033 8         1058 my $map = $self->{up_table_map};
1034 8         107 my %copy = %$href;
1035              
1036 8 100       53 my $is_update = 1 if $id;
1037              
1038 8 50 66     123 if (!$is_update && $self->{db_driver} eq 'Pg') {
1039 0         0 $id = $DBH->selectrow_array("SELECT NEXTVAL('".$self->{up_seq}."')");
1040 0         0 $copy{$map->{upload_id} } = $id;
1041             }
1042              
1043 8         56 my @orig_keys = keys %copy;
1044 8         46 for (@orig_keys) {
1045 54 100       143 if (exists $map->{$_}) {
1046             # We're done if the names are the same
1047 37 100       127 next if ($_ eq $map->{$_});
1048              
1049             # Replace each key name with the mapped name
1050 9         34 $copy{ $map->{$_} } = $copy{$_};
1051              
1052             }
1053             # The original field is now duplicated in the hash or unknown.
1054             # delete in either case.
1055 26         63 delete $copy{$_};
1056             }
1057              
1058 8         45 my ($stmt,@bind);
1059 8 100       47 if ($is_update) {
1060 1         17 ($stmt,@bind) = $sql->update($self->{up_table},\%copy, { $map->{upload_id} => $id });
1061             }
1062             else {
1063 7         85 ($stmt,@bind) = $sql->insert($self->{up_table},\%copy);
1064             }
1065              
1066 8         5124 $DBH->do($stmt,{},@bind);
1067 8 50 66     249761 if (!$is_update && $self->{db_driver} eq 'mysql') {
1068 0         0 $id = $DBH->{'mysql_insertid'};
1069             }
1070 8 100 66     303 if (!$is_update && $self->{db_driver} eq 'SQLite') {
1071 7         227 $id = $DBH->func('last_insert_rowid')
1072             }
1073              
1074 8         479 return $id;
1075             }
1076              
1077             =head2 delete_meta()
1078              
1079             my $dbi_rv = $self->delete_meta($id);
1080              
1081             Deletes the meta data for a file and returns the DBI return value for this operation.
1082              
1083             =cut
1084              
1085             sub delete_meta {
1086 5     5 1 48 validate_pos(@_,1,1);
1087 5         20 my $self = shift;
1088 5         11 my $id = shift;
1089              
1090 5         14 my $DBH = $self->{dbh};
1091 5         15 my $map = $self->{up_table_map};
1092              
1093 5         75 return $DBH->do("DELETE from ".$self->{up_table}." WHERE $map->{upload_id} = $id");
1094              
1095             }
1096              
1097             =head2 transform_meta()
1098              
1099             my %meta_to_display = $u->transform_meta(
1100             meta => $meta_from_db,
1101             prefix => 'my_field',
1102             prevent_browser_caching => 0,
1103             fields => [qw/id url width height/],
1104             );
1105              
1106             Prepares meta data from the database for display.
1107              
1108              
1109             Input:
1110             - meta: A hashref, as might be returned from "SELECT * FROM uploads WHERE upload_id = ?"
1111              
1112             - prefix: the resulting hashref keys will be prefixed with this,
1113             adding an underscore as well.
1114              
1115             - prevent_browse_caching: If set to true, a random query string
1116             will be added, preventing browsings from caching the image. This is very
1117             useful when displaying an image an 'update' page. Defaults to true.
1118              
1119             - fields: An arrayef of fields to format. The values here must be
1120             keys in the C. Two field names are special. 'C is
1121             used to denote the upload_id. C combines several fields into
1122             a URL to link to the upload.
1123              
1124             Output:
1125             - A formatted hash.
1126              
1127             See L for example output.
1128              
1129             =cut
1130              
1131             sub transform_meta {
1132 5     5 1 3076 my $self = shift;
1133 5         264 my %p = validate(@_, {
1134             meta => { type => HASHREF },
1135             prefix => { type => SCALAR },
1136             prevent_browser_caching => { default => 1 },
1137             fields => { type => ARRAYREF ,
1138             default => [qw/id url width height/],
1139             },
1140             });
1141             # return undef unless (ref $p{meta} eq 'HASH');
1142              
1143 5         50 my $map = $self->{up_table_map};
1144              
1145 5         14 my %result;
1146              
1147             my $qs;
1148 5 50       42 if ($p{prevent_browser_caching}) {
1149             # a random number to defeat image caching. We may want to change this later.
1150 5         71 my $rand = (int rand 100);
1151 5         54 $qs = "?$rand";
1152             }
1153              
1154 5         10 my %fields = map { $_ => 1 } @{ $p{fields} };
  20         118  
  5         16  
1155              
1156 5 50       26 if ($fields{url}) {
1157 5         41 $result{$p{prefix}.'_url'} = $self->{updir_url}.'/'.
1158             $self->build_loc(
1159             $p{meta}{ $map->{upload_id} }
1160             ,$p{meta}{ $map->{extension} })
1161             .$qs ;
1162 5         19 delete $fields{url};
1163             }
1164              
1165 5 50       27 if (exists $fields{id}) {
1166 5         27 $result{$p{prefix}.'_id'} = $p{meta}->{ $map->{upload_id} };
1167 5         16 delete $fields{id};
1168             }
1169              
1170 5         27 for my $k (keys %fields) {
1171 10         33 my $v = $p{meta}->{ $map->{$k} };
1172 10 50       41 $result{$p{prefix}.'_'.$k} = $v if defined $v;
1173             }
1174              
1175 5         47 return %result;
1176              
1177              
1178             }
1179              
1180             =head2 get_meta()
1181              
1182             my $meta_href = $self->get_meta($id);
1183              
1184             Returns a hashref of data stored in the uploads database table for the requested file id.
1185              
1186             =cut
1187              
1188             sub get_meta {
1189 0     0 1 0 validate_pos(@_,1,1);
1190 0         0 my ($self,$id) = @_;
1191              
1192 0         0 my $map = $self->{up_table_map};
1193 0         0 return $self->{dbh}->selectrow_hashref("
1194             SELECT * FROM $self->{up_table}
1195             WHERE $map->{upload_id} = ?",{},$id);
1196             }
1197              
1198              
1199              
1200             =head1 File Methods
1201              
1202             =head2 store_file()
1203              
1204             $self->store_file($file_field,$tmp_file,$id,$ext);
1205              
1206             Stores an upload file or dies if there is an error.
1207              
1208             Input:
1209             - file field name
1210             - path to tmp file for uploaded image
1211             - file id, as generated by C
1212             - file extension, as discovered by L
1213              
1214             Output: none
1215              
1216             =cut
1217              
1218             sub store_file {
1219 8     8 1 174 validate_pos(@_,1,1,1,1,1);
1220 8         32 my $self = shift;
1221 8         39 my ($file_field,$id,$ext,$tmp_file) = @_;
1222 8         136 assert($ext, 'have extension');
1223 8         62 assert($id,'have id');
1224 8         560 assert(-f $tmp_file,'tmp file exists');
1225 8         226 assert(-d $self->{updir_path},'updir_path is a directory');
1226 8         144 assert(-w $self->{updir_path},'updir_path is writeable');
1227              
1228 8         1375 require File::Copy;
1229 8         5010 import File::Copy;
1230 8 50       122 copy($tmp_file, File::Spec->catdir($self->{updir_path},$self->build_loc($id,$ext)) )
1231             || croak "Unexpected error occured when uploading the image: $!";
1232              
1233             }
1234              
1235             =head2 delete_file()
1236              
1237             $self->delete_file($id);
1238              
1239             Call from within C, this routine deletes the actual file.
1240             Dont' delete the the meta data first, you may need it build the path name
1241             of the file to delete.
1242              
1243             =cut
1244              
1245             sub delete_file {
1246 6     6 1 84 validate_pos(@_,1,1);
1247 6         20 my $self = shift;
1248 6         18 my $id = shift;
1249              
1250 6         22 my $map = $self->{up_table_map};
1251 6         14 my $dbh = $self->{dbh};
1252              
1253 6         171 my $ext = $dbh->selectrow_array("
1254             SELECT $map->{extension}
1255             FROM $self->{up_table}
1256             WHERE $map->{upload_id} = ?",{},$id);
1257 6 50       2037 $ext || croak "found no extension in meta data for ID $id. Deleting file failed.";
1258              
1259              
1260 6         90 my $file = $self->{updir_path}.'/'.$self->build_loc($id,$ext);
1261              
1262 6 50       239 if (-e $file) {
1263 6   33     858 unlink $file || croak "couldn't delete upload file: $file: $!";
1264             }
1265             else {
1266 0         0 warn "file to delete not found: $file";
1267             }
1268              
1269             }
1270              
1271             =head1 Utility Methods
1272              
1273             =head2 build_loc()
1274              
1275             my $up_loc = $self->build_loc($id,$ext);
1276              
1277             Builds a path to access a single upload, relative to C.
1278             This is used to both file-system and URL access. Also see the C
1279             option to C, which affects it's behavior.
1280              
1281             =cut
1282              
1283             sub build_loc {
1284 20     20 1 840 validate_pos(@_,1,1,0);
1285 20         98 my ($self,$id,$ext) = @_;
1286              
1287 20         65 my $scheme = $self->{file_scheme};
1288              
1289 20         61 my $loc;
1290 20 100       898 if ($scheme eq 'simple') {
    50          
1291 19         476 $loc = "$id$ext";
1292             }
1293             elsif ($scheme eq 'md5') {
1294 1         11 require Digest::MD5;
1295 1         48 import Digest::MD5 qw/md5_hex/;
1296 1         7 my $md5_path = md5_hex($id);
1297 1         10 $md5_path =~ s|^(.)(.)(.).*|$1/$2/$3|;
1298              
1299 1         7 my $full_path = $self->{updir_path}.'/'.$md5_path;
1300 1 50       38 unless (-e $full_path) {
1301 1         470 mkpath($full_path);
1302             }
1303              
1304              
1305 1         21 $loc = File::Spec->catdir($md5_path,"$id$ext");
1306             }
1307             }
1308              
1309             =head2 upload_field_names()
1310              
1311             # As a class method
1312             (@file_field_names) = CGI::Uploader->upload_field_names($q);
1313              
1314             # As an object method
1315             (@file_field_names) = $u->upload_field_names();
1316              
1317             Returns the names of all form fields which contain file uploads. Empty
1318             file upload fields may be excluded.
1319              
1320             This can be useful for auto-generating a C.
1321              
1322             Input:
1323             - A query object is required as input only when called as a class method.
1324              
1325             Output:
1326             - an array of the file upload field names.
1327              
1328             =cut
1329              
1330             sub upload_field_names {
1331 0     0 1 0 my $self = shift;
1332 0   0     0 my $q = shift || $self->{query};
1333              
1334 0         0 my @file_field_names;
1335 0 0       0 if ( $q->isa('CGI::Simple') ) {
    0          
1336 0         0 my @list_of_files = $q->upload;
1337 0         0 my @all_field_names = $q->param();
1338 0         0 for my $field (@all_field_names) {
1339 0         0 my $potential_file_name = $q->param($field);
1340 0 0       0 push @file_field_names, $field , if grep {m/^$potential_file_name/} @list_of_files;
  0         0  
1341             }
1342             }
1343             elsif ($q->isa('Apache::Request') ) {
1344 0         0 @file_field_names = map { $_->name } @{ $q->upload() };
  0         0  
  0         0  
1345             }
1346             # default to CGI.pm behavior
1347             else {
1348 0         0 my @all_field_names = $q->param();
1349 0         0 for my $field (@all_field_names) {
1350 0 0       0 push @file_field_names, $field , if $q->upload($field);
1351             }
1352             }
1353              
1354 0         0 return @file_field_names;
1355              
1356             }
1357              
1358              
1359              
1360              
1361              
1362              
1363             =head2 spec_names()
1364              
1365             $spec_names = $u->spec_names('file_field'):
1366              
1367             With no arguments, returns an array of all the upload names defined in the
1368             spec, including any generated file names.
1369              
1370             With one argument, a file field from the spec, can also be provided. It then returns
1371             that name as well as the names of any related generated files.
1372              
1373             =cut
1374              
1375             sub spec_names {
1376 3     3 1 1057 my $self = shift;
1377 3         11 my $spec_key = shift;
1378              
1379 3         18 my $all_keys = $self->{spec};
1380              
1381             # only use $spec_key if it was passed in
1382 3 100       23 my @primary_spec_keys_to_use = (defined $spec_key) ? $spec_key : keys %$all_keys;
1383              
1384 3         36 my @gen_files = @primary_spec_keys_to_use,
1385 3         16 map { keys %{ $all_keys->{$_}{gen_files} } } @primary_spec_keys_to_use;
  3         5  
1386             }
1387              
1388             1;
1389             __END__