1 package SL::Controller::File;
 
   5 use parent qw(SL::Controller::Base);
 
   7 use List::Util qw(first max);
 
  10 use Encode qw(decode);
 
  16 use File::Spec::Win32;
 
  17 use File::MimeInfo::Magic;
 
  18 use SL::DB::Helper::Mappings;
 
  20 use SL::DB::DeliveryOrder;
 
  23 use SL::DB::PurchaseInvoice;
 
  25 use SL::DB::GLTransaction;
 
  29 use SL::Helper::CreatePDF qw(:all);
 
  30 use SL::Locale::String;
 
  33 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_image_type);
 
  35 use constant DO_DELETE    =>   0;
 
  36 use constant DO_UNIMPORT  =>   1;
 
  39 use Rose::Object::MakeMethods::Generic
 
  41     'scalar --get_set_init' => [ qw() ],
 
  42     'scalar' => [ qw(object object_type object_model object_id object_right file_type files is_global existing) ],
 
  45 __PACKAGE__->run_before('check_object_params', only => [ qw(list ajax_delete ajax_importdialog ajax_import ajax_unimport ajax_upload ajax_files_uploaded) ]);
 
  48   'sales_quotation' =>         { gen => 1 ,gltype => '',   dir =>'SalesQuotation',       model => 'Order',          right => 'import_ar'  },
 
  49   'sales_order'     =>         { gen => 1 ,gltype => '',   dir =>'SalesOrder',           model => 'Order',          right => 'import_ar'  },
 
  50   'sales_delivery_order' =>    { gen => 1 ,gltype => '',   dir =>'SalesDeliveryOrder',   model => 'DeliveryOrder',  right => 'import_ar'  },
 
  51   'invoice'         =>         { gen => 1 ,gltype => 'ar', dir =>'SalesInvoice',         model => 'Invoice',        right => 'import_ar'  },
 
  52   'credit_note'     =>         { gen => 1 ,gltype => '',   dir =>'CreditNote',           model => 'Invoice',        right => 'import_ar'  },
 
  53   'request_quotation' =>       { gen => 3 ,gltype => '',   dir =>'RequestForQuotation',  model => 'Order',          right => 'import_ap'  },
 
  54   'purchase_order' =>          { gen => 3 ,gltype => '',   dir =>'PurchaseOrder',        model => 'Order',          right => 'import_ap'  },
 
  55   'purchase_delivery_order' => { gen => 3 ,gltype => '',   dir =>'PurchaseDeliveryOrder',model => 'DeliveryOrder',  right => 'import_ap'  },
 
  56   'purchase_invoice' =>        { gen => 2 ,gltype => 'ap', dir =>'PurchaseInvoice',      model => 'PurchaseInvoice',right => 'import_ap'  },
 
  57   'vendor' =>                  { gen => 0 ,gltype => '',   dir =>'Vendor',               model => 'Vendor',         right => 'xx'  },
 
  58   'customer' =>                { gen => 1 ,gltype => '',   dir =>'Customer',             model => 'Customer',       right => 'xx'  },
 
  59   'part' =>                    { gen => 0 ,gltype => '',   dir =>'Part',                 model => 'Part',           right => 'xx'  },
 
  60   'gl_transaction' =>          { gen => 2 ,gltype => 'gl', dir =>'GeneralLedger',        model => 'GLTransaction',  right => 'import_ap'  },
 
  61   'draft' =>                   { gen => 0 ,gltype => '',   dir =>'Draft',                model => 'Draft',          right => 'xx'  },
 
  62   'csv_customer' =>            { gen => 1 ,gltype => '',   dir =>'Reports',              model => 'Customer',       right => 'xx'  },
 
  63   'csv_vendor'   =>            { gen => 1 ,gltype => '',   dir =>'Reports',              model => 'Vendor',         right => 'xx'  },
 
  67 # $main::locale->text('imported')
 
  77   $isjson = 1 if $::form->{json};
 
  79   $self->_do_list($isjson);
 
  82 sub action_ajax_importdialog {
 
  84   $::auth->assert($self->object_right);
 
  85   my $path   = $::form->{path};
 
  86   my @files  = $self->_get_from_import($path);
 
  88     'name'         => $::form->{source},
 
  90     'chk_action'   => $::form->{source}.'_import',
 
  91     'chk_title'    => $main::locale->text('Import scanned documents'),
 
  92     'chkall_title' => $main::locale->text('Import all'),
 
  95   $self->render('file/import_dialog',
 
 102 sub action_ajax_import {
 
 104   $::auth->assert($self->object_right);
 
 105   my $ids    = $::form->{ids};
 
 106   my $source = $::form->{source};
 
 107   my $path   = $::form->{path};
 
 108   my @files = $self->_get_from_import($path);
 
 109   foreach my $filename (@{ $::form->{$ids} || [] }) {
 
 110     my ($file,undef) = grep { $_->{name} eq $filename } @files;
 
 112       my $obj = SL::File->save(object_id     => $self->object_id,
 
 113                                object_type   => $self->object_type,
 
 114                                mime_type     => 'application/pdf',
 
 116                                file_type     => 'document',
 
 117                                file_name     => $file->{filename},
 
 118                                file_path     => $file->{path}
 
 120       unlink($file->{path}) if $obj;
 
 126 sub action_ajax_delete {
 
 128   $self->_delete_all(DO_DELETE,$::locale->text('Following files are deleted:'));
 
 131 sub action_ajax_unimport {
 
 133   $self->_delete_all(DO_UNIMPORT,$::locale->text('Following files are unimported:'));
 
 136 sub action_ajax_rename {
 
 138   my $file = SL::File->get(id => $::form->{id});
 
 140     $self->js->flash('error',$::locale->text('File not exists !'))->render();
 
 143   $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$file->object_id." object_type=".$file->object_type." dbfile=".$file);
 
 144   my $sessionfile = $::form->{sessionfile};
 
 145       $main::lxdebug->message(LXDebug->DEBUG2(), "sessionfile=".$sessionfile);
 
 146   if ( $sessionfile && -f $sessionfile ) {
 
 147      $main::lxdebug->message(LXDebug->DEBUG2(), "file=".$file->file_name." to=".$::form->{to}." sessionfile=".$sessionfile);
 
 149     if ( $::form->{to} eq $file->file_name ) {
 
 150       # no rename so use as new version
 
 151       $file->save_file($sessionfile);
 
 152       $self->js->flash('warning',$::locale->text('File \'#1\' is used as new Version !',$file->file_name));
 
 155       # new filename so it is a new file with same attributes as old file
 
 157         SL::File->save(object_id     => $file->object_id,
 
 158                        object_type   => $file->object_type,
 
 159                        mime_type     => $file->mime_type,
 
 160                        source        => $file->source,
 
 161                        file_type     => $file->file_type,
 
 162                        file_name     => $::form->{to},
 
 163                        file_path     => $sessionfile
 
 165         unlink($sessionfile);
 
 168         $self->js->flash(       'error', t8('internal error (see details)'))
 
 169                  ->flash_detail('error', $@)->render;
 
 179       $res = $file->rename($::form->{to});
 
 180       $main::lxdebug->message(LXDebug->DEBUG2(), "rename result=".$res);
 
 183       $self->js->flash(       'error', t8('internal error (see details)'))
 
 184                ->flash_detail('error', $@)->render;
 
 188     if ($res != SL::File::RENAME_OK) {
 
 189       $self->js->flash('error',
 
 190                          $res == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
 
 191                        : $res == SL::File::RENAME_SAME   ? $::locale->text('Same Filename !')
 
 192                        :                                   $::locale->text('File not exists !'))
 
 197   $self->is_global($::form->{is_global});
 
 198   $self->file_type(  $file->file_type);
 
 199   $self->object_type($file->object_type);
 
 200   $self->object_id(  $file->object_id);
 
 201   #$self->object_model($file_types{$file->module}->{model});
 
 202   #$self->object_right($file_types{$file->module}->{right});
 
 203   if ( $::form->{next_ids} ) {
 
 204     my @existing = split(/,/, $::form->{next_ids});
 
 205     $self->existing(\@existing);
 
 210 sub action_ajax_upload {
 
 212   $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
 
 213   $self->{accept_types} = '';
 
 214   $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
 
 215   $self->render('file/upload_dialog',
 
 221 sub action_ajax_files_uploaded {
 
 224   my $source = 'uploaded';
 
 225   $main::lxdebug->message(LXDebug->DEBUG2(), "file_upload UPLOAD=".$::form->{ATTACHMENTS}->{uploadfiles});
 
 227   if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
 
 228     my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
 
 229     foreach my $idx (0 .. scalar(@upfiles) - 1) {
 
 231         my $fname = uri_unescape($upfiles[$idx]->{filename});
 
 232         $main::lxdebug->message(LXDebug->DEBUG2(), "file_upload name=".$fname);
 
 233         ## normalize and find basename
 
 234         # first split with unix rules
 
 235         # after that split with windows rules
 
 236         my ($volume,$directories,$basefile) = File::Spec::Unix->splitpath($fname);
 
 237         ($volume,$directories,$basefile) = File::Spec::Win32->splitpath($basefile);
 
 239         # to find real mime_type by magic we must save the filedata
 
 241         my $sess_fname = "file_upload_".$self->object_type."_".$self->object_id."_".$idx;
 
 242         my $sfile     = SL::SessionFile->new($sess_fname, mode => 'w');
 
 244         $sfile->fh->print(${$upfiles[$idx]->{data}});
 
 246         my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
 
 249           # if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
 
 250           $mime_type = File::MimeInfo::Magic::mimetype($basefile);
 
 251           $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
 
 253         $main::lxdebug->message(LXDebug->DEBUG2(), "mime_type=".$mime_type);
 
 254         if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
 
 257         my ($existobj) = SL::File->get_all(object_id     => $self->object_id,
 
 258                                         object_type   => $self->object_type,
 
 259                                         mime_type     => $mime_type,
 
 261                                         file_type     => $self->file_type,
 
 262                                         file_name     => $basefile,
 
 265         $main::lxdebug->message(LXDebug->DEBUG2(), "store1 exist=".$existobj);
 
 267   $main::lxdebug->message(LXDebug->DEBUG2(), "id=".$existobj->id." sessionfile=". $sfile->file_name);
 
 268           push @existing, $existobj->id.'_'.$sfile->file_name;
 
 270           my $fileobj = SL::File->save(object_id     => $self->object_id,
 
 271                                        object_type   => $self->object_type,
 
 272                                        mime_type     => $mime_type,
 
 274                                        file_type     => $self->file_type,
 
 275                                        file_name     => $basefile,
 
 276                                        ## two possibilities: what is better ? content or sessionfile ??
 
 277                                        #file_contents => ${$upfiles[$idx]->{data}},
 
 278                                        file_path     => $sfile->file_name
 
 280           $main::lxdebug->message(LXDebug->DEBUG2(), "obj=".$fileobj);
 
 281           unlink($sfile->file_name);
 
 285         $self->js->flash(       'error', t8('internal error (see details)'))
 
 286                  ->flash_detail('error', $@)->render;
 
 291   $self->existing(\@existing);
 
 295 sub action_download {
 
 297   my ($id,$version) = split /_/, $::form->{id};
 
 298   my $file = SL::File->get(id => $id );
 
 299   $file->version($version) if $version;
 
 300   my $ref  = $file->get_content;
 
 301   if ( $file && $ref ) {
 
 302     return $self->send_file($ref,
 
 303       type => $file->mime_type,
 
 304       name => $file->file_name,
 
 313 sub check_object_params {
 
 316   my $id = ($::form->{object_id} // 0) * 1;
 
 317   my $draftid = ($::form->{draft_id} // 0) * 1;
 
 321   if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
 
 323     $type = $::form->{object_type};
 
 326     $id = $::form->{draft_id};
 
 328   } elsif ( $::form->{object_type} ) {
 
 329     $type = $::form->{object_type};
 
 331   die "No object type"     if ! $type;
 
 332   die "No file type"       if ! $::form->{file_type};
 
 333   die "Unkown object type" if ! $file_types{$type};
 
 335   $self->is_global($gldoc);
 
 336   $self->file_type($::form->{file_type});
 
 337   $self->object_type($type);
 
 338   $self->object_id($id);
 
 339   $self->object_model($file_types{$type}->{model});
 
 340   $self->object_right($file_types{$type}->{right});
 
 341   $main::lxdebug->message(LXDebug->DEBUG2(), "checked: object_id=".$self->object_id." object_type=".$self->object_type." is_global=".$self->is_global);
 
 343  # $::auth->assert($self->object_right);
 
 345  # my $model = 'SL::DB::' . $self->object_model;
 
 346  # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
 
 356   my ($self,$do_unimport,$infotext) = @_;
 
 358   my $ids = $::form->{ids};
 
 359   foreach my $id_version (@{ $::form->{$ids} || [] }) {
 
 360     my ($id,$version) = split /_/, $id_version;
 
 361     my $dbfile = SL::File->get(id => $id);
 
 362     $dbfile->version($version) if $dbfile && $version;
 
 363     if ( $dbfile && $dbfile->delete ) {
 
 364       $files .= ' '.$dbfile->file_name;
 
 367   $self->js->flash('info',$infotext.$files) if $files;
 
 372   my ($self,$json) = @_;
 
 374   $main::lxdebug->message(LXDebug->DEBUG2(), "do_list: object_id=".$self->object_id." object_type=".$self->object_type." file_type=".$self->file_type." json=".$json);
 
 375   if ( $self->file_type eq 'document' ) {
 
 376     @files   = SL::File->get_all_versions(object_id   => $self->object_id  ,
 
 377                                           object_type => $self->object_type,
 
 378                                           file_type   => $self->file_type  );
 
 380     $main::lxdebug->message(LXDebug->DEBUG2(), "cnt1=".scalar(@files));
 
 382   elsif ( $self->file_type eq 'attachment' ||  $self->file_type eq 'image' ) {
 
 383     @files   = SL::File->get_all(object_id   => $self->object_id  ,
 
 384                                  object_type => $self->object_type,
 
 385                                  file_type   => $self->file_type  );
 
 386     $main::lxdebug->message(LXDebug->DEBUG2(), "cnt2=".scalar(@files));
 
 388   $self->files(\@files);
 
 389   $self->_mk_render('file/list',1,0,$json);
 
 392 sub _get_from_import {
 
 393   my ($self,$path) = @_;
 
 396   $main::lxdebug->message(LXDebug->DEBUG2(), "import path=".$path);
 
 397   my $language = $::lx_office_conf{system}->{language};
 
 398   my $timezone = $::locale->get_local_time_zone()->name;
 
 399   if (opendir my $dir, $path) {
 
 400     my @files = ( readdir $dir);
 
 401     foreach my $file ( @files) {
 
 402       next if (($file eq '.') || ($file eq '..'));
 
 403       $file = Encode::decode('utf-8', $file);
 
 404       $main::lxdebug->message(LXDebug->DEBUG2(), "file=".$file);
 
 406       next if( -d "$path/$file");
 
 408       my $tmppath = File::Spec->catfile( $path, $file );
 
 409       $main::lxdebug->message(LXDebug->DEBUG2(), "tmppath=".$tmppath." file=".$file);
 
 410       next if( ! -f $tmppath);
 
 412       my $st = stat($tmppath);
 
 413       my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language);
 
 414       my $sname = $main::locale->quote_special_chars('HTML',$file);
 
 417         'filename' => $sname,
 
 419         'mtime'    => $st->mtime,
 
 420         'date'     => $dt->dmy('.')." ".$dt->hms,
 
 425   $main::lxdebug->message(LXDebug->DEBUG2(), "return ".scalar(@foundfiles)." files");
 
 430   my ($self,$template,$edit,$scanner,$json) = @_;
 
 433     ##TODO  here a configurable code must be implemented
 
 436     $main::lxdebug->message(LXDebug->DEBUG2(), "mk_render: object_id=".$self->object_id." object_type=".$self->object_type.
 
 437                               " file_type=".$self->file_type." json=".$json." filecount=".scalar(@{ $self->files })." is_global=".$self->is_global);
 
 438     my @sources = $self->_get_sources();
 
 439     foreach my $source ( @sources ) {
 
 440       $main::lxdebug->message(LXDebug->DEBUG2(), "mk_render: source name=".$source->{name});
 
 441       @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
 
 443     if ( $self->file_type eq 'document' ) {
 
 444       $title = $main::locale->text('Documents');
 
 445     } elsif ( $self->file_type eq 'attachment' ) {
 
 446       $title = $main::locale->text('Attachments');
 
 447     } elsif ( $self->file_type eq 'image' ) {
 
 448       $title = $main::locale->text('Images');
 
 451     my $output         = SL::Presenter->get->render(
 
 454       SOURCES           => \@sources,
 
 455       edit_attachments  => $edit,
 
 456       object_type       => $self->object_type,
 
 457       object_id         => $self->object_id,
 
 458       file_type         => $self->file_type,
 
 459       is_global         => $self->is_global,
 
 463       $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
 
 464       if ( $self->existing && scalar(@{$self->existing}) > 0) {
 
 465         my $first = shift @{$self->existing};
 
 466         my ($first_id,$sfile) = split('_',$first,2);
 
 467         #$main::lxdebug->message(LXDebug->DEBUG2(), "id=".$first_id." sessionfile=". $sfile);
 
 468         my $file = SL::File->get(id => $first_id );
 
 469         $self->js->run('kivi.File.askForRename',$first_id,$file->file_name,$sfile,join (',', @{$self->existing}), $self->is_global);
 
 473         $self->render(\$output, { layout => 0, process => 0 });
 
 478       $self->js->flash(       'error', t8('internal error (see details)'))
 
 479                ->flash_detail('error', $@)->render;
 
 481       $self->render('generic/error', { layout => 0 }, label_error => $@);
 
 490   $main::lxdebug->message(LXDebug->DEBUG2(), "get_sources file_type=". $self->file_type);
 
 491   if ( $self->file_type eq 'document' ) {
 
 492     ##TODO statt gen neue attribute in filetypes :
 
 493     if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
 
 496         'title'        => $main::locale->text('generated Files'),
 
 497         'chk_action'   => 'documents_delete',
 
 498         'chk_title'    => $main::locale->text('Delete Documents'),
 
 499         'chkall_title' => $main::locale->text('Delete all'),
 
 500         'file_title'   => $main::locale->text('filename'),
 
 501         'confirm_text' => $main::locale->text('delete'),
 
 503         'rename_title' => $main::locale->text('Rename Documents'),
 
 504         'done_text'    => $main::locale->text('deleted')
 
 506       push @sources , $gendata;
 
 508     if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
 
 509       my @others =  SL::File->get_other_sources();
 
 510       $main::lxdebug->message(LXDebug->DEBUG2(), "other cnt=". scalar(@others));
 
 511       foreach my $scanner_or_mailrx (@others) {
 
 513           'name'         => $scanner_or_mailrx->{name},
 
 514           'title'        => $main::locale->text('from \'#1\' imported Files',$scanner_or_mailrx->{description}),
 
 515           'chk_action'   => $scanner_or_mailrx->{name}.'_unimport',
 
 516           'chk_title'    => $main::locale->text('Unimport documents'),
 
 517           'chkall_title' => $main::locale->text('Unimport all'),
 
 518           'file_title'   => $main::locale->text('filename'),
 
 519           'confirm_text' => $main::locale->text('unimport'),
 
 521           'rename_title' => $main::locale->text('Rename Documents'),
 
 523           'import_title' => $main::locale->text('Add Document from \'#1\'',$scanner_or_mailrx->{name}),
 
 524           'path'         => $scanner_or_mailrx->{directory},
 
 525           'done_text'    => $main::locale->text('unimported')
 
 527         push @sources , $other;
 
 531   elsif ( $self->file_type eq 'attachment' ) {
 
 533       'name'         => 'uploaded',
 
 534       'title'        => $main::locale->text(''),
 
 535       'chk_action'   => 'attachments_delete',
 
 536       'chk_title'    => $main::locale->text('Delete Attachments'),
 
 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'),
 
 544       'upload_title' => $main::locale->text('Upload Attachments'),
 
 545       'done_text'    => $main::locale->text('deleted')
 
 547     push @sources , $attdata;
 
 549   elsif ( $self->file_type eq 'image' ) {
 
 551       'name'         => 'uploaded',
 
 552       'title'        => $main::locale->text(''),
 
 553       'chk_action'   => 'images_delete',
 
 554       'chk_title'    => $main::locale->text('Delete Images'),
 
 555       'chkall_title' => $main::locale->text('Delete all'),
 
 556       'file_title'   => $main::locale->text('filename'),
 
 557       'confirm_text' => $main::locale->text('delete'),
 
 559       'are_existing' => $self->existing ? 1 : 0,
 
 560       'rename_title' => $main::locale->text('Rename Images'),
 
 562       'upload_title' => $main::locale->text('Upload Images'),
 
 563       'done_text'    => $main::locale->text('deleted')
 
 565     push @sources , $attdata;
 
 567   $main::lxdebug->message(LXDebug->DEBUG2(), "get_sources count=".scalar(@sources));
 
 581 SL::Controller::File - Controller for managing files
 
 588     # The Controller is called direct from the webpages
 
 591     <a href="controller.pl?action=File/list&file_type=document\
 
 592        &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
 
 595     # or indirect via javascript functions from js/kivi.File.js
 
 598     kivi.popup_dialog({ url:     'controller.pl',
 
 599                         data:    { action     : 'File/ajax_upload',
 
 600                                    file_type  : 'uploaded',
 
 611 This is a controller for handling files in a storage independent way.
 
 612 The storage may be a Filesystem,a WebDAV, a Database or DMS.
 
 613 These backends must be configered in ClientConfig.
 
 614 This Controller use as intermediate layer for storage C<SL::File>.
 
 616 The Controller is responsible to display forms for displaying the files at the ERP-objects and
 
 617 for uploading and downloading the files.
 
 619 More description of the intermediate layer see L<SL::File>.
 
 623 =head2 C<action_list>
 
 625 This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
 
 626 Dependent of file_type different sources are available.
 
 628 For documents there are the 'created' source and the imports from scanners or email.
 
 629 For attachments and images only the 'uploaded' source available.
 
 631 Available C<FORM PARAMS>:
 
 635 =item C<form.object_id>
 
 637 The Id of the ERP-object.
 
 639 =item C<form.object_type>
 
 641 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
 
 643 =item C<form.file_type>
 
 645 For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
 
 646 This file_type is a filter for the list.
 
 650 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.
 
 655 =head2 C<action_ajax_upload>
 
 658 A new file or more files can selected by a dialog and insert into the system.
 
 661 Available C<FORM PARAMS>:
 
 665 =item C<form.file_type>
 
 667 This parameter describe here the source for a new file :
 
 668 "attachments" and "images"
 
 670 This is a normal upload selection, which may be more then one file to upload.
 
 672 =item C<form.object_id>
 
 676 =item C<form.object_type>
 
 678 are the same as at C<action_list>
 
 682 =head2  C<action_ajax_files_uploaded>
 
 684 The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
 
 685 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?).
 
 686 If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
 
 688 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.
 
 690 Available C<FORM PARAMS>:
 
 694 =item C<form.ATTACHMENTS.uploadfiles>
 
 696 This is an array of elements which have {filename} for the name and {data} for the contents.
 
 698 Also object_id, object_type and file_type
 
 702 =head2 C<action_download>
 
 704 This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
 
 706 Available C<FORM PARAMS>:
 
 710 Also object_id, object_type and file_type
 
 714 =head2 C<action_ajax_importdialog>
 
 716 A Dialog with all available and not imported files to import is open.
 
 717 More then one file can be selected.
 
 719 Available C<FORM PARAMS>:
 
 725 The name of the source like "scanner1" or "email"
 
 729 The full path to the directory on the server, where the files to import can found
 
 731 Also object_id, object_type and file_type
 
 735 =head2 C<action_ajax_delete>
 
 737 Some files can be deleted
 
 739 Available C<FORM PARAMS>:
 
 745 The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
 
 749 =head2 C<action_ajax_unimport>
 
 751 Some files can be unimported, dependent of the source of the file. This means they are moved
 
 752 back to the directory of the source
 
 754 Available C<FORM PARAMS>:
 
 760 The ids of the files to unimport. Only this files are unimported not all versions of a file if the exists
 
 764 =head2 C<action_ajax_rename>
 
 766 One file can be renamed. There can be some checks if the same filename still exists at one object.
 
 771 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>