Dateimanagement: Checkbox zum Löschen/Umbenennen fehlt teilweise bei ERP-Dokumenten
[kivitendo-erp.git] / SL / File.pm
1 package SL::File;
2
3 use strict;
4
5 use parent qw(Rose::Object);
6
7 use Clone qw(clone);
8 use SL::File::Backend;
9 use SL::File::Object;
10 use SL::DB::History;
11 use SL::DB::ShopImage;
12 use SL::DB::File;
13 use SL::Helper::UserPreferences;
14 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_type);
15 use SL::JSON;
16
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;
22
23 sub get {
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);
30 }
31
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);
39 }
40
41 sub get_all {
42   my ($self, %params) = @_;
43
44   my @files;
45   return @files unless $params{object_type};
46   return @files unless defined($params{object_id});
47
48   my @query = (
49     object_id   => $params{object_id},
50     object_type => $params{object_type}
51   );
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};
56
57   my $sortby = $params{sort_by} || 'itime DESC,file_name ASC';
58
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;
61 }
62
63 sub get_all_versions {
64   my ($self, %params) = @_;
65   my @versionobjs;
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);
69   } else {
70     @fileobjs = $self->get_all(%params);
71   }
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));
80         eval {
81           my $clone = clone($fileobj);
82           $clone->version($maxversion-$version+1);
83           $clone->newest(0);
84           $main::lxdebug->message(LXDebug->DEBUG2(), "clone version=".$clone->version." mtime=". $clone->mtime);
85           push @versionobjs, $clone;
86           1;
87         }
88       }
89     }
90   }
91   return @versionobjs;
92 }
93
94 sub get_all_count {
95   my ($self, %params) = @_;
96   return 0 unless $params{object_type};
97
98   my @query = (
99     object_id   => $params{object_id},
100     object_type => $params{object_type}
101   );
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
107   my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
108   return $cnt;
109 }
110
111 sub delete_all {
112   my ($self, %params) = @_;
113   return 0 unless defined($params{object_id}) || $params{object_type};
114   my $files = SL::DB::Manager::File->get_all(
115     query => [
116       object_id   => $params{object_id},
117       object_type => $params{object_type}
118     ]
119   );
120   foreach my $file (@{$files}) {
121     $params{dbfile} = $file;
122     $self->delete(%params);
123   }
124 }
125
126 sub delete {
127   my ($self, %params) = @_;
128   die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
129   my $rc = 0;
130   eval {
131     $rc = SL::DB->client->with_transaction(\&_delete, $self, %params);
132     1;
133   } or do { die $@ };
134   return $rc;
135 }
136
137 sub _delete {
138   my ($self, %params) = @_;
139   $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
140
141   my $backend = $self->_get_backend($params{dbfile}->backend);
142   if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
143   {
144     ## must unimport
145     my $hist = SL::DB::Manager::History->get_first(
146       where => [
147         addition  => 'IMPORT',
148         trans_id  => $params{dbfile}->object_id,
149         what_done => $params{dbfile}->id
150       ]
151     );
152
153     if ($hist) {
154       if (!$main::auth->assert('import_ar | import_ap', 1)) {
155         die 'no permission to unimport';
156       }
157       my $file = $backend->get_filepath(dbfile => $params{dbfile});
158       $main::lxdebug->message(LXDebug->DEBUG2(), "del file=" . $file . " to=" . $hist->snumbers);
159       File::Copy::copy($file, $hist->snumbers) if $file;
160       $hist->addition('UNIMPORT');
161       $hist->save;
162     }
163   }
164   if ($backend->delete(%params)) {
165     my $do_delete = 0;
166     if ( $params{last} || $params{version} || $params{all_but_notlast} ) {
167       if ( $backend->get_version_count(%params) > 0 ) {
168         $params{dbfile}->mtime(DateTime->now_local);
169         $params{dbfile}->save;
170       } else {
171         $do_delete = 1;
172       }
173     } else {
174       $do_delete = 1;
175     }
176     $params{dbfile}->delete if $do_delete;
177     return 1;
178   }
179   return 0;
180 }
181
182 sub save {
183   my ($self, %params) = @_;
184
185   my $obj;
186   eval {
187     $obj = SL::DB->client->with_transaction(\&_save, $self, %params);
188     1;
189   } or do { die $@ };
190   return $obj;
191 }
192
193 sub _save {
194   my ($self, %params) = @_;
195   my $file = $params{dbfile};
196   my $exists = 0;
197
198   if ($params{id}) {
199     $file = SL::DB::File->new(id => $params{id})->load;
200     die 'dbfile not exists'     unless $file;
201   } elsif (!$file) {
202   $main::lxdebug->message(LXDebug->DEBUG2(), "obj_id=" .$params{object_id});
203     die 'no object type set'    unless $params{object_type};
204     die 'no object id set'      unless defined($params{object_id});
205
206     $exists = $self->get_all_count(%params);
207     die 'filename still exist' if $exists && $params{fail_if_exists};
208     if ($exists) {
209       my ($obj1) = $self->get_all(%params);
210       $file = $obj1->db_file;
211     } else {
212       $file = SL::DB::File->new();
213       $file->assign_attributes(
214         object_id      => $params{object_id},
215         object_type    => $params{object_type},
216         source         => $params{source},
217         file_type      => $params{file_type},
218         file_name      => $params{file_name},
219         mime_type      => $params{mime_type},
220         title          => $params{title},
221         description    => $params{description},
222       );
223     }
224   } else {
225     $exists = 1;
226   }
227   if ($exists) {
228     #change attr on existing file
229     $file->file_name  ($params{file_name})   if $params{file_name};
230     $file->mime_type  ($params{mime_type})   if $params{mime_type};
231     $file->title      ($params{title})       if $params{title};
232     $file->description($params{description}) if $params{description};
233   }
234   if ( !$file->backend ) {
235     $file->backend($self->_get_backend_by_file_type($file));
236     # load itime for new file
237     $file->save->load;
238   }
239   $main::lxdebug->message(LXDebug->DEBUG2(), "backend3=" .$file->backend);
240   my $backend = $self->_get_backend($file->backend);
241   $params{dbfile} = $file;
242   $backend->save(%params);
243
244   $file->mtime(DateTime->now_local);
245   $file->save;
246   #ShopImage
247   if($file->object_type eq "shop_image"){
248     my $image_content = $params{file_contents};
249     my $thumbnail = file_probe_type($image_content);
250     my $shopimage = SL::DB::ShopImage->new();
251     $shopimage->assign_attributes(
252                                   file_id                => $file->id,
253                                   thumbnail_content      => $thumbnail->{thumbnail_img_content},
254                                   org_file_height        => $thumbnail->{file_image_height},
255                                   org_file_width         => $thumbnail->{file_image_width},
256                                   thumbnail_content_type => $thumbnail->{thumbnail_img_content_type},
257                                   object_id              => $file->object_id,
258                                  );
259     $shopimage->save;
260   }
261   if ($params{file_type} eq 'document' && $params{source} ne 'created') {
262     SL::DB::History->new(
263       addition    => 'IMPORT',
264       trans_id    => $params{object_id},
265       snumbers    => $params{file_path},
266       employee_id => SL::DB::Manager::Employee->current->id,
267       what_done   => $params{dbfile}->id
268     )->save();
269   }
270   return $params{obj} if $params{dbfile} && $params{obj};
271   return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
272 }
273
274 sub rename {
275   my ($self, %params) = @_;
276   return RENAME_NOFILE unless $params{id} || $params{dbfile};
277   my $file = $params{dbfile};
278   $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file;
279   return RENAME_NOFILE unless $file;
280
281   $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
282   if ($params{to}) {
283     return RENAME_SAME   if $params{to} eq $file->file_name;
284     return RENAME_EXISTS if $self->get_all_count( object_id     => $file->object_id,
285                                                   object_type   => $file->object_type,
286                                                   mime_type     => $file->mime_type,
287                                                   source        => $file->source,
288                                                   file_type     => $file->file_type,
289                                                   file_name     => $params{to}
290                                                 ) > 0;
291
292     my $backend = $self->_get_backend($file->backend);
293     $backend->rename(dbfile => $file) if $backend;
294     $file->file_name($params{to});
295     $file->save;
296   }
297   return RENAME_OK;
298 }
299
300 sub get_backend_class {
301   my ($self, $backendname) = @_;
302   die "no backend name set" unless $backendname;
303   $self->_get_backend($backendname);
304 }
305
306 sub get_other_sources {
307   my ($self) = @_;
308   my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
309   $pref->login("#default#");
310   my @sources;
311   foreach my $tuple (@{ $pref->get_all() }) {
312     my %lkeys  = %{ SL::JSON::from_json($tuple->{value}) };
313     my $source = {
314       'name'        => $tuple->{key},
315       'description' => $lkeys{desc},
316       'directory'   => $lkeys{dir}
317     };
318     push @sources, $source;
319   }
320   return @sources;
321 }
322
323 sub sync_from_backend {
324   my ($self, %params) = @_;
325   return unless $params{file_type};
326   my $file = SL::DB::File->new;
327   $file->file_type($params{file_type});
328   my $backend = $self->_get_backend($self->_get_backend_by_file_type($file));
329   return unless $backend;
330   $backend->sync_from_backend(%params);
331 }
332
333 #
334 # internal
335 #
336 sub _get_backend {
337   my ($self, $backend_name) = @_;
338   my $class = 'SL::File::Backend::' . $backend_name;
339   my $obj   = undef;
340   die $::locale->text('no backend enabled') if $backend_name eq 'None';
341   eval {
342     eval "require $class";
343     $obj = $class->new;
344     die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
345     1;
346   } or do {
347     if ( $obj ) {
348       die $@;
349     } else {
350       die $::locale->text('backend "#1" not found',$backend_name);
351     }
352   };
353   return $obj;
354 }
355
356 sub _get_backend_by_file_type {
357   my ($self, $dbfile) = @_;
358
359   $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
360   return "Filesystem" unless $dbfile;
361   return $::instance_conf->get_doc_storage_for_documents   if $dbfile->file_type eq 'document';
362   return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
363   return $::instance_conf->get_doc_storage_for_images      if $dbfile->file_type eq 'image';
364   return "Filesystem";
365 }
366
367 1;
368
369 __END__
370
371 =pod
372
373 =encoding utf8
374
375 =head1 NAME
376
377 SL::File - The intermediate Layer for handling files
378
379 =head1 SYNOPSIS
380
381   # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
382   # you can create, remove, delete etc. a file in a backend independent way
383
384   my $file  = SL::File->save(
385                      object_id     => $self->object_id,
386                      object_type   => $self->object_type,
387                      mime_type     => 'application/pdf',
388                      file_type     => 'documents',
389                      file_contents => 'this is no pdf');
390
391   my $file1  = SL::File->get(id => $id);
392   SL::File->delete(id => $id);
393   SL::File->delete(dbfile => $file1);
394   SL::File->delete_all(object_id   => $object_id,
395                        object_type => $object_type,
396                        file_type   => $filetype      # may be optional
397                       );
398   SL::File->rename(id => $id,to => $newname);
399   my $files1 = SL::File->get_all(object_id   => $object_id,
400                                  object_type => $object_type,
401                                  file_type   => 'images',  # may be optional
402                                  source      => 'uploaded' # may be optional
403                                 );
404
405   # Alternativelly some operation can be done with the filemangement object wrapper
406   # and additional oparations see L<SL::File::Object>
407
408 =head1 OVERVIEW
409
410 The Filemanagemt can handle files in a storage independent way. Internal the File
411 use the configured storage backend for the type of file.
412 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
413
414 There are three types of files:
415
416 =over 2
417
418 =item - documents,
419
420 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
421 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
422 deleted by the user if she/he is allowed to do this.
423
424 =item - attachments,
425
426 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
427 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
428
429 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
430 combinide/merged with the document-file in the time of printing.
431 Today only PDF-Attachmnets can be merged with the generated document-PDF.
432
433 =item - images,
434
435 they are like attachments, but they may be have thumbnails for displaying.
436 So the must have an image format like png,jpg. The versioning is like attachments
437
438 =back
439
440 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
441
442 The files have also the parameter C<Source>:
443
444 =over 2
445
446 =item - created, generated by LaTeX
447
448 =item - uploaded
449
450 =item - scanner, import from scanner
451
452 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
453
454 =item - email, received by email and imported by hand or automatic.
455
456 =back
457
458 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
459 This means they are moved back into the correspondent source directories.
460
461 The scanner and email import must be configured  via Table UserPreferences:
462
463 =begin text
464
465  id |  login  |  namespace   | version |   key    |                        value
466 ----+---------+--------------+---------+----------+------------------------------------------------------
467   1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
468   2 | default | file_sources | 0.00000 | emails   | {"dir":"/var/tmp/emails"  ,"desc":"Empfangene Mails"}
469
470 =end text
471
472 .
473
474 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
475 The modul and object_id describe the link to the object.
476
477 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
478
479 The storage backends are extra classes which depends from L<SL::File::Backend>.
480 So additional backend classes can be added.
481
482 The implementation of versioning is done in the different backends.
483
484 =head1 METHODS
485
486 =over 4
487
488 =item C<save>
489
490 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
491 and config, like
492
493 =begin text
494
495           SL::File->save(
496                          object_id    => $self->object_id,
497                          object_type  => $self->object_type,
498                          content_type => 'application/pdf'
499                         );
500
501 =end text
502
503 .
504
505 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
506 so in the history the import is documented also as a hint to can unimport the file later.
507
508 Available C<PARAMS>:
509
510 =over 4
511
512 =item C<id>
513
514 The id of SL::DB::File for an existing file
515
516 =item C<object_id>
517
518 The Id of the ERP-object for a new file.
519
520 =item C<object_type>
521
522 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
523
524 =item C<file_type>
525
526 The type may be "documents", "attachments" or "images" for a new file.
527
528 =item C<source>
529
530 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
531
532 =item C<file_name>
533
534 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
535
536 =item C<mime_type>
537
538 The mime_type of a new file. This is used for downloading or for email attachments.
539
540 =item C<description> or C<title>
541
542 The description or title of a new file. This must be discussed if this attribute is needed.
543
544 =back
545
546 =item C<delete PARAMS>
547
548 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
549 they moved back to the source folders. This is documented in the history.
550
551 Available C<PARAMS>:
552
553 =over 4
554
555 =item C<id>
556
557 The id of SL::DB::File
558
559 =item C<dbfile>
560
561 As alternative if the SL::DB::File as object is available.
562
563 =back
564
565 =item C<delete_all PARAMS>
566
567 All file data of an ERP-object is deleted in the backend.
568
569 =over 4
570
571 =item C<object_id>
572
573 The Id of the ERP-object.
574
575 =item C<object_type>
576
577 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
578
579 =back
580
581 =item C<rename PARAMS>
582
583 The Filename of the file is changed
584
585 Available C<PARAMS>:
586
587 =over 4
588
589 =item C<id>
590
591 The id of SL::DB::File
592
593 =item C<to>
594
595 The new filename
596
597 =back
598
599 =item C<get PARAMS>
600
601 The actual file object is retrieved. The id of the object is needed.
602
603 Available C<PARAMS>:
604
605 =over 4
606
607 =item C<id>
608
609 The id of SL::DB::File
610
611 =back
612
613 =item C<get_all PARAMS>
614
615 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
616
617 Available C<PARAMS>:
618
619 =over 4
620
621 =item C<object_id>
622
623 The Id of the ERP-object.
624
625 =item C<object_type>
626
627 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
628
629 =item C<file_type>
630
631 The type may be "documents", "attachments" or "images". This parameter is optional.
632
633 =item C<file_name>
634
635 The name of the file . This parameter is optional.
636
637 =item C<mime_type>
638
639 The MIME type of the file . This parameter is optional.
640
641 =item C<source>
642
643 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
644
645 =item C<sort_by>
646
647 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
648
649 =back
650
651 =item C<get_all_versions PARAMS>
652
653 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
654 If only the versions of one file are wanted, additional parameter like file_name must be set.
655 If the param C<dbfile> set, only the versions of this file are returned.
656
657 Available C<PARAMS> ar the same as L<get_all>
658
659
660
661 =item C<get_all_count PARAMS>
662
663 The count of available files is returned.
664 Available C<PARAMS> ar the same as L<get_all>
665
666
667 =item C<get_content PARAMS>
668
669 The data of a file can retrieved. A reference to the data is returned.
670
671 Available C<PARAMS>:
672
673 =over 4
674
675 =item C<id>
676
677 The id of SL::DB::File
678
679 =item C<dbfile>
680
681 If no Id exists the object SL::DB::File as param.
682
683 =back
684
685 =item C<get_file_path PARAMS>
686
687 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
688 it is in the responsibility of the backend to create a tempory session file.
689
690 Available C<PARAMS>:
691
692 =over 4
693
694 =item C<id>
695
696 The id of SL::DB::File
697
698 =item C<dbfile>
699
700 If no Id exists the object SL::DB::File as param.
701
702 =back
703
704 =item C<get_other_sources>
705
706 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
707
708 =item C<sync_from_backend>
709
710 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
711 This sync must be triggered by a periodical task.
712
713 Needed C<PARAMS>:
714
715 =over 4
716
717 =item C<file_type>
718
719 The synchronization is done file_type by file_type.
720
721 =back
722
723 =back
724
725 =head1 AUTHOR
726
727 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
728
729 =cut