5 use parent qw(Rose::Object);
12 use SL::DB::ShopImage;
14 use SL::DB::FileVersion;
15 use SL::Helper::UserPreferences;
16 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_type);
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;
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';
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;
40 if (defined $params{id}) {
41 croak "id doesn't match guid" if $dbfile->id != $params{id};
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};
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;
58 $object_params{file_version} = $file_version if $file_version;
59 SL::File::Object->new(%object_params);
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);
72 my ($self, %params) = @_;
75 return @files unless $params{object_type};
76 return @files unless defined($params{object_id});
79 object_id => $params{object_id},
80 object_type => $params{object_type}
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};
89 my $sortby = $params{sort_by} || 'itime DESC,file_name ASC';
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;
95 sub get_all_versions {
96 my ($self, %params) = @_;
99 if ( $params{dbfile} ) {
100 push @fileobjs, SL::File::Object->new(db_file => $params{dbfile}, id => $params{dbfile}->id, loaded => 1);
102 @fileobjs = $self->get_all(%params);
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);
115 push @versionobjs, $clone;
122 my ($self, %params) = @_;
123 return 0 unless $params{object_type};
126 object_id => $params{object_id},
127 object_type => $params{object_type}
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};
136 my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
141 my ($self, %params) = @_;
142 return 0 unless defined($params{object_id}) || $params{object_type};
143 my $files = SL::DB::Manager::File->get_all(
145 object_id => $params{object_id},
146 object_type => $params{object_type}
149 foreach my $file (@{$files}) {
150 $params{dbfile} = $file;
151 $self->delete(%params);
156 my ($self, %params) = @_;
157 die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
159 SL::DB->client->with_transaction(sub {
160 $rc = $self->_delete(%params);
162 }) or do { die SL::DB->client->error };
167 my ($self, %params) = @_;
168 $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
170 my $backend = $self->_get_backend($params{dbfile}->backend);
171 if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
174 my $hist = SL::DB::Manager::History->get_first(
176 addition => 'IMPORT',
177 trans_id => $params{dbfile}->object_id,
178 what_done => $params{dbfile}->id
183 if (!$main::auth->assert('import_ar | import_ap', 1)) {
184 die 'no permission to unimport';
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');
193 if ($backend->delete(%params)) {
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;
205 $params{dbfile}->delete if $do_delete;
212 my ($self, %params) = @_;
215 SL::DB->client->with_transaction(sub {
216 $obj = $self->_save(%params);
218 }) or do { die SL::DB->client->error };
223 my ($self, %params) = @_;
224 my $file = $params{dbfile};
228 $file = SL::DB::File->new(id => $params{id})->load;
229 die 'dbfile not exists' unless $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});
235 $exists = $self->get_all_count(%params);
236 die 'filename still exist' if $exists && $params{fail_if_exists};
238 my ($obj1) = $self->get_all(%params);
239 $file = $obj1->db_file;
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},
254 $file->itime($params{mtime}) if $params{mtime};
255 $params{itime} = $params{mtime} if $params{mtime};
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};
267 if ( !$file->backend ) {
268 $file->backend($self->_get_backend_by_file_type($file));
269 # load itime for new file
273 $file->mtime(DateTime->now_local) unless $params{mtime};
274 $file->mtime($params{mtime} ) if $params{mtime};
276 my $backend = $self->_get_backend($file->backend);
277 $params{dbfile} = $file;
278 $backend->save(%params);
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,
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
305 return $params{obj} if $params{dbfile} && $params{obj};
306 return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
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;
316 $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $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,
325 file_name => $params{to}
328 my $backend = $self->_get_backend($file->backend);
329 $backend->rename(dbfile => $file) if $backend;
330 $file->file_name($params{to});
336 sub get_backend_class {
337 my ($self, $backendname) = @_;
338 die "no backend name set" unless $backendname;
339 $self->_get_backend($backendname);
342 sub get_other_sources {
344 my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
345 $pref->login("#default#");
347 foreach my $tuple (@{ $pref->get_all() }) {
348 my %lkeys = %{ SL::JSON::from_json($tuple->{value}) };
350 'name' => $tuple->{key},
351 'description' => $lkeys{desc},
352 'directory' => $lkeys{dir}
354 push @sources, $source;
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);
373 my ($self, $backend_name) = @_;
374 my $class = 'SL::File::Backend::' . $backend_name;
376 die $::locale->text('no backend enabled') if $backend_name eq 'None';
378 eval "require $class";
380 die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
386 die $::locale->text('backend "#1" not found',$backend_name);
392 sub _get_backend_by_file_type {
393 my ($self, $dbfile) = @_;
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';
413 SL::File - The intermediate Layer for handling files
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
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');
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
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
441 # Alternativelly some operation can be done with the filemangement object wrapper
442 # and additional oparations see L<SL::File::Object>
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.
450 There are three types of files:
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.
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.
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.
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
476 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
478 The files have also the parameter C<Source>:
482 =item - created, generated by LaTeX
486 =item - scanner, import from scanner
488 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
490 =item - email, received by email and imported by hand or automatic.
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.
497 The scanner and email import must be configured via Table UserPreferences:
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"}
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.
513 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
515 The storage backends are extra classes which depends from L<SL::File::Backend>.
516 So additional backend classes can be added.
518 The implementation of versioning is done in the different backends.
526 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
532 object_id => $self->object_id,
533 object_type => $self->object_type,
534 content_type => 'application/pdf'
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.
550 The id of SL::DB::File for an existing file
554 The Id of the ERP-object for a new file.
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.
562 The type may be "document", "attachment" or "image" for a new file.
566 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
570 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
574 The mime_type of a new file. This is used for downloading or for email attachments.
576 =item C<description> or C<title>
578 The description or title of a new file. This must be discussed if this attribute is needed.
582 =item C<delete PARAMS>
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.
593 The id of SL::DB::File
597 As alternative if the SL::DB::File as object is available.
601 =item C<delete_all PARAMS>
603 All file data of an ERP-object is deleted in the backend.
609 The Id of the ERP-object.
613 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
617 =item C<rename PARAMS>
619 The Filename of the file is changed
627 The id of SL::DB::File
637 The actual file object is retrieved. The id of the object is needed.
645 The id of SL::DB::File
649 =item C<get_all PARAMS>
651 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
659 The Id of the ERP-object.
663 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
667 The type may be "document", "attachment" or "image". This parameter is optional.
671 The name of the file . This parameter is optional.
675 The MIME type of the file . This parameter is optional.
679 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
683 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
687 =item C<get_all_versions PARAMS>
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.
693 Available C<PARAMS> ar the same as L<get_all>
697 =item C<get_all_count PARAMS>
699 The count of available files is returned.
700 Available C<PARAMS> ar the same as L<get_all>
703 =item C<get_content PARAMS>
705 The data of a file can retrieved. A reference to the data is returned.
713 The id of SL::DB::File
717 If no Id exists the object SL::DB::File as param.
721 =item C<get_file_path PARAMS>
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.
732 The id of SL::DB::File
736 If no Id exists the object SL::DB::File as param.
740 =item C<get_other_sources>
742 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
744 =item C<sync_from_backend>
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.
755 The synchronization is done file_type by file_type.
763 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>