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