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