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