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 'credit_note' => { gen => 1, gltype => '', dir =>'CreditNote', model => 'Invoice', right => 'import_ar' },
63 'request_quotation' => { gen => 7, gltype => '', dir =>'RequestForQuotation', model => 'Order', right => 'import_ap' },
64 'purchase_order' => { gen => 7, gltype => '', dir =>'PurchaseOrder', model => 'Order', right => 'import_ap' },
65 'purchase_delivery_order' => { gen => 7, gltype => '', dir =>'PurchaseDeliveryOrder',model => 'DeliveryOrder', right => 'import_ap' },
66 'purchase_invoice' => { gen => 6, gltype => 'ap', dir =>'PurchaseInvoice', model => 'PurchaseInvoice',right => 'import_ap' },
67 'vendor' => { gen => 0, gltype => '', dir =>'Vendor', model => 'Vendor', right => 'xx' },
68 'customer' => { gen => 1, gltype => '', dir =>'Customer', model => 'Customer', right => 'xx' },
69 'project' => { gen => 0, gltype => '', dir =>'Project', model => 'Project', right => 'xx' },
70 'part' => { gen => 0, gltype => '', dir =>'Part', model => 'Part', right => 'xx' },
71 'gl_transaction' => { gen => 6, gltype => 'gl', dir =>'GeneralLedger', model => 'GLTransaction', right => 'import_ap' },
72 'draft' => { gen => 0, gltype => '', dir =>'Draft', model => 'Draft', right => 'xx' },
73 'csv_customer' => { gen => 1, gltype => '', dir =>'Reports', model => 'Customer', right => 'xx' },
74 'csv_vendor' => { gen => 1, gltype => '', dir =>'Reports', model => 'Vendor', right => 'xx' },
75 'shop_image' => { gen => 0, gltype => '', dir =>'ShopImages', model => 'Part', right => 'xx' },
76 'letter' => { gen => 7, gltype => '', dir =>'Letter', model => 'Letter', right => 'sales_letter_edit | purchase_letter_edit' },
80 # $main::locale->text('imported')
90 $is_json = 1 if $::form->{json};
92 $self->_do_list($is_json);
95 sub action_ajax_importdialog {
97 $::auth->assert($self->object_right);
98 my $path = $::form->{path};
99 my @files = $self->_get_from_import($path);
101 'name' => $::form->{source},
103 'chk_action' => $::form->{source}.'_import',
104 'chk_title' => $main::locale->text('Import scanned documents'),
105 'chkall_title' => $main::locale->text('Import all'),
108 $self->render('file/import_dialog',
115 sub action_ajax_import {
117 $::auth->assert($self->object_right);
118 my $ids = $::form->{ids};
119 my $source = $::form->{source};
120 my $path = $::form->{path};
121 my @files = $self->_get_from_import($path);
122 foreach my $filename (@{ $::form->{$ids} || [] }) {
123 my ($file, undef) = grep { $_->{name} eq $filename } @files;
125 my $obj = SL::File->save(object_id => $self->object_id,
126 object_type => $self->object_type,
127 mime_type => 'application/pdf',
129 file_type => 'document',
130 file_name => $file->{filename},
131 file_path => $file->{path}
133 unlink($file->{path}) if $obj;
139 sub action_ajax_delete {
141 $self->_delete_all(DO_DELETE, $::locale->text('Following files are deleted:'));
144 sub action_ajax_unimport {
146 $self->_delete_all(DO_UNIMPORT, $::locale->text('Following files are unimported:'));
149 sub action_ajax_rename {
151 my ($id, $version) = split /_/, $::form->{id};
152 my $file = SL::File->get(id => $id);
154 $self->js->flash('error', $::locale->text('File not exists !'))->render();
157 my $sessionfile = $::form->{sessionfile};
158 if ( $sessionfile && -f $sessionfile ) {
160 if ( $::form->{to} eq $file->file_name ) {
161 # no rename so use as new version
162 $file->save_file($sessionfile);
163 $self->js->flash('warning', $::locale->text('File \'#1\' is used as new Version !', $file->file_name));
166 # new filename, so it is a new file with the same attributes as the old file
168 SL::File->save(object_id => $file->object_id,
169 object_type => $file->object_type,
170 mime_type => $file->mime_type,
171 source => $file->source,
172 file_type => $file->file_type,
173 file_name => $::form->{to},
174 file_path => $sessionfile
176 unlink($sessionfile);
179 $self->js->flash( 'error', t8('internal error (see details)'))
180 ->flash_detail('error', $@)->render;
190 $result = $file->rename($::form->{to});
193 $self->js->flash( 'error', t8('internal error (see details)'))
194 ->flash_detail('error', $@)->render;
198 if ($result != SL::File::RENAME_OK) {
199 $self->js->flash('error',
200 $result == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
201 : $result == SL::File::RENAME_SAME ? $::locale->text('Same Filename !')
202 : $::locale->text('File not exists !'))
207 $self->is_global($::form->{is_global});
208 $self->file_type( $file->file_type);
209 $self->object_type($file->object_type);
210 $self->object_id( $file->object_id);
211 #$self->object_model($file_types{$file->module}->{model});
212 #$self->object_right($file_types{$file->module}->{right});
213 if ( $::form->{next_ids} ) {
214 my @existing = split(/,/, $::form->{next_ids});
215 $self->existing(\@existing);
220 sub action_ajax_upload {
222 $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
223 $self->{accept_types} = '';
224 $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
225 $self->render('file/upload_dialog',
231 sub action_ajax_files_uploaded {
234 my $source = 'uploaded';
236 if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
237 my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
238 foreach my $idx (0 .. scalar(@upfiles) - 1) {
240 my $fname = uri_unescape($upfiles[$idx]->{filename});
241 # normalize and find basename
242 # first split with unix rules
243 # after that split with windows rules
244 my ($volume, $directories, $basefile) = File::Spec::Unix->splitpath($fname);
245 ($volume, $directories, $basefile) = File::Spec::Win32->splitpath($basefile);
247 # to find real mime_type by magic we must save the filedata
249 my $sess_fname = "file_upload_" . $self->object_type . "_" . $self->object_id . "_" . $idx;
250 my $sfile = SL::SessionFile->new($sess_fname, mode => 'w');
252 $sfile->fh->print(${$upfiles[$idx]->{data}});
254 my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
257 # if filename has the suffix "pdf", but isn't really a pdf, set mimetype for no suffix
258 $mime_type = File::MimeInfo::Magic::mimetype($basefile);
259 $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
261 if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
264 my ($existobj) = SL::File->get_all(object_id => $self->object_id,
265 object_type => $self->object_type,
266 mime_type => $mime_type,
268 file_type => $self->file_type,
269 file_name => $basefile,
273 push @existing, $existobj->id.'_'.$sfile->file_name;
275 my $fileobj = SL::File->save(object_id => $self->object_id,
276 object_type => $self->object_type,
277 mime_type => $mime_type,
279 file_type => $self->file_type,
280 file_name => $basefile,
281 title => $::form->{title},
282 description => $::form->{description},
283 ## two possibilities: what is better ? content or sessionfile ??
284 file_contents => ${$upfiles[$idx]->{data}},
285 file_path => $sfile->file_name
287 unlink($sfile->file_name);
291 $self->js->flash( 'error', t8('internal error (see details)'))
292 ->flash_detail('error', $@)->render;
297 $self->existing(\@existing);
301 sub action_download {
304 my $id = $::form->{id};
305 my $version = $::form->{version};
307 my $file = SL::File->get(id => $id );
308 $file->version($version) if $version;
309 my $ref = $file->get_content;
310 if ( $file && $ref ) {
311 return $self->send_file($ref,
312 type => $file->mime_type,
313 name => $file->file_name,
318 sub action_ajax_get_thumbnail {
321 my $id = $::form->{file_id};
322 my $version = $::form->{file_version};
323 my $file = SL::File->get(id => $id);
325 $file->version($version) if $version;
327 my $thumbnail = _create_thumbnail($file, $::form->{size});
329 my $overlay_selector = '#enlarged_thumb_' . $id;
330 $overlay_selector .= '_' . $version if $version;
332 ->attr($overlay_selector, 'src', 'data:' . $thumbnail->{thumbnail_img_content_type} . ';base64,' . MIME::Base64::encode_base64($thumbnail->{thumbnail_img_content}))
333 ->data($overlay_selector, 'is-overlay-loaded', '1')
342 sub check_object_params {
345 my $id = ($::form->{object_id} // 0) * 1;
346 my $draftid = ($::form->{draft_id} // 0) * 1;
350 if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
352 $type = $::form->{object_type};
355 $id = $::form->{draft_id};
357 } elsif ( $::form->{object_type} ) {
358 $type = $::form->{object_type};
360 die "No object type" unless $type;
361 die "No file type" unless $::form->{file_type};
362 die "Unknown object type" unless $file_types{$type};
364 $self->is_global($gldoc);
365 $self->file_type($::form->{file_type});
366 $self->object_type($type);
367 $self->object_id($id);
368 $self->object_model($file_types{$type}->{model});
369 $self->object_right($file_types{$type}->{right});
371 # $::auth->assert($self->object_right);
373 # my $model = 'SL::DB::' . $self->object_model;
374 # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
384 my ($self, $do_unimport, $infotext) = @_;
386 my $ids = $::form->{ids};
387 foreach my $id_version (@{ $::form->{$ids} || [] }) {
388 my ($id, $version) = split /_/, $id_version;
389 my $dbfile = SL::File->get(id => $id);
392 $dbfile->version($version);
393 $files .= ' ' . $dbfile->file_name if $dbfile->delete_version;
395 $files .= ' ' . $dbfile->file_name if $dbfile->delete;
399 $self->js->flash('info', $infotext . $files) if $files;
404 my ($self, $json) = @_;
406 if ( $self->file_type eq 'document' ) {
408 push @object_types, $self->object_type;
409 push @object_types, qw(dunning1 dunning2 dunning3 dunning_invoice dunning_orig_invoice) if $self->object_type eq 'invoice'; # hardcoded object types?
410 @files = SL::File->get_all_versions(object_id => $self->object_id,
411 object_type => \@object_types,
412 file_type => $self->file_type,
416 elsif ( $self->file_type eq 'attachment' || $self->file_type eq 'image' ) {
417 @files = SL::File->get_all(object_id => $self->object_id,
418 object_type => $self->object_type,
419 file_type => $self->file_type,
422 $self->files(\@files);
424 $_->{thumbnail} = _create_thumbnail($_) for @files;
426 if($self->object_type eq 'shop_image'){
428 ->run('kivi.ShopPart.show_images', $self->object_id)
431 $self->_mk_render('file/list', 1, 0, $json);
435 sub _get_from_import {
436 my ($self, $path) = @_;
439 my $language = $::lx_office_conf{system}->{language};
440 my $timezone = $::locale->get_local_time_zone()->name;
441 if (opendir my $dir, $path) {
442 my @files = (readdir $dir);
443 foreach my $file ( @files) {
444 next if (($file eq '.') || ($file eq '..'));
445 $file = Encode::decode('utf-8', $file);
447 next if ( -d "$path/$file" );
449 my $tmppath = File::Spec->catfile( $path, $file );
450 next if( ! -f $tmppath );
452 my $st = stat($tmppath);
453 my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
454 my $sname = $main::locale->quote_special_chars('HTML', $file);
457 'filename' => $sname,
459 'mtime' => $st->mtime,
460 'date' => $dt->dmy('.') . " " . $dt->hms,
467 $::lxdebug->message(LXDebug::WARN(), "SL::File::_get_from_import opendir failed to open dir " . $path);
474 my ($self, $template, $edit, $scanner, $json) = @_;
477 ##TODO make code configurable
480 my @sources = $self->_get_sources();
481 foreach my $source ( @sources ) {
482 @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
484 if ( $self->file_type eq 'document' ) {
485 $title = $main::locale->text('Documents');
486 } elsif ( $self->file_type eq 'attachment' ) {
487 $title = $main::locale->text('Attachments');
488 } elsif ( $self->file_type eq 'image' ) {
489 $title = $main::locale->text('Images');
492 my $output = SL::Presenter->get->render(
495 SOURCES => \@sources,
496 edit_attachments => $edit,
497 object_type => $self->object_type,
498 object_id => $self->object_id,
499 file_type => $self->file_type,
500 is_global => $self->is_global,
504 $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
505 if ( $self->existing && scalar(@{$self->existing}) > 0) {
506 my $first = shift @{$self->existing};
507 my ($first_id, $sfile) = split('_', $first, 2);
508 my $file = SL::File->get(id => $first_id );
509 $self->js->run('kivi.File.askForRename', $first_id, $file->file_type, $file->file_name, $sfile, join (',', @{$self->existing}), $self->is_global);
513 $self->render(\$output, { layout => 0, process => 0 });
518 $self->js->flash( 'error', t8('internal error (see details)'))
519 ->flash_detail('error', $@)->render;
521 $self->render('generic/error', { layout => 0 }, label_error => $@);
530 if ( $self->file_type eq 'document' ) {
531 # TODO statt gen neue attribute in filetypes :
532 if (($file_types{$self->object_type}->{gen}*1 & 4)==4) {
533 # bit 3 is set => means upload
535 'name' => 'uploaded',
536 'title' => $main::locale->text('uploaded Documents'),
537 'chk_action' => 'uploaded_documents_delete',
538 'chk_title' => $main::locale->text('Delete Documents'),
539 'chkall_title' => $main::locale->text('Delete all'),
540 'file_title' => $main::locale->text('filename'),
541 'confirm_text' => $main::locale->text('delete'),
543 'are_existing' => $self->existing ? 1 : 0,
544 'rename_title' => $main::locale->text('Rename Attachments'),
547 'upload_title' => $main::locale->text('Upload Documents'),
548 'done_text' => $main::locale->text('deleted')
550 push @sources , $source;
553 if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
556 'title' => $main::locale->text('generated Files'),
557 'chk_action' => 'documents_delete',
558 'chk_title' => $main::locale->text('Delete Documents'),
559 'chkall_title' => $main::locale->text('Delete all'),
560 'file_title' => $main::locale->text('filename'),
561 'confirm_text' => $main::locale->text('delete'),
562 'can_delete' => $::instance_conf->get_doc_delete_printfiles,
563 'can_rename' => $::instance_conf->get_doc_delete_printfiles,
564 'rename_title' => $main::locale->text('Rename Documents'),
565 'done_text' => $main::locale->text('deleted')
567 push @sources , $gendata;
570 if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
571 my @others = SL::File->get_other_sources();
572 foreach my $scanner_or_mailrx (@others) {
574 'name' => $scanner_or_mailrx->{name},
575 'title' => $main::locale->text('from \'#1\' imported Files', $scanner_or_mailrx->{description}),
576 'chk_action' => $scanner_or_mailrx->{name}.'_unimport',
577 'chk_title' => $main::locale->text('Unimport documents'),
578 'chkall_title' => $main::locale->text('Unimport all'),
579 'file_title' => $main::locale->text('filename'),
580 'confirm_text' => $main::locale->text('unimport'),
582 'rename_title' => $main::locale->text('Rename Documents'),
585 'import_title' => $main::locale->text('Add Document from \'#1\'', $scanner_or_mailrx->{name}),
586 'path' => $scanner_or_mailrx->{directory},
587 'done_text' => $main::locale->text('unimported')
589 push @sources , $other;
593 elsif ( $self->file_type eq 'attachment' ) {
595 'name' => 'uploaded',
596 'title' => $main::locale->text(''),
597 'chk_action' => 'attachments_delete',
598 'chk_title' => $main::locale->text('Delete Attachments'),
599 'chkall_title' => $main::locale->text('Delete all'),
600 'file_title' => $main::locale->text('filename'),
601 'confirm_text' => $main::locale->text('delete'),
603 'are_existing' => $self->existing ? 1 : 0,
604 'rename_title' => $main::locale->text('Rename Attachments'),
607 'upload_title' => $main::locale->text('Upload Attachments'),
608 'done_text' => $main::locale->text('deleted')
610 push @sources , $attdata;
612 elsif ( $self->file_type eq 'image' ) {
614 'name' => 'uploaded',
615 'title' => $main::locale->text(''),
616 'chk_action' => 'images_delete',
617 'chk_title' => $main::locale->text('Delete Images'),
618 'chkall_title' => $main::locale->text('Delete all'),
619 'file_title' => $main::locale->text('filename'),
620 'confirm_text' => $main::locale->text('delete'),
622 'are_existing' => $self->existing ? 1 : 0,
623 'rename_title' => $main::locale->text('Rename Images'),
626 'upload_title' => $main::locale->text('Upload Images'),
627 'done_text' => $main::locale->text('deleted')
629 push @sources , $attdata;
635 # todo: cache thumbs?
636 sub _create_thumbnail {
637 my ($file, $size) = @_;
642 if (!eval { $filename = $file->get_file(); 1; }) {
643 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail get_file failed: " . $EVAL_ERROR);
647 # Workaround for pfds which are not handled by file_probe_type.
648 # Maybe use mime info stored in db?
649 my $mime_type = File::MimeInfo::Magic::magic($filename);
650 if ($mime_type =~ m{pdf}) {
651 $filename = _convert_pdf_to_png($filename, size => $size);
653 return if !$filename;
656 if (!eval { $content = slurp $filename; 1; }) {
657 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail slurp failed: " . $EVAL_ERROR);
662 if (!eval { $ret = file_probe_type($content, size => $size); 1; }) {
663 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type failed: " . $EVAL_ERROR);
667 # file_probe_type returns a hash ref with thumbnail info and content
668 # or an error message
669 if ('HASH' ne ref $ret) {
670 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type returned an error: " . $ret);
677 sub _convert_pdf_to_png {
678 my ($filename, %params) = @_;
680 my $size = $params{size} // 64;
681 my $sfile = SL::SessionFile::Random->new();
682 unless (-f $filename) {
683 $::lxdebug->message(LXDebug::WARN(), "_convert_pdf_to_png failed, no file found: $filename");
686 # quotemeta for storno case "storno\ zu\ 1020" *nix only
687 my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . quotemeta($filename) . ' ' . $sfile->file_name;
689 if (system($command) == -1) {
690 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: system call failed: " . $ERRNO);
694 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: pdftoppm failed with error code: " . ($CHILD_ERROR >> 8));
695 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: File: $filename");
699 return $sfile->file_name . '.png';
712 SL::Controller::File - Controller for managing files
716 The Controller is called directly from the webpages
718 <a href="controller.pl?action=File/list&file_type=document\
719 &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
722 or indirectly via javascript functions from js/kivi.File.js
724 kivi.popup_dialog({ url: 'controller.pl',
725 data: { action : 'File/ajax_upload',
726 file_type : 'uploaded',
734 This is a controller for handling files in a storage independent way.
735 The storage may be a Filesystem,a WebDAV, a Database or DMS.
736 These backends must be configered in ClientConfig.
737 This Controller use as intermediate layer for storage C<SL::File>.
739 The Controller is responsible to display forms for displaying the files at the ERP-objects and
740 for uploading and downloading the files.
742 More description of the intermediate layer see L<SL::File>.
746 =head2 C<action_list>
748 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
749 Dependent of file_type different sources are available.
751 For documents there are the 'created' source and the imports from scanners or email.
752 For attachments and images only the 'uploaded' source available.
754 Available C<FORM PARAMS>:
758 =item C<form.object_id>
760 The Id of the ERP-object.
762 =item C<form.object_type>
764 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
766 =item C<form.file_type>
768 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
769 This file_type is a filter for the list.
773 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.
778 =head2 C<action_ajax_upload>
781 A new file or more files can selected by a dialog and insert into the system.
784 Available C<FORM PARAMS>:
788 =item C<form.file_type>
790 This parameter describe here the source for a new file :
791 "attachments" and "images"
793 This is a normal upload selection, which may be more then one file to upload.
795 =item C<form.object_id>
799 =item C<form.object_type>
801 are the same as at C<action_list>
805 =head2 C<action_ajax_files_uploaded>
807 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
808 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?).
809 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
811 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.
813 Available C<FORM PARAMS>:
817 =item C<form.ATTACHMENTS.uploadfiles>
819 This is an array of elements which have {filename} for the name and {data} for the contents.
821 Also object_id, object_type and file_type
825 =head2 C<action_download>
827 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
829 Available C<FORM PARAMS>:
833 Also object_id, object_type and file_type
837 =head2 C<action_ajax_importdialog>
839 A Dialog with all available and not imported files to import is open.
840 More then one file can be selected.
842 Available C<FORM PARAMS>:
848 The name of the source like "scanner1" or "email"
852 The full path to the directory on the server, where the files to import can found
854 Also object_id, object_type and file_type
858 =head2 C<action_ajax_delete>
860 Some files can be deleted
862 Available C<FORM PARAMS>:
868 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
872 =head2 C<action_ajax_unimport>
874 Some files can be unimported, dependent of the source of the file. This means they are moved
875 back to the directory of the source
877 Available C<FORM PARAMS>:
883 The ids of the files to unimport. Only these files are unimported not all versions of a file if the exists
887 =head2 C<action_ajax_rename>
889 One file can be renamed. There can be some checks if the same filename still exists at one object.
893 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>