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