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