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) = @_;
408 if ( $self->file_type eq 'document' ) {
410 push @object_types, $self->object_type;
411 push @object_types, qw(dunning1 dunning2 dunning3 dunning_invoice dunning_orig_invoice) if $self->object_type eq 'invoice'; # hardcoded object types?
412 @files = SL::File->get_all_versions(object_id => $self->object_id,
413 object_type => \@object_types,
414 file_type => $self->file_type,
418 elsif ( $self->file_type eq 'attachment' || $self->file_type eq 'image' ) {
419 @files = SL::File->get_all(object_id => $self->object_id,
420 object_type => $self->object_type,
421 file_type => $self->file_type,
424 $self->files(\@files);
426 $_->{thumbnail} = _create_thumbnail($_) for @files;
428 if($self->object_type eq 'shop_image'){
430 ->run('kivi.ShopPart.show_images', $self->object_id)
433 $self->_mk_render('file/list', 1, 0, $json);
437 sub _get_from_import {
438 my ($self, $path) = @_;
441 my $language = $::lx_office_conf{system}->{language};
442 my $timezone = $::locale->get_local_time_zone()->name;
443 if (opendir my $dir, $path) {
444 my @files = (readdir $dir);
445 foreach my $file ( @files) {
446 next if (($file eq '.') || ($file eq '..'));
447 $file = Encode::decode('utf-8', $file);
449 next if ( -d "$path/$file" );
451 my $tmppath = File::Spec->catfile( $path, $file );
452 next if( ! -f $tmppath );
454 my $st = stat($tmppath);
455 my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
456 my $sname = $main::locale->quote_special_chars('HTML', $file);
459 'filename' => $sname,
461 'mtime' => $st->mtime,
462 'date' => $dt->dmy('.') . " " . $dt->hms,
469 $::lxdebug->message(LXDebug::WARN(), "SL::File::_get_from_import opendir failed to open dir " . $path);
476 my ($self, $template, $edit, $scanner, $json) = @_;
479 ##TODO make code configurable
482 my @sources = $self->_get_sources();
483 foreach my $source ( @sources ) {
484 @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
486 if ( $self->file_type eq 'document' ) {
487 $title = $main::locale->text('Documents');
488 } elsif ( $self->file_type eq 'attachment' ) {
489 $title = $main::locale->text('Attachments');
490 } elsif ( $self->file_type eq 'image' ) {
491 $title = $main::locale->text('Images');
494 my $output = SL::Presenter->get->render(
497 SOURCES => \@sources,
498 edit_attachments => $edit,
499 object_type => $self->object_type,
500 object_id => $self->object_id,
501 file_type => $self->file_type,
502 is_global => $self->is_global,
506 $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
507 if ( $self->existing && scalar(@{$self->existing}) > 0) {
508 my $first = shift @{$self->existing};
509 my ($first_id, $sfile) = split('_', $first, 2);
510 my $file = SL::File->get(id => $first_id );
511 $self->js->run('kivi.File.askForRename', $first_id, $file->file_type, $file->file_name, $sfile, join (',', @{$self->existing}), $self->is_global);
515 $self->render(\$output, { layout => 0, process => 0 });
520 $self->js->flash( 'error', t8('internal error (see details)'))
521 ->flash_detail('error', $@)->render;
523 $self->render('generic/error', { layout => 0 }, label_error => $@);
532 if ( $self->file_type eq 'document' ) {
533 # TODO statt gen neue attribute in filetypes :
534 if (($file_types{$self->object_type}->{gen}*1 & 4)==4) {
535 # bit 3 is set => means upload
537 'name' => 'uploaded',
538 'title' => $main::locale->text('uploaded Documents'),
539 'chk_action' => 'uploaded_documents_delete',
540 'chk_title' => $main::locale->text('Delete Documents'),
541 'chkall_title' => $main::locale->text('Delete all'),
542 'file_title' => $main::locale->text('filename'),
543 'confirm_text' => $main::locale->text('delete'),
545 'are_existing' => $self->existing ? 1 : 0,
546 'rename_title' => $main::locale->text('Rename Attachments'),
549 'upload_title' => $main::locale->text('Upload Documents'),
550 'done_text' => $main::locale->text('deleted')
552 push @sources , $source;
555 if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
558 'title' => $main::locale->text('generated Files'),
559 'chk_action' => 'documents_delete',
560 'chk_title' => $main::locale->text('Delete Documents'),
561 'chkall_title' => $main::locale->text('Delete all'),
562 'file_title' => $main::locale->text('filename'),
563 'confirm_text' => $main::locale->text('delete'),
564 'can_delete' => $::instance_conf->get_doc_delete_printfiles,
565 'can_rename' => $::instance_conf->get_doc_delete_printfiles,
566 'rename_title' => $main::locale->text('Rename Documents'),
567 'done_text' => $main::locale->text('deleted')
569 push @sources , $gendata;
572 if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
573 my @others = SL::File->get_other_sources();
574 foreach my $scanner_or_mailrx (@others) {
576 'name' => $scanner_or_mailrx->{name},
577 'title' => $main::locale->text('from \'#1\' imported Files', $scanner_or_mailrx->{description}),
578 'chk_action' => $scanner_or_mailrx->{name}.'_unimport',
579 'chk_title' => $main::locale->text('Unimport documents'),
580 'chkall_title' => $main::locale->text('Unimport all'),
581 'file_title' => $main::locale->text('filename'),
582 'confirm_text' => $main::locale->text('unimport'),
584 'rename_title' => $main::locale->text('Rename Documents'),
587 'import_title' => $main::locale->text('Add Document from \'#1\'', $scanner_or_mailrx->{name}),
588 'path' => $scanner_or_mailrx->{directory},
589 'done_text' => $main::locale->text('unimported')
591 push @sources , $other;
595 elsif ( $self->file_type eq 'attachment' ) {
597 'name' => 'uploaded',
598 'title' => $main::locale->text(''),
599 'chk_action' => 'attachments_delete',
600 'chk_title' => $main::locale->text('Delete Attachments'),
601 'chkall_title' => $main::locale->text('Delete all'),
602 'file_title' => $main::locale->text('filename'),
603 'confirm_text' => $main::locale->text('delete'),
605 'are_existing' => $self->existing ? 1 : 0,
606 'rename_title' => $main::locale->text('Rename Attachments'),
609 'upload_title' => $main::locale->text('Upload Attachments'),
610 'done_text' => $main::locale->text('deleted')
612 push @sources , $attdata;
614 elsif ( $self->file_type eq 'image' ) {
616 'name' => 'uploaded',
617 'title' => $main::locale->text(''),
618 'chk_action' => 'images_delete',
619 'chk_title' => $main::locale->text('Delete Images'),
620 'chkall_title' => $main::locale->text('Delete all'),
621 'file_title' => $main::locale->text('filename'),
622 'confirm_text' => $main::locale->text('delete'),
624 'are_existing' => $self->existing ? 1 : 0,
625 'rename_title' => $main::locale->text('Rename Images'),
628 'upload_title' => $main::locale->text('Upload Images'),
629 'done_text' => $main::locale->text('deleted')
631 push @sources , $attdata;
637 # todo: cache thumbs?
638 sub _create_thumbnail {
639 my ($file, $size) = @_;
644 if (!eval { $filename = $file->get_file(); 1; }) {
645 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail get_file failed: " . $EVAL_ERROR);
649 # Workaround for pfds which are not handled by file_probe_type.
650 # Maybe use mime info stored in db?
651 my $mime_type = File::MimeInfo::Magic::magic($filename);
652 if ($mime_type =~ m{pdf}) {
653 $filename = _convert_pdf_to_png($filename, size => $size);
655 return if !$filename;
658 if (!eval { $content = slurp $filename; 1; }) {
659 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail slurp failed: " . $EVAL_ERROR);
664 if (!eval { $ret = file_probe_type($content, size => $size); 1; }) {
665 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type failed: " . $EVAL_ERROR);
669 # file_probe_type returns a hash ref with thumbnail info and content
670 # or an error message
671 if ('HASH' ne ref $ret) {
672 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type returned an error: " . $ret);
679 sub _convert_pdf_to_png {
680 my ($filename, %params) = @_;
682 my $size = $params{size} // 64;
683 my $sfile = SL::SessionFile::Random->new();
684 unless (-f $filename) {
685 $::lxdebug->message(LXDebug::WARN(), "_convert_pdf_to_png failed, no file found: $filename");
688 # quotemeta for storno case "storno\ zu\ 1020" *nix only
689 my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . quotemeta($filename) . ' ' . $sfile->file_name;
691 if (system($command) == -1) {
692 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: system call failed: " . $ERRNO);
696 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: pdftoppm failed with error code: " . ($CHILD_ERROR >> 8));
697 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: File: $filename");
701 return $sfile->file_name . '.png';
714 SL::Controller::File - Controller for managing files
718 The Controller is called directly from the webpages
720 <a href="controller.pl?action=File/list&file_type=document\
721 &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
724 or indirectly via javascript functions from js/kivi.File.js
726 kivi.popup_dialog({ url: 'controller.pl',
727 data: { action : 'File/ajax_upload',
728 file_type : 'uploaded',
736 This is a controller for handling files in a storage independent way.
737 The storage may be a Filesystem,a WebDAV, a Database or DMS.
738 These backends must be configered in ClientConfig.
739 This Controller use as intermediate layer for storage C<SL::File>.
741 The Controller is responsible to display forms for displaying the files at the ERP-objects and
742 for uploading and downloading the files.
744 More description of the intermediate layer see L<SL::File>.
748 =head2 C<action_list>
750 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
751 Dependent of file_type different sources are available.
753 For documents there are the 'created' source and the imports from scanners or email.
754 For attachments and images only the 'uploaded' source available.
756 Available C<FORM PARAMS>:
760 =item C<form.object_id>
762 The Id of the ERP-object.
764 =item C<form.object_type>
766 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
768 =item C<form.file_type>
770 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
771 This file_type is a filter for the list.
775 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.
780 =head2 C<action_ajax_upload>
783 A new file or more files can selected by a dialog and insert into the system.
786 Available C<FORM PARAMS>:
790 =item C<form.file_type>
792 This parameter describe here the source for a new file :
793 "attachments" and "images"
795 This is a normal upload selection, which may be more then one file to upload.
797 =item C<form.object_id>
801 =item C<form.object_type>
803 are the same as at C<action_list>
807 =head2 C<action_ajax_files_uploaded>
809 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
810 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?).
811 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
813 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.
815 Available C<FORM PARAMS>:
819 =item C<form.ATTACHMENTS.uploadfiles>
821 This is an array of elements which have {filename} for the name and {data} for the contents.
823 Also object_id, object_type and file_type
827 =head2 C<action_download>
829 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
831 Available C<FORM PARAMS>:
835 Also object_id, object_type and file_type
839 =head2 C<action_ajax_importdialog>
841 A Dialog with all available and not imported files to import is open.
842 More then one file can be selected.
844 Available C<FORM PARAMS>:
850 The name of the source like "scanner1" or "email"
854 The full path to the directory on the server, where the files to import can found
856 Also object_id, object_type and file_type
860 =head2 C<action_ajax_delete>
862 Some files can be deleted
864 Available C<FORM PARAMS>:
870 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
874 =head2 C<action_ajax_unimport>
876 Some files can be unimported, dependent of the source of the file. This means they are moved
877 back to the directory of the source
879 Available C<FORM PARAMS>:
885 The ids of the files to unimport. Only these files are unimported not all versions of a file if the exists
889 =head2 C<action_ajax_rename>
891 One file can be renamed. There can be some checks if the same filename still exists at one object.
895 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>