830559460898609c55e3e583686f52ccfafff744
[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       $file->itime($params{mtime})    if $params{mtime};
224       $params{itime} = $params{mtime} if $params{mtime};
225     }
226   } else {
227     $exists = 1;
228   }
229   if ($exists) {
230     #change attr on existing file
231     $file->file_name  ($params{file_name})   if $params{file_name};
232     $file->mime_type  ($params{mime_type})   if $params{mime_type};
233     $file->title      ($params{title})       if $params{title};
234     $file->description($params{description}) if $params{description};
235   }
236   if ( !$file->backend ) {
237     $file->backend($self->_get_backend_by_file_type($file));
238     # load itime for new file
239     $file->save->load;
240   }
241
242   $file->mtime(DateTime->now_local) unless $params{mtime};
243   $file->mtime($params{mtime}     ) if     $params{mtime};
244
245   my $backend = $self->_get_backend($file->backend);
246   $params{dbfile} = $file;
247   $backend->save(%params);
248
249   $file->save;
250   #ShopImage
251   if($file->object_type eq "shop_image"){
252     my $image_content = $params{file_contents};
253     my $thumbnail = file_probe_type($image_content);
254     my $shopimage = SL::DB::ShopImage->new();
255     $shopimage->assign_attributes(
256                                   file_id                => $file->id,
257                                   thumbnail_content      => $thumbnail->{thumbnail_img_content},
258                                   org_file_height        => $thumbnail->{file_image_height},
259                                   org_file_width         => $thumbnail->{file_image_width},
260                                   thumbnail_content_type => $thumbnail->{thumbnail_img_content_type},
261                                   object_id              => $file->object_id,
262                                  );
263     $shopimage->save;
264   }
265   if ($params{file_type} eq 'document' && $params{source} ne 'created') {
266     SL::DB::History->new(
267       addition    => 'IMPORT',
268       trans_id    => $params{object_id},
269       snumbers    => $params{file_path},
270       employee_id => SL::DB::Manager::Employee->current->id,
271       what_done   => $params{dbfile}->id
272     )->save();
273   }
274   return $params{obj} if $params{dbfile} && $params{obj};
275   return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
276 }
277
278 sub rename {
279   my ($self, %params) = @_;
280   return RENAME_NOFILE unless $params{id} || $params{dbfile};
281   my $file = $params{dbfile};
282   $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file;
283   return RENAME_NOFILE unless $file;
284
285   $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
286   if ($params{to}) {
287     return RENAME_SAME   if $params{to} eq $file->file_name;
288     return RENAME_EXISTS if $self->get_all_count( object_id     => $file->object_id,
289                                                   object_type   => $file->object_type,
290                                                   mime_type     => $file->mime_type,
291                                                   source        => $file->source,
292                                                   file_type     => $file->file_type,
293                                                   file_name     => $params{to}
294                                                 ) > 0;
295
296     my $backend = $self->_get_backend($file->backend);
297     $backend->rename(dbfile => $file) if $backend;
298     $file->file_name($params{to});
299     $file->save;
300   }
301   return RENAME_OK;
302 }
303
304 sub get_backend_class {
305   my ($self, $backendname) = @_;
306   die "no backend name set" unless $backendname;
307   $self->_get_backend($backendname);
308 }
309
310 sub get_other_sources {
311   my ($self) = @_;
312   my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
313   $pref->login("#default#");
314   my @sources;
315   foreach my $tuple (@{ $pref->get_all() }) {
316     my %lkeys  = %{ SL::JSON::from_json($tuple->{value}) };
317     my $source = {
318       'name'        => $tuple->{key},
319       'description' => $lkeys{desc},
320       'directory'   => $lkeys{dir}
321     };
322     push @sources, $source;
323   }
324   return @sources;
325 }
326
327 sub sync_from_backend {
328   my ($self, %params) = @_;
329   return unless $params{file_type};
330   my $file = SL::DB::File->new;
331   $file->file_type($params{file_type});
332   my $backend = $self->_get_backend($self->_get_backend_by_file_type($file));
333   return unless $backend;
334   $backend->sync_from_backend(%params);
335 }
336
337 #
338 # internal
339 #
340 sub _get_backend {
341   my ($self, $backend_name) = @_;
342   my $class = 'SL::File::Backend::' . $backend_name;
343   my $obj   = undef;
344   die $::locale->text('no backend enabled') if $backend_name eq 'None';
345   eval {
346     eval "require $class";
347     $obj = $class->new;
348     die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
349     1;
350   } or do {
351     if ( $obj ) {
352       die $@;
353     } else {
354       die $::locale->text('backend "#1" not found',$backend_name);
355     }
356   };
357   return $obj;
358 }
359
360 sub _get_backend_by_file_type {
361   my ($self, $dbfile) = @_;
362
363   $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
364   return "Filesystem" unless $dbfile;
365   return $::instance_conf->get_doc_storage_for_documents   if $dbfile->file_type eq 'document';
366   return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
367   return $::instance_conf->get_doc_storage_for_images      if $dbfile->file_type eq 'image';
368   return "Filesystem";
369 }
370
371 1;
372
373 __END__
374
375 =pod
376
377 =encoding utf8
378
379 =head1 NAME
380
381 SL::File - The intermediate Layer for handling files
382
383 =head1 SYNOPSIS
384
385   # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
386   # you can create, remove, delete etc. a file in a backend independent way
387
388   my $file  = SL::File->save(
389                      object_id     => $self->object_id,
390                      object_type   => $self->object_type,
391                      mime_type     => 'application/pdf',
392                      file_type     => 'documents',
393                      file_contents => 'this is no pdf');
394
395   my $file1  = SL::File->get(id => $id);
396   SL::File->delete(id => $id);
397   SL::File->delete(dbfile => $file1);
398   SL::File->delete_all(object_id   => $object_id,
399                        object_type => $object_type,
400                        file_type   => $filetype      # may be optional
401                       );
402   SL::File->rename(id => $id,to => $newname);
403   my $files1 = SL::File->get_all(object_id   => $object_id,
404                                  object_type => $object_type,
405                                  file_type   => 'images',  # may be optional
406                                  source      => 'uploaded' # may be optional
407                                 );
408
409   # Alternativelly some operation can be done with the filemangement object wrapper
410   # and additional oparations see L<SL::File::Object>
411
412 =head1 OVERVIEW
413
414 The Filemanagemt can handle files in a storage independent way. Internal the File
415 use the configured storage backend for the type of file.
416 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
417
418 There are three types of files:
419
420 =over 2
421
422 =item - documents,
423
424 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
425 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
426 deleted by the user if she/he is allowed to do this.
427
428 =item - attachments,
429
430 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
431 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
432
433 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
434 combinide/merged with the document-file in the time of printing.
435 Today only PDF-Attachmnets can be merged with the generated document-PDF.
436
437 =item - images,
438
439 they are like attachments, but they may be have thumbnails for displaying.
440 So the must have an image format like png,jpg. The versioning is like attachments
441
442 =back
443
444 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
445
446 The files have also the parameter C<Source>:
447
448 =over 2
449
450 =item - created, generated by LaTeX
451
452 =item - uploaded
453
454 =item - scanner, import from scanner
455
456 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
457
458 =item - email, received by email and imported by hand or automatic.
459
460 =back
461
462 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
463 This means they are moved back into the correspondent source directories.
464
465 The scanner and email import must be configured  via Table UserPreferences:
466
467 =begin text
468
469  id |  login  |  namespace   | version |   key    |                        value
470 ----+---------+--------------+---------+----------+------------------------------------------------------
471   1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
472   2 | default | file_sources | 0.00000 | emails   | {"dir":"/var/tmp/emails"  ,"desc":"Empfangene Mails"}
473
474 =end text
475
476 .
477
478 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
479 The modul and object_id describe the link to the object.
480
481 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
482
483 The storage backends are extra classes which depends from L<SL::File::Backend>.
484 So additional backend classes can be added.
485
486 The implementation of versioning is done in the different backends.
487
488 =head1 METHODS
489
490 =over 4
491
492 =item C<save>
493
494 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
495 and config, like
496
497 =begin text
498
499           SL::File->save(
500                          object_id    => $self->object_id,
501                          object_type  => $self->object_type,
502                          content_type => 'application/pdf'
503                         );
504
505 =end text
506
507 .
508
509 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
510 so in the history the import is documented also as a hint to can unimport the file later.
511
512 Available C<PARAMS>:
513
514 =over 4
515
516 =item C<id>
517
518 The id of SL::DB::File for an existing file
519
520 =item C<object_id>
521
522 The Id of the ERP-object for a new file.
523
524 =item C<object_type>
525
526 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
527
528 =item C<file_type>
529
530 The type may be "documents", "attachments" or "images" for a new file.
531
532 =item C<source>
533
534 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
535
536 =item C<file_name>
537
538 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
539
540 =item C<mime_type>
541
542 The mime_type of a new file. This is used for downloading or for email attachments.
543
544 =item C<description> or C<title>
545
546 The description or title of a new file. This must be discussed if this attribute is needed.
547
548 =back
549
550 =item C<delete PARAMS>
551
552 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
553 they moved back to the source folders. This is documented in the history.
554
555 Available C<PARAMS>:
556
557 =over 4
558
559 =item C<id>
560
561 The id of SL::DB::File
562
563 =item C<dbfile>
564
565 As alternative if the SL::DB::File as object is available.
566
567 =back
568
569 =item C<delete_all PARAMS>
570
571 All file data of an ERP-object is deleted in the backend.
572
573 =over 4
574
575 =item C<object_id>
576
577 The Id of the ERP-object.
578
579 =item C<object_type>
580
581 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
582
583 =back
584
585 =item C<rename PARAMS>
586
587 The Filename of the file is changed
588
589 Available C<PARAMS>:
590
591 =over 4
592
593 =item C<id>
594
595 The id of SL::DB::File
596
597 =item C<to>
598
599 The new filename
600
601 =back
602
603 =item C<get PARAMS>
604
605 The actual file object is retrieved. The id of the object is needed.
606
607 Available C<PARAMS>:
608
609 =over 4
610
611 =item C<id>
612
613 The id of SL::DB::File
614
615 =back
616
617 =item C<get_all PARAMS>
618
619 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
620
621 Available C<PARAMS>:
622
623 =over 4
624
625 =item C<object_id>
626
627 The Id of the ERP-object.
628
629 =item C<object_type>
630
631 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
632
633 =item C<file_type>
634
635 The type may be "documents", "attachments" or "images". This parameter is optional.
636
637 =item C<file_name>
638
639 The name of the file . This parameter is optional.
640
641 =item C<mime_type>
642
643 The MIME type of the file . This parameter is optional.
644
645 =item C<source>
646
647 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
648
649 =item C<sort_by>
650
651 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
652
653 =back
654
655 =item C<get_all_versions PARAMS>
656
657 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
658 If only the versions of one file are wanted, additional parameter like file_name must be set.
659 If the param C<dbfile> set, only the versions of this file are returned.
660
661 Available C<PARAMS> ar the same as L<get_all>
662
663
664
665 =item C<get_all_count PARAMS>
666
667 The count of available files is returned.
668 Available C<PARAMS> ar the same as L<get_all>
669
670
671 =item C<get_content PARAMS>
672
673 The data of a file can retrieved. A reference to the data is returned.
674
675 Available C<PARAMS>:
676
677 =over 4
678
679 =item C<id>
680
681 The id of SL::DB::File
682
683 =item C<dbfile>
684
685 If no Id exists the object SL::DB::File as param.
686
687 =back
688
689 =item C<get_file_path PARAMS>
690
691 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
692 it is in the responsibility of the backend to create a tempory session file.
693
694 Available C<PARAMS>:
695
696 =over 4
697
698 =item C<id>
699
700 The id of SL::DB::File
701
702 =item C<dbfile>
703
704 If no Id exists the object SL::DB::File as param.
705
706 =back
707
708 =item C<get_other_sources>
709
710 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
711
712 =item C<sync_from_backend>
713
714 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
715 This sync must be triggered by a periodical task.
716
717 Needed C<PARAMS>:
718
719 =over 4
720
721 =item C<file_type>
722
723 The synchronization is done file_type by file_type.
724
725 =back
726
727 =back
728
729 =head1 AUTHOR
730
731 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
732
733 =cut