1 package SL::Controller::File;
5 use parent qw(SL::Controller::Base);
7 use List::Util qw(first max);
10 use Encode qw(decode);
11 use English qw( -no_match_vars );
16 use File::Slurp qw(slurp);
18 use File::Spec::Win32;
19 use File::MimeInfo::Magic;
21 use SL::DB::Helper::Mappings;
23 use SL::DB::DeliveryOrder;
26 use SL::DB::PurchaseInvoice;
28 use SL::DB::GLTransaction;
32 use SL::Helper::CreatePDF qw(:all);
33 use SL::Locale::String;
35 use SL::SessionFile::Random;
37 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_image_type file_probe_type);
39 use constant DO_DELETE => 0;
40 use constant DO_UNIMPORT => 1;
42 use Rose::Object::MakeMethods::Generic
44 'scalar --get_set_init' => [ qw() ],
45 'scalar' => [ qw(object object_type object_model object_id object_right file_type files is_global existing) ],
48 __PACKAGE__->run_before('check_object_params', only => [ qw(list ajax_delete ajax_importdialog ajax_import ajax_unimport ajax_upload ajax_files_uploaded) ]);
50 # gen: bitmask: bit 1 (value is 1, 3, 5 or 7) => file created
51 # bit 2 (value is 2, 3, 6 or 7) => file from other source (e.g. directory for scanned documents)
52 # bit 3 (value is 4, 5, 6 or 7) => upload as other source
53 # gltype: is this used somewhere?
54 # dir: is this used somewhere?
55 # model: base name of the rose model
56 # right: access right used for import
58 'sales_quotation' => { gen => 1, gltype => '', dir =>'SalesQuotation', model => 'Order', right => 'import_ar' },
59 'sales_order' => { gen => 5, gltype => '', dir =>'SalesOrder', model => 'Order', right => 'import_ar' },
60 'sales_delivery_order' => { gen => 1, gltype => '', dir =>'SalesDeliveryOrder', model => 'DeliveryOrder', right => 'import_ar' },
61 'invoice' => { gen => 1, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
62 'invoice_for_advance_payment' => { gen => 1, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
63 'final_invoice' => { gen => 1, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
64 'credit_note' => { gen => 1, gltype => '', dir =>'CreditNote', model => 'Invoice', right => 'import_ar' },
65 'request_quotation' => { gen => 7, gltype => '', dir =>'RequestForQuotation', model => 'Order', right => 'import_ap' },
66 'purchase_order' => { gen => 7, gltype => '', dir =>'PurchaseOrder', model => 'Order', right => 'import_ap' },
67 'purchase_delivery_order' => { gen => 7, gltype => '', dir =>'PurchaseDeliveryOrder',model => 'DeliveryOrder', right => 'import_ap' },
68 'purchase_invoice' => { gen => 6, gltype => 'ap', dir =>'PurchaseInvoice', model => 'PurchaseInvoice',right => 'import_ap' },
69 'vendor' => { gen => 0, gltype => '', dir =>'Vendor', model => 'Vendor', right => 'xx' },
70 'customer' => { gen => 1, gltype => '', dir =>'Customer', model => 'Customer', right => 'xx' },
71 'project' => { gen => 0, gltype => '', dir =>'Project', model => 'Project', right => 'xx' },
72 'part' => { gen => 0, gltype => '', dir =>'Part', model => 'Part', right => 'xx' },
73 'gl_transaction' => { gen => 6, gltype => 'gl', dir =>'GeneralLedger', model => 'GLTransaction', right => 'import_ap' },
74 'draft' => { gen => 0, gltype => '', dir =>'Draft', model => 'Draft', right => 'xx' },
75 'csv_customer' => { gen => 1, gltype => '', dir =>'Reports', model => 'Customer', right => 'xx' },
76 'csv_vendor' => { gen => 1, gltype => '', dir =>'Reports', model => 'Vendor', right => 'xx' },
77 'shop_image' => { gen => 0, gltype => '', dir =>'ShopImages', model => 'Part', right => 'xx' },
78 'letter' => { gen => 7, gltype => '', dir =>'Letter', model => 'Letter', right => 'sales_letter_edit | purchase_letter_edit' },
82 # $main::locale->text('imported')
92 $is_json = 1 if $::form->{json};
94 $self->_do_list($is_json);
97 sub action_ajax_importdialog {
99 $::auth->assert($self->object_right);
100 my $path = $::form->{path};
101 my @files = $self->_get_from_import($path);
103 'name' => $::form->{source},
105 'chk_action' => $::form->{source}.'_import',
106 'chk_title' => $main::locale->text('Import scanned documents'),
107 'chkall_title' => $main::locale->text('Import all'),
110 $self->render('file/import_dialog',
117 sub action_ajax_import {
119 $::auth->assert($self->object_right);
120 my $ids = $::form->{ids};
121 my $source = $::form->{source};
122 my $path = $::form->{path};
123 my @files = $self->_get_from_import($path);
124 foreach my $filename (@{ $::form->{$ids} || [] }) {
125 my ($file, undef) = grep { $_->{name} eq $filename } @files;
127 my $obj = SL::File->save(object_id => $self->object_id,
128 object_type => $self->object_type,
129 mime_type => 'application/pdf',
131 file_type => 'document',
132 file_name => $file->{filename},
133 file_path => $file->{path}
135 unlink($file->{path}) if $obj;
141 sub action_ajax_delete {
143 $self->_delete_all(DO_DELETE, $::locale->text('Following files are deleted:'));
146 sub action_ajax_unimport {
148 $self->_delete_all(DO_UNIMPORT, $::locale->text('Following files are unimported:'));
151 sub action_ajax_rename {
153 my ($id, $version) = split /_/, $::form->{id};
154 my $file = SL::File->get(id => $id);
156 $self->js->flash('error', $::locale->text('File not exists !'))->render();
159 my $sessionfile = $::form->{sessionfile};
160 if ( $sessionfile && -f $sessionfile ) {
162 if ( $::form->{to} eq $file->file_name ) {
163 # no rename so use as new version
164 $file->save_file($sessionfile);
165 $self->js->flash('warning', $::locale->text('File \'#1\' is used as new Version !', $file->file_name));
168 # new filename, so it is a new file with the same attributes as the old file
170 SL::File->save(object_id => $file->object_id,
171 object_type => $file->object_type,
172 mime_type => $file->mime_type,
173 source => $file->source,
174 file_type => $file->file_type,
175 file_name => $::form->{to},
176 file_path => $sessionfile
178 unlink($sessionfile);
181 $self->js->flash( 'error', t8('internal error (see details)'))
182 ->flash_detail('error', $@)->render;
192 $result = $file->rename($::form->{to});
195 $self->js->flash( 'error', t8('internal error (see details)'))
196 ->flash_detail('error', $@)->render;
200 if ($result != SL::File::RENAME_OK) {
201 $self->js->flash('error',
202 $result == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
203 : $result == SL::File::RENAME_SAME ? $::locale->text('Same Filename !')
204 : $::locale->text('File not exists !'))
209 $self->is_global($::form->{is_global});
210 $self->file_type( $file->file_type);
211 $self->object_type($file->object_type);
212 $self->object_id( $file->object_id);
213 #$self->object_model($file_types{$file->module}->{model});
214 #$self->object_right($file_types{$file->module}->{right});
215 if ( $::form->{next_ids} ) {
216 my @existing = split(/,/, $::form->{next_ids});
217 $self->existing(\@existing);
222 sub action_ajax_upload {
224 $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
225 $self->{accept_types} = '';
226 $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
227 $self->render('file/upload_dialog',
233 sub action_ajax_files_uploaded {
236 my $source = 'uploaded';
238 if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
239 my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
240 foreach my $idx (0 .. scalar(@upfiles) - 1) {
242 my $fname = uri_unescape($upfiles[$idx]->{filename});
243 # normalize and find basename
244 # first split with unix rules
245 # after that split with windows rules
246 my ($volume, $directories, $basefile) = File::Spec::Unix->splitpath($fname);
247 ($volume, $directories, $basefile) = File::Spec::Win32->splitpath($basefile);
249 # to find real mime_type by magic we must save the filedata
251 my $sess_fname = "file_upload_" . $self->object_type . "_" . $self->object_id . "_" . $idx;
252 my $sfile = SL::SessionFile->new($sess_fname, mode => 'w');
254 $sfile->fh->print(${$upfiles[$idx]->{data}});
256 my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
259 # if filename has the suffix "pdf", but isn't really a pdf, set mimetype for no suffix
260 $mime_type = File::MimeInfo::Magic::mimetype($basefile);
261 $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
263 if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
266 my ($existobj) = SL::File->get_all(object_id => $self->object_id,
267 object_type => $self->object_type,
268 mime_type => $mime_type,
270 file_type => $self->file_type,
271 file_name => $basefile,
275 push @existing, $existobj->id.'_'.$sfile->file_name;
277 my $fileobj = SL::File->save(object_id => $self->object_id,
278 object_type => $self->object_type,
279 mime_type => $mime_type,
281 file_type => $self->file_type,
282 file_name => $basefile,
283 title => $::form->{title},
284 description => $::form->{description},
285 ## two possibilities: what is better ? content or sessionfile ??
286 file_contents => ${$upfiles[$idx]->{data}},
287 file_path => $sfile->file_name
289 unlink($sfile->file_name);
293 $self->js->flash( 'error', t8('internal error (see details)'))
294 ->flash_detail('error', $@)->render;
299 $self->existing(\@existing);
303 sub action_download {
306 my $id = $::form->{id};
307 my $version = $::form->{version};
309 my $file = SL::File->get(id => $id );
310 $file->version($version) if $version;
311 my $ref = $file->get_content;
312 if ( $file && $ref ) {
313 return $self->send_file($ref,
314 type => $file->mime_type,
315 name => $file->file_name,
320 sub action_ajax_get_thumbnail {
323 my $id = $::form->{file_id};
324 my $version = $::form->{file_version};
325 my $file = SL::File->get(id => $id);
327 $file->version($version) if $version;
329 my $thumbnail = _create_thumbnail($file, $::form->{size});
331 my $overlay_selector = '#enlarged_thumb_' . $id;
332 $overlay_selector .= '_' . $version if $version;
334 ->attr($overlay_selector, 'src', 'data:' . $thumbnail->{thumbnail_img_content_type} . ';base64,' . MIME::Base64::encode_base64($thumbnail->{thumbnail_img_content}))
335 ->data($overlay_selector, 'is-overlay-loaded', '1')
344 sub check_object_params {
347 my $id = ($::form->{object_id} // 0) * 1;
348 my $draftid = ($::form->{draft_id} // 0) * 1;
352 if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
354 $type = $::form->{object_type};
357 $id = $::form->{draft_id};
359 } elsif ( $::form->{object_type} ) {
360 $type = $::form->{object_type};
362 die "No object type" unless $type;
363 die "No file type" unless $::form->{file_type};
364 die "Unknown object type" unless $file_types{$type};
366 $self->is_global($gldoc);
367 $self->file_type($::form->{file_type});
368 $self->object_type($type);
369 $self->object_id($id);
370 $self->object_model($file_types{$type}->{model});
371 $self->object_right($file_types{$type}->{right});
373 # $::auth->assert($self->object_right);
375 # my $model = 'SL::DB::' . $self->object_model;
376 # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
386 my ($self, $do_unimport, $infotext) = @_;
388 my $ids = $::form->{ids};
389 foreach my $id_version (@{ $::form->{$ids} || [] }) {
390 my ($id, $version) = split /_/, $id_version;
391 my $dbfile = SL::File->get(id => $id);
394 $dbfile->version($version);
395 $files .= ' ' . $dbfile->file_name if $dbfile->delete_version;
397 $files .= ' ' . $dbfile->file_name if $dbfile->delete;
401 $self->js->flash('info', $infotext . $files) if $files;
406 my ($self, $json) = @_;
409 my @object_types = ($self->object_type);
410 if ( $self->file_type eq 'document' ) {
411 push @object_types, qw(dunning1 dunning2 dunning3 dunning_invoice dunning_orig_invoice) if $self->object_type eq 'invoice'; # hardcoded object types?
413 @files = SL::File->get_all_versions(object_id => $self->object_id,
414 object_type => \@object_types,
415 file_type => $self->file_type,
418 $self->files(\@files);
420 $_->{thumbnail} = _create_thumbnail($_) for @files;
421 $_->{version_count} = SL::File->get_version_count(id => $_->id) for @files;
423 if($self->object_type eq 'shop_image'){
425 ->run('kivi.ShopPart.show_images', $self->object_id)
428 $self->_mk_render('file/list', 1, 0, $json);
432 sub _get_from_import {
433 my ($self, $path) = @_;
436 my $language = $::lx_office_conf{system}->{language};
437 my $timezone = $::locale->get_local_time_zone()->name;
438 if (opendir my $dir, $path) {
439 my @files = (readdir $dir);
440 foreach my $file ( @files) {
441 next if (($file eq '.') || ($file eq '..'));
442 $file = Encode::decode('utf-8', $file);
444 next if ( -d "$path/$file" );
446 my $tmppath = File::Spec->catfile( $path, $file );
447 next if( ! -f $tmppath );
449 my $st = stat($tmppath);
450 my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
451 my $sname = $main::locale->quote_special_chars('HTML', $file);
454 'filename' => $sname,
456 'mtime' => $st->mtime,
457 'date' => $dt->dmy('.') . " " . $dt->hms,
464 $::lxdebug->message(LXDebug::WARN(), "SL::File::_get_from_import opendir failed to open dir " . $path);
471 my ($self, $template, $edit, $scanner, $json) = @_;
474 ##TODO make code configurable
477 my @sources = $self->_get_sources();
478 foreach my $source ( @sources ) {
479 @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
481 if ( $self->file_type eq 'document' ) {
482 $title = $main::locale->text('Documents');
483 } elsif ( $self->file_type eq 'attachment' ) {
484 $title = $main::locale->text('Attachments');
485 } elsif ( $self->file_type eq 'image' ) {
486 $title = $main::locale->text('Images');
489 my $output = SL::Presenter->get->render(
492 SOURCES => \@sources,
493 edit_attachments => $edit,
494 object_type => $self->object_type,
495 object_id => $self->object_id,
496 file_type => $self->file_type,
497 is_global => $self->is_global,
501 $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
502 if ( $self->existing && scalar(@{$self->existing}) > 0) {
503 my $first = shift @{$self->existing};
504 my ($first_id, $sfile) = split('_', $first, 2);
505 my $file = SL::File->get(id => $first_id );
506 $self->js->run('kivi.File.askForRename', $first_id, $file->file_type, $file->file_name, $sfile, join (',', @{$self->existing}), $self->is_global);
510 $self->render(\$output, { layout => 0, process => 0 });
515 $self->js->flash( 'error', t8('internal error (see details)'))
516 ->flash_detail('error', $@)->render;
518 $self->render('generic/error', { layout => 0 }, label_error => $@);
527 if ( $self->file_type eq 'document' ) {
528 # TODO statt gen neue attribute in filetypes :
529 if (($file_types{$self->object_type}->{gen}*1 & 4)==4) {
530 # bit 3 is set => means upload
532 'name' => 'uploaded',
533 'title' => $main::locale->text('uploaded Documents'),
534 'chk_action' => 'uploaded_documents_delete',
535 'chk_title' => $main::locale->text('Delete Documents'),
536 'chkall_title' => $main::locale->text('Delete all'),
537 'file_title' => $main::locale->text('filename'),
538 'confirm_text' => $main::locale->text('delete'),
540 'are_existing' => $self->existing ? 1 : 0,
541 'rename_title' => $main::locale->text('Rename Attachments'),
544 'upload_title' => $main::locale->text('Upload Documents'),
545 'done_text' => $main::locale->text('deleted')
547 push @sources , $source;
550 if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
553 'title' => $main::locale->text('generated Files'),
554 'chk_action' => 'documents_delete',
555 'chk_title' => $main::locale->text('Delete Documents'),
556 'chkall_title' => $main::locale->text('Delete all'),
557 'file_title' => $main::locale->text('filename'),
558 'confirm_text' => $main::locale->text('delete'),
559 'can_delete' => $::instance_conf->get_doc_delete_printfiles,
560 'can_rename' => $::instance_conf->get_doc_delete_printfiles,
561 'rename_title' => $main::locale->text('Rename Documents'),
562 'done_text' => $main::locale->text('deleted')
564 push @sources , $gendata;
567 if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
568 my @others = SL::File->get_other_sources();
569 foreach my $scanner_or_mailrx (@others) {
571 'name' => $scanner_or_mailrx->{name},
572 'title' => $main::locale->text('from \'#1\' imported Files', $scanner_or_mailrx->{description}),
573 'chk_action' => $scanner_or_mailrx->{name}.'_unimport',
574 'chk_title' => $main::locale->text('Unimport documents'),
575 'chkall_title' => $main::locale->text('Unimport all'),
576 'file_title' => $main::locale->text('filename'),
577 'confirm_text' => $main::locale->text('unimport'),
579 'rename_title' => $main::locale->text('Rename Documents'),
582 'import_title' => $main::locale->text('Add Document from \'#1\'', $scanner_or_mailrx->{name}),
583 'path' => $scanner_or_mailrx->{directory},
584 'done_text' => $main::locale->text('unimported')
586 push @sources , $other;
590 elsif ( $self->file_type eq 'attachment' ) {
592 'name' => 'uploaded',
593 'title' => $main::locale->text(''),
594 'chk_action' => 'attachments_delete',
595 'chk_title' => $main::locale->text('Delete Attachments'),
596 'chkall_title' => $main::locale->text('Delete all'),
597 'file_title' => $main::locale->text('filename'),
598 'confirm_text' => $main::locale->text('delete'),
600 'are_existing' => $self->existing ? 1 : 0,
601 'rename_title' => $main::locale->text('Rename Attachments'),
604 'upload_title' => $main::locale->text('Upload Attachments'),
605 'done_text' => $main::locale->text('deleted')
607 push @sources , $attdata;
609 elsif ( $self->file_type eq 'image' ) {
611 'name' => 'uploaded',
612 'title' => $main::locale->text(''),
613 'chk_action' => 'images_delete',
614 'chk_title' => $main::locale->text('Delete Images'),
615 'chkall_title' => $main::locale->text('Delete all'),
616 'file_title' => $main::locale->text('filename'),
617 'confirm_text' => $main::locale->text('delete'),
619 'are_existing' => $self->existing ? 1 : 0,
620 'rename_title' => $main::locale->text('Rename Images'),
623 'upload_title' => $main::locale->text('Upload Images'),
624 'done_text' => $main::locale->text('deleted')
626 push @sources , $attdata;
632 # todo: cache thumbs?
633 sub _create_thumbnail {
634 my ($file, $size) = @_;
639 if (!eval { $filename = $file->get_file(); 1; }) {
640 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail get_file failed: " . $EVAL_ERROR);
644 # Workaround for pfds which are not handled by file_probe_type.
645 # Maybe use mime info stored in db?
646 my $mime_type = File::MimeInfo::Magic::magic($filename);
647 if ($mime_type =~ m{pdf}) {
648 $filename = _convert_pdf_to_png($filename, size => $size);
650 return if !$filename;
653 if (!eval { $content = slurp $filename; 1; }) {
654 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail slurp failed: " . $EVAL_ERROR);
659 if (!eval { $ret = file_probe_type($content, size => $size); 1; }) {
660 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type failed: " . $EVAL_ERROR);
664 # file_probe_type returns a hash ref with thumbnail info and content
665 # or an error message
666 if ('HASH' ne ref $ret) {
667 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type returned an error: " . $ret);
674 sub _convert_pdf_to_png {
675 my ($filename, %params) = @_;
677 my $size = $params{size} // 64;
678 my $sfile = SL::SessionFile::Random->new();
679 unless (-f $filename) {
680 $::lxdebug->message(LXDebug::WARN(), "_convert_pdf_to_png failed, no file found: $filename");
683 # quotemeta for storno case "storno\ zu\ 1020" *nix only
684 my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . quotemeta($filename) . ' ' . $sfile->file_name;
686 if (system($command) == -1) {
687 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: system call failed: " . $ERRNO);
691 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: pdftoppm failed with error code: " . ($CHILD_ERROR >> 8));
692 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: File: $filename");
696 return $sfile->file_name . '.png';
709 SL::Controller::File - Controller for managing files
713 The Controller is called directly from the webpages
715 <a href="controller.pl?action=File/list&file_type=document\
716 &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
719 or indirectly via javascript functions from js/kivi.File.js
721 kivi.popup_dialog({ url: 'controller.pl',
722 data: { action : 'File/ajax_upload',
723 file_type : 'uploaded',
731 This is a controller for handling files in a storage independent way.
732 The storage may be a Filesystem,a WebDAV, a Database or DMS.
733 These backends must be configered in ClientConfig.
734 This Controller use as intermediate layer for storage C<SL::File>.
736 The Controller is responsible to display forms for displaying the files at the ERP-objects and
737 for uploading and downloading the files.
739 More description of the intermediate layer see L<SL::File>.
743 =head2 C<action_list>
745 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
746 Dependent of file_type different sources are available.
748 For documents there are the 'created' source and the imports from scanners or email.
749 For attachments and images only the 'uploaded' source available.
751 Available C<FORM PARAMS>:
755 =item C<form.object_id>
757 The Id of the ERP-object.
759 =item C<form.object_type>
761 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
763 =item C<form.file_type>
765 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
766 This file_type is a filter for the list.
770 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.
775 =head2 C<action_ajax_upload>
778 A new file or more files can selected by a dialog and insert into the system.
781 Available C<FORM PARAMS>:
785 =item C<form.file_type>
787 This parameter describe here the source for a new file :
788 "attachments" and "images"
790 This is a normal upload selection, which may be more then one file to upload.
792 =item C<form.object_id>
796 =item C<form.object_type>
798 are the same as at C<action_list>
802 =head2 C<action_ajax_files_uploaded>
804 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
805 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?).
806 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
808 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.
810 Available C<FORM PARAMS>:
814 =item C<form.ATTACHMENTS.uploadfiles>
816 This is an array of elements which have {filename} for the name and {data} for the contents.
818 Also object_id, object_type and file_type
822 =head2 C<action_download>
824 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
826 Available C<FORM PARAMS>:
830 Also object_id, object_type and file_type
834 =head2 C<action_ajax_importdialog>
836 A Dialog with all available and not imported files to import is open.
837 More then one file can be selected.
839 Available C<FORM PARAMS>:
845 The name of the source like "scanner1" or "email"
849 The full path to the directory on the server, where the files to import can found
851 Also object_id, object_type and file_type
855 =head2 C<action_ajax_delete>
857 Some files can be deleted
859 Available C<FORM PARAMS>:
865 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
869 =head2 C<action_ajax_unimport>
871 Some files can be unimported, dependent of the source of the file. This means they are moved
872 back to the directory of the source
874 Available C<FORM PARAMS>:
880 The ids of the files to unimport. Only these files are unimported not all versions of a file if the exists
884 =head2 C<action_ajax_rename>
886 One file can be renamed. There can be some checks if the same filename still exists at one object.
890 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>