5 use parent qw(Rose::Object);
 
  11 use SL::DB::ShopImage;
 
  13 use SL::Helper::UserPreferences;
 
  14 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_type);
 
  17 use constant RENAME_OK          => 0;
 
  18 use constant RENAME_EXISTS      => 1;
 
  19 use constant RENAME_NOFILE      => 2;
 
  20 use constant RENAME_SAME        => 3;
 
  21 use constant RENAME_NEW_VERSION => 4;
 
  24   my ($self, %params) = @_;
 
  25   die 'no id' unless $params{id};
 
  26   my $dbfile = SL::DB::Manager::File->get_first(query => [id => $params{id}]);
 
  27   die 'not found' unless $dbfile;
 
  28   $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$dbfile->object_id." object_type=".$dbfile->object_type." dbfile=".$dbfile);
 
  29   SL::File::Object->new(db_file => $dbfile, id => $dbfile->id, loaded => 1);
 
  32 sub get_version_count {
 
  33   my ($self, %params) = @_;
 
  34   die "no id or dbfile" unless $params{id} || $params{dbfile};
 
  35   $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
 
  36   die 'not found' unless $params{dbfile};
 
  37   my $backend = $self->_get_backend($params{dbfile}->backend);
 
  38   return $backend->get_version_count(%params);
 
  42   my ($self, %params) = @_;
 
  45   return @files unless $params{object_type};
 
  46   return @files unless defined($params{object_id});
 
  49     object_id   => $params{object_id},
 
  50     object_type => $params{object_type}
 
  52   push @query, (file_name => $params{file_name}) if $params{file_name};
 
  53   push @query, (file_type => $params{file_type}) if $params{file_type};
 
  54   push @query, (mime_type => $params{mime_type}) if $params{mime_type};
 
  55   push @query, (source    => $params{source})    if $params{source};
 
  57   my $sortby = $params{sort_by} || 'itime DESC,file_name ASC';
 
  59   @files = @{ SL::DB::Manager::File->get_all(query => [@query], sort_by => $sortby) };
 
  60   map { SL::File::Object->new(db_file => $_, id => $_->id, loaded => 1) } @files;
 
  63 sub get_all_versions {
 
  64   my ($self, %params) = @_;
 
  66   my @fileobjs = $self->get_all(%params);
 
  67   if ( $params{dbfile} ) {
 
  68     push @fileobjs, SL::File::Object->new(dbfile => $params{db_file}, id => $params{dbfile}->id, loaded => 1);
 
  70     @fileobjs = $self->get_all(%params);
 
  72   foreach my $fileobj (@fileobjs) {
 
  73     $main::lxdebug->message(LXDebug->DEBUG2(), "obj=" . $fileobj . " id=" . $fileobj->id." versions=".$fileobj->version_count);
 
  74     my $maxversion = $fileobj->version_count;
 
  75     push @versionobjs, $fileobj;
 
  76     if ($maxversion > 1) {
 
  77       for my $version (2..$maxversion) {
 
  78         $main::lxdebug->message(LXDebug->DEBUG2(), "clone for version=".($maxversion-$version+1));
 
  80           my $clone = clone($fileobj);
 
  81           $clone->version($maxversion-$version+1);
 
  83           $main::lxdebug->message(LXDebug->DEBUG2(), "clone version=".$clone->version." mtime=". $clone->mtime);
 
  84           push @versionobjs, $clone;
 
  94   my ($self, %params) = @_;
 
  95   return 0 unless $params{object_type};
 
  98     object_id   => $params{object_id},
 
  99     object_type => $params{object_type}
 
 101   push @query, (file_name => $params{file_name}) if $params{file_name};
 
 102   push @query, (file_type => $params{file_type}) if $params{file_type};
 
 103   push @query, (mime_type => $params{mime_type}) if $params{mime_type};
 
 104   push @query, (source    => $params{source})    if $params{source};
 
 106   my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
 
 111   my ($self, %params) = @_;
 
 112   return 0 unless defined($params{object_id}) || $params{object_type};
 
 113   my $files = SL::DB::Manager::File->get_all(
 
 115       object_id   => $params{object_id},
 
 116       object_type => $params{object_type}
 
 119   foreach my $file (@{$files}) {
 
 120     $params{dbfile} = $file;
 
 121     $self->delete(%params);
 
 126   my ($self, %params) = @_;
 
 127   die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
 
 130     $rc = SL::DB->client->with_transaction(\&_delete, $self, %params);
 
 137   my ($self, %params) = @_;
 
 138   $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
 
 140   my $backend = $self->_get_backend($params{dbfile}->backend);
 
 141   if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
 
 144     my $hist = SL::DB::Manager::History->get_first(
 
 146         addition  => 'IMPORT',
 
 147         trans_id  => $params{dbfile}->object_id,
 
 148         what_done => $params{dbfile}->id
 
 153       if (!$main::auth->assert('import_ar | import_ap', 1)) {
 
 154         die 'no permission to unimport';
 
 156       my $file = $backend->get_filepath(dbfile => $params{dbfile});
 
 157       $main::lxdebug->message(LXDebug->DEBUG2(), "del file=" . $file . " to=" . $hist->snumbers);
 
 158       File::Copy::copy($file, $hist->snumbers) if $file;
 
 159       $hist->addition('UNIMPORT');
 
 163   if ($backend->delete(%params)) {
 
 165     if ( $params{last} || $params{all_but_notlast} ) {
 
 166       if ( $backend->get_version_count > 0 ) {
 
 167         $params{dbfile}->mtime(DateTime->now_local);
 
 168         $params{dbfile}->save;
 
 175     $params{dbfile}->delete if $do_delete;
 
 182   my ($self, %params) = @_;
 
 186     $obj = SL::DB->client->with_transaction(\&_save, $self, %params);
 
 193   my ($self, %params) = @_;
 
 194   my $file = $params{dbfile};
 
 198     $file = SL::DB::File->new(id => $params{id})->load;
 
 199     die 'dbfile not exists'     unless $file;
 
 201   $main::lxdebug->message(LXDebug->DEBUG2(), "obj_id=" .$params{object_id});
 
 202     die 'no object type set'    unless $params{object_type};
 
 203     die 'no object id set'      unless defined($params{object_id});
 
 205     $exists = $self->get_all_count(%params);
 
 206     die 'filename still exist' if $exists && $params{fail_if_exists};
 
 208       my ($obj1) = $self->get_all(%params);
 
 209       $file = $obj1->db_file;
 
 211       $file = SL::DB::File->new();
 
 212       $file->assign_attributes(
 
 213         object_id      => $params{object_id},
 
 214         object_type    => $params{object_type},
 
 215         source         => $params{source},
 
 216         file_type      => $params{file_type},
 
 217         file_name      => $params{file_name},
 
 218         mime_type      => $params{mime_type},
 
 219         title          => $params{title},
 
 220         description    => $params{description},
 
 227     #change attr on existing file
 
 228     $file->file_name  ($params{file_name})   if $params{file_name};
 
 229     $file->mime_type  ($params{mime_type})   if $params{mime_type};
 
 230     $file->title      ($params{title})       if $params{title};
 
 231     $file->description($params{description}) if $params{description};
 
 233   if ( !$file->backend ) {
 
 234     $file->backend($self->_get_backend_by_file_type($file));
 
 235     # load itime for new file
 
 238   $main::lxdebug->message(LXDebug->DEBUG2(), "backend3=" .$file->backend);
 
 239   my $backend = $self->_get_backend($file->backend);
 
 240   $params{dbfile} = $file;
 
 241   $backend->save(%params);
 
 243   $file->mtime(DateTime->now_local);
 
 246   if($file->object_type eq "shop_image"){
 
 247     my $image_content = $params{file_contents};
 
 248     my $thumbnail = file_probe_type($image_content);
 
 249     my $shopimage = SL::DB::ShopImage->new();
 
 250     $shopimage->assign_attributes(
 
 251                                   file_id                => $file->id,
 
 252                                   thumbnail_content      => $thumbnail->{thumbnail_img_content},
 
 253                                   org_file_height        => $thumbnail->{file_image_height},
 
 254                                   org_file_width         => $thumbnail->{file_image_width},
 
 255                                   thumbnail_content_type => $thumbnail->{thumbnail_img_content_type},
 
 256                                   object_id              => $file->object_id,
 
 260   if ($params{file_type} eq 'document' && $params{source} ne 'created') {
 
 261     SL::DB::History->new(
 
 262       addition    => 'IMPORT',
 
 263       trans_id    => $params{object_id},
 
 264       snumbers    => $params{file_path},
 
 265       employee_id => SL::DB::Manager::Employee->current->id,
 
 266       what_done   => $params{dbfile}->id
 
 269   return $params{obj} if $params{dbfile} && $params{obj};
 
 270   return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
 
 274   my ($self, %params) = @_;
 
 275   return RENAME_NOFILE unless $params{id} || $params{dbfile};
 
 276   my $file = $params{dbfile};
 
 277   $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file;
 
 278   return RENAME_NOFILE unless $file;
 
 280   $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
 
 282     return RENAME_SAME   if $params{to} eq $file->file_name;
 
 283     return RENAME_EXISTS if $self->get_all_count( object_id     => $file->object_id,
 
 284                                                   object_type   => $file->object_type,
 
 285                                                   mime_type     => $file->mime_type,
 
 286                                                   source        => $file->source,
 
 287                                                   file_type     => $file->file_type,
 
 288                                                   file_name     => $params{to}
 
 291     my $backend = $self->_get_backend($file->backend);
 
 292     $backend->rename(dbfile => $file) if $backend;
 
 293     $file->file_name($params{to});
 
 299 sub get_backend_class {
 
 300   my ($self, $backendname) = @_;
 
 301   die "no backend name set" unless $backendname;
 
 302   $self->_get_backend($backendname);
 
 305 sub get_other_sources {
 
 307   my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
 
 308   $pref->login("#default#");
 
 310   foreach my $tuple (@{ $pref->get_all() }) {
 
 311     my %lkeys  = %{ SL::JSON::from_json($tuple->{value}) };
 
 313       'name'        => $tuple->{key},
 
 314       'description' => $lkeys{desc},
 
 315       'directory'   => $lkeys{dir}
 
 317     push @sources, $source;
 
 322 sub sync_from_backend {
 
 323   my ($self, %params) = @_;
 
 324   return unless $params{file_type};
 
 325   my $file = SL::DB::File->new;
 
 326   $file->file_type($params{file_type});
 
 327   my $backend = $self->_get_backend($self->_get_backend_by_file_type($file));
 
 328   return unless $backend;
 
 329   $backend->sync_from_backend(%params);
 
 336   my ($self, $backend_name) = @_;
 
 337   my $class = 'SL::File::Backend::' . $backend_name;
 
 339   die $::locale->text('no backend enabled') if $backend_name eq 'None';
 
 341     eval "require $class";
 
 343     die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
 
 349       die $::locale->text('backend "#1" not found',$backend_name);
 
 355 sub _get_backend_by_file_type {
 
 356   my ($self, $dbfile) = @_;
 
 358   $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
 
 359   return "Filesystem" unless $dbfile;
 
 360   return $::instance_conf->get_doc_storage_for_documents   if $dbfile->file_type eq 'document';
 
 361   return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
 
 362   return $::instance_conf->get_doc_storage_for_images      if $dbfile->file_type eq 'image';
 
 376 SL::File - The intermediate Layer for handling files
 
 380   # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
 
 381   # you can create, remove, delete etc. a file in a backend independent way
 
 383   my $file  = SL::File->save(
 
 384                      object_id     => $self->object_id,
 
 385                      object_type   => $self->object_type,
 
 386                      mime_type     => 'application/pdf',
 
 387                      file_type     => 'documents',
 
 388                      file_contents => 'this is no pdf');
 
 390   my $file1  = SL::File->get(id => $id);
 
 391   SL::File->delete(id => $id);
 
 392   SL::File->delete(dbfile => $file1);
 
 393   SL::File->delete_all(object_id   => $object_id,
 
 394                        object_type => $object_type,
 
 395                        file_type   => $filetype      # may be optional
 
 397   SL::File->rename(id => $id,to => $newname);
 
 398   my $files1 = SL::File->get_all(object_id   => $object_id,
 
 399                                  object_type => $object_type,
 
 400                                  file_type   => 'images',  # may be optional
 
 401                                  source      => 'uploaded' # may be optional
 
 404   # Alternativelly some operation can be done with the filemangement object wrapper
 
 405   # and additional oparations see L<SL::File::Object>
 
 409 The Filemanagemt can handle files in a storage independent way. Internal the File
 
 410 use the configured storage backend for the type of file.
 
 411 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
 
 413 There are three types of files:
 
 419 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
 
 420 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
 
 421 deleted by the user if she/he is allowed to do this.
 
 425 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
 
 426 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
 
 428 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
 
 429 combinide/merged with the document-file in the time of printing.
 
 430 Today only PDF-Attachmnets can be merged with the generated document-PDF.
 
 434 they are like attachments, but they may be have thumbnails for displaying.
 
 435 So the must have an image format like png,jpg. The versioning is like attachments
 
 439 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
 
 441 The files have also the parameter C<Source>:
 
 445 =item - created, generated by LaTeX
 
 449 =item - scanner, import from scanner
 
 451 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
 
 453 =item - email, received by email and imported by hand or automatic.
 
 457 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
 
 458 This means they are moved back into the correspondent source directories.
 
 460 The scanner and email import must be configured  via Table UserPreferences:
 
 464  id |  login  |  namespace   | version |   key    |                        value
 
 465 ----+---------+--------------+---------+----------+------------------------------------------------------
 
 466   1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
 
 467   2 | default | file_sources | 0.00000 | emails   | {"dir":"/var/tmp/emails"  ,"desc":"Empfangene Mails"}
 
 473 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
 
 474 The modul and object_id describe the link to the object.
 
 476 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
 
 478 The storage backends are extra classes which depends from L<SL::File::Backend>.
 
 479 So additional backend classes can be added.
 
 481 The implementation of versioning is done in the different backends.
 
 489 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
 
 495                          object_id    => $self->object_id,
 
 496                          object_type  => $self->object_type,
 
 497                          content_type => 'application/pdf'
 
 504 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
 
 505 so in the history the import is documented also as a hint to can unimport the file later.
 
 513 The id of SL::DB::File for an existing file
 
 517 The Id of the ERP-object for a new file.
 
 521 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
 
 525 The type may be "documents", "attachments" or "images" for a new file.
 
 529 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
 
 533 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
 
 537 The mime_type of a new file. This is used for downloading or for email attachments.
 
 539 =item C<description> or C<title>
 
 541 The description or title of a new file. This must be discussed if this attribute is needed.
 
 545 =item C<delete PARAMS>
 
 547 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
 
 548 they moved back to the source folders. This is documented in the history.
 
 556 The id of SL::DB::File
 
 560 As alternative if the SL::DB::File as object is available.
 
 564 =item C<delete_all PARAMS>
 
 566 All file data of an ERP-object is deleted in the backend.
 
 572 The Id of the ERP-object.
 
 576 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
 
 580 =item C<rename PARAMS>
 
 582 The Filename of the file is changed
 
 590 The id of SL::DB::File
 
 600 The actual file object is retrieved. The id of the object is needed.
 
 608 The id of SL::DB::File
 
 612 =item C<get_all PARAMS>
 
 614 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
 
 622 The Id of the ERP-object.
 
 626 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
 
 630 The type may be "documents", "attachments" or "images". This parameter is optional.
 
 634 The name of the file . This parameter is optional.
 
 638 The MIME type of the file . This parameter is optional.
 
 642 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
 
 646 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
 
 650 =item C<get_all_versions PARAMS>
 
 652 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
 
 653 If only the versions of one file are wanted, additional parameter like file_name must be set.
 
 654 If the param C<dbfile> set, only the versions of this file are returned.
 
 656 Available C<PARAMS> ar the same as L<get_all>
 
 660 =item C<get_all_count PARAMS>
 
 662 The count of available files is returned.
 
 663 Available C<PARAMS> ar the same as L<get_all>
 
 666 =item C<get_content PARAMS>
 
 668 The data of a file can retrieved. A reference to the data is returned.
 
 676 The id of SL::DB::File
 
 680 If no Id exists the object SL::DB::File as param.
 
 684 =item C<get_file_path PARAMS>
 
 686 Sometimes it is more useful to have a path to the file not the contents. If the backend has not stored the content as file
 
 687 it is in the responsibility of the backend to create a tempory session file.
 
 695 The id of SL::DB::File
 
 699 If no Id exists the object SL::DB::File as param.
 
 703 =item C<get_other_sources>
 
 705 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
 
 707 =item C<sync_from_backend>
 
 709 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
 
 710 This sync must be triggered by a periodical task.
 
 718 The synchronization is done file_type by file_type.
 
 726 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>