5 use parent qw(Rose::Object);
 
  10 use SL::DB::ShopImage;
 
  12 use SL::Helper::UserPreferences;
 
  13 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_type);
 
  16 use constant RENAME_OK          => 0;
 
  17 use constant RENAME_EXISTS      => 1;
 
  18 use constant RENAME_NOFILE      => 2;
 
  19 use constant RENAME_SAME        => 3;
 
  20 use constant RENAME_NEW_VERSION => 4;
 
  23   my ($self, %params) = @_;
 
  24   die 'no id' unless $params{id};
 
  25   my $dbfile = SL::DB::Manager::File->get_first(query => [id => $params{id}]);
 
  26   die 'not found' unless $dbfile;
 
  27   $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$dbfile->object_id." object_type=".$dbfile->object_type." dbfile=".$dbfile);
 
  28   SL::File::Object->new(db_file => $dbfile, id => $dbfile->id, loaded => 1);
 
  31 sub get_version_count {
 
  32   my ($self, %params) = @_;
 
  33   die "no id or dbfile" unless $params{id} || $params{dbfile};
 
  34   $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
 
  35   die 'not found' unless $params{dbfile};
 
  36   my $backend = $self->_get_backend($params{dbfile}->backend);
 
  37   return $backend->get_version_count(%params);
 
  41   my ($self, %params) = @_;
 
  44   return @files unless $params{object_type};
 
  45   return @files unless defined($params{object_id});
 
  48     object_id   => $params{object_id},
 
  49     object_type => $params{object_type}
 
  51   push @query, (file_name     => $params{file_name})     if $params{file_name};
 
  52   push @query, (file_type     => $params{file_type})     if $params{file_type};
 
  53   push @query, (mime_type     => $params{mime_type})     if $params{mime_type};
 
  54   push @query, (source        => $params{source})        if $params{source};
 
  55   push @query, (print_variant => $params{print_variant}) if $params{print_variant};
 
  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     $fileobj->version($maxversion);
 
  76     push @versionobjs, $fileobj;
 
  77     if ($maxversion > 1) {
 
  78       for my $version (2..$maxversion) {
 
  79         $main::lxdebug->message(LXDebug->DEBUG2(), "clone for version=".($maxversion-$version+1));
 
  81           my $clone = $fileobj->clone;
 
  82           $clone->version($maxversion-$version+1);
 
  84           $main::lxdebug->message(LXDebug->DEBUG2(), "clone version=".$clone->version." mtime=". $clone->mtime);
 
  85           push @versionobjs, $clone;
 
  87         } or do {$::lxdebug->message(LXDebug::WARN(), "clone for version=".($maxversion-$version+1) . "failed: " . $@)};
 
  95   my ($self, %params) = @_;
 
  96   return 0 unless $params{object_type};
 
  99     object_id   => $params{object_id},
 
 100     object_type => $params{object_type}
 
 102   push @query, (file_name     => $params{file_name})     if $params{file_name};
 
 103   push @query, (file_type     => $params{file_type})     if $params{file_type};
 
 104   push @query, (mime_type     => $params{mime_type})     if $params{mime_type};
 
 105   push @query, (source        => $params{source})        if $params{source};
 
 106   push @query, (print_variant => $params{print_variant}) if $params{print_variant};
 
 108   my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
 
 113   my ($self, %params) = @_;
 
 114   return 0 unless defined($params{object_id}) || $params{object_type};
 
 115   my $files = SL::DB::Manager::File->get_all(
 
 117       object_id   => $params{object_id},
 
 118       object_type => $params{object_type}
 
 121   foreach my $file (@{$files}) {
 
 122     $params{dbfile} = $file;
 
 123     $self->delete(%params);
 
 128   my ($self, %params) = @_;
 
 129   die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
 
 132     $rc = SL::DB->client->with_transaction(\&_delete, $self, %params);
 
 139   my ($self, %params) = @_;
 
 140   $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
 
 142   my $backend = $self->_get_backend($params{dbfile}->backend);
 
 143   if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
 
 146     my $hist = SL::DB::Manager::History->get_first(
 
 148         addition  => 'IMPORT',
 
 149         trans_id  => $params{dbfile}->object_id,
 
 150         what_done => $params{dbfile}->id
 
 155       if (!$main::auth->assert('import_ar | import_ap', 1)) {
 
 156         die 'no permission to unimport';
 
 158       my $file = $backend->get_filepath(dbfile => $params{dbfile});
 
 159       $main::lxdebug->message(LXDebug->DEBUG2(), "del file=" . $file . " to=" . $hist->snumbers);
 
 160       File::Copy::copy($file, $hist->snumbers) if $file;
 
 161       $hist->addition('UNIMPORT');
 
 165   if ($backend->delete(%params)) {
 
 167     if ( $params{last} || $params{version} || $params{all_but_notlast} ) {
 
 168       if ( $backend->get_version_count(%params) > 0 ) {
 
 169         $params{dbfile}->mtime(DateTime->now_local);
 
 170         $params{dbfile}->save;
 
 177     $params{dbfile}->delete if $do_delete;
 
 184   my ($self, %params) = @_;
 
 188     $obj = SL::DB->client->with_transaction(\&_save, $self, %params);
 
 195   my ($self, %params) = @_;
 
 196   my $file = $params{dbfile};
 
 200     $file = SL::DB::File->new(id => $params{id})->load;
 
 201     die 'dbfile not exists'     unless $file;
 
 203   $main::lxdebug->message(LXDebug->DEBUG2(), "obj_id=" .$params{object_id});
 
 204     die 'no object type set'    unless $params{object_type};
 
 205     die 'no object id set'      unless defined($params{object_id});
 
 207     $exists = $self->get_all_count(%params);
 
 208     die 'filename still exist' if $exists && $params{fail_if_exists};
 
 210       my ($obj1) = $self->get_all(%params);
 
 211       $file = $obj1->db_file;
 
 213       $file = SL::DB::File->new();
 
 214       $file->assign_attributes(
 
 215         object_id      => $params{object_id},
 
 216         object_type    => $params{object_type},
 
 217         source         => $params{source},
 
 218         file_type      => $params{file_type},
 
 219         file_name      => $params{file_name},
 
 220         mime_type      => $params{mime_type},
 
 221         title          => $params{title},
 
 222         description    => $params{description},
 
 223         print_variant  => $params{print_variant},
 
 225       $file->itime($params{mtime})    if $params{mtime};
 
 226       $params{itime} = $params{mtime} if $params{mtime};
 
 232     #change attr on existing file
 
 233     $file->file_name  ($params{file_name})   if $params{file_name};
 
 234     $file->mime_type  ($params{mime_type})   if $params{mime_type};
 
 235     $file->title      ($params{title})       if $params{title};
 
 236     $file->description($params{description}) if $params{description};
 
 238   if ( !$file->backend ) {
 
 239     $file->backend($self->_get_backend_by_file_type($file));
 
 240     # load itime for new file
 
 244   $file->mtime(DateTime->now_local) unless $params{mtime};
 
 245   $file->mtime($params{mtime}     ) if     $params{mtime};
 
 247   my $backend = $self->_get_backend($file->backend);
 
 248   $params{dbfile} = $file;
 
 249   $backend->save(%params);
 
 253   if($file->object_type eq "shop_image"){
 
 254     my $image_content = $params{file_contents};
 
 255     my $thumbnail = file_probe_type($image_content);
 
 256     my $shopimage = SL::DB::ShopImage->new();
 
 257     $shopimage->assign_attributes(
 
 258                                   file_id                => $file->id,
 
 259                                   thumbnail_content      => $thumbnail->{thumbnail_img_content},
 
 260                                   org_file_height        => $thumbnail->{file_image_height},
 
 261                                   org_file_width         => $thumbnail->{file_image_width},
 
 262                                   thumbnail_content_type => $thumbnail->{thumbnail_img_content_type},
 
 263                                   object_id              => $file->object_id,
 
 267   if ($params{file_type} eq 'document' && $params{source} ne 'created') {
 
 268     SL::DB::History->new(
 
 269       addition    => 'IMPORT',
 
 270       trans_id    => $params{object_id},
 
 271       snumbers    => $params{file_path},
 
 272       employee_id => SL::DB::Manager::Employee->current->id,
 
 273       what_done   => $params{dbfile}->id
 
 276   return $params{obj} if $params{dbfile} && $params{obj};
 
 277   return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
 
 281   my ($self, %params) = @_;
 
 282   return RENAME_NOFILE unless $params{id} || $params{dbfile};
 
 283   my $file = $params{dbfile};
 
 284   $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file;
 
 285   return RENAME_NOFILE unless $file;
 
 287   $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
 
 289     return RENAME_SAME   if $params{to} eq $file->file_name;
 
 290     return RENAME_EXISTS if $self->get_all_count( object_id     => $file->object_id,
 
 291                                                   object_type   => $file->object_type,
 
 292                                                   mime_type     => $file->mime_type,
 
 293                                                   source        => $file->source,
 
 294                                                   file_type     => $file->file_type,
 
 295                                                   file_name     => $params{to}
 
 298     my $backend = $self->_get_backend($file->backend);
 
 299     $backend->rename(dbfile => $file) if $backend;
 
 300     $file->file_name($params{to});
 
 306 sub get_backend_class {
 
 307   my ($self, $backendname) = @_;
 
 308   die "no backend name set" unless $backendname;
 
 309   $self->_get_backend($backendname);
 
 312 sub get_other_sources {
 
 314   my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
 
 315   $pref->login("#default#");
 
 317   foreach my $tuple (@{ $pref->get_all() }) {
 
 318     my %lkeys  = %{ SL::JSON::from_json($tuple->{value}) };
 
 320       'name'        => $tuple->{key},
 
 321       'description' => $lkeys{desc},
 
 322       'directory'   => $lkeys{dir}
 
 324     push @sources, $source;
 
 329 sub sync_from_backend {
 
 330   my ($self, %params) = @_;
 
 331   return unless $params{file_type};
 
 332   my $file = SL::DB::File->new;
 
 333   $file->file_type($params{file_type});
 
 334   my $backend = $self->_get_backend($self->_get_backend_by_file_type($file));
 
 335   return unless $backend;
 
 336   $backend->sync_from_backend(%params);
 
 343   my ($self, $backend_name) = @_;
 
 344   my $class = 'SL::File::Backend::' . $backend_name;
 
 346   die $::locale->text('no backend enabled') if $backend_name eq 'None';
 
 348     eval "require $class";
 
 350     die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
 
 356       die $::locale->text('backend "#1" not found',$backend_name);
 
 362 sub _get_backend_by_file_type {
 
 363   my ($self, $dbfile) = @_;
 
 365   $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
 
 366   return "Filesystem" unless $dbfile;
 
 367   return $::instance_conf->get_doc_storage_for_documents   if $dbfile->file_type eq 'document';
 
 368   return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
 
 369   return $::instance_conf->get_doc_storage_for_images      if $dbfile->file_type eq 'image';
 
 383 SL::File - The intermediate Layer for handling files
 
 387   # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
 
 388   # you can create, remove, delete etc. a file in a backend independent way
 
 390   my $file  = SL::File->save(
 
 391                      object_id     => $self->object_id,
 
 392                      object_type   => $self->object_type,
 
 393                      mime_type     => 'application/pdf',
 
 394                      file_type     => 'document',
 
 395                      file_contents => 'this is no pdf');
 
 397   my $file1  = SL::File->get(id => $id);
 
 398   SL::File->delete(id => $id);
 
 399   SL::File->delete(dbfile => $file1);
 
 400   SL::File->delete_all(object_id   => $object_id,
 
 401                        object_type => $object_type,
 
 402                        file_type   => $filetype      # may be optional
 
 404   SL::File->rename(id => $id,to => $newname);
 
 405   my $files1 = SL::File->get_all(object_id   => $object_id,
 
 406                                  object_type => $object_type,
 
 407                                  file_type   => 'image',   # may be optional
 
 408                                  source      => 'uploaded' # may be optional
 
 411   # Alternativelly some operation can be done with the filemangement object wrapper
 
 412   # and additional oparations see L<SL::File::Object>
 
 416 The Filemanagemt can handle files in a storage independent way. Internal the File
 
 417 use the configured storage backend for the type of file.
 
 418 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
 
 420 There are three types of files:
 
 426 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
 
 427 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
 
 428 deleted by the user if she/he is allowed to do this.
 
 432 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
 
 433 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
 
 435 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
 
 436 combinide/merged with the document-file in the time of printing.
 
 437 Today only PDF-Attachmnets can be merged with the generated document-PDF.
 
 441 they are like attachments, but they may be have thumbnails for displaying.
 
 442 So the must have an image format like png,jpg. The versioning is like attachments
 
 446 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
 
 448 The files have also the parameter C<Source>:
 
 452 =item - created, generated by LaTeX
 
 456 =item - scanner, import from scanner
 
 458 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
 
 460 =item - email, received by email and imported by hand or automatic.
 
 464 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
 
 465 This means they are moved back into the correspondent source directories.
 
 467 The scanner and email import must be configured  via Table UserPreferences:
 
 471  id |  login  |  namespace   | version |   key    |                        value
 
 472 ----+---------+--------------+---------+----------+------------------------------------------------------
 
 473   1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
 
 474   2 | default | file_sources | 0.00000 | emails   | {"dir":"/var/tmp/emails"  ,"desc":"Empfangene Mails"}
 
 480 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
 
 481 The modul and object_id describe the link to the object.
 
 483 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
 
 485 The storage backends are extra classes which depends from L<SL::File::Backend>.
 
 486 So additional backend classes can be added.
 
 488 The implementation of versioning is done in the different backends.
 
 496 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
 
 502                          object_id    => $self->object_id,
 
 503                          object_type  => $self->object_type,
 
 504                          content_type => 'application/pdf'
 
 511 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
 
 512 so in the history the import is documented also as a hint to can unimport the file later.
 
 520 The id of SL::DB::File for an existing file
 
 524 The Id of the ERP-object for a new file.
 
 528 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
 
 532 The type may be "document", "attachment" or "image" for a new file.
 
 536 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
 
 540 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
 
 544 The mime_type of a new file. This is used for downloading or for email attachments.
 
 546 =item C<description> or C<title>
 
 548 The description or title of a new file. This must be discussed if this attribute is needed.
 
 552 =item C<delete PARAMS>
 
 554 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
 
 555 they moved back to the source folders. This is documented in the history.
 
 563 The id of SL::DB::File
 
 567 As alternative if the SL::DB::File as object is available.
 
 571 =item C<delete_all PARAMS>
 
 573 All file data of an ERP-object is deleted in the backend.
 
 579 The Id of the ERP-object.
 
 583 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
 
 587 =item C<rename PARAMS>
 
 589 The Filename of the file is changed
 
 597 The id of SL::DB::File
 
 607 The actual file object is retrieved. The id of the object is needed.
 
 615 The id of SL::DB::File
 
 619 =item C<get_all PARAMS>
 
 621 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
 
 629 The Id of the ERP-object.
 
 633 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
 
 637 The type may be "document", "attachment" or "image". This parameter is optional.
 
 641 The name of the file . This parameter is optional.
 
 645 The MIME type of the file . This parameter is optional.
 
 649 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
 
 653 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
 
 657 =item C<get_all_versions PARAMS>
 
 659 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
 
 660 If only the versions of one file are wanted, additional parameter like file_name must be set.
 
 661 If the param C<dbfile> set, only the versions of this file are returned.
 
 663 Available C<PARAMS> ar the same as L<get_all>
 
 667 =item C<get_all_count PARAMS>
 
 669 The count of available files is returned.
 
 670 Available C<PARAMS> ar the same as L<get_all>
 
 673 =item C<get_content PARAMS>
 
 675 The data of a file can retrieved. A reference to the data is returned.
 
 683 The id of SL::DB::File
 
 687 If no Id exists the object SL::DB::File as param.
 
 691 =item C<get_file_path PARAMS>
 
 693 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
 
 694 it is in the responsibility of the backend to create a tempory session file.
 
 702 The id of SL::DB::File
 
 706 If no Id exists the object SL::DB::File as param.
 
 710 =item C<get_other_sources>
 
 712 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
 
 714 =item C<sync_from_backend>
 
 716 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
 
 717 This sync must be triggered by a periodical task.
 
 725 The synchronization is done file_type by file_type.
 
 733 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>