Dateimanagement: Controller zum Laden und Generierung der Dateien
[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
39 use Rose::Object::MakeMethods::Generic
40 (
41     'scalar --get_set_init' => [ qw() ],
42     'scalar' => [ qw(object object_type object_model object_id object_right file_type files is_global existing) ],
43 );
44
45 __PACKAGE__->run_before('check_object_params', only => [ qw(list ajax_delete ajax_importdialog ajax_import ajax_unimport ajax_upload ajax_files_uploaded) ]);
46
47 my %file_types = (
48   'sales_quotation' =>         { gen => 1 ,gltype => '',   dir =>'SalesQuotation',       model => 'Order',          right => 'import_ar'  },
49   'sales_order'     =>         { gen => 1 ,gltype => '',   dir =>'SalesOrder',           model => 'Order',          right => 'import_ar'  },
50   'sales_delivery_order' =>    { gen => 1 ,gltype => '',   dir =>'SalesDeliveryOrder',   model => 'DeliveryOrder',  right => 'import_ar'  },
51   'invoice'         =>         { gen => 1 ,gltype => 'ar', dir =>'SalesInvoice',         model => 'Invoice',        right => 'import_ar'  },
52   'credit_note'     =>         { gen => 1 ,gltype => '',   dir =>'CreditNote',           model => 'Invoice',        right => 'import_ar'  },
53   'request_quotation' =>       { gen => 3 ,gltype => '',   dir =>'RequestForQuotation',  model => 'Order',          right => 'import_ap'  },
54   'purchase_order' =>          { gen => 3 ,gltype => '',   dir =>'PurchaseOrder',        model => 'Order',          right => 'import_ap'  },
55   'purchase_delivery_order' => { gen => 3 ,gltype => '',   dir =>'PurchaseDeliveryOrder',model => 'DeliveryOrder',  right => 'import_ap'  },
56   'purchase_invoice' =>        { gen => 2 ,gltype => 'ap', dir =>'PurchaseInvoice',      model => 'PurchaseInvoice',right => 'import_ap'  },
57   'vendor' =>                  { gen => 0 ,gltype => '',   dir =>'Vendor',               model => 'Vendor',         right => 'xx'  },
58   'customer' =>                { gen => 1 ,gltype => '',   dir =>'Customer',             model => 'Customer',       right => 'xx'  },
59   'part' =>                    { gen => 0 ,gltype => '',   dir =>'Part',                 model => 'Part',           right => 'xx'  },
60   'gl_transaction' =>          { gen => 2 ,gltype => 'gl', dir =>'GeneralLedger',        model => 'GLTransaction',  right => 'import_ap'  },
61   'draft' =>                   { gen => 0 ,gltype => '',   dir =>'Draft',                model => 'Draft',          right => 'xx'  },
62   'csv_customer' =>            { gen => 1 ,gltype => '',   dir =>'Reports',              model => 'Customer',       right => 'xx'  },
63   'csv_vendor'   =>            { gen => 1 ,gltype => '',   dir =>'Reports',              model => 'Vendor',         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 $isjson = 0;
77   $isjson = 1 if $::form->{json};
78
79   $self->_do_list($isjson);
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   $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$file->object_id." object_type=".$file->object_type." dbfile=".$file);
144   my $sessionfile = $::form->{sessionfile};
145       $main::lxdebug->message(LXDebug->DEBUG2(), "sessionfile=".$sessionfile);
146   if ( $sessionfile && -f $sessionfile ) {
147      $main::lxdebug->message(LXDebug->DEBUG2(), "file=".$file->file_name." to=".$::form->{to}." sessionfile=".$sessionfile);
148     # new uploaded file
149     if ( $::form->{to} eq $file->file_name ) {
150       # no rename so use as new version
151       $file->save_file($sessionfile);
152       $self->js->flash('warning',$::locale->text('File \'#1\' is used as new Version !',$file->file_name));
153
154     } else {
155       # new filename so it is a new file with same attributes as old file
156       eval {
157         SL::File->save(object_id     => $file->object_id,
158                        object_type   => $file->object_type,
159                        mime_type     => $file->mime_type,
160                        source        => $file->source,
161                        file_type     => $file->file_type,
162                        file_name     => $::form->{to},
163                        file_path     => $sessionfile
164                      );
165         unlink($sessionfile);
166         1;
167       } or do {
168         $self->js->flash(       'error', t8('internal error (see details)'))
169                  ->flash_detail('error', $@)->render;
170         return;
171       }
172     }
173
174   } else {
175     # normal rename
176     eval {
177       my $res = $file->rename($::form->{to});
178       $main::lxdebug->message(LXDebug->DEBUG2(), "rename result=".$res);
179       if ($res > SL::File::RENAME_OK) {
180         $self->js->flash('error',
181                          $res == SL::File::RENAME_EXISTS      ? $::locale->text('File still exists !')
182                        : $res == SL::File::RENAME_SAME        ? $::locale->text('Same Filename !')
183                        :                                        $::locale->text('File not exists !'))->render;
184         return 1;
185       }
186       1;
187     } or do {
188       $self->js->flash(       'error', t8('internal error (see details)'))
189                ->flash_detail('error', $@)->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   $main::lxdebug->message(LXDebug->DEBUG2(), "file_upload UPLOAD=".$::form->{ATTACHMENTS}->{uploadfiles});
222   my @existing;
223   if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
224     my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
225     foreach my $idx (0 .. scalar(@upfiles) - 1) {
226       eval {
227         my $fname = uri_unescape($upfiles[$idx]->{filename});
228         $main::lxdebug->message(LXDebug->DEBUG2(), "file_upload name=".$fname);
229         ## normalize and find basename
230         # first split with unix rules
231         # after that split with windows rules
232         my ($volume,$directories,$basefile) = File::Spec::Unix->splitpath($fname);
233         ($volume,$directories,$basefile) = File::Spec::Win32->splitpath($basefile);
234
235         # to find real mime_type by magic we must save the filedata
236
237         my $sess_fname = "file_upload_".$self->object_type."_".$self->object_id."_".$idx;
238         my $sfile     = SL::SessionFile->new($sess_fname, mode => 'w');
239
240         $sfile->fh->print(${$upfiles[$idx]->{data}});
241         $sfile->fh->close;
242         my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
243
244         if (! $mime_type) {
245           # if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
246           $mime_type = File::MimeInfo::Magic::mimetype($basefile);
247           $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
248         }
249         $main::lxdebug->message(LXDebug->DEBUG2(), "mime_type=".$mime_type);
250         if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
251           next;
252         }
253         my ($existobj) = SL::File->get_all(object_id     => $self->object_id,
254                                         object_type   => $self->object_type,
255                                         mime_type     => $mime_type,
256                                         source        => $source,
257                                         file_type     => $self->file_type,
258                                         file_name     => $basefile,
259                                       );
260
261         $main::lxdebug->message(LXDebug->DEBUG2(), "store1 exist=".$existobj);
262         if ($existobj) {
263   $main::lxdebug->message(LXDebug->DEBUG2(), "id=".$existobj->id." sessionfile=". $sfile->file_name);
264           push @existing, $existobj->id.'_'.$sfile->file_name;
265         } else {
266           my $fileobj = SL::File->save(object_id     => $self->object_id,
267                                        object_type   => $self->object_type,
268                                        mime_type     => $mime_type,
269                                        source        => $source,
270                                        file_type     => $self->file_type,
271                                        file_name     => $basefile,
272                                        ## two possibilities: what is better ? content or sessionfile ??
273                                        #file_contents => ${$upfiles[$idx]->{data}},
274                                        file_path     => $sfile->file_name
275                                      );
276           $main::lxdebug->message(LXDebug->DEBUG2(), "obj=".$fileobj);
277           unlink($sfile->file_name);
278         }
279         1;
280       } or do {
281         $self->js->flash(       'error', t8('internal error (see details)'))
282                  ->flash_detail('error', $@)->render;
283         return;
284       }
285     }
286   }
287   $self->existing(\@existing);
288   $self->_do_list(1);
289 }
290
291 sub action_download {
292   my ($self) = @_;
293   my ($id,$version) = split /_/, $::form->{id};
294   my $file = SL::File->get(id => $id );
295   $file->version($version) if $version;
296   my $ref  = $file->get_content;
297   if ( $file && $ref ) {
298     return $self->send_file($ref,
299       type => $file->mime_type,
300       name => $file->file_name,
301     );
302   }
303 }
304
305 #
306 # filters
307 #
308
309 sub check_object_params {
310   my ($self) = @_;
311
312   my $id = $::form->{object_id} +0;
313   my $draftid = $::form->{draft_id} +0;
314   my $gldoc = 0;
315   my $type = undef;
316
317   if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
318     $gldoc = 1;
319     $type = $::form->{object_type};
320   }
321   elsif ( $id == 0 ) {
322     $id = $::form->{draft_id};
323     $type = 'draft';
324   } elsif ( $::form->{object_type} ) {
325     $type = $::form->{object_type};
326   }
327   die "No object type"     if ! $type;
328   die "No file type"       if ! $::form->{file_type};
329   die "Unkown object type" if ! $file_types{$type};
330
331   $self->is_global($gldoc);
332   $self->file_type($::form->{file_type});
333   $self->object_type($type);
334   $self->object_id($id);
335   $self->object_model($file_types{$type}->{model});
336   $self->object_right($file_types{$type}->{right});
337   $main::lxdebug->message(LXDebug->DEBUG2(), "checked: object_id=".$self->object_id." object_type=".$self->object_type." is_global=".$self->is_global);
338
339  # $::auth->assert($self->object_right);
340
341  # my $model = 'SL::DB::' . $self->object_model;
342  # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
343
344   return 1;
345 }
346
347 #
348 # private methods
349 #
350
351 sub _delete_all {
352   my ($self,$do_unimport,$infotext) = @_;
353   my $files = '';
354   my $ids = $::form->{ids};
355   foreach my $id_version (@{ $::form->{$ids} || [] }) {
356     my ($id,$version) = split /_/, $id_version;
357     my $dbfile = SL::File->get(id => $id);
358     $dbfile->version($version) if $dbfile && $version;
359     if ( $dbfile && $dbfile->delete ) {
360       $files .= ' '.$dbfile->file_name;
361     }
362   }
363   $self->js->flash('info',$infotext.$files) if $files;
364   $self->_do_list(1);
365 }
366
367 sub _do_list {
368   my ($self,$json) = @_;
369   my @files;
370   $main::lxdebug->message(LXDebug->DEBUG2(), "do_list: object_id=".$self->object_id." object_type=".$self->object_type." file_type=".$self->file_type." json=".$json);
371   if ( $self->file_type eq 'document' ) {
372     @files   = SL::File->get_all_versions(object_id   => $self->object_id  ,
373                                           object_type => $self->object_type,
374                                           file_type   => $self->file_type  );
375
376     $main::lxdebug->message(LXDebug->DEBUG2(), "cnt1=".scalar(@files));
377   }
378   elsif ( $self->file_type eq 'attachment' ||  $self->file_type eq 'image' ) {
379     @files   = SL::File->get_all(object_id   => $self->object_id  ,
380                                  object_type => $self->object_type,
381                                  file_type   => $self->file_type  );
382     $main::lxdebug->message(LXDebug->DEBUG2(), "cnt2=".scalar(@files));
383   }
384   $self->files(\@files);
385   $self->_mk_render('file/list',1,0,$json);
386 }
387
388 sub _get_from_import {
389   my ($self,$path) = @_;
390   my @foundfiles ;
391
392   $main::lxdebug->message(LXDebug->DEBUG2(), "import path=".$path);
393   my $language = $::lx_office_conf{system}->{language};
394   my $timezone = $::locale->get_local_time_zone()->name;
395   if (opendir my $dir, $path) {
396     my @files = ( readdir $dir);
397     foreach my $file ( @files) {
398       next if (($file eq '.') || ($file eq '..'));
399       $file = Encode::decode('utf-8', $file);
400       $main::lxdebug->message(LXDebug->DEBUG2(), "file=".$file);
401
402       next if( -d "$path/$file");
403
404       my $tmppath = File::Spec->catfile( $path, $file );
405       $main::lxdebug->message(LXDebug->DEBUG2(), "tmppath=".$tmppath." file=".$file);
406       next if( ! -f $tmppath);
407
408       my $st = stat($tmppath);
409       my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language);
410       my $sname = $main::locale->quote_special_chars('HTML',$file);
411       push @foundfiles , {
412         'name'     => $file,
413         'filename' => $sname,
414         'path'     => $tmppath,
415         'mtime'    => $st->mtime,
416         'date'     => $dt->dmy('.')." ".$dt->hms,
417       };
418
419     }
420   }
421   $main::lxdebug->message(LXDebug->DEBUG2(), "return ".scalar(@foundfiles)." files");
422   return @foundfiles;
423 }
424
425 sub _mk_render {
426   my ($self,$template,$edit,$scanner,$json) = @_;
427   my $err;
428   eval {
429     ##TODO  here a configurable code must be implemented
430
431     my $title;
432     $main::lxdebug->message(LXDebug->DEBUG2(), "mk_render: object_id=".$self->object_id." object_type=".$self->object_type.
433                               " file_type=".$self->file_type." json=".$json." filecount=".scalar(@{ $self->files })." is_global=".$self->is_global);
434     my @sources = $self->_get_sources();
435     foreach my $source ( @sources ) {
436       $main::lxdebug->message(LXDebug->DEBUG2(), "mk_render: source name=".$source->{name});
437       @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
438     }
439     if ( $self->file_type eq 'document' ) {
440       $title = $main::locale->text('Documents');
441     } elsif ( $self->file_type eq 'attachment' ) {
442       $title = $main::locale->text('Attachments');
443     } elsif ( $self->file_type eq 'image' ) {
444       $title = $main::locale->text('Images');
445     }
446
447     my $output         = SL::Presenter->get->render(
448       $template,
449       title             => $title,
450       SOURCES           => \@sources,
451       edit_attachments  => $edit,
452       object_type       => $self->object_type,
453       object_id         => $self->object_id,
454       file_type         => $self->file_type,
455       is_global         => $self->is_global,
456       json              => $json,
457     );
458     if ( $json ) {
459       $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
460       if ( $self->existing && scalar(@{$self->existing}) > 0) {
461         my $first = shift @{$self->existing};
462         my ($first_id,$sfile) = split('_',$first,2);
463         #$main::lxdebug->message(LXDebug->DEBUG2(), "id=".$first_id." sessionfile=". $sfile);
464         my $file = SL::File->get(id => $first_id );
465         $self->js->run('kivi.File.askForRename',$first_id,$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   $main::lxdebug->message(LXDebug->DEBUG2(), "get_sources file_type=". $self->file_type);
487   if ( $self->file_type eq 'document' ) {
488     ##TODO statt gen neue attribute in filetypes :
489     if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
490       my $gendata = {
491         'name'         => 'created',
492         'title'        => $main::locale->text('generated Files'),
493         'chk_action'   => 'documents_delete',
494         'chk_title'    => $main::locale->text('Delete Documents'),
495         'chkall_title' => $main::locale->text('Delete all'),
496         'file_title'   => $main::locale->text('filename'),
497         'confirm_text' => $main::locale->text('delete'),
498         'can_rename'   => 1,
499         'rename_title' => $main::locale->text('Rename Documents'),
500         'done_text'    => $main::locale->text('deleted')
501       };
502       push @sources , $gendata;
503     }
504     if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
505       my @others =  SL::File->get_other_sources();
506       $main::lxdebug->message(LXDebug->DEBUG2(), "other cnt=". scalar(@others));
507       foreach my $scanner_or_mailrx (@others) {
508         my $other = {
509           'name'         => $scanner_or_mailrx->{name},
510           'title'        => $main::locale->text('from \'#1\' imported Files',$scanner_or_mailrx->{description}),
511           'chk_action'   => $scanner_or_mailrx->{name}.'_unimport',
512           'chk_title'    => $main::locale->text('Unimport documents'),
513           'chkall_title' => $main::locale->text('Unimport all'),
514           'file_title'   => $main::locale->text('filename'),
515           'confirm_text' => $main::locale->text('unimport'),
516           'can_rename'   => 1,
517           'rename_title' => $main::locale->text('Rename Documents'),
518           'can_import'   => 1,
519           'import_title' => $main::locale->text('Add Document from \'#1\'',$scanner_or_mailrx->{name}),
520           'path'         => $scanner_or_mailrx->{directory},
521           'done_text'    => $main::locale->text('unimported')
522         };
523         push @sources , $other;
524       }
525     }
526   }
527   elsif ( $self->file_type eq 'attachment' ) {
528     my $attdata = {
529       'name'         => 'uploaded',
530       'title'        => $main::locale->text(''),
531       'chk_action'   => 'attachments_delete',
532       'chk_title'    => $main::locale->text('Delete Attachments'),
533       'chkall_title' => $main::locale->text('Delete all'),
534       'file_title'   => $main::locale->text('filename'),
535       'confirm_text' => $main::locale->text('delete'),
536       'can_rename'   => 1,
537       'are_existing' => $self->existing ? 1 : 0,
538       'rename_title' => $main::locale->text('Rename Attachments'),
539       'can_upload'   => 1,
540       'upload_title' => $main::locale->text('Upload Attachments'),
541       'done_text'    => $main::locale->text('deleted')
542     };
543     push @sources , $attdata;
544   }
545   elsif ( $self->file_type eq 'image' ) {
546     my $attdata = {
547       'name'         => 'uploaded',
548       'title'        => $main::locale->text(''),
549       'chk_action'   => 'images_delete',
550       'chk_title'    => $main::locale->text('Delete Images'),
551       'chkall_title' => $main::locale->text('Delete all'),
552       'file_title'   => $main::locale->text('filename'),
553       'confirm_text' => $main::locale->text('delete'),
554       'can_rename'   => 1,
555       'are_existing' => $self->existing ? 1 : 0,
556       'rename_title' => $main::locale->text('Rename Images'),
557       'can_upload'   => 1,
558       'upload_title' => $main::locale->text('Upload Images'),
559       'done_text'    => $main::locale->text('deleted')
560     };
561     push @sources , $attdata;
562   }
563   $main::lxdebug->message(LXDebug->DEBUG2(), "get_sources count=".scalar(@sources));
564   return @sources;
565 }
566
567 1;
568
569 __END__
570
571 =pod
572
573 =encoding utf-8
574
575 =head1 NAME
576
577 SL::Controller::File - Controller for managing files
578
579
580 =head1 SYNOPSIS
581
582 =begin text
583
584     # The Controller is called direct from the webpages
585
586
587     <a href="controller.pl?action=File/list&file_type=document\
588        &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
589
590
591     # or indirect via javascript functions from js/kivi.File.js
592
593
594     kivi.popup_dialog({ url:     'controller.pl',
595                         data:    { action     : 'File/ajax_upload',
596                                    file_type  : 'uploaded',
597                                    object_type: type,
598                                    object_id  : id
599                                  }
600                            ...
601
602 =end text
603
604
605 =head1 DESCRIPTION
606
607 This is a controller for handling files in a storage independant way.
608 The storage may be a Filesystem,a WebDAV, a Database or DMS.
609 These backends must be configered in ClientConfig.
610 This Controller use as intermediate layer for storage C<SL::File>.
611
612 The Controller is responsible to display forms for displaying the files at the ERP-objects and
613 for uploading and downloading the files.
614
615 More description of the intermediate layer see L<SL::File>.
616
617 =head1 METHODS
618
619 =head2 C<action_list>
620
621 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
622 Dependant of file_type different sources are available.
623
624 For documents there are the 'created' source and the imports from scanners or email.
625 For attachments and images only the 'uploaded' source available.
626
627 Available C<FORM PARAMS>:
628
629 =over 4
630
631 =item C<form.object_id>
632
633 The Id of the ERP-object.
634
635 =item C<form.object_type>
636
637 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
638
639 =item C<form.file_type>
640
641 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
642 This file_type is a filter for the list.
643
644 =item C<form.json>
645
646 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.
647
648 =back
649
650
651 =head2 C<action_ajax_upload>
652
653
654 A new file or more files can selected by a dialog and insert into the system.
655
656
657 Available C<FORM PARAMS>:
658
659 =over 4
660
661 =item C<form.file_type>
662
663 This parameter describe here the source for a new file :
664 "attachments" and "images"
665
666 This is a normal upload selection, which may be more then one file to upload.
667
668 =item C<form.object_id>
669
670 and
671
672 =item C<form.object_type>
673
674 are the same as at C<action_list>
675
676 =back
677
678 =head2  C<action_ajax_files_uploaded>
679
680 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
681 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?).
682 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
683
684 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.
685
686 Available C<FORM PARAMS>:
687
688 =over 4
689
690 =item C<form.ATTACHMENTS.uploadfiles>
691
692 This is an array of elements which have {filename} for the name and {data} for the contents.
693
694 Also object_id, object_type and file_type
695
696 =back
697
698 =head2 C<action_download>
699
700 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
701
702 Available C<FORM PARAMS>:
703
704 =over 4
705
706 Also object_id, object_type and file_type
707
708 =back
709
710 =head2 C<action_ajax_importdialog>
711
712 A Dialog with all available and not imported files to import is open.
713 More then one file can be selected.
714
715 Available C<FORM PARAMS>:
716
717 =over 4
718
719 =item C<form.source>
720
721 The name of the source like "scanner1" or "email"
722
723 =item C<form.path>
724
725 The full path to the directory on the server, where the files to import can found
726
727 Also object_id, object_type and file_type
728
729 =back
730
731 =head2 C<action_ajax_delete>
732
733 Some files can be deleted
734
735 Available C<FORM PARAMS>:
736
737 =over 4
738
739 =item C<form.ids>
740
741 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
742
743 =back
744
745 =head2 C<action_ajax_unimport>
746
747 Some files can be unimported, dependant of the source of the file. This means they are moved
748 back to the directory of the source
749
750 Available C<FORM PARAMS>:
751
752 =over 4
753
754 =item C<form.ids>
755
756 The ids of the files to unimport. Only this files are unimported not all versions of a file if the exists
757
758 =back
759
760 =head2 C<action_ajax_rename>
761
762 One file can be renamed. There can be some checks if the same filename still exists at one object.
763
764
765 =head1 AUTHOR
766
767 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
768
769 =cut
770