Form::read_logo gibt es in dieser Version nicht
[kivitendo-erp.git] / SL / Controller / File.pm
1 package SL::Controller::File;
2
3 use strict;
4
5 use parent qw(SL::Controller::Base);
6
7 use List::Util qw(first max);
8
9 use utf8;
10 use Encode qw(decode);
11 use URI::Escape;
12 use Cwd;
13 use DateTime;
14 use File::stat;
15 use File::Spec::Unix;
16 use File::Spec::Win32;
17 use File::MimeInfo::Magic;
18 use SL::DB::Helper::Mappings;
19 use SL::DB::Order;
20 use SL::DB::DeliveryOrder;
21 use SL::DB::Invoice;
22
23 use SL::DB::PurchaseInvoice;
24 use SL::DB::Part;
25 use SL::DB::GLTransaction;
26 use SL::DB::Draft;
27 use SL::DB::History;
28 use SL::JSON;
29 use SL::Helper::CreatePDF qw(:all);
30 use SL::Locale::String;
31 use SL::SessionFile;
32 use SL::File;
33 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_image_type);
34
35 use constant DO_DELETE   => 0;
36 use constant DO_UNIMPORT => 1;
37
38 use Rose::Object::MakeMethods::Generic
39 (
40     'scalar --get_set_init' => [ qw() ],
41     'scalar' => [ qw(object object_type object_model object_id object_right file_type files is_global existing) ],
42 );
43
44 __PACKAGE__->run_before('check_object_params', only => [ qw(list ajax_delete ajax_importdialog ajax_import ajax_unimport ajax_upload ajax_files_uploaded) ]);
45
46 my %file_types = (
47   'sales_quotation'         => { gen => 1, gltype => '',   dir =>'SalesQuotation',       model => 'Order',          right => 'import_ar'  },
48   'sales_order'             => { gen => 1, gltype => '',   dir =>'SalesOrder',           model => 'Order',          right => 'import_ar'  },
49   'sales_delivery_order'    => { gen => 1, gltype => '',   dir =>'SalesDeliveryOrder',   model => 'DeliveryOrder',  right => 'import_ar'  },
50   'invoice'                 => { gen => 1, gltype => 'ar', dir =>'SalesInvoice',         model => 'Invoice',        right => 'import_ar'  },
51   'credit_note'             => { gen => 1, gltype => '',   dir =>'CreditNote',           model => 'Invoice',        right => 'import_ar'  },
52   'request_quotation'       => { gen => 3, gltype => '',   dir =>'RequestForQuotation',  model => 'Order',          right => 'import_ap'  },
53   'purchase_order'          => { gen => 3, gltype => '',   dir =>'PurchaseOrder',        model => 'Order',          right => 'import_ap'  },
54   'purchase_delivery_order' => { gen => 3, gltype => '',   dir =>'PurchaseDeliveryOrder',model => 'DeliveryOrder',  right => 'import_ap'  },
55   'purchase_invoice'        => { gen => 2, gltype => 'ap', dir =>'PurchaseInvoice',      model => 'PurchaseInvoice',right => 'import_ap'  },
56   'vendor'                  => { gen => 0, gltype => '',   dir =>'Vendor',               model => 'Vendor',         right => 'xx'         },
57   'customer'                => { gen => 1, gltype => '',   dir =>'Customer',             model => 'Customer',       right => 'xx'         },
58   'part'                    => { gen => 0, gltype => '',   dir =>'Part',                 model => 'Part',           right => 'xx'         },
59   'gl_transaction'          => { gen => 2, gltype => 'gl', dir =>'GeneralLedger',        model => 'GLTransaction',  right => 'import_ap'  },
60   'draft'                   => { gen => 0, gltype => '',   dir =>'Draft',                model => 'Draft',          right => 'xx'         },
61   'csv_customer'            => { gen => 1, gltype => '',   dir =>'Reports',              model => 'Customer',       right => 'xx'         },
62   'csv_vendor'              => { gen => 1, gltype => '',   dir =>'Reports',              model => 'Vendor',         right => 'xx'         },
63   'shop_image'              => { gen => 0, gltype => '',   dir =>'ShopImages',           model => 'Part',           right => 'xx'         },
64 );
65
66 #--- 4 locale ---#
67 # $main::locale->text('imported')
68
69 #
70 # actions
71 #
72
73 sub action_list {
74   my ($self) = @_;
75
76   my $is_json = 0;
77   $is_json = 1 if $::form->{json};
78
79   $self->_do_list($is_json);
80 }
81
82 sub action_ajax_importdialog {
83   my ($self) = @_;
84   $::auth->assert($self->object_right);
85   my $path   = $::form->{path};
86   my @files  = $self->_get_from_import($path);
87   my $source = {
88     'name'         => $::form->{source},
89     'path'         => $path ,
90     'chk_action'   => $::form->{source}.'_import',
91     'chk_title'    => $main::locale->text('Import scanned documents'),
92     'chkall_title' => $main::locale->text('Import all'),
93     'files'        => \@files
94   };
95   $self->render('file/import_dialog',
96                 { layout => 0
97                 },
98                 source => $source
99   );
100 }
101
102 sub action_ajax_import {
103   my ($self) = @_;
104   $::auth->assert($self->object_right);
105   my $ids    = $::form->{ids};
106   my $source = $::form->{source};
107   my $path   = $::form->{path};
108   my @files  = $self->_get_from_import($path);
109   foreach my $filename (@{ $::form->{$ids} || [] }) {
110     my ($file, undef) = grep { $_->{name} eq $filename } @files;
111     if ( $file ) {
112       my $obj = SL::File->save(object_id   => $self->object_id,
113                                object_type => $self->object_type,
114                                mime_type   => 'application/pdf',
115                                source      => $source,
116                                file_type   => 'document',
117                                file_name   => $file->{filename},
118                                file_path   => $file->{path}
119                              );
120       unlink($file->{path}) if $obj;
121     }
122   }
123   $self->_do_list(1);
124 }
125
126 sub action_ajax_delete {
127   my ($self) = @_;
128   $self->_delete_all(DO_DELETE, $::locale->text('Following files are deleted:'));
129 }
130
131 sub action_ajax_unimport {
132   my ($self) = @_;
133   $self->_delete_all(DO_UNIMPORT, $::locale->text('Following files are unimported:'));
134 }
135
136 sub action_ajax_rename {
137   my ($self) = @_;
138   my $file = SL::File->get(id => $::form->{id});
139   if ( ! $file ) {
140     $self->js->flash('error', $::locale->text('File not exists !'))->render();
141     return;
142   }
143   my $sessionfile = $::form->{sessionfile};
144   if ( $sessionfile && -f $sessionfile ) {
145     # new uploaded file
146     if ( $::form->{to} eq $file->file_name ) {
147       # no rename so use as new version
148       $file->save_file($sessionfile);
149       $self->js->flash('warning', $::locale->text('File \'#1\' is used as new Version !', $file->file_name));
150
151     } else {
152       # new filename, so it is a new file with the same attributes as the old file
153       eval {
154         SL::File->save(object_id   => $file->object_id,
155                        object_type => $file->object_type,
156                        mime_type   => $file->mime_type,
157                        source      => $file->source,
158                        file_type   => $file->file_type,
159                        file_name   => $::form->{to},
160                        file_path   => $sessionfile
161                      );
162         unlink($sessionfile);
163         1;
164       } or do {
165         $self->js->flash(       'error', t8('internal error (see details)'))
166                  ->flash_detail('error', $@)->render;
167         return;
168       }
169     }
170
171   } else {
172     # normal rename
173     my $result;
174
175     eval {
176       $result = $file->rename($::form->{to});
177       1;
178     } or do {
179       $self->js->flash(       'error', t8('internal error (see details)'))
180                ->flash_detail('error', $@)->render;
181       return;
182     };
183
184     if ($result != SL::File::RENAME_OK) {
185       $self->js->flash('error',
186                          $result == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
187                        : $result == SL::File::RENAME_SAME   ? $::locale->text('Same Filename !')
188                        :                                      $::locale->text('File not exists !'))
189         ->render;
190       return;
191     }
192   }
193   $self->is_global($::form->{is_global});
194   $self->file_type(  $file->file_type);
195   $self->object_type($file->object_type);
196   $self->object_id(  $file->object_id);
197   #$self->object_model($file_types{$file->module}->{model});
198   #$self->object_right($file_types{$file->module}->{right});
199   if ( $::form->{next_ids} ) {
200     my @existing = split(/,/, $::form->{next_ids});
201     $self->existing(\@existing);
202   }
203   $self->_do_list(1);
204 }
205
206 sub action_ajax_upload {
207   my ($self) = @_;
208   $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
209   $self->{accept_types} = '';
210   $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
211   $self->render('file/upload_dialog',
212                 { layout => 0
213                 },
214   );
215 }
216
217 sub action_ajax_files_uploaded {
218   my ($self) = @_;
219
220   my $source = 'uploaded';
221   my @existing;
222   if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
223     my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
224     foreach my $idx (0 .. scalar(@upfiles) - 1) {
225       eval {
226         my $fname = uri_unescape($upfiles[$idx]->{filename});
227         # normalize and find basename
228         # first split with unix rules
229         # after that split with windows rules
230         my ($volume, $directories, $basefile) = File::Spec::Unix->splitpath($fname);
231         ($volume, $directories, $basefile) = File::Spec::Win32->splitpath($basefile);
232
233         # to find real mime_type by magic we must save the filedata
234
235         my $sess_fname = "file_upload_" . $self->object_type . "_" . $self->object_id . "_" . $idx;
236         my $sfile      = SL::SessionFile->new($sess_fname, mode => 'w');
237
238         $sfile->fh->print(${$upfiles[$idx]->{data}});
239         $sfile->fh->close;
240         my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
241
242         if (! $mime_type) {
243           # if filename has the suffix "pdf", but isn't really a pdf, set mimetype for no suffix
244           $mime_type = File::MimeInfo::Magic::mimetype($basefile);
245           $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
246         }
247         if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
248           next;
249         }
250         my ($existobj) = SL::File->get_all(object_id   => $self->object_id,
251                                            object_type => $self->object_type,
252                                            mime_type   => $mime_type,
253                                            source      => $source,
254                                            file_type   => $self->file_type,
255                                            file_name   => $basefile,
256                                       );
257
258         if ($existobj) {
259           push @existing, $existobj->id.'_'.$sfile->file_name;
260         } else {
261           my $fileobj = SL::File->save(object_id        => $self->object_id,
262                                        object_type      => $self->object_type,
263                                        mime_type        => $mime_type,
264                                        source           => $source,
265                                        file_type        => $self->file_type,
266                                        file_name        => $basefile,
267                                        title            => $::form->{title},
268                                        description      => $::form->{description},
269                                        ## two possibilities: what is better ? content or sessionfile ??
270                                        file_contents    => ${$upfiles[$idx]->{data}},
271                                        file_path        => $sfile->file_name
272                                      );
273           unlink($sfile->file_name);
274         }
275         1;
276       } or do {
277         $self->js->flash(       'error', t8('internal error (see details)'))
278                  ->flash_detail('error', $@)->render;
279         return;
280       }
281     }
282   }
283   $self->existing(\@existing);
284   $self->_do_list(1);
285 }
286
287 sub action_download {
288   my ($self) = @_;
289   my ($id, $version) = split /_/, $::form->{id};
290   my $file = SL::File->get(id => $id );
291   $file->version($version) if $version;
292   my $ref  = $file->get_content;
293   if ( $file && $ref ) {
294     return $self->send_file($ref,
295       type => $file->mime_type,
296       name => $file->file_name,
297     );
298   }
299 }
300
301 #
302 # filters
303 #
304
305 sub check_object_params {
306   my ($self) = @_;
307
308   my $id      = ($::form->{object_id} // 0) * 1;
309   my $draftid = ($::form->{draft_id}  // 0) * 1;
310   my $gldoc   = 0;
311   my $type    = undef;
312
313   if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
314     $gldoc = 1;
315     $type  = $::form->{object_type};
316   }
317   elsif ( $id == 0 ) {
318     $id   = $::form->{draft_id};
319     $type = 'draft';
320   } elsif ( $::form->{object_type} ) {
321     $type = $::form->{object_type};
322   }
323   die "No object type"     unless $type;
324   die "No file type"       unless $::form->{file_type};
325   die "Unkown object type" unless $file_types{$type};
326
327   $self->is_global($gldoc);
328   $self->file_type($::form->{file_type});
329   $self->object_type($type);
330   $self->object_id($id);
331   $self->object_model($file_types{$type}->{model});
332   $self->object_right($file_types{$type}->{right});
333
334  # $::auth->assert($self->object_right);
335
336  # my $model = 'SL::DB::' . $self->object_model;
337  # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
338
339   return 1;
340 }
341
342 #
343 # private methods
344 #
345
346 sub _delete_all {
347   my ($self, $do_unimport, $infotext) = @_;
348   my $files = '';
349   my $ids = $::form->{ids};
350   foreach my $id_version (@{ $::form->{$ids} || [] }) {
351     my ($id, $version) = split /_/, $id_version;
352     my $dbfile = SL::File->get(id => $id);
353     if ( $dbfile ) {
354       if ( $version ) {
355         $dbfile->version($version);
356         $files .= ' ' . $dbfile->file_name if $dbfile->delete_version;
357       } else {
358         $files .= ' ' . $dbfile->file_name if $dbfile->delete;
359       }
360     }
361   }
362   $self->js->flash('info', $infotext . $files) if $files;
363   $self->_do_list(1);
364 }
365
366 sub _do_list {
367   my ($self, $json) = @_;
368   my @files;
369   if ( $self->file_type eq 'document' ) {
370     my @object_types;
371     push @object_types, $self->object_type;
372     push @object_types, qw(dunning dunning1 dunning2 dunning3) if $self->object_type eq 'invoice'; # hardcoded object types?
373     @files = SL::File->get_all_versions(object_id   => $self->object_id,
374                                         object_type => \@object_types,
375                                         file_type   => $self->file_type,
376                                        );
377
378   }
379   elsif ( $self->file_type eq 'attachment' || $self->file_type eq 'image' ) {
380     @files   = SL::File->get_all(object_id   => $self->object_id,
381                                  object_type => $self->object_type,
382                                  file_type   => $self->file_type,
383                                 );
384   }
385   $self->files(\@files);
386
387   if($self->object_type eq 'shop_image'){
388     $self->js
389       ->run('kivi.ShopPart.show_images', $self->object_id)
390       ->render();
391   }else{
392     $self->_mk_render('file/list', 1, 0, $json);
393   }
394 }
395
396 sub _get_from_import {
397   my ($self, $path) = @_;
398   my @foundfiles ;
399
400   my $language = $::lx_office_conf{system}->{language};
401   my $timezone = $::locale->get_local_time_zone()->name;
402   if (opendir my $dir, $path) {
403     my @files = ( readdir $dir);
404     foreach my $file ( @files) {
405       next if (($file eq '.') || ($file eq '..'));
406       $file = Encode::decode('utf-8', $file);
407
408       next if ( -d "$path/$file" );
409
410       my $tmppath = File::Spec->catfile( $path, $file );
411       next if( ! -f $tmppath );
412
413       my $st = stat($tmppath);
414       my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
415       my $sname = $main::locale->quote_special_chars('HTML', $file);
416       push @foundfiles, {
417         'name'     => $file,
418         'filename' => $sname,
419         'path'     => $tmppath,
420         'mtime'    => $st->mtime,
421         'date'     => $dt->dmy('.') . " " . $dt->hms,
422       };
423
424     }
425   }
426   return @foundfiles;
427 }
428
429 sub _mk_render {
430   my ($self, $template, $edit, $scanner, $json) = @_;
431   my $err;
432   eval {
433     ##TODO make code configurable
434
435     my $title;
436     my @sources = $self->_get_sources();
437     foreach my $source ( @sources ) {
438       @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
439     }
440     if ( $self->file_type eq 'document' ) {
441       $title = $main::locale->text('Documents');
442     } elsif ( $self->file_type eq 'attachment' ) {
443       $title = $main::locale->text('Attachments');
444     } elsif ( $self->file_type eq 'image' ) {
445       $title = $main::locale->text('Images');
446     }
447
448     my $output         = SL::Presenter->get->render(
449       $template,
450       title            => $title,
451       SOURCES          => \@sources,
452       edit_attachments => $edit,
453       object_type      => $self->object_type,
454       object_id        => $self->object_id,
455       file_type        => $self->file_type,
456       is_global        => $self->is_global,
457       json             => $json,
458     );
459     if ( $json ) {
460       $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
461       if ( $self->existing && scalar(@{$self->existing}) > 0) {
462         my $first = shift @{$self->existing};
463         my ($first_id, $sfile) = split('_', $first, 2);
464         my $file = SL::File->get(id => $first_id );
465         $self->js->run('kivi.File.askForRename', $first_id, $file->file_type, $file->file_name, $sfile, join (',', @{$self->existing}), $self->is_global);
466       }
467       $self->js->render();
468     } else {
469         $self->render(\$output, { layout => 0, process => 0 });
470     }
471     1;
472   } or do {
473     if ($json ){
474       $self->js->flash(       'error', t8('internal error (see details)'))
475                ->flash_detail('error', $@)->render;
476     } else {
477       $self->render('generic/error', { layout => 0 }, label_error => $@);
478     }
479   };
480 }
481
482
483 sub _get_sources {
484   my ($self) = @_;
485   my @sources;
486   if ( $self->file_type eq 'document' ) {
487     # TODO statt gen neue attribute in filetypes :
488     if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
489       my $gendata = {
490         'name'         => 'created',
491         'title'        => $main::locale->text('generated Files'),
492         'chk_action'   => 'documents_delete',
493         'chk_title'    => $main::locale->text('Delete Documents'),
494         'chkall_title' => $main::locale->text('Delete all'),
495         'file_title'   => $main::locale->text('filename'),
496         'confirm_text' => $main::locale->text('delete'),
497         'can_rename'   => 1,
498         'rename_title' => $main::locale->text('Rename Documents'),
499         'done_text'    => $main::locale->text('deleted')
500       };
501       push @sources , $gendata;
502     }
503     if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
504       my @others =  SL::File->get_other_sources();
505       foreach my $scanner_or_mailrx (@others) {
506         my $other = {
507           'name'         => $scanner_or_mailrx->{name},
508           'title'        => $main::locale->text('from \'#1\' imported Files', $scanner_or_mailrx->{description}),
509           'chk_action'   => $scanner_or_mailrx->{name}.'_unimport',
510           'chk_title'    => $main::locale->text('Unimport documents'),
511           'chkall_title' => $main::locale->text('Unimport all'),
512           'file_title'   => $main::locale->text('filename'),
513           'confirm_text' => $main::locale->text('unimport'),
514           'can_rename'   => 1,
515           'rename_title' => $main::locale->text('Rename Documents'),
516           'can_import'   => 1,
517           'import_title' => $main::locale->text('Add Document from \'#1\'', $scanner_or_mailrx->{name}),
518           'path'         => $scanner_or_mailrx->{directory},
519           'done_text'    => $main::locale->text('unimported')
520         };
521         push @sources , $other;
522       }
523     }
524   }
525   elsif ( $self->file_type eq 'attachment' ) {
526     my $attdata = {
527       'name'         => 'uploaded',
528       'title'        => $main::locale->text(''),
529       'chk_action'   => 'attachments_delete',
530       'chk_title'    => $main::locale->text('Delete Attachments'),
531       'chkall_title' => $main::locale->text('Delete all'),
532       'file_title'   => $main::locale->text('filename'),
533       'confirm_text' => $main::locale->text('delete'),
534       'can_rename'   => 1,
535       'are_existing' => $self->existing ? 1 : 0,
536       'rename_title' => $main::locale->text('Rename Attachments'),
537       'can_upload'   => 1,
538       'upload_title' => $main::locale->text('Upload Attachments'),
539       'done_text'    => $main::locale->text('deleted')
540     };
541     push @sources , $attdata;
542   }
543   elsif ( $self->file_type eq 'image' ) {
544     my $attdata = {
545       'name'         => 'uploaded',
546       'title'        => $main::locale->text(''),
547       'chk_action'   => 'images_delete',
548       'chk_title'    => $main::locale->text('Delete Images'),
549       'chkall_title' => $main::locale->text('Delete all'),
550       'file_title'   => $main::locale->text('filename'),
551       'confirm_text' => $main::locale->text('delete'),
552       'can_rename'   => 1,
553       'are_existing' => $self->existing ? 1 : 0,
554       'rename_title' => $main::locale->text('Rename Images'),
555       'can_upload'   => 1,
556       'upload_title' => $main::locale->text('Upload Images'),
557       'done_text'    => $main::locale->text('deleted')
558     };
559     push @sources , $attdata;
560   }
561   return @sources;
562 }
563
564 1;
565
566 __END__
567
568 =pod
569
570 =encoding utf-8
571
572 =head1 NAME
573
574 SL::Controller::File - Controller for managing files
575
576 =head1 SYNOPSIS
577
578 The Controller is called directly from the webpages
579
580     <a href="controller.pl?action=File/list&file_type=document\
581        &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
582
583
584 or indirectly via javascript functions from js/kivi.File.js
585
586     kivi.popup_dialog({ url:     'controller.pl',
587                         data:    { action     : 'File/ajax_upload',
588                                    file_type  : 'uploaded',
589                                    object_type: type,
590                                    object_id  : id
591                                  }
592                            ...
593
594 =head1 DESCRIPTION
595
596 This is a controller for handling files in a storage independent way.
597 The storage may be a Filesystem,a WebDAV, a Database or DMS.
598 These backends must be configered in ClientConfig.
599 This Controller use as intermediate layer for storage C<SL::File>.
600
601 The Controller is responsible to display forms for displaying the files at the ERP-objects and
602 for uploading and downloading the files.
603
604 More description of the intermediate layer see L<SL::File>.
605
606 =head1 METHODS
607
608 =head2 C<action_list>
609
610 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
611 Dependent of file_type different sources are available.
612
613 For documents there are the 'created' source and the imports from scanners or email.
614 For attachments and images only the 'uploaded' source available.
615
616 Available C<FORM PARAMS>:
617
618 =over 4
619
620 =item C<form.object_id>
621
622 The Id of the ERP-object.
623
624 =item C<form.object_type>
625
626 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
627
628 =item C<form.file_type>
629
630 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
631 This file_type is a filter for the list.
632
633 =item C<form.json>
634
635 The method can be used as normal HTTP-Request (json=0) or as AJAX-JSON call to refresh the list if the parameter is set to 1.
636
637 =back
638
639
640 =head2 C<action_ajax_upload>
641
642
643 A new file or more files can selected by a dialog and insert into the system.
644
645
646 Available C<FORM PARAMS>:
647
648 =over 4
649
650 =item C<form.file_type>
651
652 This parameter describe here the source for a new file :
653 "attachments" and "images"
654
655 This is a normal upload selection, which may be more then one file to upload.
656
657 =item C<form.object_id>
658
659 and
660
661 =item C<form.object_type>
662
663 are the same as at C<action_list>
664
665 =back
666
667 =head2  C<action_ajax_files_uploaded>
668
669 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
670 The filepaths are checked about Unix and Windows paths. Also the MIME type of the files are verified ( IS the contents of a *.pdf real PDF?).
671 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
672
673 If the filename is not changed the new uploaded file is a new version of the file, if the name is changed it is a new file.
674
675 Available C<FORM PARAMS>:
676
677 =over 4
678
679 =item C<form.ATTACHMENTS.uploadfiles>
680
681 This is an array of elements which have {filename} for the name and {data} for the contents.
682
683 Also object_id, object_type and file_type
684
685 =back
686
687 =head2 C<action_download>
688
689 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
690
691 Available C<FORM PARAMS>:
692
693 =over 4
694
695 Also object_id, object_type and file_type
696
697 =back
698
699 =head2 C<action_ajax_importdialog>
700
701 A Dialog with all available and not imported files to import is open.
702 More then one file can be selected.
703
704 Available C<FORM PARAMS>:
705
706 =over 4
707
708 =item C<form.source>
709
710 The name of the source like "scanner1" or "email"
711
712 =item C<form.path>
713
714 The full path to the directory on the server, where the files to import can found
715
716 Also object_id, object_type and file_type
717
718 =back
719
720 =head2 C<action_ajax_delete>
721
722 Some files can be deleted
723
724 Available C<FORM PARAMS>:
725
726 =over 4
727
728 =item C<form.ids>
729
730 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
731
732 =back
733
734 =head2 C<action_ajax_unimport>
735
736 Some files can be unimported, dependent of the source of the file. This means they are moved
737 back to the directory of the source
738
739 Available C<FORM PARAMS>:
740
741 =over 4
742
743 =item C<form.ids>
744
745 The ids of the files to unimport. Only these files are unimported not all versions of a file if the exists
746
747 =back
748
749 =head2 C<action_ajax_rename>
750
751 One file can be renamed. There can be some checks if the same filename still exists at one object.
752
753 =head1 AUTHOR
754
755 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
756
757 =cut