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 => 7, gltype => '', dir =>'SalesQuotation', model => 'Order', right => 'import_ar' },
59 'sales_order_intake' => { gen => 7, gltype => '', dir =>'SalesOrderIntake', model => 'Order', right => 'import_ar' },
60 'sales_order' => { gen => 7, gltype => '', dir =>'SalesOrder', model => 'Order', right => 'import_ar' },
61 'sales_delivery_order' => { gen => 7, gltype => '', dir =>'SalesDeliveryOrder', model => 'DeliveryOrder', right => 'import_ar' },
62 'sales_reclamation' => { gen => 7, gltype => '', dir =>'SalesReclamation', model => 'Reclamation', right => 'import_ar' },
63 'invoice' => { gen => 7, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
64 'invoice_for_advance_payment' => { gen => 7, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
65 'final_invoice' => { gen => 7, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
66 'credit_note' => { gen => 7, gltype => '', dir =>'CreditNote', model => 'Invoice', right => 'import_ar' },
67 'request_quotation' => { gen => 7, gltype => '', dir =>'RequestForQuotation', model => 'Order', right => 'import_ap' },
68 'purchase_quotation_intake' => { gen => 7, gltype => '', dir =>'PurchaseQuotationIntake', model => 'Order', right => 'import_ap' },
69 'purchase_order' => { gen => 7, gltype => '', dir =>'PurchaseOrder', model => 'Order', right => 'import_ap' },
70 'purchase_order_confirmation' => { gen => 7, gltype => '', dir =>'PurchaseOrderConfirmation', model => 'Order', right => 'import_ap' },
71 'purchase_delivery_order' => { gen => 7, gltype => '', dir =>'PurchaseDeliveryOrder', model => 'DeliveryOrder', right => 'import_ap' },
72 'purchase_reclamation' => { gen => 7, gltype => '', dir =>'PurchaseReclamation', model => 'Reclamation', right => 'import_ap' },
73 'purchase_invoice' => { gen => 7, gltype => 'ap', dir =>'PurchaseInvoice', model => 'PurchaseInvoice',right => 'import_ap' },
74 'supplier_delivery_order' => { gen => 7, gltype => '', dir =>'SupplierDeliveryOrder', model => 'DeliveryOrder', right => 'import_ap' },
75 'rma_delivery_order' => { gen => 7, gltype => '', dir =>'RMADeliveryOrder', model => 'DeliveryOrder', right => 'import_ar' },
76 'vendor' => { gen => 0, gltype => '', dir =>'Vendor', model => 'Vendor', right => 'xx' },
77 'customer' => { gen => 1, gltype => '', dir =>'Customer', model => 'Customer', right => 'xx' },
78 'project' => { gen => 0, gltype => '', dir =>'Project', model => 'Project', right => 'xx' },
79 'part' => { gen => 0, gltype => '', dir =>'Part', model => 'Part', right => 'xx' },
80 'gl_transaction' => { gen => 6, gltype => 'gl', dir =>'GeneralLedger', model => 'GLTransaction', right => 'import_ap' },
81 'draft' => { gen => 0, gltype => '', dir =>'Draft', model => 'Draft', right => 'xx' },
82 'csv_customer' => { gen => 1, gltype => '', dir =>'Reports', model => 'Customer', right => 'xx' },
83 'csv_vendor' => { gen => 1, gltype => '', dir =>'Reports', model => 'Vendor', right => 'xx' },
84 'shop_image' => { gen => 0, gltype => '', dir =>'ShopImages', model => 'Part', right => 'xx' },
85 'letter' => { gen => 7, gltype => '', dir =>'Letter', model => 'Letter', right => 'sales_letter_edit | purchase_letter_edit' },
89 # $main::locale->text('imported')
99 $is_json = 1 if $::form->{json};
101 $self->_do_list($is_json);
104 sub action_ajax_importdialog {
106 $::auth->assert($self->object_right);
107 my $path = $::form->{path};
108 my @files = $self->_get_from_import($path);
110 'name' => $::form->{source},
112 'chk_action' => $::form->{source}.'_import',
113 'chk_title' => $main::locale->text('Import scanned documents'),
114 'chkall_title' => $main::locale->text('Import all'),
117 $self->render('file/import_dialog',
124 sub action_ajax_import {
126 $::auth->assert($self->object_right);
127 my $ids = $::form->{ids};
128 my $source = $::form->{source};
129 my $path = $::form->{path};
130 my @files = $self->_get_from_import($path);
131 foreach my $filename (@{ $::form->{$ids} || [] }) {
132 my ($file, undef) = grep { $_->{name} eq $filename } @files;
134 my $obj = SL::File->save(object_id => $self->object_id,
135 object_type => $self->object_type,
136 mime_type => 'application/pdf',
138 file_type => 'document',
139 file_name => $file->{filename},
140 file_path => $file->{path}
142 unlink($file->{path}) if $obj;
148 sub action_ajax_delete {
150 $self->_delete_all(DO_DELETE, $::locale->text('Following files are deleted:'));
153 sub action_ajax_unimport {
155 $self->_delete_all(DO_UNIMPORT, $::locale->text('Following files are unimported:'));
158 sub action_ajax_rename {
160 my $guid = $::form->{id};
161 my $file = SL::File->get(guid => $guid);
163 $self->js->flash('error', $::locale->text('File not exists !'))->render();
166 my $sessionfile = $::form->{sessionfile};
167 if ( $sessionfile && -f $sessionfile ) {
169 if ( $::form->{to} eq $file->file_name ) {
170 # no rename so use as new version
171 $file->save_file($sessionfile);
172 $self->js->flash('warning', $::locale->text('File \'#1\' is used as new Version !', $file->file_name));
175 # new filename, so it is a new file with the same attributes as the old file
177 SL::File->save(object_id => $file->object_id,
178 object_type => $file->object_type,
179 mime_type => $file->mime_type,
180 source => $file->source,
181 file_type => $file->file_type,
182 file_name => $::form->{to},
183 file_path => $sessionfile
185 unlink($sessionfile);
188 $self->js->flash( 'error', t8('internal error (see details)'))
189 ->flash_detail('error', $@)->render;
199 $result = $file->rename($::form->{to});
202 $self->js->flash( 'error', t8('internal error (see details)'))
203 ->flash_detail('error', $@)->render;
207 if ($result != SL::File::RENAME_OK) {
208 $self->js->flash('error',
209 $result == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
210 : $result == SL::File::RENAME_SAME ? $::locale->text('Same Filename !')
211 : $::locale->text('File not exists !'))
216 $self->is_global($::form->{is_global});
217 $self->file_type( $file->file_type);
218 $self->object_type($file->object_type);
219 $self->object_id( $file->object_id);
220 #$self->object_model($file_types{$file->module}->{model});
221 #$self->object_right($file_types{$file->module}->{right});
222 if ( $::form->{next_ids} ) {
223 my @existing = split(/,/, $::form->{next_ids});
224 $self->existing(\@existing);
229 sub action_ajax_upload {
231 $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
232 $self->{accept_types} = '';
233 $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
234 $self->render('file/upload_dialog',
240 sub action_ajax_files_uploaded {
243 my $source = 'uploaded';
245 if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
246 my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
247 foreach my $idx (0 .. scalar(@upfiles) - 1) {
249 my $fname = uri_unescape($upfiles[$idx]->{filename});
250 # normalize and find basename
251 # first split with unix rules
252 # after that split with windows rules
253 my ($volume, $directories, $basefile) = File::Spec::Unix->splitpath($fname);
254 ($volume, $directories, $basefile) = File::Spec::Win32->splitpath($basefile);
256 # to find real mime_type by magic we must save the filedata
258 my $sess_fname = "file_upload_" . $self->object_type . "_" . $self->object_id . "_" . $idx;
259 my $sfile = SL::SessionFile->new($sess_fname, mode => 'w');
261 $sfile->fh->print(${$upfiles[$idx]->{data}});
263 my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
266 # if filename has the suffix "pdf", but isn't really a pdf, set mimetype for no suffix
267 $mime_type = File::MimeInfo::Magic::mimetype($basefile);
268 $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
270 if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
273 my ($existobj) = SL::File->get_all(object_id => $self->object_id,
274 object_type => $self->object_type,
275 mime_type => $mime_type,
277 file_type => $self->file_type,
278 file_name => $basefile,
282 push @existing, ($existobj->versions)[0]->file_version->guid.'_'.$sfile->file_name;
284 my $fileobj = SL::File->save(object_id => $self->object_id,
285 object_type => $self->object_type,
286 mime_type => $mime_type,
288 file_type => $self->file_type,
289 file_name => $basefile,
290 title => $::form->{title},
291 description => $::form->{description},
292 ## two possibilities: what is better ? content or sessionfile ??
293 file_contents => ${$upfiles[$idx]->{data}},
294 file_path => $sfile->file_name
296 unlink($sfile->file_name);
300 $self->js->flash( 'error', t8('internal error (see details)'))
301 ->flash_detail('error', $@)->render;
306 $self->existing(\@existing);
310 sub action_download {
313 my $id = $::form->{id};
314 my $version = $::form->{version};
316 my $file = SL::File->get(id => $id );
317 $file->version($version) if $version;
318 my $ref = $file->get_content;
319 if ( $file && $ref ) {
320 return $self->send_file($ref,
321 type => $file->mime_type,
322 name => $file->file_name,
327 sub action_ajax_get_thumbnail {
330 my $id = $::form->{file_id};
331 my $version = $::form->{file_version};
332 my $file = SL::File->get(id => $id);
334 $file->version($version) if $version;
336 my $thumbnail = _create_thumbnail($file, $::form->{size});
338 my $overlay_selector = '#enlarged_thumb_' . $id;
339 $overlay_selector .= '_' . $version if $version;
341 ->attr($overlay_selector, 'src', 'data:' . $thumbnail->{thumbnail_img_content_type} . ';base64,' . MIME::Base64::encode_base64($thumbnail->{thumbnail_img_content}))
342 ->data($overlay_selector, 'is-overlay-loaded', '1')
351 sub check_object_params {
354 my $id = ($::form->{object_id} // 0) * 1;
355 my $draftid = ($::form->{draft_id} // 0) * 1;
359 if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
361 $type = $::form->{object_type};
364 $id = $::form->{draft_id};
366 } elsif ( $::form->{object_type} ) {
367 $type = $::form->{object_type};
369 die "No object type" unless $type;
370 die "No file type" unless $::form->{file_type};
371 die "Unknown object type" unless $file_types{$type};
373 $self->is_global($gldoc);
374 $self->file_type($::form->{file_type});
375 $self->object_type($type);
376 $self->object_id($id);
377 $self->object_model($file_types{$type}->{model});
378 $self->object_right($file_types{$type}->{right});
380 # $::auth->assert($self->object_right);
382 # my $model = 'SL::DB::' . $self->object_model;
383 # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
393 my ($self, $do_unimport, $infotext) = @_;
395 my $ids = $::form->{ids};
396 foreach my $version_guid (@{ $::form->{$ids} || [] }) {
397 my $dbfile = SL::File->get(guid => $version_guid);
399 $files .= ' ' . $dbfile->file_name if $dbfile->delete_file_version;
402 $self->js->flash('info', $infotext . $files) if $files;
407 my ($self, $json) = @_;
410 my @object_types = ($self->object_type);
411 if ( $self->file_type eq 'document' ) {
412 push @object_types, qw(dunning1 dunning2 dunning3 dunning_invoice dunning_orig_invoice) if $self->object_type eq 'invoice'; # hardcoded object types?
414 @files = SL::File->get_all_versions(object_id => $self->object_id,
415 object_type => \@object_types,
416 file_type => $self->file_type,
419 $self->files(\@files);
421 $_->{thumbnail} = _create_thumbnail($_) for @files;
422 $_->{version_count} = SL::File->get_version_count(id => $_->id) for @files;
424 if($self->object_type eq 'shop_image'){
426 ->run('kivi.ShopPart.show_images', $self->object_id)
429 $self->_mk_render('file/list', 1, 0, $json);
433 sub _get_from_import {
434 my ($self, $path) = @_;
437 my $language = $::lx_office_conf{system}->{language};
438 my $timezone = $::locale->get_local_time_zone()->name;
439 if (opendir my $dir, $path) {
440 my @files = (readdir $dir);
441 foreach my $file ( @files) {
442 next if (($file eq '.') || ($file eq '..'));
443 $file = Encode::decode('utf-8', $file);
445 next if ( -d "$path/$file" );
447 my $tmppath = File::Spec->catfile( $path, $file );
448 next if( ! -f $tmppath );
450 my $st = stat($tmppath);
451 my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
452 my $sname = $main::locale->quote_special_chars('HTML', $file);
455 'filename' => $sname,
457 'mtime' => $st->mtime,
458 'date' => $dt->dmy('.') . " " . $dt->hms,
465 $::lxdebug->message(LXDebug::WARN(), "SL::File::_get_from_import opendir failed to open dir " . $path);
472 my ($self, $template, $edit, $scanner, $json) = @_;
475 ##TODO make code configurable
478 my @sources = $self->_get_sources();
479 foreach my $source ( @sources ) {
480 @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
482 if ( $self->file_type eq 'document' ) {
483 $title = $main::locale->text('Documents');
484 } elsif ( $self->file_type eq 'attachment' ) {
485 $title = $main::locale->text('Attachments');
486 } elsif ( $self->file_type eq 'image' ) {
487 $title = $main::locale->text('Images');
490 my $output = SL::Presenter->get->render(
493 SOURCES => \@sources,
494 edit_attachments => $edit,
495 object_type => $self->object_type,
496 object_id => $self->object_id,
497 file_type => $self->file_type,
498 is_global => $self->is_global,
502 $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
503 if ( $self->existing && scalar(@{$self->existing}) > 0) {
504 my $first = shift @{$self->existing};
505 my ($first_guid, $sfile) = split('_', $first, 2);
506 my $file = SL::File->get(guid => $first_guid );
507 $self->js->run('kivi.File.askForRename', $first_guid, $file->file_type, $file->file_name, $sfile, join (',', @{$self->existing}), $self->is_global);
511 $self->render(\$output, { layout => 0, process => 0 });
516 $self->js->flash( 'error', t8('internal error (see details)'))
517 ->flash_detail('error', $@)->render;
519 $self->render('generic/error', { layout => 0 }, label_error => $@);
528 if ( $self->file_type eq 'document' ) {
529 # TODO statt gen neue attribute in filetypes :
530 if (($file_types{$self->object_type}->{gen}*1 & 4)==4) {
531 # bit 3 is set => means upload
533 'name' => 'uploaded',
534 'title' => $main::locale->text('uploaded Documents'),
535 'chk_action' => 'uploaded_documents_delete',
536 'chk_title' => $main::locale->text('Delete Documents'),
537 'chkall_title' => $main::locale->text('Delete all'),
538 'file_title' => $main::locale->text('filename'),
539 'confirm_text' => $main::locale->text('delete'),
541 'are_existing' => $self->existing ? 1 : 0,
542 'rename_title' => $main::locale->text('Rename Attachments'),
545 'upload_title' => $main::locale->text('Upload Documents'),
546 'done_text' => $main::locale->text('deleted')
548 push @sources , $source;
551 if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
554 'title' => $main::locale->text('generated Files'),
555 'chk_action' => 'documents_delete',
556 'chk_title' => $main::locale->text('Delete Documents'),
557 'chkall_title' => $main::locale->text('Delete all'),
558 'file_title' => $main::locale->text('filename'),
559 'confirm_text' => $main::locale->text('delete'),
560 'can_delete' => $::instance_conf->get_doc_delete_printfiles,
561 'can_rename' => $::instance_conf->get_doc_delete_printfiles,
562 'rename_title' => $main::locale->text('Rename Documents'),
563 'done_text' => $main::locale->text('deleted')
565 push @sources , $gendata;
568 if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
569 my @others = SL::File->get_other_sources();
570 foreach my $scanner_or_mailrx (@others) {
572 'name' => $scanner_or_mailrx->{name},
573 'title' => $main::locale->text('from \'#1\' imported Files', $scanner_or_mailrx->{description}),
574 'chk_action' => $scanner_or_mailrx->{name}.'_unimport',
575 'chk_title' => $main::locale->text('Unimport documents'),
576 'chkall_title' => $main::locale->text('Unimport all'),
577 'file_title' => $main::locale->text('filename'),
578 'confirm_text' => $main::locale->text('unimport'),
580 'rename_title' => $main::locale->text('Rename Documents'),
583 'import_title' => $main::locale->text('Add Document from \'#1\'', $scanner_or_mailrx->{name}),
584 'path' => $scanner_or_mailrx->{directory},
585 'done_text' => $main::locale->text('unimported')
587 push @sources , $other;
591 elsif ( $self->file_type eq 'attachment' ) {
593 'name' => 'uploaded',
594 'title' => $main::locale->text(''),
595 'chk_action' => 'attachments_delete',
596 'chk_title' => $main::locale->text('Delete Attachments'),
597 'chkall_title' => $main::locale->text('Delete all'),
598 'file_title' => $main::locale->text('filename'),
599 'confirm_text' => $main::locale->text('delete'),
601 'are_existing' => $self->existing ? 1 : 0,
602 'rename_title' => $main::locale->text('Rename Attachments'),
605 'upload_title' => $main::locale->text('Upload Attachments'),
606 'done_text' => $main::locale->text('deleted')
608 push @sources , $attdata;
610 elsif ( $self->file_type eq 'image' ) {
612 'name' => 'uploaded',
613 'title' => $main::locale->text(''),
614 'chk_action' => 'images_delete',
615 'chk_title' => $main::locale->text('Delete Images'),
616 'chkall_title' => $main::locale->text('Delete all'),
617 'file_title' => $main::locale->text('filename'),
618 'confirm_text' => $main::locale->text('delete'),
620 'are_existing' => $self->existing ? 1 : 0,
621 'rename_title' => $main::locale->text('Rename Images'),
624 'upload_title' => $main::locale->text('Upload Images'),
625 'done_text' => $main::locale->text('deleted')
627 push @sources , $attdata;
633 # todo: cache thumbs?
634 sub _create_thumbnail {
635 my ($file, $size) = @_;
640 if (!eval { $filename = $file->get_file(); 1; }) {
641 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail get_file failed: " . $EVAL_ERROR);
645 # Workaround for pfds which are not handled by file_probe_type.
646 # Maybe use mime info stored in db?
647 my $mime_type = File::MimeInfo::Magic::magic($filename);
648 if ($mime_type =~ m{pdf}) {
649 $filename = _convert_pdf_to_png($filename, size => $size);
651 return if !$filename;
654 if (!eval { $content = slurp $filename; 1; }) {
655 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail slurp failed: " . $EVAL_ERROR);
660 if (!eval { $ret = file_probe_type($content, size => $size); 1; }) {
661 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type failed: " . $EVAL_ERROR);
665 # file_probe_type returns a hash ref with thumbnail info and content
666 # or an error message
667 if ('HASH' ne ref $ret) {
668 $::lxdebug->message(LXDebug::WARN(), "SL::File::_create_thumbnail file_probe_type returned an error: " . $ret);
675 sub _convert_pdf_to_png {
676 my ($filename, %params) = @_;
678 my $size = $params{size} // 64;
679 my $sfile = SL::SessionFile::Random->new();
680 unless (-f $filename) {
681 $::lxdebug->message(LXDebug::WARN(), "_convert_pdf_to_png failed, no file found: $filename");
684 # quotemeta for storno case "storno\ zu\ 1020" *nix only
685 my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . quotemeta($filename) . ' ' . $sfile->file_name;
687 if (system($command) == -1) {
688 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: system call failed: " . $ERRNO);
692 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: pdftoppm failed with error code: " . ($CHILD_ERROR >> 8));
693 $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: File: $filename");
697 return $sfile->file_name . '.png';
710 SL::Controller::File - Controller for managing files
714 The Controller is called directly from the webpages
716 <a href="controller.pl?action=File/list&file_type=document\
717 &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
720 or indirectly via javascript functions from js/kivi.File.js
722 kivi.popup_dialog({ url: 'controller.pl',
723 data: { action : 'File/ajax_upload',
724 file_type : 'uploaded',
732 This is a controller for handling files in a storage independent way.
733 The storage may be a Filesystem,a WebDAV, a Database or DMS.
734 These backends must be configered in ClientConfig.
735 This Controller use as intermediate layer for storage C<SL::File>.
737 The Controller is responsible to display forms for displaying the files at the ERP-objects and
738 for uploading and downloading the files.
740 More description of the intermediate layer see L<SL::File>.
744 =head2 C<action_list>
746 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
747 Dependent of file_type different sources are available.
749 For documents there are the 'created' source and the imports from scanners or email.
750 For attachments and images only the 'uploaded' source available.
752 Available C<FORM PARAMS>:
756 =item C<form.object_id>
758 The Id of the ERP-object.
760 =item C<form.object_type>
762 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
764 =item C<form.file_type>
766 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
767 This file_type is a filter for the list.
771 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.
776 =head2 C<action_ajax_upload>
779 A new file or more files can selected by a dialog and insert into the system.
782 Available C<FORM PARAMS>:
786 =item C<form.file_type>
788 This parameter describe here the source for a new file :
789 "attachments" and "images"
791 This is a normal upload selection, which may be more then one file to upload.
793 =item C<form.object_id>
797 =item C<form.object_type>
799 are the same as at C<action_list>
803 =head2 C<action_ajax_files_uploaded>
805 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
806 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?).
807 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
809 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.
811 Available C<FORM PARAMS>:
815 =item C<form.ATTACHMENTS.uploadfiles>
817 This is an array of elements which have {filename} for the name and {data} for the contents.
819 Also object_id, object_type and file_type
823 =head2 C<action_download>
825 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
827 Available C<FORM PARAMS>:
831 Also object_id, object_type and file_type
835 =head2 C<action_ajax_importdialog>
837 A Dialog with all available and not imported files to import is open.
838 More then one file can be selected.
840 Available C<FORM PARAMS>:
846 The name of the source like "scanner1" or "email"
850 The full path to the directory on the server, where the files to import can found
852 Also object_id, object_type and file_type
856 =head2 C<action_ajax_delete>
858 Some files can be deleted
860 Available C<FORM PARAMS>:
866 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
870 =head2 C<action_ajax_unimport>
872 Some files can be unimported, dependent of the source of the file. This means they are moved
873 back to the directory of the source
875 Available C<FORM PARAMS>:
881 The ids of the files to unimport. Only these files are unimported not all versions of a file if the exists
885 =head2 C<action_ajax_rename>
887 One file can be renamed. There can be some checks if the same filename still exists at one object.
891 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>