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