Filemanagement: Korrekte Fehlermeldung anzeigen
[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   die $::locale->text('no backend enabled') if $backend_name eq 'None';
323   eval {
324     eval "require $class";
325     $obj = $class->new;
326     die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
327     1;
328   } or do {
329     if ( $obj ) {
330       die $@;
331     } else {
332       die $::locale->text('backend "#1" not found',$backend_name);
333     }
334   };
335   return $obj;
336 }
337
338 sub _get_backend_by_file_type {
339   my ($self, $dbfile) = @_;
340
341   $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
342   return "Filesystem" unless $dbfile;
343   return $::instance_conf->get_doc_storage_for_documents   if $dbfile->file_type eq 'document';
344   return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
345   return $::instance_conf->get_doc_storage_for_images      if $dbfile->file_type eq 'image';
346   return "Filesystem";
347 }
348
349 1;
350
351 __END__
352
353 =pod
354
355 =encoding utf8
356
357 =head1 NAME
358
359 SL::File - The intermediate Layer for handling files
360
361 =head1 SYNOPSIS
362
363   # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
364   # you can create, remove, delete etc. a file in a backend independent way
365
366   my $file  = SL::File->save(
367                      object_id     => $self->object_id,
368                      object_type   => $self->object_type,
369                      mime_type     => 'application/pdf',
370                      file_type     => 'documents',
371                      file_contents => 'this is no pdf');
372
373   my $file1  = SL::File->get(id => $id);
374   SL::File->delete(id => $id);
375   SL::File->delete(dbfile => $file1);
376   SL::File->delete_all(object_id   => $object_id,
377                        object_type => $object_type,
378                        file_type   => $filetype      # may be optional
379                       );
380   SL::File->rename(id => $id,to => $newname);
381   my $files1 = SL::File->get_all(object_id   => $object_id,
382                                  object_type => $object_type,
383                                  file_type   => 'images',  # may be optional
384                                  source      => 'uploaded' # may be optional
385                                 );
386
387   # Alternativelly some operation can be done with the filemangement object wrapper
388   # and additional oparations see L<SL::File::Object>
389
390 =head1 OVERVIEW
391
392 The Filemanagemt can handle files in a storage independent way. Internal the File
393 use the configured storage backend for the type of file.
394 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
395
396 There are three types of files:
397
398 =over 2
399
400 =item - documents,
401
402 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
403 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
404 deleted by the user if she/he is allowed to do this.
405
406 =item - attachments,
407
408 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
409 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
410
411 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
412 combinide/merged with the document-file in the time of printing.
413 Today only PDF-Attachmnets can be merged with the generated document-PDF.
414
415 =item - images,
416
417 they are like attachments, but they may be have thumbnails for displaying.
418 So the must have an image format like png,jpg. The versioning is like attachments
419
420 =back
421
422 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
423
424 The files have also the parameter C<Source>:
425
426 =over 2
427
428 =item - created, generated by LaTeX
429
430 =item - uploaded
431
432 =item - scanner, import from scanner
433
434 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
435
436 =item - email, received by email and imported by hand or automatic.
437
438 =back
439
440 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
441 This means they are moved back into the correspondent source directories.
442
443 The scanner and email import must be configured  via Table UserPreferences:
444
445 =begin text
446
447  id |  login  |  namespace   | version |   key    |                        value
448 ----+---------+--------------+---------+----------+------------------------------------------------------
449   1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
450   2 | default | file_sources | 0.00000 | emails   | {"dir":"/var/tmp/emails"  ,"desc":"Empfangene Mails"}
451
452 =end text
453
454 .
455
456 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
457 The modul and object_id describe the link to the object.
458
459 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
460
461 The storage backends are extra classes which depends from L<SL::File::Backend>.
462 So additional backend classes can be added.
463
464 The implementation of versioning is done in the different backends.
465
466 =head1 METHODS
467
468 =over 4
469
470 =item C<save>
471
472 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
473 and config, like
474
475 =begin text
476
477           SL::File->save(
478                          object_id    => $self->object_id,
479                          object_type  => $self->object_type,
480                          content_type => 'application/pdf'
481                         );
482
483 =end text
484
485 .
486
487 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
488 so in the history the import is documented also as a hint to can unimport the file later.
489
490 Available C<PARAMS>:
491
492 =over 4
493
494 =item C<id>
495
496 The id of SL::DB::File for an existing file
497
498 =item C<object_id>
499
500 The Id of the ERP-object for a new file.
501
502 =item C<object_type>
503
504 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
505
506 =item C<file_type>
507
508 The type may be "documents", "attachments" or "images" for a new file.
509
510 =item C<source>
511
512 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
513
514 =item C<file_name>
515
516 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
517
518 =item C<mime_type>
519
520 The mime_type of a new file. This is used for downloading or for email attachments.
521
522 =item C<description> or C<title>
523
524 The description or title of a new file. This must be discussed if this attribute is needed.
525
526 =back
527
528 =item C<delete PARAMS>
529
530 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
531 they moved back to the source folders. This is documented in the history.
532
533 Available C<PARAMS>:
534
535 =over 4
536
537 =item C<id>
538
539 The id of SL::DB::File
540
541 =item C<dbfile>
542
543 As alternative if the SL::DB::File as object is available.
544
545 =back
546
547 =item C<delete_all PARAMS>
548
549 All file data of an ERP-object is deleted in the backend.
550
551 =over 4
552
553 =item C<object_id>
554
555 The Id of the ERP-object.
556
557 =item C<object_type>
558
559 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
560
561 =back
562
563 =item C<rename PARAMS>
564
565 The Filename of the file is changed
566
567 Available C<PARAMS>:
568
569 =over 4
570
571 =item C<id>
572
573 The id of SL::DB::File
574
575 =item C<to>
576
577 The new filename
578
579 =back
580
581 =item C<get PARAMS>
582
583 The actual file object is retrieved. The id of the object is needed.
584
585 Available C<PARAMS>:
586
587 =over 4
588
589 =item C<id>
590
591 The id of SL::DB::File
592
593 =back
594
595 =item C<get_all PARAMS>
596
597 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
598
599 Available C<PARAMS>:
600
601 =over 4
602
603 =item C<object_id>
604
605 The Id of the ERP-object.
606
607 =item C<object_type>
608
609 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
610
611 =item C<file_type>
612
613 The type may be "documents", "attachments" or "images". This parameter is optional.
614
615 =item C<file_name>
616
617 The name of the file . This parameter is optional.
618
619 =item C<mime_type>
620
621 The MIME type of the file . This parameter is optional.
622
623 =item C<source>
624
625 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
626
627 =item C<sort_by>
628
629 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
630
631 =back
632
633 =item C<get_all_versions PARAMS>
634
635 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
636 If only the versions of one file are wanted, additional parameter like file_name must be set.
637 If the param C<dbfile> set, only the versions of this file are returned.
638
639 Available C<PARAMS> ar the same as L<get_all>
640
641
642
643 =item C<get_all_count PARAMS>
644
645 The count of available files is returned.
646 Available C<PARAMS> ar the same as L<get_all>
647
648
649 =item C<get_content PARAMS>
650
651 The data of a file can retrieved. A reference to the data is returned.
652
653 Available C<PARAMS>:
654
655 =over 4
656
657 =item C<id>
658
659 The id of SL::DB::File
660
661 =item C<dbfile>
662
663 If no Id exists the object SL::DB::File as param.
664
665 =back
666
667 =item C<get_file_path PARAMS>
668
669 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
670 it is in the responsibility of the backend to create a tempory session file.
671
672 Available C<PARAMS>:
673
674 =over 4
675
676 =item C<id>
677
678 The id of SL::DB::File
679
680 =item C<dbfile>
681
682 If no Id exists the object SL::DB::File as param.
683
684 =back
685
686 =item C<get_other_sources>
687
688 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
689
690 =item C<sync_from_backend>
691
692 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
693 This sync must be triggered by a periodical task.
694
695 Needed C<PARAMS>:
696
697 =over 4
698
699 =item C<file_type>
700
701 The synchronization is done file_type by file_type.
702
703 =back
704
705 =back
706
707 =head1 AUTHOR
708
709 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
710
711 =cut