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};
106 push @query, (print_variant => $params{print_variant}) if $params{print_variant};
108 my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]);
113 my ($self, %params) = @_;
114 return 0 unless defined($params{object_id}) || $params{object_type};
115 my $files = SL::DB::Manager::File->get_all(
117 object_id => $params{object_id},
118 object_type => $params{object_type}
121 foreach my $file (@{$files}) {
122 $params{dbfile} = $file;
123 $self->delete(%params);
128 my ($self, %params) = @_;
129 die "no id or dbfile in delete" unless $params{id} || $params{dbfile};
132 $rc = SL::DB->client->with_transaction(\&_delete, $self, %params);
139 my ($self, %params) = @_;
140 $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile};
142 my $backend = $self->_get_backend($params{dbfile}->backend);
143 if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created')
146 my $hist = SL::DB::Manager::History->get_first(
148 addition => 'IMPORT',
149 trans_id => $params{dbfile}->object_id,
150 what_done => $params{dbfile}->id
155 if (!$main::auth->assert('import_ar | import_ap', 1)) {
156 die 'no permission to unimport';
158 my $file = $backend->get_filepath(dbfile => $params{dbfile});
159 $main::lxdebug->message(LXDebug->DEBUG2(), "del file=" . $file . " to=" . $hist->snumbers);
160 File::Copy::copy($file, $hist->snumbers) if $file;
161 $hist->addition('UNIMPORT');
165 if ($backend->delete(%params)) {
167 if ( $params{last} || $params{version} || $params{all_but_notlast} ) {
168 if ( $backend->get_version_count(%params) > 0 ) {
169 $params{dbfile}->mtime(DateTime->now_local);
170 $params{dbfile}->save;
177 $params{dbfile}->delete if $do_delete;
184 my ($self, %params) = @_;
188 $obj = SL::DB->client->with_transaction(\&_save, $self, %params);
195 my ($self, %params) = @_;
196 my $file = $params{dbfile};
200 $file = SL::DB::File->new(id => $params{id})->load;
201 die 'dbfile not exists' unless $file;
203 $main::lxdebug->message(LXDebug->DEBUG2(), "obj_id=" .$params{object_id});
204 die 'no object type set' unless $params{object_type};
205 die 'no object id set' unless defined($params{object_id});
207 $exists = $self->get_all_count(%params);
208 die 'filename still exist' if $exists && $params{fail_if_exists};
210 my ($obj1) = $self->get_all(%params);
211 $file = $obj1->db_file;
213 $file = SL::DB::File->new();
214 $file->assign_attributes(
215 object_id => $params{object_id},
216 object_type => $params{object_type},
217 source => $params{source},
218 file_type => $params{file_type},
219 file_name => $params{file_name},
220 mime_type => $params{mime_type},
221 title => $params{title},
222 description => $params{description},
223 print_variant => $params{print_variant},
225 $file->itime($params{mtime}) if $params{mtime};
226 $params{itime} = $params{mtime} if $params{mtime};
232 #change attr on existing file
233 $file->file_name ($params{file_name}) if $params{file_name};
234 $file->mime_type ($params{mime_type}) if $params{mime_type};
235 $file->title ($params{title}) if $params{title};
236 $file->description($params{description}) if $params{description};
238 if ( !$file->backend ) {
239 $file->backend($self->_get_backend_by_file_type($file));
240 # load itime for new file
244 $file->mtime(DateTime->now_local) unless $params{mtime};
245 $file->mtime($params{mtime} ) if $params{mtime};
247 my $backend = $self->_get_backend($file->backend);
248 $params{dbfile} = $file;
249 $backend->save(%params);
253 if($file->object_type eq "shop_image"){
254 my $image_content = $params{file_contents};
255 my $thumbnail = file_probe_type($image_content);
256 my $shopimage = SL::DB::ShopImage->new();
257 $shopimage->assign_attributes(
258 file_id => $file->id,
259 thumbnail_content => $thumbnail->{thumbnail_img_content},
260 org_file_height => $thumbnail->{file_image_height},
261 org_file_width => $thumbnail->{file_image_width},
262 thumbnail_content_type => $thumbnail->{thumbnail_img_content_type},
263 object_id => $file->object_id,
267 if ($params{file_type} eq 'document' && $params{source} ne 'created') {
268 SL::DB::History->new(
269 addition => 'IMPORT',
270 trans_id => $params{object_id},
271 snumbers => $params{file_path},
272 employee_id => SL::DB::Manager::Employee->current->id,
273 what_done => $params{dbfile}->id
276 return $params{obj} if $params{dbfile} && $params{obj};
277 return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1);
281 my ($self, %params) = @_;
282 return RENAME_NOFILE unless $params{id} || $params{dbfile};
283 my $file = $params{dbfile};
284 $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file;
285 return RENAME_NOFILE unless $file;
287 $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to});
289 return RENAME_SAME if $params{to} eq $file->file_name;
290 return RENAME_EXISTS if $self->get_all_count( object_id => $file->object_id,
291 object_type => $file->object_type,
292 mime_type => $file->mime_type,
293 source => $file->source,
294 file_type => $file->file_type,
295 file_name => $params{to}
298 my $backend = $self->_get_backend($file->backend);
299 $backend->rename(dbfile => $file) if $backend;
300 $file->file_name($params{to});
306 sub get_backend_class {
307 my ($self, $backendname) = @_;
308 die "no backend name set" unless $backendname;
309 $self->_get_backend($backendname);
312 sub get_other_sources {
314 my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources');
315 $pref->login("#default#");
317 foreach my $tuple (@{ $pref->get_all() }) {
318 my %lkeys = %{ SL::JSON::from_json($tuple->{value}) };
320 'name' => $tuple->{key},
321 'description' => $lkeys{desc},
322 'directory' => $lkeys{dir}
324 push @sources, $source;
329 sub sync_from_backend {
330 my ($self, %params) = @_;
331 return unless $params{file_type};
332 my $file = SL::DB::File->new;
333 $file->file_type($params{file_type});
334 my $backend = $self->_get_backend($self->_get_backend_by_file_type($file));
335 return unless $backend;
336 $backend->sync_from_backend(%params);
343 my ($self, $backend_name) = @_;
344 my $class = 'SL::File::Backend::' . $backend_name;
346 die $::locale->text('no backend enabled') if $backend_name eq 'None';
348 eval "require $class";
350 die $::locale->text('backend "#1" not enabled',$backend_name) unless $obj->enabled;
356 die $::locale->text('backend "#1" not found',$backend_name);
362 sub _get_backend_by_file_type {
363 my ($self, $dbfile) = @_;
365 $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type);
366 return "Filesystem" unless $dbfile;
367 return $::instance_conf->get_doc_storage_for_documents if $dbfile->file_type eq 'document';
368 return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment';
369 return $::instance_conf->get_doc_storage_for_images if $dbfile->file_type eq 'image';
383 SL::File - The intermediate Layer for handling files
387 # In a controller or helper ( see SL::Controller::File or SL::Helper::File )
388 # you can create, remove, delete etc. a file in a backend independent way
390 my $file = SL::File->save(
391 object_id => $self->object_id,
392 object_type => $self->object_type,
393 mime_type => 'application/pdf',
394 file_type => 'document',
395 file_contents => 'this is no pdf');
397 my $file1 = SL::File->get(id => $id);
398 SL::File->delete(id => $id);
399 SL::File->delete(dbfile => $file1);
400 SL::File->delete_all(object_id => $object_id,
401 object_type => $object_type,
402 file_type => $filetype # may be optional
404 SL::File->rename(id => $id,to => $newname);
405 my $files1 = SL::File->get_all(object_id => $object_id,
406 object_type => $object_type,
407 file_type => 'image', # may be optional
408 source => 'uploaded' # may be optional
411 # Alternativelly some operation can be done with the filemangement object wrapper
412 # and additional oparations see L<SL::File::Object>
416 The Filemanagemt can handle files in a storage independent way. Internal the File
417 use the configured storage backend for the type of file.
418 These backends must be configured in L<SL::Controller::ClientConfig> or an extra database table.
420 There are three types of files:
426 which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object.
427 They can exist in different versions. The versioning is handled implicit. All versions of a file may be
428 deleted by the user if she/he is allowed to do this.
432 which have additional information for an ERP-objects. They are uploadable. If a filename still exists
433 on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user.
435 There are generic attachments for a specific document group (like sales_invoices). This attachments can be
436 combinide/merged with the document-file in the time of printing.
437 Today only PDF-Attachmnets can be merged with the generated document-PDF.
441 they are like attachments, but they may be have thumbnails for displaying.
442 So the must have an image format like png,jpg. The versioning is like attachments
446 For each type of files the backend can configured in L<SL::Controller::ClientConfig>.
448 The files have also the parameter C<Source>:
452 =item - created, generated by LaTeX
456 =item - scanner, import from scanner
458 ( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences )
460 =item - email, received by email and imported by hand or automatic.
464 The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources.
465 This means they are moved back into the correspondent source directories.
467 The scanner and email import must be configured via Table UserPreferences:
471 id | login | namespace | version | key | value
472 ----+---------+--------------+---------+----------+------------------------------------------------------
473 1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" }
474 2 | default | file_sources | 0.00000 | emails | {"dir":"/var/tmp/emails" ,"desc":"Empfangene Mails"}
480 The Fileinformation is stored in the table L<SL::DB::File> for saving the information.
481 The modul and object_id describe the link to the object.
483 The interface SL::File:Object encapsulate SL::DB:File, see L<SL::DB::Object>
485 The storage backends are extra classes which depends from L<SL::File::Backend>.
486 So additional backend classes can be added.
488 The implementation of versioning is done in the different backends.
496 Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C<file_type>
502 object_id => $self->object_id,
503 object_type => $self->object_type,
504 content_type => 'application/pdf'
511 The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported,
512 so in the history the import is documented also as a hint to can unimport the file later.
520 The id of SL::DB::File for an existing file
524 The Id of the ERP-object for a new file.
528 The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller.
532 The type may be "document", "attachment" or "image" for a new file.
536 The type may be "created", "uploaded" or email sources or scanner sources for a new file.
540 The file_name of the file for a new file. This name is used in the WebGUI and as name for download.
544 The mime_type of a new file. This is used for downloading or for email attachments.
546 =item C<description> or C<title>
548 The description or title of a new file. This must be discussed if this attribute is needed.
552 =item C<delete PARAMS>
554 The file data is deleted in the backend. If the file comes from source 'scanner' or 'email'
555 they moved back to the source folders. This is documented in the history.
563 The id of SL::DB::File
567 As alternative if the SL::DB::File as object is available.
571 =item C<delete_all PARAMS>
573 All file data of an ERP-object is deleted in the backend.
579 The Id of the ERP-object.
583 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
587 =item C<rename PARAMS>
589 The Filename of the file is changed
597 The id of SL::DB::File
607 The actual file object is retrieved. The id of the object is needed.
615 The id of SL::DB::File
619 =item C<get_all PARAMS>
621 All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
629 The Id of the ERP-object.
633 The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
637 The type may be "document", "attachment" or "image". This parameter is optional.
641 The name of the file . This parameter is optional.
645 The MIME type of the file . This parameter is optional.
649 The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional.
653 An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename
657 =item C<get_all_versions PARAMS>
659 All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved.
660 If only the versions of one file are wanted, additional parameter like file_name must be set.
661 If the param C<dbfile> set, only the versions of this file are returned.
663 Available C<PARAMS> ar the same as L<get_all>
667 =item C<get_all_count PARAMS>
669 The count of available files is returned.
670 Available C<PARAMS> ar the same as L<get_all>
673 =item C<get_content PARAMS>
675 The data of a file can retrieved. A reference to the data is returned.
683 The id of SL::DB::File
687 If no Id exists the object SL::DB::File as param.
691 =item C<get_file_path PARAMS>
693 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
694 it is in the responsibility of the backend to create a tempory session file.
702 The id of SL::DB::File
706 If no Id exists the object SL::DB::File as param.
710 =item C<get_other_sources>
712 A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File
714 =item C<sync_from_backend>
716 For Backends which may be changed outside of kivitendo a synchronization of the database is done.
717 This sync must be triggered by a periodical task.
725 The synchronization is done file_type by file_type.
733 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>