SL::File: get_all_versions mit dbfile als Parameter gefixed
[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;
67   if ( $params{dbfile} ) {
68     push @fileobjs, SL::File::Object->new(db_file => $params{dbfile}, 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   push @query, (print_variant => $params{print_variant}) if $params{print_variant};
107
108   my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
109   return $cnt;
110 }
111
112 sub delete_all {
113   my ($self, %params) = @_;
114   return 0 unless defined($params{object_id}) || $params{object_type};
115   my $files = SL::DB::Manager::File->get_all(
116     query => [
117       object_id   => $params{object_id},
118       object_type => $params{object_type}
119     ]
120   );
121   foreach my $file (@{$files}) {
122     $params{dbfile} = $file;
123     $self->delete(%params);
124   }
125 }
126
127 sub delete {
128   my ($self, %params) = @_;
129   die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
130   my $rc = 0;
131   eval {
132     $rc = SL::DB->client->with_transaction(\&_delete, $self, %params);
133     1;
134   } or do { die $@ };
135   return $rc;
136 }
137
138 sub _delete {
139   my ($self, %params) = @_;
140   $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
141
142   my $backend = $self->_get_backend($params{dbfile}->backend);
143   if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
144   {
145     ## must unimport
146     my $hist = SL::DB::Manager::History->get_first(
147       where => [
148         addition  => 'IMPORT',
149         trans_id  => $params{dbfile}->object_id,
150         what_done => $params{dbfile}->id
151       ]
152     );
153
154     if ($hist) {
155       if (!$main::auth->assert('import_ar | import_ap', 1)) {
156         die 'no permission to unimport';
157       }
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');
162       $hist->save;
163     }
164   }
165   if ($backend->delete(%params)) {
166     my $do_delete = 0;
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;
171       } else {
172         $do_delete = 1;
173       }
174     } else {
175       $do_delete = 1;
176     }
177     $params{dbfile}->delete if $do_delete;
178     return 1;
179   }
180   return 0;
181 }
182
183 sub save {
184   my ($self, %params) = @_;
185
186   my $obj;
187   eval {
188     $obj = SL::DB->client->with_transaction(\&_save, $self, %params);
189     1;
190   } or do { die $@ };
191   return $obj;
192 }
193
194 sub _save {
195   my ($self, %params) = @_;
196   my $file = $params{dbfile};
197   my $exists = 0;
198
199   if ($params{id}) {
200     $file = SL::DB::File->new(id => $params{id})->load;
201     die 'dbfile not exists'     unless $file;
202   } elsif (!$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});
206
207     $exists = $self->get_all_count(%params);
208     die 'filename still exist' if $exists && $params{fail_if_exists};
209     if ($exists) {
210       my ($obj1) = $self->get_all(%params);
211       $file = $obj1->db_file;
212     } else {
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},
224       );
225       $file->itime($params{mtime})    if $params{mtime};
226       $params{itime} = $params{mtime} if $params{mtime};
227     }
228   } else {
229     $exists = 1;
230   }
231   if ($exists) {
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};
237   }
238   if ( !$file->backend ) {
239     $file->backend($self->_get_backend_by_file_type($file));
240     # load itime for new file
241     $file->save->load;
242   }
243
244   $file->mtime(DateTime->now_local) unless $params{mtime};
245   $file->mtime($params{mtime}     ) if     $params{mtime};
246
247   my $backend = $self->_get_backend($file->backend);
248   $params{dbfile} = $file;
249   $backend->save(%params);
250
251   $file->save;
252   #ShopImage
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,
264                                  );
265     $shopimage->save;
266   }
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
274     )->save();
275   }
276   return $params{obj} if $params{dbfile} && $params{obj};
277   return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
278 }
279
280 sub rename {
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;
286
287   $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
288   if ($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}
296                                                 ) > 0;
297
298     my $backend = $self->_get_backend($file->backend);
299     $backend->rename(dbfile => $file) if $backend;
300     $file->file_name($params{to});
301     $file->save;
302   }
303   return RENAME_OK;
304 }
305
306 sub get_backend_class {
307   my ($self, $backendname) = @_;
308   die "no backend name set" unless $backendname;
309   $self->_get_backend($backendname);
310 }
311
312 sub get_other_sources {
313   my ($self) = @_;
314   my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
315   $pref->login("#default#");
316   my @sources;
317   foreach my $tuple (@{ $pref->get_all() }) {
318     my %lkeys  = %{ SL::JSON::from_json($tuple->{value}) };
319     my $source = {
320       'name'        => $tuple->{key},
321       'description' => $lkeys{desc},
322       'directory'   => $lkeys{dir}
323     };
324     push @sources, $source;
325   }
326   return @sources;
327 }
328
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);
337 }
338
339 #
340 # internal
341 #
342 sub _get_backend {
343   my ($self, $backend_name) = @_;
344   my $class = 'SL::File::Backend::' . $backend_name;
345   my $obj   = undef;
346   die $::locale->text('no backend enabled') if $backend_name eq 'None';
347   eval {
348     eval "require $class";
349     $obj = $class->new;
350     die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
351     1;
352   } or do {
353     if ( $obj ) {
354       die $@;
355     } else {
356       die $::locale->text('backend "#1" not found',$backend_name);
357     }
358   };
359   return $obj;
360 }
361
362 sub _get_backend_by_file_type {
363   my ($self, $dbfile) = @_;
364
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';
370   return "Filesystem";
371 }
372
373 1;
374
375 __END__
376
377 =pod
378
379 =encoding utf8
380
381 =head1 NAME
382
383 SL::File - The intermediate Layer for handling files
384
385 =head1 SYNOPSIS
386
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
389
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');
396
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
403                       );
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
409                                 );
410
411   # Alternativelly some operation can be done with the filemangement object wrapper
412   # and additional oparations see L<SL::File::Object>
413
414 =head1 OVERVIEW
415
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.
419
420 There are three types of files:
421
422 =over 2
423
424 =item - document,
425
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.
429
430 =item - attachment,
431
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.
434
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.
438
439 =item - image,
440
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
443
444 =back
445
446 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
447
448 The files have also the parameter C<Source>:
449
450 =over 2
451
452 =item - created, generated by LaTeX
453
454 =item - uploaded
455
456 =item - scanner, import from scanner
457
458 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
459
460 =item - email, received by email and imported by hand or automatic.
461
462 =back
463
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.
466
467 The scanner and email import must be configured  via Table UserPreferences:
468
469 =begin text
470
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"}
475
476 =end text
477
478 .
479
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.
482
483 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
484
485 The storage backends are extra classes which depends from L<SL::File::Backend>.
486 So additional backend classes can be added.
487
488 The implementation of versioning is done in the different backends.
489
490 =head1 METHODS
491
492 =over 4
493
494 =item C<save>
495
496 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
497 and config, like
498
499 =begin text
500
501           SL::File->save(
502                          object_id    => $self->object_id,
503                          object_type  => $self->object_type,
504                          content_type => 'application/pdf'
505                         );
506
507 =end text
508
509 .
510
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.
513
514 Available C<PARAMS>:
515
516 =over 4
517
518 =item C<id>
519
520 The id of SL::DB::File for an existing file
521
522 =item C<object_id>
523
524 The Id of the ERP-object for a new file.
525
526 =item C<object_type>
527
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.
529
530 =item C<file_type>
531
532 The type may be "document", "attachment" or "image" for a new file.
533
534 =item C<source>
535
536 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
537
538 =item C<file_name>
539
540 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
541
542 =item C<mime_type>
543
544 The mime_type of a new file. This is used for downloading or for email attachments.
545
546 =item C<description> or C<title>
547
548 The description or title of a new file. This must be discussed if this attribute is needed.
549
550 =back
551
552 =item C<delete PARAMS>
553
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.
556
557 Available C<PARAMS>:
558
559 =over 4
560
561 =item C<id>
562
563 The id of SL::DB::File
564
565 =item C<dbfile>
566
567 As alternative if the SL::DB::File as object is available.
568
569 =back
570
571 =item C<delete_all PARAMS>
572
573 All file data of an ERP-object is deleted in the backend.
574
575 =over 4
576
577 =item C<object_id>
578
579 The Id of the ERP-object.
580
581 =item C<object_type>
582
583 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
584
585 =back
586
587 =item C<rename PARAMS>
588
589 The Filename of the file is changed
590
591 Available C<PARAMS>:
592
593 =over 4
594
595 =item C<id>
596
597 The id of SL::DB::File
598
599 =item C<to>
600
601 The new filename
602
603 =back
604
605 =item C<get PARAMS>
606
607 The actual file object is retrieved. The id of the object is needed.
608
609 Available C<PARAMS>:
610
611 =over 4
612
613 =item C<id>
614
615 The id of SL::DB::File
616
617 =back
618
619 =item C<get_all PARAMS>
620
621 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
622
623 Available C<PARAMS>:
624
625 =over 4
626
627 =item C<object_id>
628
629 The Id of the ERP-object.
630
631 =item C<object_type>
632
633 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
634
635 =item C<file_type>
636
637 The type may be "document", "attachment" or "image". This parameter is optional.
638
639 =item C<file_name>
640
641 The name of the file . This parameter is optional.
642
643 =item C<mime_type>
644
645 The MIME type of the file . This parameter is optional.
646
647 =item C<source>
648
649 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
650
651 =item C<sort_by>
652
653 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
654
655 =back
656
657 =item C<get_all_versions PARAMS>
658
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.
662
663 Available C<PARAMS> ar the same as L<get_all>
664
665
666
667 =item C<get_all_count PARAMS>
668
669 The count of available files is returned.
670 Available C<PARAMS> ar the same as L<get_all>
671
672
673 =item C<get_content PARAMS>
674
675 The data of a file can retrieved. A reference to the data is returned.
676
677 Available C<PARAMS>:
678
679 =over 4
680
681 =item C<id>
682
683 The id of SL::DB::File
684
685 =item C<dbfile>
686
687 If no Id exists the object SL::DB::File as param.
688
689 =back
690
691 =item C<get_file_path PARAMS>
692
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.
695
696 Available C<PARAMS>:
697
698 =over 4
699
700 =item C<id>
701
702 The id of SL::DB::File
703
704 =item C<dbfile>
705
706 If no Id exists the object SL::DB::File as param.
707
708 =back
709
710 =item C<get_other_sources>
711
712 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
713
714 =item C<sync_from_backend>
715
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.
718
719 Needed C<PARAMS>:
720
721 =over 4
722
723 =item C<file_type>
724
725 The synchronization is done file_type by file_type.
726
727 =back
728
729 =back
730
731 =head1 AUTHOR
732
733 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
734
735 =cut