|
package SL::Controller::File;
|
|
|
|
use strict;
|
|
|
|
use parent qw(SL::Controller::Base);
|
|
|
|
use List::Util qw(first max);
|
|
|
|
use utf8;
|
|
use Encode qw(decode);
|
|
use English qw( -no_match_vars );
|
|
use URI::Escape;
|
|
use Cwd;
|
|
use DateTime;
|
|
use File::stat;
|
|
use File::Slurp qw(slurp);
|
|
use File::Spec::Unix;
|
|
use File::Spec::Win32;
|
|
use File::MimeInfo::Magic;
|
|
use MIME::Base64;
|
|
use SL::DB::Helper::Mappings;
|
|
use SL::DB::Order;
|
|
use SL::DB::DeliveryOrder;
|
|
use SL::DB::Invoice;
|
|
|
|
use SL::DB::PurchaseInvoice;
|
|
use SL::DB::Part;
|
|
use SL::DB::GLTransaction;
|
|
use SL::DB::Draft;
|
|
use SL::DB::History;
|
|
use SL::JSON;
|
|
use SL::Helper::CreatePDF qw(:all);
|
|
use SL::Locale::String;
|
|
use SL::SessionFile;
|
|
use SL::SessionFile::Random;
|
|
use SL::File;
|
|
use SL::Controller::Helper::ThumbnailCreator qw(file_probe_image_type file_probe_type);
|
|
|
|
use constant DO_DELETE => 0;
|
|
use constant DO_UNIMPORT => 1;
|
|
|
|
use Rose::Object::MakeMethods::Generic
|
|
(
|
|
'scalar --get_set_init' => [ qw() ],
|
|
'scalar' => [ qw(object object_type object_model object_id object_right file_type files is_global existing) ],
|
|
);
|
|
|
|
__PACKAGE__->run_before('check_object_params', only => [ qw(list ajax_delete ajax_importdialog ajax_import ajax_unimport ajax_upload ajax_files_uploaded) ]);
|
|
|
|
# gen: bitmask: bit 1 (value is 1, 3, 5 or 7) => file created
|
|
# bit 2 (value is 2, 3, 6 or 7) => file from other source (e.g. directory for scanned documents)
|
|
# bit 3 (value is 4, 5, 6 or 7) => upload as other source
|
|
# gltype: is this used somewhere?
|
|
# dir: is this used somewhere?
|
|
# model: base name of the rose model
|
|
# right: access right used for import
|
|
my %file_types = (
|
|
'sales_quotation' => { gen => 7, gltype => '', dir =>'SalesQuotation', model => 'Order', right => 'import_ar' },
|
|
'sales_order_intake' => { gen => 7, gltype => '', dir =>'SalesOrderIntake', model => 'Order', right => 'import_ar' },
|
|
'sales_order' => { gen => 7, gltype => '', dir =>'SalesOrder', model => 'Order', right => 'import_ar' },
|
|
'sales_delivery_order' => { gen => 7, gltype => '', dir =>'SalesDeliveryOrder', model => 'DeliveryOrder', right => 'import_ar' },
|
|
'sales_reclamation' => { gen => 7, gltype => '', dir =>'SalesReclamation', model => 'Reclamation', right => 'import_ar' },
|
|
'invoice' => { gen => 7, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
|
|
'invoice_for_advance_payment' => { gen => 7, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
|
|
'final_invoice' => { gen => 7, gltype => 'ar', dir =>'SalesInvoice', model => 'Invoice', right => 'import_ar' },
|
|
'credit_note' => { gen => 7, gltype => '', dir =>'CreditNote', model => 'Invoice', right => 'import_ar' },
|
|
'request_quotation' => { gen => 7, gltype => '', dir =>'RequestForQuotation', model => 'Order', right => 'import_ap' },
|
|
'purchase_quotation_intake' => { gen => 7, gltype => '', dir =>'PurchaseQuotationIntake', model => 'Order', right => 'import_ap' },
|
|
'purchase_order' => { gen => 7, gltype => '', dir =>'PurchaseOrder', model => 'Order', right => 'import_ap' },
|
|
'purchase_delivery_order' => { gen => 7, gltype => '', dir =>'PurchaseDeliveryOrder', model => 'DeliveryOrder', right => 'import_ap' },
|
|
'purchase_reclamation' => { gen => 7, gltype => '', dir =>'PurchaseReclamation', model => 'Reclamation', right => 'import_ap' },
|
|
'purchase_invoice' => { gen => 7, gltype => 'ap', dir =>'PurchaseInvoice', model => 'PurchaseInvoice',right => 'import_ap' },
|
|
'supplier_delivery_order' => { gen => 7, gltype => '', dir =>'SupplierDeliveryOrder', model => 'DeliveryOrder', right => 'import_ap' },
|
|
'rma_delivery_order' => { gen => 7, gltype => '', dir =>'RMADeliveryOrder', model => 'DeliveryOrder', right => 'import_ar' },
|
|
'vendor' => { gen => 0, gltype => '', dir =>'Vendor', model => 'Vendor', right => 'xx' },
|
|
'customer' => { gen => 1, gltype => '', dir =>'Customer', model => 'Customer', right => 'xx' },
|
|
'project' => { gen => 0, gltype => '', dir =>'Project', model => 'Project', right => 'xx' },
|
|
'part' => { gen => 0, gltype => '', dir =>'Part', model => 'Part', right => 'xx' },
|
|
'gl_transaction' => { gen => 6, gltype => 'gl', dir =>'GeneralLedger', model => 'GLTransaction', right => 'import_ap' },
|
|
'draft' => { gen => 0, gltype => '', dir =>'Draft', model => 'Draft', right => 'xx' },
|
|
'csv_customer' => { gen => 1, gltype => '', dir =>'Reports', model => 'Customer', right => 'xx' },
|
|
'csv_vendor' => { gen => 1, gltype => '', dir =>'Reports', model => 'Vendor', right => 'xx' },
|
|
'shop_image' => { gen => 0, gltype => '', dir =>'ShopImages', model => 'Part', right => 'xx' },
|
|
'letter' => { gen => 7, gltype => '', dir =>'Letter', model => 'Letter', right => 'sales_letter_edit | purchase_letter_edit' },
|
|
);
|
|
|
|
#--- 4 locale ---#
|
|
# $main::locale->text('imported')
|
|
|
|
#
|
|
# actions
|
|
#
|
|
|
|
sub action_list {
|
|
my ($self) = @_;
|
|
$main::lxdebug->dump(0, "TST: action_list", 1);
|
|
|
|
|
|
my $is_json = 0;
|
|
$is_json = 1 if $::form->{json};
|
|
|
|
$self->_do_list($is_json);
|
|
}
|
|
|
|
sub action_ajax_importdialog {
|
|
my ($self) = @_;
|
|
$::auth->assert($self->object_right);
|
|
my $path = $::form->{path};
|
|
my @files = $self->_get_from_import($path);
|
|
my $source = {
|
|
'name' => $::form->{source},
|
|
'path' => $path ,
|
|
'chk_action' => $::form->{source}.'_import',
|
|
'chk_title' => $main::locale->text('Import scanned documents'),
|
|
'chkall_title' => $main::locale->text('Import all'),
|
|
'files' => \@files
|
|
};
|
|
$self->render('file/import_dialog',
|
|
{ layout => 0
|
|
},
|
|
source => $source
|
|
);
|
|
}
|
|
|
|
sub action_ajax_import {
|
|
my ($self) = @_;
|
|
$::auth->assert($self->object_right);
|
|
my $ids = $::form->{ids};
|
|
my $source = $::form->{source};
|
|
my $path = $::form->{path};
|
|
my @files = $self->_get_from_import($path);
|
|
foreach my $filename (@{ $::form->{$ids} || [] }) {
|
|
my ($file, undef) = grep { $_->{name} eq $filename } @files;
|
|
if ( $file ) {
|
|
my $obj = SL::File->save(object_id => $self->object_id,
|
|
object_type => $self->object_type,
|
|
mime_type => 'application/pdf',
|
|
source => $source,
|
|
file_type => 'document',
|
|
file_name => $file->{filename},
|
|
file_path => $file->{path}
|
|
);
|
|
unlink($file->{path}) if $obj;
|
|
}
|
|
}
|
|
$self->_do_list(1);
|
|
}
|
|
|
|
sub action_ajax_delete {
|
|
my ($self) = @_;
|
|
$self->_delete_all(DO_DELETE, $::locale->text('Following files are deleted:'));
|
|
}
|
|
|
|
sub action_ajax_unimport {
|
|
my ($self) = @_;
|
|
$self->_delete_all(DO_UNIMPORT, $::locale->text('Following files are unimported:'));
|
|
}
|
|
|
|
sub action_ajax_rename {
|
|
my ($self) = @_;
|
|
my ($id, $version) = split /_/, $::form->{id};
|
|
my $file = SL::File->get(id => $id);
|
|
if ( ! $file ) {
|
|
$self->js->flash('error', $::locale->text('File not exists !'))->render();
|
|
return;
|
|
}
|
|
my $sessionfile = $::form->{sessionfile};
|
|
if ( $sessionfile && -f $sessionfile ) {
|
|
# new uploaded file
|
|
if ( $::form->{to} eq $file->file_name ) {
|
|
# no rename so use as new version
|
|
$file->save_file($sessionfile);
|
|
$self->js->flash('warning', $::locale->text('File \'#1\' is used as new Version !', $file->file_name));
|
|
|
|
} else {
|
|
# new filename, so it is a new file with the same attributes as the old file
|
|
eval {
|
|
SL::File->save(object_id => $file->object_id,
|
|
object_type => $file->object_type,
|
|
mime_type => $file->mime_type,
|
|
source => $file->source,
|
|
file_type => $file->file_type,
|
|
file_name => $::form->{to},
|
|
file_path => $sessionfile
|
|
);
|
|
unlink($sessionfile);
|
|
1;
|
|
} or do {
|
|
$self->js->flash( 'error', t8('internal error (see details)'))
|
|
->flash_detail('error', $@)->render;
|
|
return;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
# normal rename
|
|
my $result;
|
|
|
|
eval {
|
|
$result = $file->rename($::form->{to});
|
|
1;
|
|
} or do {
|
|
$self->js->flash( 'error', t8('internal error (see details)'))
|
|
->flash_detail('error', $@)->render;
|
|
return;
|
|
};
|
|
|
|
if ($result != SL::File::RENAME_OK) {
|
|
$self->js->flash('error',
|
|
$result == SL::File::RENAME_EXISTS ? $::locale->text('File still exists !')
|
|
: $result == SL::File::RENAME_SAME ? $::locale->text('Same Filename !')
|
|
: $::locale->text('File not exists !'))
|
|
->render;
|
|
return;
|
|
}
|
|
}
|
|
$self->is_global($::form->{is_global});
|
|
$self->file_type( $file->file_type);
|
|
$self->object_type($file->object_type);
|
|
$self->object_id( $file->object_id);
|
|
#$self->object_model($file_types{$file->module}->{model});
|
|
#$self->object_right($file_types{$file->module}->{right});
|
|
if ( $::form->{next_ids} ) {
|
|
my @existing = split(/,/, $::form->{next_ids});
|
|
$self->existing(\@existing);
|
|
}
|
|
$self->_do_list(1);
|
|
}
|
|
|
|
sub action_ajax_upload {
|
|
my ($self) = @_;
|
|
$self->{maxsize} = $::instance_conf->get_doc_max_filesize;
|
|
$self->{accept_types} = '';
|
|
$self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
|
|
$self->render('file/upload_dialog',
|
|
{ layout => 0
|
|
},
|
|
);
|
|
}
|
|
|
|
sub action_ajax_files_uploaded {
|
|
my ($self) = @_;
|
|
|
|
my $source = 'uploaded';
|
|
my @existing;
|
|
if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
|
|
my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
|
|
foreach my $idx (0 .. scalar(@upfiles) - 1) {
|
|
eval {
|
|
my $fname = uri_unescape($upfiles[$idx]->{filename});
|
|
# normalize and find basename
|
|
# first split with unix rules
|
|
# after that split with windows rules
|
|
my ($volume, $directories, $basefile) = File::Spec::Unix->splitpath($fname);
|
|
($volume, $directories, $basefile) = File::Spec::Win32->splitpath($basefile);
|
|
|
|
# to find real mime_type by magic we must save the filedata
|
|
|
"File.pm.html#L292" data-txt="292">
description => $::form->{description},
|
|
## two possibilities: what is better ? content or sessionfile ??
|
|
file_contents => ${$upfiles[$idx]->{data}},
|
|
file_path => $sfile->file_name
|
|
);
|
|
unlink($sfile->file_name);
|
|
}
|
|
1;
|
|
} or do {
|
|
$self->js->flash( 'error', t8('internal error (see details)'))
|
|
->flash_detail('error', $@)->render;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
$self->existing(\@existing);
|
|
$self->_do_list(1);
|
|
}
|
|
|
|
sub action_download {
|
|
my ($self) = @_;
|
|
|
|
my $id = $::form->{id};
|
|
my $version = $::form->{version};
|
|
|
|
my $file = SL::File->get(id => $id );
|
|
$file->version($version) if $version;
|
|
my $ref = $file->get_content;
|
|
if ( $file && $ref ) {
|
|
return $self->send_file($ref,
|
|
type => $file->mime_type,
|
|
name => $file->file_name,
|
|
);
|
|
}
|
|
}
|
|
|
|
sub action_ajax_get_thumbnail {
|
|
my ($self) = @_;
|
|
|
|
my $id = $::form->{file_id};
|
|
my $version = $::form->{file_version};
|
|
my $file = SL::File->get(id => $id);
|
|
|
|
$file->version($version) if $version;
|
|
|
|
my $thumbnail = _create_thumbnail($file, $::form->{size});
|
|
|
|
my $overlay_selector = '#enlarged_thumb_' . $id;
|
|
$overlay_selector .= '_' . $version if $version;
|
|
$self->js
|
|
->attr($overlay_selector, 'src', 'data:' . $thumbnail->{thumbnail_img_content_type} . ';base64,' . MIME::Base64::encode_base64($thumbnail->{thumbnail_img_content}))
|
|
->data($overlay_selector, 'is-overlay-loaded', '1')
|
|
->render;
|
|
}
|
|
|
|
|
|
#
|
|
# filters
|
|
#
|
|
|
|
sub check_object_params {
|
|
my ($self) = @_;
|
|
|
|
my $id = ($::form->{object_id} // 0) * 1;
|
|
my $draftid = ($::form->{draft_id} // 0) * 1;
|
|
my $gldoc = 0;
|
|
my $type = undef;
|
|
|
|
if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
|
|
$gldoc = 1;
|
|
$type = $::form->{object_type};
|
|
}
|
|
elsif ( $id == 0 ) {
|
|
$id = $::form->{draft_id};
|
|
$type = 'draft';
|
|
} elsif ( $::form->{object_type} ) {
|
|
$type = $::form->{object_type};
|
|
}
|
|
die "No object type" unless $type;
|
|
die "No file type" unless $::form->{file_type};
|
|
die "Unknown object type" unless $file_types{$type};
|
|
|
|
$self->is_global($gldoc);
|
|
$self->file_type($::form->{file_type});
|
|
$self->object_type($type);
|
|
$self->object_id($id);
|
|
$self->object_model($file_types{$type}->{model});
|
|
$self->object_right($file_types{$type}->{right});
|
|
|
|
# $::auth->assert($self->object_right);
|
|
|
|
# my $model = 'SL::DB::' . $self->object_model;
|
|
# $self->object($model->new(id => $self->object_id)->load || die "Record not found");
|
|
|
|
return 1;
|
|
}
|
|
|
|
#
|
|
# private methods
|
|
#
|
|
|
|
sub _delete_all {
|
|
my ($self, $do_unimport, $infotext) = @_;
|
|
my $files = '';
|
|
my $ids = $::form->{ids};
|
|
foreach my $id_version (@{ $::form->{$ids} || [] }) {
|
|
my ($id, $version) = split /_/, $id_version;
|
|
my $dbfile = SL::File->get(id => $id);
|
|
if ( $dbfile ) {
|
|
if ( $version ) {
|
|
$dbfile->version($version);
|
|
$files .= ' ' . $dbfile->file_name if $dbfile->delete_version;
|
|
} else {
|
|
$files .= ' ' . $dbfile->file_name if $dbfile->delete;
|
|
}
|
|
}
|
|
}
|
|
$self->js->flash('info', $infotext . $files) if $files;
|
|
$self->_do_list(1);
|
|
}
|
|
|
|
sub _do_list {
|
|
my ($self, $json) = @_;
|
|
|
|
my @files;
|
|
my @object_types = ($self->object_type);
|
|
if ( $self->file_type eq 'document' ) {
|
|
push @object_types, qw(dunning1 dunning2 dunning3 dunning_invoice dunning_orig_invoice) if $self->object_type eq 'invoice'; # hardcoded object types?
|
|
}
|
|
@files = SL::File->get_all_versions(object_id => $self->object_id,
|
|
object_type => \@object_types,
|
|
file_type => $self->file_type,
|
|
);
|
|
$main::lxdebug->dump(0, "TST: files", \@files);
|
|
|
|
$self->files(\@files);
|
|
|
|
$_->{thumbnail} = _create_thumbnail($_) for @files;
|
|
$_->{version_count} = SL::File->get_version_count(id => $_->id) for @files;
|
|
|
|
if($self->object_type eq 'shop_image'){
|
|
$self->js
|
|
->run('kivi.ShopPart.show_images', $self->object_id)
|
|
->render();
|
|
}else{
|
|
$self->_mk_render('file/list', 1, 0, $json);
|
|
}
|
|
}
|
|
|
|
sub _get_from_import {
|
|
my ($self, $path) = @_;
|
|
my @foundfiles ;
|
|
|
|
my $language = $::lx_office_conf{system}->{language};
|
|
my $timezone = $::locale->get_local_time_zone()->name;
|
|
if (opendir my $dir, $path) {
|
|
my @files = (readdir $dir);
|
|
foreach my $file ( @files) {
|
|
next if (($file eq '.') || ($file eq '..'));
|
|
$file = Encode::decode('utf-8', $file);
|
|
|
|
next if ( -d "$path/$file" );
|
|
|
|
my $tmppath = File::Spec->catfile( $path, $file );
|
|
next if( ! -f $tmppath );
|
|
|
|
my $st = stat($tmppath);
|
|
my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language );
|
|
my $sname = $main::locale->quote_special_chars('HTML', $file);
|
|
push @foundfiles, {
|
|
'name' => $file,
|
|
'filename' => $sname,
|
|
'path' => $tmppath,
|
|
'mtime' => $st->mtime,
|
|
'date' => $dt->dmy('.') . " " . $dt->hms |