5 use parent qw(Rose::Object);
10 use SL::DB::ShopImage;
12 use SL::Helper::UserPreferences;
13 use SL::Controller::Helper::ThumbnailCreator qw(file_probe_type);
16 use constant RENAME_OK => 0;
17 use constant RENAME_EXISTS => 1;
18 use constant RENAME_NOFILE => 2;
19 use constant RENAME_SAME => 3;
20 use constant RENAME_NEW_VERSION => 4;
23 my ($self, %params) = @_;
24 die 'no id' unless $params{id};
25 my $dbfile = SL::DB::Manager::File->get_first(query => [id => $params{id}]);
26 die 'not found' unless $dbfile;
27 $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$dbfile->object_id." object_type=".$dbfile->object_type." dbfile=".$dbfile);
28 SL::File::Object->new(db_file => $dbfile, id => $dbfile->id, loaded => 1);
31 sub get_version_count {
32 my ($self, %params) = @_;
33 die "no id or dbfile" unless $params{id} || $params{dbfile};
34 $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
35 die 'not found' unless $params{dbfile};
36 my $backend = $self->_get_backend($params{dbfile}->backend);
37 return $backend->get_version_count(%params);
41 my ($self, %params) = @_;
44 return @files unless $params{object_type};
45 return @files unless defined($params{object_id});
48 object_id => $params{object_id},
49 object_type => $params{object_type}
51 push @query, (file_name => $params{file_name}) if $params{file_name};
52 push @query, (file_type => $params{file_type}) if $params{file_type};
53 push @query, (mime_type => $params{mime_type}) if $params{mime_type};
54 push @query, (source => $params{source}) if $params{source};
55 push @query, (print_variant => $params{print_variant}) if $params{print_variant};
57 my $sortby = $params{sort_by} || 'itime DESC,file_name ASC';
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;
63 sub get_all_versions {
64 my ($self, %params) = @_;
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);
70 @fileobjs = $self->get_all(%params);
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 $fileobj->version($maxversion);
76 push @versionobjs, $fileobj;
77 if ($maxversion > 1) {
78 for my $version (2..$maxversion) {
79 $main::lxdebug->message(LXDebug->DEBUG2(), "clone for version=".($maxversion-$version+1));
81 my $clone = $fileobj->clone;
82 $clone->version($maxversion-$version+1);
84 $main::lxdebug->message(LXDebug->DEBUG2(), "clone version=".$clone->version." mtime=". $clone->mtime);
85 push @versionobjs, $clone;
87 } or do {$::lxdebug->message(LXDebug::WARN(), "clone for version=".($maxversion-$version+1) . "failed: " . $@)};
95 my ($self, %params) = @_;
96 return 0 unless $params{object_type};
99 object_id => $params{object_id},
100 object_type => $params{object_type}
102 push @query, (file_name => $params{file_name}) if $params{file_name};
103 push @query, (file_type => $params{file_type}) if $params{file_type};
104 push @query, (mime_type => $params{mime_type}) if $params{mime_type};
105 push @query, (source => $params{source}) if $params{source};
107 my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
112 my ($self, %params) = @_;
113 return 0 unless defined($params{object_id}) || $params{object_type};
114 my $files = SL::DB::Manager::File->get_all(
116 object_id => $params{object_id},
117 object_type => $params{object_type}
120 foreach my $file (@{$files}) {
121 $params{dbfile} = $file;
122 $self->delete(%params);
127 my ($self, %params) = @_;
128 die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
131 $rc = SL::DB->client->with_transaction(\&_delete, $self, %params);
138 my ($self, %params) = @_;
139 $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
141 my $backend = $self->_get_backend($params{dbfile}->backend);
142 if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
145 my $hist = SL::DB::Manager::History->get_first(
147 addition => 'IMPORT',
148 trans_id => $params{dbfile}->object_id,
149 what_done => $params{dbfile}->id
154 if (!$main::auth->assert('import_ar | import_ap', 1)) {
155 die 'no permission to unimport';
157 my $file = $backend->get_filepath(dbfile => $params{dbfile});
158 $main::lxdebug->message(LXDebug->DEBUG2(), "del file=" . $file . " to=" . $hist->snumbers);
159 File::Copy::copy($file, $hist->snumbers) if $file;
160 $hist->addition('UNIMPORT');
164 if ($backend->delete(%params)) {
166 if ( $params{last} || $params{version} || $params{all_but_notlast} ) {
167 if ( $backend->get_version_count(%params) > 0 ) {
168 $params{dbfile}->mtime(DateTime->now_local);
169 $params{dbfile}->save;
176 $params{dbfile}->delete if $do_delete;
183 my ($self, %params) = @_;
187 $obj = SL::DB->client->with_transaction(\&_save, $self, %params);
194 my ($self, %params) = @_;
195 my $file = $params{dbfile};
199 $file = SL::DB::File->new(id => $params{id})->load;
200 die 'dbfile not exists' unless $file;
202 $main::lxdebug->message(LXDebug->DEBUG2(), "obj_id=" .$params{object_id});
203 die 'no object type set' unless $params{object_type};
204 die 'no object id set' unless defined($params{object_id});
206 $exists = $self->get_all_count(%params);
207 die 'filename still exist' if $exists && $params{fail_if_exists};
209 my ($obj1) = $self->get_all(%params);
210 $file = $obj1->db_file;
212 $file = SL::DB::File->new();
213 $file->assign_attributes(
214 object_id => $params{object_id},
215 object_type => $params{object_type},
216 source => $params{source},
217 file_type => $params{file_type},
218 file_name => $params{file_name},
219 mime_type => $params{mime_type},
220 title => $params{title},
221 description => $params{description},
222 print_variant => $params{print_variant},
224 $file->itime($params{mtime}) if $params{mtime};
225 $params{itime} = $params{mtime} if $params{mtime};
231 #change attr on existing file
232 $file->file_name ($params{file_name}) if $params{file_name};
233 $file->mime_type ($params{mime_type}) if $params{mime_type};
234 $file->title ($params{title}) if $params{title};
235 $file->description($params{description}) if $params{description};
237 if ( !$file->backend ) {
238 $file->backend($self->_get_backend_by_file_type($file));
239 # load itime for new file
243 $file->mtime(DateTime->now_local) unless $params{mtime};
244 $file->mtime($params{mtime} ) if $params{mtime};
246 my $backend = $self->_get_backend($file->backend);
247 $params{dbfile} = $file;
248 $backend->save(%params);
252 if($file->object_type eq "shop_image"){
253 my $image_content = $params{file_contents};
254 my $thumbnail = file_probe_type($image_content);
255 my $shopimage = SL::DB::ShopImage->new();
256 $shopimage->assign_attributes(
257 file_id => $file->id,
258 thumbnail_content => $thumbnail->{thumbnail_img_content},
259 org_file_height => $thumbnail->{file_image_height},
260 org_file_width => $thumbnail->{file_image_width},
261 thumbnail_content_type => $thumbnail->{thumbnail_img_content_type},
262 object_id => $file->object_id,
266 if ($params{file_type} eq 'document' && $params{source} ne 'created') {
267 SL::DB::History->new(
268 addition => 'IMPORT',
269 trans_id => $params{object_id},
270 snumbers => $params{file_path},
271 employee_id => SL::DB::Manager::Employee->current->id,
272 what_done => $params{dbfile}->id
275 return $params{obj} if $params{dbfile} && $params{obj};
276 return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
280 my ($self, %params) = @_;
281 return RENAME_NOFILE unless $params{id} || $params{dbfile};
282 my $file = $params{dbfile};
283 $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file;
284 return RENAME_NOFILE unless $file;
286 $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
288 return RENAME_SAME if $params{to} eq $file->file_name;
289 return RENAME_EXISTS if $self->get_all_count( object_id => $file->object_id,
290 object_type => $file->object_type,
291 mime_type => $file->mime_type,
292 source => $file->source,
293 file_type => $file->file_type,
294 file_name => $params{to}
297 my $backend = $self->_get_backend($file->backend);
298 $backend->rename(dbfile => $file) if $backend;
299 $file->file_name($params{to});
305 sub get_backend_class {
306 my ($self, $backendname) = @_;
307 die "no backend name set" unless $backendname;
308 $self->_get_backend($backendname);
311 sub get_other_sources {
313 my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
314 $pref->login("#default#");
316 foreach my $tuple (@{ $pref->get_all() }) {
317 my %lkeys = %{ SL::JSON::from_json($tuple->{value}) };
319 'name' => $tuple->{key},
320 'description' => $lkeys{desc},
321 'directory' => $lkeys{dir}
323 push @sources, $source;
328 sub sync_from_backend {
329 my ($self, %params) = @_;
330 return unless $params{file_type};
331 my $file = SL::DB::File->new;
332 $file->file_type($params{file_type});
333 my $backend = $self->_get_backend($self->_get_backend_by_file_type($file));
334 return unless $backend;
335 $backend->sync_from_backend(%params);
342 my ($self, $backend_name) = @_;
343 my $class = 'SL::File::Backend::' . $backend_name;
345 die $::locale->text('no backend enabled') if $backend_name eq 'None';
347 eval "require $class";
349 die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
355 die $::locale->text('backend "#1" not found',$backend_name);
361 sub _get_backend_by_file_type {
362 my ($self, $dbfile) = @_;
364 $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
365 return "Filesystem" unless $dbfile;
366 return $::instance_conf->get_doc_storage_for_documents if $dbfile->file_type eq 'document';
367 return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
368 return $::instance_conf->get_doc_storage_for_images if $dbfile->file_type eq 'image';
382 SL::File - The intermediate Layer for handling files
386 # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
387 # you can create, remove, delete etc. a file in a backend independent way
389 my $file = SL::File->save(
390 object_id => $self->object_id,
391 object_type => $self->object_type,
392 mime_type => 'application/pdf',
393 file_type => 'document',
394 file_contents => 'this is no pdf');
396 my $file1 = SL::File->get(id => $id);
397 SL::File->delete(id => $id);
398 SL::File->delete(dbfile => $file1);
399 SL::File->delete_all(object_id => $object_id,
400 object_type => $object_type,
401 file_type => $filetype # may be optional
403 SL::File->rename(id => $id,to => $newname);
404 my $files1 = SL::File->get_all(object_id => $object_id,
405 object_type => $object_type,
406 file_type => 'image', # may be optional
407 source => 'uploaded' # may be optional
410 # Alternativelly some operation can be done with the filemangement object wrapper
411 # and additional oparations see L<SL::File::Object>
415 The Filemanagemt can handle files in a storage independent way. Internal the File
416 use the configured storage backend for the type of file.
417 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
419 There are three types of files:
425 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
426 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
427 deleted by the user if she/he is allowed to do this.
431 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
432 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
434 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
435 combinide/merged with the document-file in the time of printing.
436 Today only PDF-Attachmnets can be merged with the generated document-PDF.
440 they are like attachments, but they may be have thumbnails for displaying.
441 So the must have an image format like png,jpg. The versioning is like attachments
445 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
447 The files have also the parameter C<Source>:
451 =item - created, generated by LaTeX
455 =item - scanner, import from scanner
457 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
459 =item - email, received by email and imported by hand or automatic.
463 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
464 This means they are moved back into the correspondent source directories.
466 The scanner and email import must be configured via Table UserPreferences:
470 id | login | namespace | version | key | value
471 ----+---------+--------------+---------+----------+------------------------------------------------------
472 1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
473 2 | default | file_sources | 0.00000 | emails | {"dir":"/var/tmp/emails" ,"desc":"Empfangene Mails"}
479 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
480 The modul and object_id describe the link to the object.
482 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
484 The storage backends are extra classes which depends from L<SL::File::Backend>.
485 So additional backend classes can be added.
487 The implementation of versioning is done in the different backends.
495 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
501 object_id => $self->object_id,
502 object_type => $self->object_type,
503 content_type => 'application/pdf'
510 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
511 so in the history the import is documented also as a hint to can unimport the file later.
519 The id of SL::DB::File for an existing file
523 The Id of the ERP-object for a new file.
527 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
531 The type may be "document", "attachment" or "image" for a new file.
535 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
539 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
543 The mime_type of a new file. This is used for downloading or for email attachments.
545 =item C<description> or C<title>
547 The description or title of a new file. This must be discussed if this attribute is needed.
551 =item C<delete PARAMS>
553 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
554 they moved back to the source folders. This is documented in the history.
562 The id of SL::DB::File
566 As alternative if the SL::DB::File as object is available.
570 =item C<delete_all PARAMS>
572 All file data of an ERP-object is deleted in the backend.
578 The Id of the ERP-object.
582 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
586 =item C<rename PARAMS>
588 The Filename of the file is changed
596 The id of SL::DB::File
606 The actual file object is retrieved. The id of the object is needed.
614 The id of SL::DB::File
618 =item C<get_all PARAMS>
620 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
628 The Id of the ERP-object.
632 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
636 The type may be "document", "attachment" or "image". This parameter is optional.
640 The name of the file . This parameter is optional.
644 The MIME type of the file . This parameter is optional.
648 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
652 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
656 =item C<get_all_versions PARAMS>
658 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
659 If only the versions of one file are wanted, additional parameter like file_name must be set.
660 If the param C<dbfile> set, only the versions of this file are returned.
662 Available C<PARAMS> ar the same as L<get_all>
666 =item C<get_all_count PARAMS>
668 The count of available files is returned.
669 Available C<PARAMS> ar the same as L<get_all>
672 =item C<get_content PARAMS>
674 The data of a file can retrieved. A reference to the data is returned.
682 The id of SL::DB::File
686 If no Id exists the object SL::DB::File as param.
690 =item C<get_file_path PARAMS>
692 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
693 it is in the responsibility of the backend to create a tempory session file.
701 The id of SL::DB::File
705 If no Id exists the object SL::DB::File as param.
709 =item C<get_other_sources>
711 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
713 =item C<sync_from_backend>
715 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
716 This sync must be triggered by a periodical task.
724 The synchronization is done file_type by file_type.
732 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>