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 'part' => { gen => 0, gltype => '', dir =>'Part', model => 'Part', right => 'xx' },
70 'gl_transaction' => { gen => 6, gltype => 'gl', dir =>'GeneralLedger', model => 'GLTransaction', right => 'import_ap' },
71 'draft' => { gen => 0, gltype => '', dir =>'Draft', model => 'Draft', right => 'xx' },
72 'csv_customer' => { gen => 1, gltype => '', dir =>'Reports', model => 'Customer', right => 'xx' },
73 'csv_vendor' => { gen => 1, gltype => '', dir =>'Reports', model => 'Vendor', right => 'xx' },
74 'shop_image' => { gen => 0, gltype => '', dir =>'ShopImages', model => 'Part', right => 'xx' },
75 'letter' => { gen => 7, gltype => '', dir =>'Letter', model => 'Letter', right => 'sales_letter_edit | purchase_letter_edit' },
79 # $main::locale->text('imported')
89 $is_json = 1 if $::form->{json};
91 $self->_do_list($is_json);
94 sub action_ajax_importdialog {
96 $::auth->assert($self->object_right);
97 my $path = $::form->{path};
98 my @files = $self->_get_from_import($path);
100 'name' => $::form->{source},
102 'chk_action' => $::form->{source}.'_import',
103 'chk_title' => $main::locale->text('Import scanned documents'),
104 'chkall_title' => $main::locale->text('Import all'),
107 $self->render('file/import_dialog',
114 sub action_ajax_import {
116 $::auth->assert($self->object_right);
117 my $ids = $::form->{ids};
118 my $source = $::form->{source};
119 my $path = $::form->{path};
120 my @files = $self->_get_from_import($path);
121 foreach my $filename (@{ $::form->{$ids} || [] }) {
122 my ($file, undef) = grep { $_->{name} eq $filename } @files;
124 my $obj = SL::File->save(object_id => $self->object_id,
125 object_type => $self->object_type,
126 mime_type => 'application/pdf',
128 file_type => 'document',
129 file_name => $file->{filename},
130 file_path => $file->{path}
132 unlink($file->{path}) if $obj;
138 sub action_ajax_delete {
140 $self->_delete_all(DO_DELETE, $::locale->text('Following files are deleted:'));
143 sub action_ajax_unimport {
145 $self->_delete_all(DO_UNIMPORT, $::locale->text('Following files are unimported:'));
148 sub action_ajax_rename {
150 my ($id, $version) = split /_/, $::form->{id};
151 my $file = SL::File->get(id => $id);
153 $self->js->flash('error', $::locale->text('File not exists !'))->render();
156 my $sessionfile = $::form->{sessionfile};
157 if ( $sessionfile && -f $sessionfile ) {
159 if ( $::form->{to} eq $file->file_name ) {
160 # no rename so use as new version
161 $file->save_file($sessionfile);
162 $self->js->flash('warning', $::locale->text('File \'#1\' is used as new Version !', $file->file_name));
165 # new filename, so it is a new file with the same attributes as the old file
167 SL::File->save(object_id => $file->object_id,
168 object_type => $file->object_type,
169 mime_type => $file->mime_type,
170 source => $file->source,
171 file_type => $file->file_type,
172 file_name => $::form->{to},
173 file_path => $sessionfile
175 unlink($sessionfile);
178 $self->js->flash( 'error', t8('internal error (see details)'))
179 ->flash_detail('error', $@)->render;
189 $result = $file->rename($::form->{to});
192 $self->js->flash( 'error', t8('internal error (see details)'))
193 ->flash_detail('error', $@)->render;
197 if ($result != SL::File::RENAME_OK) {
198 $self->js->flash('error',
199 $result == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
200 : $result == SL::File::RENAME_SAME ? $::locale->text('Same Filename !')
201 : $::locale->text('File not exists !'))
206 $self->is_global($::form->{is_global});
207 $self->file_type( $file->file_type);
208 $self->object_type($file->object_type);
209 $self->object_id( $file->object_id);
210 #$self->object_model($file_types{$file->module}->{model});
211 #$self->object_right($file_types{$file->module}->{right});
212 if ( $::form->{next_ids} ) {
213 my @existing = split(/,/, $::form->{next_ids});
214 $self->existing(\@existing);
219 sub action_ajax_upload {
221 $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
222 $self->{accept_types} = '';
223 $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
224 $self->render('file/upload_dialog',
230 sub action_ajax_files_uploaded {
233 my $source = 'uploaded';
235 if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
236 my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
237 foreach my $idx (0 .. scalar(@upfiles) - 1) {
239 my $fname = uri_unescape($upfiles[$idx]->{filename});
240 # normalize and find basename
241 # first split with unix rules
242 # after that split with windows rules
243 my ($volume, $directories, $basefile) = File::Spec::Unix->splitpath($fname);
244 ($volume, $directories, $basefile) = File::Spec::Win32->splitpath($basefile);
246 # to find real mime_type by magic we must save the filedata
248 my $sess_fname = "file_upload_" . $self->object_type . "_" . $self->object_id . "_" . $idx;
249 my $sfile = SL::SessionFile->new($sess_fname, mode => 'w');
251 $sfile->fh->print(${$upfiles[$idx]->{data}});
253 my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
256 # if filename has the suffix "pdf", but isn't really a pdf, set mimetype for no suffix
257 $mime_type = File::MimeInfo::Magic::mimetype($basefile);
258 $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
260 if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
263 my ($existobj) = SL::File->get_all(object_id => $self->object_id,
264 object_type => $self->object_type,
265 mime_type => $mime_type,
267 file_type => $self->file_type,
268 file_name => $basefile,
272 push @existing, $existobj->id.'_'.$sfile->file_name;
274 my $fileobj = SL::File->save(object_id => $self->object_id,
275 object_type => $self->object_type,
276 mime_type => $mime_type,
278 file_type => $self->file_type,
279 file_name => $basefile,
280 title => $::form->{title},
281 description => $::form->{description},
282 ## two possibilities: what is better ? content or sessionfile ??
283 file_contents => ${$upfiles[$idx]->{data}},
284 file_path => $sfile->file_name
286 unlink($sfile->file_name);
290 $self->js->flash( 'error', t8('internal error (see details)'))
291 ->flash_detail('error', $@)->render;
296 $self->existing(\@existing);
300 sub action_download {
303 my $id = $::form->{id};
304 my $version = $::form->{version};
306 my $file = SL::File->get(id => $id );
307 $file->version($version) if $version;
308 my $ref = $file->get_content;
309 if ( $file && $ref ) {
310 return $self->send_file($ref,
311 type => $file->mime_type,
312 name => $file->file_name,
317 sub action_ajax_get_thumbnail {
320 my $file = SL::File->get(id => $::form->{file_id});
321 my $thumbnail = _create_thumbnail($file, $::form->{size});
323 my $overlay_selector = '#enlarged_thumb_' . $::form->{file_id};
325 ->attr($overlay_selector, 'src', 'data:' . $thumbnail->{thumbnail_img_content_type} . ';base64,' . MIME::Base64::encode_base64($thumbnail->{thumbnail_img_content}))
326 ->data($overlay_selector, 'is-overlay-loaded', '1')
335 sub check_object_params {
338 my $id = ($::form->{object_id} // 0) * 1;
339 my $draftid = ($::form->{draft_id} // 0) * 1;
343 if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
345 $type = $::form->{object_type};
348 $id = $::form->{draft_id};
350 } elsif ( $::form->{object_type} ) {
351 $type = $::form->{object_type};
353 die "No object type" unless $type;
354 die "No file type" unless $::form->{file_type};
355 die "Unknown object type" unless $file_types{$type};
357 $self->is_global($gldoc);
358 $self->file_type($::form->{file_type});
359 $self->object_type($type);
360 $self->object_id($id);
361 $self->object_model($file_types{$type}->{model});
362 $self->object_right($file_types{$type}->{right});
364 # $::auth->assert($self->object_right);
366 # my $model = 'SL::DB::' . $self->object_model;
367 # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
377 my ($self, $do_unimport, $infotext) = @_;
379 my $ids = $::form->{ids};
380 foreach my $id_version (@{ $::form->{$ids} || [] }) {
381 my ($id, $version) = split /_/, $id_version;
382 my $dbfile = SL::File->get(id => $id);
385 $dbfile->version($version);
386 $files .= ' ' . $dbfile->file_name if $dbfile->delete_version;
388 $files .= ' ' . $dbfile->file_name if $dbfile->delete;
392 $self->js->flash('info', $infotext . $files) if $files;
397 my ($self, $json) = @_;
399 if ( $self->file_type eq 'document' ) {
401 push @object_types, $self->object_type;
402 push @object_types, qw(dunning1 dunning2 dunning3 dunning_invoice dunning_orig_invoice) if $self->object_type eq 'invoice'; # hardcoded object types?
403 @files = SL::File->get_all_versions(object_id => $self->object_id,
404 object_type => \@object_types,
405 file_type => $self->file_type,
409 elsif ( $self->file_type eq 'attachment' || $self->file_type eq 'image' ) {
410 @files = SL::File->get_all(object_id => $self->object_id,
411 object_type => $self->object_type,
412 file_type => $self->file_type,
415 $self->files(\@files);
417 $_->{thumbnail} = _create_thumbnail($_) for @files;
419 if($self->object_type eq 'shop_image'){
421 ->run('kivi.ShopPart.show_images', $self->object_id)
424 $self->_mk_render('file/list', 1, 0, $json);
428 sub _get_from_import {
429 my ($self, $path) = @_;
432 my $language = $::lx_office_conf{system}->{language};
433 my $timezone = $::locale->get_local_time_zone()->name;
434 if (opendir my $dir, $path) {
435 my @files = (readdir $dir);
436 foreach my $file ( @files) {
437 next if (($file eq '.') || ($file eq '..'));
438 $file = Encode::decode('utf-8', $file);
440 next if ( -d "$path/$file" );
442 my $tmppath = File::Spec->catfile( $path, $file );
443 next if( ! -f $tmppath );
445 my $st = stat($tmppath);
446 my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
447 my $sname = $main::locale->quote_special_chars('HTML', $file);
450 'filename' => $sname,
452 'mtime' => $st->mtime,
453 'date' => $dt->dmy('.') . " " . $dt->hms,
460 $::lxdebug->message(LXDebug::WARN(), "SL::File::_get_from_import opendir failed to open dir " . $path);
467 my ($self, $template, $edit, $scanner, $json) = @_;
470 ##TODO make code configurable
473 my @sources = $self->_get_sources();
474 foreach my $source ( @sources ) {
475 @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
477 if ( $self->file_type eq 'document' ) {
478 $title = $main::locale->text('Documents');
479 } elsif ( $self->file_type eq 'attachment' ) {
480 $title = $main::locale->text('Attachments');
481 } elsif ( $self->file_type eq 'image' ) {
482 $title = $main::locale->text('Images');
485 my $output = SL::Presenter->get->render(
488 SOURCES => \@sources,
489 edit_attachments => $edit,
490 object_type => $self->object_type,
491 object_id => $self->object_id,
492 file_type => $self->file_type,
493 is_global => $self->is_global,
497 $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
498 if ( $self->existing && scalar(@{$self->existing}) > 0) {
499 my $first = shift @{$self->existing};
500 my ($first_id, $sfile) = split('_', $first, 2);
501 my $file = SL::File->get(id => $first_id );
502 $self->js->run('kivi.File.askForRename', $first_id, $file->file_type, $file->file_name, $sfile, join (',', @{$self->existing}), $self->is_global);
506 $self->render(\$output, { layout => 0, process => 0 });
511 $self->js->flash( 'error', t8('internal error (see details)'))
512 ->flash_detail('error', $@)->render;
514 $self->render('generic/error', { layout => 0 }, label_error => $@);
523 if ( $self->file_type eq 'document' ) {
524 # TODO statt gen neue attribute in filetypes :
525 if (($file_types{$self->object_type}->{gen}*1 & 4)==4) {
526 # bit 3 is set => means upload
528 'name' => 'uploaded',
529 'title' => $main::locale->text('uploaded Documents'),
530 'chk_action' => 'uploaded_documents_delete',
531 'chk_title' => $main::locale->text('Delete Documents'),
532 'chkall_title' => $main::locale->text('Delete all'),
533 'file_title' => $main::locale->text('filename'),
534 'confirm_text' => $main::locale->text('delete'),
536 'are_existing' => $self->existing ? 1 : 0,
537 'rename_title' => $main::locale->text('Rename Attachments'),
540 'upload_title' => $main::locale->text('Upload Documents'),
541 'done_text' => $main::locale->text('deleted')
543 push @sources , $source;
546 if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
549 'title' => $main::locale->text('generated Files'),
550 'chk_action' => 'documents_delete',
551 'chk_title' => $main::locale->text('Delete Documents'),
552 'chkall_title' => $main::locale->text('Delete all'),
553 'file_title' => $main::locale->text('filename'),
554 'confirm_text' => $main::locale->text('delete'),
555 'can_delete' => $::instance_conf->get_doc_delete_printfiles,
556 'can_rename' => $::instance_conf->get_doc_delete_printfiles,
557 'rename_title' => $main::locale->text('Rename Documents'),
558 'done_text' => $main::locale->text('deleted')
560 push @sources , $gendata;
563 if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
564 my @others = SL::File->get_other_sources();
565 foreach my $scanner_or_mailrx (@others) {
567 'name' => $scanner_or_mailrx->{name},
568 'title' => $main::locale->text('from \'#1\' imported Files', $scanner_or_mailrx->{description}),
569 'chk_action' => $scanner_or_mailrx->{name}.'_unimport',
570 'chk_title' => $main::locale->text('Unimport documents'),
571 'chkall_title' => $main::locale->text('Unimport all'),
572 'file_title' => $main::locale->text('filename'),
573 'confirm_text' => $main::locale->text('unimport'),
575 'rename_title' => $main::locale->text('Rename Documents'),
578 'import_title' => $main::locale->text('Add Document from \'#1\'', $scanner_or_mailrx->{name}),
579 'path' => $scanner_or_mailrx->{directory},
580 'done_text' => $main::locale->text('unimported')
582 push @sources , $other;
586 elsif ( $self->file_type eq 'attachment' ) {
588 'name' => 'uploaded',
589 'title' => $main::locale->text(''),
590 'chk_action' => 'attachments_delete',
591 'chk_title' => $main::locale->text('Delete Attachments'),
592 'chkall_title' => $main::locale->text('Delete all'),
593 'file_title' => $main::locale->text('filename'),
594 'confirm_text' => $main::locale->text('delete'),
596 'are_existing' => $self->existing ? 1 : 0,
597 'rename_title' => $main::locale->text('Rename Attachments'),
600 'upload_title' => $main::locale->text('Upload Attachments'),
601 'done_text' => $main::locale->text('deleted')
603 push @sources , $attdata;
605 elsif ( $self->file_type eq 'image' ) {
607 'name' => 'uploaded',
608 'title' => $main::locale->text(''),
609 'chk_action' => 'images_delete',
610 'chk_title' => $main::locale->text('Delete Images'),
611 'chkall_title' => $main::locale->text('Delete all'),
612 'file_title' => $main::locale->text('filename'),
613 'confirm_text' => $main::locale->text('delete'),
615 'are_existing' => $self->existing ? 1 : 0,
616 'rename_title' => $main::locale->text('Rename Images'),
619 'upload_title' => $main::locale->text('Upload Images'),
620 'done_text' => $main::locale->text('deleted')
622 push @sources , $attdata;
628 # todo: cache thumbs?
629 sub _create_thumbnail {
630 my ($file, $size) = @_;
635 if (!eval { $filename = $file->get_file(); 1; }) {
636 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail get_file failed: " . $EVAL_ERROR);
640 # Workaround for pfds which are not handled by file_probe_type.
641 # Maybe use mime info stored in db?
642 my $mime_type = File::MimeInfo::Magic::magic($filename);
643 if ($mime_type =~ m{pdf}) {
644 $filename = _convert_pdf_to_png($filename, size => $size);
646 return if !$filename;
649 if (!eval { $content = slurp $filename; 1; }) {
650 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail slurp failed: " . $EVAL_ERROR);
655 if (!eval { $ret = file_probe_type($content, size => $size); 1; }) {
656 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type failed: " . $EVAL_ERROR);
660 # file_probe_type returns a hash ref with thumbnail info and content
661 # or an error message
662 if ('HASH' ne ref $ret) {
663 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type returned an error: " . $ret);
670 sub _convert_pdf_to_png {
671 my ($filename, %params) = @_;
673 my $size = $params{size} // 64;
674 my $sfile = SL::SessionFile::Random->new();
675 my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . $filename . ' ' . $sfile->file_name;
677 if (system($command) == -1) {
678 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: system call failed: " . $ERRNO);
682 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: pdftoppm failed with error code: " . ($CHILD_ERROR >> 8));
686 return $sfile->file_name . '.png';
699 SL::Controller::File - Controller for managing files
703 The Controller is called directly from the webpages
705 <a href="controller.pl?action=File/list&file_type=document\
706 &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
709 or indirectly via javascript functions from js/kivi.File.js
711 kivi.popup_dialog({ url: 'controller.pl',
712 data: { action : 'File/ajax_upload',
713 file_type : 'uploaded',
721 This is a controller for handling files in a storage independent way.
722 The storage may be a Filesystem,a WebDAV, a Database or DMS.
723 These backends must be configered in ClientConfig.
724 This Controller use as intermediate layer for storage C<SL::File>.
726 The Controller is responsible to display forms for displaying the files at the ERP-objects and
727 for uploading and downloading the files.
729 More description of the intermediate layer see L<SL::File>.
733 =head2 C<action_list>
735 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
736 Dependent of file_type different sources are available.
738 For documents there are the 'created' source and the imports from scanners or email.
739 For attachments and images only the 'uploaded' source available.
741 Available C<FORM PARAMS>:
745 =item C<form.object_id>
747 The Id of the ERP-object.
749 =item C<form.object_type>
751 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
753 =item C<form.file_type>
755 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
756 This file_type is a filter for the list.
760 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.
765 =head2 C<action_ajax_upload>
768 A new file or more files can selected by a dialog and insert into the system.
771 Available C<FORM PARAMS>:
775 =item C<form.file_type>
777 This parameter describe here the source for a new file :
778 "attachments" and "images"
780 This is a normal upload selection, which may be more then one file to upload.
782 =item C<form.object_id>
786 =item C<form.object_type>
788 are the same as at C<action_list>
792 =head2 C<action_ajax_files_uploaded>
794 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
795 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?).
796 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
798 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.
800 Available C<FORM PARAMS>:
804 =item C<form.ATTACHMENTS.uploadfiles>
806 This is an array of elements which have {filename} for the name and {data} for the contents.
808 Also object_id, object_type and file_type
812 =head2 C<action_download>
814 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
816 Available C<FORM PARAMS>:
820 Also object_id, object_type and file_type
824 =head2 C<action_ajax_importdialog>
826 A Dialog with all available and not imported files to import is open.
827 More then one file can be selected.
829 Available C<FORM PARAMS>:
835 The name of the source like "scanner1" or "email"
839 The full path to the directory on the server, where the files to import can found
841 Also object_id, object_type and file_type
845 =head2 C<action_ajax_delete>
847 Some files can be deleted
849 Available C<FORM PARAMS>:
855 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
859 =head2 C<action_ajax_unimport>
861 Some files can be unimported, dependent of the source of the file. This means they are moved
862 back to the directory of the source
864 Available C<FORM PARAMS>:
870 The ids of the files to unimport. Only these files are unimported not all versions of a file if the exists
874 =head2 C<action_ajax_rename>
876 One file can be renamed. There can be some checks if the same filename still exists at one object.
880 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>