Initial commit

This commit is contained in:
Manuel Cillero 2020-04-10 12:48:19 +02:00
commit f4bfb0e367
71 changed files with 10399 additions and 0 deletions

357
Dam/Application.pm Normal file
View file

@ -0,0 +1,357 @@
=head1 NAME
Dam::Application
=head1 DECRIPTION
Initialization of the default global variables and definition of the entry point
for the predefined and customized run modes created using the Dam framework.
=cut
use strict;
use warnings;
use utf8;
package Dam::Application;
use base 'CGI::Application';
use CGI qw(-utf8);
use Authen::Simple::ActiveDirectory;
use Crypt::Tea_JS;
use String::Random qw(random_string);
use Date::Calc qw(Today_and_Now);
use Dam::Util;
use Dam::Debug;
use Dam::Database;
use Dam::DamLogic qw(
is_report is_download Show__error_403 Show__about Show
Session__new Session__flush Session__close
User__is_logged_in User__access
cgiapp_param cgiapp_cookie cgiapp_header_add cgiapp_header_props
_t package_config tmpl_core
);
use Dam::Var;
my %CURRENT_PACKAGE = ();
sub pre__session_user_params {
my ($CGIAPP, $uid) = @_;
return ();
}
sub pre__load_stylesheets {
}
sub pre__load_javascripts {
}
sub setup {
my $CGIAPP = shift;
RESERVED(CGIAPP => $CGIAPP);
# Connect to database:
RESERVED(DBH => database_connect(
DB_DSN => CONFIG('DB_DSN'),
DB_USER => CONFIG('DB_USER'),
DB_PASSWORD => CONFIG('DB_PASSWORD')
));
my @run_modes = (
'APP_login',
'APP_confirm',
'RUN_home',
'RUN_report',
'RUN_error403',
'RUN_close'
);
if (User__is_logged_in()) {
my %ROUTES = %{CONFIG('REF_ROUTES')};
my @user_access = User__access();
foreach my $menu (sort keys(%ROUTES)) {
__setup_run_modes(\@run_modes, \%{$ROUTES{$menu}{OPTIONS}}, \@user_access);
}
}
$CGIAPP->run_modes(\@run_modes);
$CGIAPP->start_mode('RUN_home');
# The template directory is initialized:
$CGIAPP->tmpl_path(CONFIG('DIR_TEMPLATES'));
# Enable file uploads:
$CGIAPP::DISABLE_UPLOADS = 0;
$CGIAPP::POST_SIZE = CONFIG('UPLOAD_MAX_FILESIZE');
}
sub teardown {
# Discconnect from database:
database_disconnect();
}
sub cgiapp_prerun {
my ($CGIAPP, $run_mode) = @_;
my $binmode = 1;
cgiapp_header_props(
-charset => 'UTF-8'
);
if (!is_eq(substr($run_mode, 0, 4), 'APP_')) {
if (User__is_logged_in()) {
if (!is_eq(substr($run_mode, 0, 4), 'RUN_')) {
# 1. Get the user access permissions:
my @user_access = User__access();
# 2. Debugging mode is disabled if user is not a developer:
CONFIG('DEBUG_MODE' => 0) if !in_array(0, \@user_access);
# 3. Load report:
info($run_mode);
my %ROUTES = %{CONFIG('REF_ROUTES')};
foreach my $menu (sort (%ROUTES)) {
%CURRENT_PACKAGE = __search_run_mode($run_mode, \%{$ROUTES{$menu}{OPTIONS}});
if (%CURRENT_PACKAGE) {
$CURRENT_PACKAGE{ID} = $ROUTES{$menu}{ID};
RESERVED('REF_CURRENT_PACKAGE' => \%CURRENT_PACKAGE);
last;
}
}
# 4. Check if user has access permission to the current report:
if (!defined($CURRENT_PACKAGE{ACCESS}) || match_arrays(\@user_access, $CURRENT_PACKAGE{ACCESS})) {
# 5. Check if it's a download:
if (is_download()) {
my ($download_mode, $filename_extension) = split(',', cgiapp_param('dm'));
my $package = $run_mode;
my @packages = split('::', $run_mode);
if (scalar(@packages) > 1) {
$package = pop(@packages);
$package = pop(@packages) if is_eq($package, $CURRENT_PACKAGE{RUN});
}
my $filename = strval($package, '_', $download_mode, '-', sprintf("%04d%02d%02d_%02d%02d%02d", Today_and_Now()), '.', $filename_extension);
cgiapp_header_props(
-type => 'application/x-download',
-Content_Disposition => strval('attachment; filename="', $filename, '"')
);
$binmode = 0;
}
elsif (!is_report()) {
$CGIAPP->prerun_mode('RUN_report');
}
}
else {
$CGIAPP->prerun_mode('RUN_error403');
}
}
}
else {
$CGIAPP->prerun_mode('APP_login');
}
}
binmode STDOUT, ":utf8" if $binmode;
}
sub cgiapp_postrun {
DROP_TEMP_TABLES(); # Drop temporal database tables
Session__flush(); # Synchronize the session with the database
}
sub APP_login {
return __login();
}
sub APP_confirm {
my $user = lc cgiapp_param('user');
my $pass = decrypt(cgiapp_param('pass'), cgiapp_param('key'));
# Check if the user exists (according to input parameters):
my $user_db = QUERY(SELECT(
FIELDS => 'user_uid, user_password, user_firstname, user_name, user_access, user_active',
FROM => 'users',
WHERE => COMPARE_STR('BINARY user_login', '=', $user)
));
# User not registered. Request the login again:
return __login($user, 1) if $user_db->rows == 0;
my @user_data = $user_db->fetchrow_array();
# Non-active user. Show warning message:
return __login($user, 2, _t('User not active!'), _t('Consult with your systems manager to activate your user')) if $user_data[5] eq 0;
# Check if user is in the Active Directory:
my $ad = Authen::Simple::ActiveDirectory->new(host => CONFIG('LDAP_DOMAIN'), principal => CONFIG('LDAP_DOMAIN'), timeout => 20);
if (!$ad->authenticate($user, $pass)) {
# Unidentified user. Or is it a local user:
my $passcrypt = __crypt_password($pass);
# Unidentified user. Request the login again:
return __login($user, 3) if !defined($user_data[1]) || $user_data[1] ne $passcrypt;
}
# Validated user. Login with user settings:
Session__new($user_data[0], $user_data[2], $user_data[3], $user_data[4], RESERVED('CGIAPP')->pre__session_user_params($user_data[0]));
# Show home page:
return RUN_home();
}
sub RUN_home {
return Show__about();
}
sub RUN_report {
return Show();
}
sub RUN_error403 {
return Show__error_403();
}
sub RUN_close {
Session__close(); # Delete the user session
return __login(); # Return to the login form
}
# PRIVATE FUNCTIONS:
sub __setup_run_modes {
my ($run_modes_ref, $options_ref, $user_access_ref) = @_;
my $superuser = in_array(0, $user_access_ref);
foreach my $option (sort keys(%$options_ref)) {
next if !is_eq(ref($$options_ref{$option}), 'HASH') && is_eq($$options_ref{$option}, _DIVIDER_);
if (defined($$options_ref{$option}{OPTIONS})) {
__setup_run_modes($run_modes_ref, \%{$$options_ref{$option}{OPTIONS}}, $user_access_ref);
}
else {
next if is_empty($$options_ref{$option}{PACKAGE}) && is_empty($$options_ref{$option}{RUN});
next if defined($$options_ref{$option}{ENABLED}) && $$options_ref{$option}{ENABLED} == 0;
next unless $superuser || (defined($$options_ref{$option}{ACCESS}) && match_arrays($user_access_ref, $$options_ref{$option}{ACCESS}));
my $run_mode = strval_trio($$options_ref{$option}{PACKAGE}, '::', defined($$options_ref{$option}{RUN}) ? $$options_ref{$option}{RUN} : 'Run');
fatal('Duplicated "', $run_mode, '" run mode.') if in_array($run_mode, $run_modes_ref);
push(@$run_modes_ref, $run_mode);
}
}
}
sub __search_run_mode {
my ($run_mode, $options_ref) = @_;
foreach my $option (sort keys(%$options_ref)) {
next if !is_eq(ref($$options_ref{$option}), 'HASH') && is_eq($$options_ref{$option}, _DIVIDER_);
if (defined($$options_ref{$option}{OPTIONS})) {
my %search_option = __search_run_mode($run_mode, \%{$$options_ref{$option}{OPTIONS}});
return %search_option if %search_option;
}
else {
next if is_empty($$options_ref{$option}{PACKAGE}) && is_empty($$options_ref{$option}{RUN});
next if !is_eq($run_mode, strval_trio($$options_ref{$option}{PACKAGE}, '::', defined($$options_ref{$option}{RUN}) ? $$options_ref{$option}{RUN} : 'Run'));
return package_config(\%{$$options_ref{$option}});
}
}
return ();
}
sub __login {
my ($login, $error, $error_title, $error_message) = @_;
cgiapp_header_add(-cookie => cgiapp_cookie(CGISESSID => ''));
my $tmpl_login = tmpl_core('Login',
APP_NAME => CONFIG('APP_NAME'),
KEY => random_string('ssssssssssssssssssssssssssssss'),
LOGIN => $login,
CRYPT_TEA => tea_in_javascript(),
T_USERNAME => _t('Username'),
T_PASSWORD => _t('Password'),
T_LOGIN => _t('Login')
);
if (defined($error)) {
$tmpl_login->param(
ERROR => $error,
ERROR_TITLE => defined($error_title) ? $error_title : _t('Access error!'),
ERROR_MESSAGE => defined($error_message) ? $error_message : _t('Verify username and retype password')
);
}
return Show(DISPLAY => 'PAGE', TITLE => 'Login', TEMPLATE => $tmpl_login);
}
sub __crypt_password {
my $password = shift;
return length($password) ? crypt($password, substr(crypt($password, 'CRTSGR'), -2, 2)) : '';
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,124 @@
=head1 NAME
Dam::Components::Actions::Download
=head1 SYNOPSIS
my $download_mode = Component__Get(ACTION_DOWNLOAD, ['csv']);
=head1 DESCRIPTION
Action to execute a download.
=head1 ARGUMENTS
(
TYPE => 'Download',
ID => 'csv' (default),
LABEL => 'CSV' (default),
TOOLTIP => 'Download the current report in CSV format' (default),
ICON => 'download-alt' (default),
MODE_EXT => 'CSV' (default),
FILE_EXT => 'csv' (default)
)
=cut
package Dam::Components::Actions::Download;
use Exporter qw(import);
our @EXPORT = qw(
Action__html Action__js
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'csv';
sub __arguments {
my $arg_ref = shift;
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
LABEL => [ ARG_DEFAULT, 'CSV' ],
TOOLTIP => [ ARG_DEFAULT, _t('Download current report in CSV format') ],
ICON => [ ARG_DEFAULT, 'download-alt' ],
MODE_EXT => [ ARG_DEFAULT, 'CSV' ],
FILE_EXT => [ ARG_DEFAULT, 'csv' ]
);
}
sub Action__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
return is_report() ? strval('
<button type="button" class="btn btn-info input-lg" id="', $$arg_ref{ID}, '" data-toggle="tooltip" title="', $$arg_ref{TOOLTIP}, '"><span class="glyphicon glyphicon-', $$arg_ref{ICON}, '"></span> ', $$arg_ref{LABEL}, '</button>
') : '';
}
sub Action__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
return is_report() ? strval('
$("#', $$arg_ref{ID}, '").click(function(){
$("#xt").val(3);
$("#dm").val("', $$arg_ref{MODE_EXT}, ',', $$arg_ref{FILE_EXT}, '");
$("#submit").click();
});
') : '';
}
sub Get {
my ($download_mode, $filename_extension) = split(',', cgiapp_param('dm'));
return is_download() ? $download_mode : '';
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,107 @@
=head1 NAME
App::api::actions::Action_Print
=head1 DESCRIPTION
Action to print.
=head1 ARGUMENTS
(
TYPE => 'Print',
ID => 'print' (default),
LABEL => 'Print' (default)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Actions::Print;
use Exporter qw(import);
our @EXPORT = qw(
Action__html Action__js
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'print';
sub __arguments {
my $arg_ref = shift;
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
LABEL => [ ARG_DEFAULT, _t('Print') ]
);
}
sub Action__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
return is_report() ? strval('
<button type="button" class="btn btn-success input-lg" id="', $$arg_ref{ID},'"><span class="glyphicon glyphicon-print"></span> ', $$arg_ref{LABEL},'</button>
') : '';
}
sub Action__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
return is_report() ? strval('
$("#', $$arg_ref{ID},'").click(function(){
window.print();
});
') : '';
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,122 @@
=head1 NAME
Dam::Components::Actions::Run
=head1 DESCRIPTION
Action to run a report.
=head1 ARGUMENTS
(
TYPE => 'Run',
ID => 'submit' (default),
LABEL => 'Run' (default)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Actions::Run;
use Exporter qw(import);
our @EXPORT = qw(
Action__html Action__js
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'submit';
sub __arguments {
my $arg_ref = shift;
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
LABEL => [ ARG_DEFAULT, _t('Run') ]
);
}
sub Action__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
return strval('
<button type="submit" class="btn btn-primary input-lg" id="', $$arg_ref{ID}, '"><span class="glyphicon glyphicon-repeat"></span> ', $$arg_ref{LABEL},'</button>
');
}
sub Action__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
# Required javascripts:
Component__Header(ADD => 'JS', RESOURCE => '/dam/js/spin.min.js', VERSION => '2.3.2' );
return strval('
$(function(){
var middle = Math.floor($(window).height() / 2) + "px";
var spin = { lines: 10, length: 28, width: 25, radius: 40, scale: 0.5, corners: 1, color: "#000", opacity: 0.3, rotate: 0, direction: 1, speed: 1, trail: 60, fps: 20, zIndex: 2e9, className: "spinner", top: middle, left: "50%", shadow: false, hwaccel: false, position: "absolute" }
$("#filter").on("submit", function(e) {
if ($("#nv").val() != 1 && $("#filter").valid()) {
if ($("#xt").val() == 2) $("#xt").val(1);
if ($("#xt").val() == 3) $("#xt").val(2);
if ($("#xt").val() < 2) {
var spinner = new Spinner(spin).spin(document.getElementById("loading"));
$("#loading").show();
}
}
$("#nv").val(0);
});
});
');
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,216 @@
=head1 NAME
Dam::Components::Actions::Sort
=head1 SYNOPSIS
my $orderby = Component__Get(ACTION_SORT);
=head1 DESCRIPTION
Action to order a list by columns.
=head1 ARGUMENTS
(
TYPE => 'Sort',
COLUMNS => { 'col1' => 'Column 1', 'col2' => 'Column 2' } (default),
DEFAULT => 'col1' (default valus is undef)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Actions::Sort;
use Exporter qw(import);
our @EXPORT = qw(
Action__html Action__js
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'sort';
sub __arguments {
my $arg_ref = shift;
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
COLUMNS => [ ARG_DEFAULT, { 'col1' => 'Column 1', 'col2' => 'Column 2' } ],
DEFAULT => [ ARG_OPTIONAL ]
);
}
sub Action__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $default = undef;
my %list_columns = %{$$arg_ref{COLUMNS}};
if (!is_empty($$arg_ref{DEFAULT})) {
my @default = split(' ', $$arg_ref{DEFAULT});
if (defined($list_columns{$default[0]}) && !defined($default[2])) {
my $dir = in_array(uc($default[1]), 'ASC', 'DESC') ? uc($default[1]) : 'ASC';
$default = strval($default[0], ' ', $dir);
}
}
# Order value:
my $orderby_value = cgiapp_param('orderby');
$orderby_value = $default if is_empty($orderby_value) && defined($default);
if (defined($orderby_value)) {
my @orderby = split(' ', $orderby_value);
if (defined($orderby[0]) && defined($list_columns{$orderby[0]})) {
my $orderby = $list_columns{$orderby[0]};
if (defined($orderby[1])) {
$orderby = strval($orderby, is_eq($orderby[1], 'ASC') ? strval(' (', _t('ascendant'), ')') : is_eq($orderby[1], 'DESC') ? strval(' (', _t('descendent'), ')') : '');
}
push(@{$info_ref}, { DATA => _t('Order by'), VALUE => $orderby });
}
else {
$orderby_value = '';
}
}
else {
$orderby_value = '';
}
return strval('
<input type="hidden" name="orderby" id="orderby" value="', $orderby_value, '" />
');
}
sub Action__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
my $default = undef;
my %list_columns = %{$$arg_ref{COLUMNS}};
if (!is_empty($$arg_ref{DEFAULT})) {
my @default = split(' ', $$arg_ref{DEFAULT});
if (defined($list_columns{$default[0]}) && !defined($default[2])) {
my $dir = in_array(uc($default[1]), 'ASC', 'DESC') ? uc($default[1]) : 'ASC';
$default = strval($default[0], ' ', $dir);
}
}
my $columns_id = '';
my $columns_name = '';
foreach my $column (keys(%list_columns)) {
$columns_id .= strval('#', $column, ',');
$columns_name .= strval($list_columns{$column}, ',');
}
chop($columns_id);
chop($columns_name);
return strval('
$(function(){
var current_orderby = $("#orderby").val().split(" ");
var columns_id = ["', strval_join('","', split(',', $columns_id)), '"];
var columns_name = ["', strval_join('","', split(',', $columns_name)), '"];
columns_id.forEach(function(value,index,array){
var glypho = "sort";
if (current_orderby[0] == value.substring(1)) {
if (current_orderby[1] == "ASC") {
glypho = "triangle-bottom";
}
else if (current_orderby[1] == "DESC") {
glypho = "triangle-top";
}
}
$(value).css("white-space","nowrap");
$(value).prepend("<span style=\"font-size: medium; color: #999;\" class=\"minitip glyphicon glyphicon-" + glypho + " hidden-print\" data-toggle=\"tooltip\" title=\"', _t('Sort by'), ' " + columns_name[index] + "\"></span>");
$(value).hover(function(){
$(this).css("cursor","pointer");
});
$(value).click(function(){
var current_column = $(this).attr("id");
if (current_column == current_orderby[0]) {
if (current_orderby[1] == "ASC") {
current_column += " DESC";
}
else if (current_orderby[1] != "DESC") {
current_column += " ASC";
}
else {
current_column = "', $default, '";
}
}
else {
current_column += " ASC";
}
$("#orderby").val(current_column);
$("#submit").click();
});
});
$(".minitip").tooltip();
});
');
}
sub Get {
my $orderby_value = cgiapp_param('orderby');
return undef if is_empty($orderby_value);
return $orderby_value;
}
sub Set {
my ($self, $value, $id) = @_;
cgiapp_param('orderby', $value);
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,127 @@
=head1 NAME
App::api::controls::Control_Check
=head1 SYNOPSIS
my $check = Component__Get(CONTROL_CHECK, ['check']);
=head1 DESCRIPTION
Control para marcar/desmarcar una opción.
=head1 ARGUMENTS
(
TYPE => 'Check',
ID => 'check' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Check' (default),
LABEL_INFO => Same as LABEL (default),
DEFAULT => 0 (unchecked; default) or 1 (checked)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Check;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'check';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = 'Check' if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
DEFAULT => [ ARG_DEFAULT, 0, 1 ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $check_value = !is_report() ? $$arg_ref{DEFAULT} : is_empty(cgiapp_param($$arg_ref{ID})) ? 0 : 1;
push(@{$info_ref}, { DATA => _t('Option'), VALUE => $$arg_ref{LABEL_INFO} }) if $$arg_ref{INFO} && $check_value;
return strval('
<div class="form-group form-group-', $ID_DEFAULT, ' input-lg">
<label for="', $$arg_ref{ID}, '"><input type="checkbox" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '" value="check"', $check_value ? ' checked ' : ' ', '/>&nbsp;&nbsp;', $$arg_ref{LABEL}, '</label>
</div>
');
}
sub Control__js {
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
return is_empty(cgiapp_param($id)) ? 0 : 1;
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,158 @@
=head1 NAME
Dam::Components::Controls::Date
=head1 SYNOPSIS
my $date = Component__Get(CONTROL_DATE, ['date']);
=head1 DESCRIPTION
Control para seleccionar una fecha.
=head1 ARGUMENTS
(
TYPE => 'Date',
ID => 'date' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Date' (default),
LABEL_INFO => Same as LABEL (default),
REQUIRED => 1 (control required; default) or 0 (not required)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Date;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Date::Calc qw(Today Add_Delta_Days);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'date';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = _t('Date') if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $date_value = cgiapp_param($$arg_ref{ID});
if (!is_report()) {
my @previous = Add_Delta_Days(Today(), -1);
$date_value = strval(sprintf("%02d", $previous[2]), '/', sprintf("%02d", $previous[1]), '/', $previous[0]);
}
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => $date_value }) if $$arg_ref{INFO};
# Required stylesheets:
Component__Header(ADD => 'CSS', RESOURCE => PACK_DATEPICKER);
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<input type="text" class="form-control input-lg input-date-date" data-provide="datepicker" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '" value="', $date_value, '" autocomplete="off" size=4', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
</div>
');
}
sub Control__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
# Required javascripts:
Component__Header(ADD => 'JS', RESOURCE => PACK_DATEPICKER);
return strval('
$(function(){
$("#', $$arg_ref{ID}, '").datepicker({
language: "', _t('LANGUAGE_CODE'), '",
autoclose: true,
todayHighlight: true,
disableTouchKeyboard: true,
endDate: "0d"
});
});
');
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
my @date_value = split('/', cgiapp_param($id));
my $date_value = "$date_value[2]-$date_value[1]-$date_value[0]";
return $date_value;
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,221 @@
=head1 NAME
Dam::Components::Controls::DateRange
=head1 SYNOPSIS
my ($ini, $end) = Component__Get(CONTROL_DATERANGE, ['range']);
=head1 DESCRIPTION
Control para obtener un rango de fechas.
=head1 ARGUMENTS
(
TYPE => 'DateRange',
ID => 'range' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Date range' (default),
LABEL_INFO => Same as LABEL (default),
REQUIRED => 1 (control required; default) or 0 (not required)
MAXDAYS => 1095 (default) or maximum number of days for range
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::DateRange;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Date::Calc qw(Today Add_Delta_YM Days_in_Month);
use Dam::Util;
use Dam::DamLogic;
use Dam::Var;
my $ID_DEFAULT = 'range';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = _t('Date range') if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ],
MAXDAYS => [ ARG_DEFAULT, 1095 ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $ini_value = cgiapp_param($$arg_ref{ID});
my $end_value = cgiapp_param(strval($$arg_ref{ID}, '_end'));
if (!is_report()) {
my @previous = Add_Delta_YM(Today(), 0, -1);
$ini_value = strval('01/', sprintf("%02d", $previous[1]), '/', $previous[0]);
$end_value = strval(Days_in_Month($previous[0], $previous[1]), '/', sprintf("%02d", $previous[1]), '/', $previous[0]);
}
my @ini = split(/\//, $ini_value);
my @end = split(/\//, $end_value);
my $range = strval($ini_value, ' al ', $end_value);
if (($ini[0] == 1) && ($ini[2] == $end[2])) {
if (($end[0] == Days_in_Month($ini[2],$ini[1])) && ($ini[1] == $end[1])) {
$range = strval($range, ' (', uc(_t('MONTHS', $ini[1])), ' ', $ini[2], ')');
}
elsif (($ini[1] == 1) && ($end[0] == 31) && ($end[1] == 12)) {
$range = strval($range, ' (', _t('YEAR'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 1) && ($end[0] == 30) && ($end[1] == 6)) {
$range = strval($range, ' (', _t('FIRST SEMESTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 7) && ($end[0] == 31) && ($end[1] == 12)) {
$range = strval($range, ' (', _t('SECOND SEMESTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 1) && ($end[0] == 30) && ($end[1] == 4)) {
$range = strval($range, ' (', _t('FIRST QUARTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 5) && ($end[0] == 31) && ($end[1] == 8)) {
$range = strval($range, ' (', _t('SECOND QUARTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 9) && ($end[0] == 31) && ($end[1] == 12)) {
$range = strval($range, ' (', _t('THIRD QUARTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 1) && ($end[0] == 31) && ($end[1] == 3)) {
$range = strval($range, ' (', _t('FIRST TRIMESTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 4) && ($end[0] == 30) && ($end[1] == 6)) {
$range = strval($range, ' (', _t('SECOND TRIMESTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 7) && ($end[0] == 30) && ($end[1] == 9)) {
$range = strval($range, ' (', _t('THIRD TRIMESTER'), ' ', $ini[2], ')');
}
elsif (($ini[1] == 10) && ($end[0] == 31) && ($end[1] == 12)) {
$range = strval($range, ' (', _t('FOURTH TRIMESTER'), ' ', $ini[2], ')');
}
}
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => $range }) if $$arg_ref{INFO};
# Required stylesheets:
Component__Header(ADD => 'CSS', RESOURCE => PACK_DATEPICKER);
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<div class="input-group input-daterange" data-provide="datepicker">
<input type="text" class="form-control input-lg" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '" value="', $ini_value, '" autocomplete="off" size=10', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
<span class="input-group-addon">al</span>
<input type="text" class="form-control input-lg" id="', $$arg_ref{ID}, '_end" name="', $$arg_ref{ID}, '_end" value="', $end_value, '" autocomplete="off" size=10', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
</div>
</div>
');
}
sub Control__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
# Required javascripts:
Component__Header(ADD => 'JS', RESOURCE => PACK_DATEPICKER);
return strval('
$(function(){
$(".input-group.input-daterange").datepicker({
language: "', _t('LANGUAGE_CODE'), '",
autoclose: true,
todayHighlight: true,
disableTouchKeyboard: true,
// endDate: "0d",
startDate: "01/01/1900" // https://github.com/uxsolutions/bootstrap-datepicker/issues/721#issuecomment-86275874 (workaround)
});
$("#filter").on("submit", function(e) {
if ($("#filter").valid()) {
var range = ', $$arg_ref{MAXDAYS}, ';
if (Math.round(($("#', $$arg_ref{ID}, '_end").datepicker("getDate") - $("#', $$arg_ref{ID}, '").datepicker("getDate")) / (1000 * 60 * 60 * 24)) > range) {
$("#nv").val(1);
$("#filter-message").text("', _t('Date ranges greater than <--max--> are not allowed.', max => $$arg_ref{MAXDAYS} % 365 ? strval($$arg_ref{MAXDAYS}, ' ', _t('day(s)')) : strval($$arg_ref{MAXDAYS} / 365, ' ', _t('year(s)'))), '");
$("#filter-error").modal();
e.preventDefault();
return false;
}
}
});
});
');
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
my @ini_value = split('/', cgiapp_param($id));
my $ini_value = "$ini_value[2]-$ini_value[1]-$ini_value[0]";
my @end_value = split('/', cgiapp_param(strval($id, '_end')));
my $end_value = "$end_value[2]-$end_value[1]-$end_value[0]";
return ($ini_value, $end_value);
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,153 @@
=head1 NAME
Dam::Components::Controls::Input
=head1 SYNOPSIS
my $input = Component__Get(CONTROL_INPUT, ['input']);
=head1 DESCRIPTION
Control para introducir un texto.
=head1 ARGUMENTS
(
TYPE => 'Input',
ID => 'input' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Enter text' (default),
LABEL_INFO => 'Input text' (default),
REQUIRED => 1 (control required; default) or 0 (not required)
ONLY => 'All' (allows any alphanumeric character; default) ó 'Digits'
(allow only numbers)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Input;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'input';
sub __arguments {
my $arg_ref = shift;
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_DEFAULT, _t('Enter text') ],
LABEL_INFO => [ ARG_DEFAULT, _t('Input text') ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ],
ONLY => [ ARG_DEFAULT, 'All', 'Digits' ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $input_value = cgiapp_param($$arg_ref{ID});
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => $input_value }) if !is_empty($input_value);
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<div class="form-div form-control-', $ID_DEFAULT, '">
<input type="input" class="form-control input-lg" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '"', !is_empty($input_value) ? strval(' value = "', $input_value, '"') : '', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
</div>
</div>
');
}
sub Control__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
return is_eq($$arg_ref{ONLY}, 'Digits') ? strval('
$(function(){
$.fn.inputFilter = function(inputFilter) {
return this.on("input keydown keyup mousedown mouseup select contextmenu drop", function() {
if (inputFilter(this.value)) {
this.oldValue = this.value;
this.oldSelectionStart = this.selectionStart;
this.oldSelectionEnd = this.selectionEnd;
} else if (this.hasOwnProperty("oldValue")) {
this.value = this.oldValue;
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
}
});
};
$("#', $$arg_ref{ID}, '").inputFilter(function(value) {
return /^\d*$/.test(value);
});
});
') : '';
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
return cgiapp_param($id);
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,177 @@
=head1 NAME
Dam::Components::Controls::Month
=head1 SYNOPSIS
my ($year, $month) = Component__Get(CONTROL_MONTH, ['month']);
=head1 DESCRIPTION
Control para seleccionar un mes del año.
=head1 ARGUMENTS
(
TYPE => 'Month',
ID => 'month' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Month' (default),
LABEL_INFO => Same as LABEL (default),
REQUIRED => 1 (control required; default) or 0 (not required)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Month;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Date::Calc qw(Today);
use Dam::Util;
use Dam::DamLogic;
use Dam::Var;
my $ID_DEFAULT = 'month';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = _t('Month') if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $value = cgiapp_param($$arg_ref{ID});
my @today = Today();
my ($month_name, $month_value, $year_value) = ('', $today[1], $today[0]);
if (!is_report() || is_empty($value)) {
if ($month_value == 1) {
$month_value = 12;
$year_value--;
}
else {
$month_value--;
}
$month_name = _t('MONTHS', $month_value);
}
else {
($month_name, $year_value) = split(/ /, $value);
$month_value = _t('MONTHS', $month_name);
}
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => "$month_name $year_value" }) if $$arg_ref{INFO};
# Required stylesheets:
Component__Header(ADD => 'CSS', RESOURCE => PACK_DATEPICKER);
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<input type="text" class="form-control input-lg input-date-month" data-provide="datepicker" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '" value="', $month_name, ' ', $year_value, '" autocomplete="off" size=4', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
</div>
');
}
sub Control__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
my @today = Today();
# Required javascripts:
Component__Header(ADD => 'JS', RESOURCE => PACK_DATEPICKER);
return strval('
$(function(){
$("#', $$arg_ref{ID}, '").datepicker({
language: "', _t('LANGUAGE_CODE'), '",
autoclose: true,
todayHighlight: true,
disableTouchKeyboard: true,
minViewMode: "months",
format: "MM yyyy",
endDate: "', $today[1] - 1, '-', $today[0], '"
});
});
');
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
my $value = cgiapp_param($id);
return (undef, undef) if is_empty($value);
my ($month_name, $year_value) = split(/ /, $value);
my $month_value = _t('MONTHS', $month_name);
return ($year_value, $month_value);
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,161 @@
=head1 NAME
Dam::Components::Controls::MultiCheck
=head1 SYNOPSIS
my ($check_1, $check_2, ...) = Component__Get(CONTROL_MULTICHECK, ['multicheck']);
=head1 DESCRIPTION
Control para seleccionar una o más opciones de una lista.
=head1 ARGUMENTS
(
TYPE => 'MultiCheck',
ID => 'multicheck' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'MultiCheck' (default),
LABEL_INFO => Same as LABEL (default),
REQUIRED => 1 (control required; default) or 0 (not required)
MULTIPLE => 1 (allows to select any number of options; default) or 0 (allows
to select only one option),
OPTIONS => { 'op1' => 'Option 1', 'op2' => 'Option 2' } (default); you can
use { ..., 'opN' => _DIVIDER_, ... } to include separators
between the options according to order,
DEFAULT => Default option(s), e.g. "'op1'" or "'op1','op3'"
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::MultiCheck;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'multicheck';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = 'MultiCheck' if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ],
MULTIPLE => [ ARG_DEFAULT, 1, 0 ],
OPTIONS => [ ARG_DEFAULT, { 'op1' => strval(_t('Option'), ' 1'), 'op2' => strval(_t('Option'), ' 2') } ],
DEFAULT => [ ARG_OPTIONAL ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $multicheck_value = !is_report() && !is_empty($$arg_ref{DEFAULT}) ? $$arg_ref{DEFAULT} : strval_join(',', cgiapp_param($$arg_ref{ID}));
my $form_group = 'form-group';
my $list_opts = '';
my $info_opts = '';
my $count_opts = 0;
for my $key (sort keys(%{$$arg_ref{OPTIONS}})) {
if (is_eq(${$$arg_ref{OPTIONS}}{$key}, _DIVIDER_)) {
$list_opts .= '<option data-divider="true"></option>';
}
else {
my $checked = defined($multicheck_value) && index($multicheck_value, $key) >= 0;
my $selected = $checked ? '" selected="selected">' : '">';
$list_opts .= strval('<option value="\'', $key, '\'" title="', ${$$arg_ref{OPTIONS}}{$key}, $selected, ${$$arg_ref{OPTIONS}}{$key}, '</option>');
$form_group .= ' form-group-smaller' if $count_opts++ == 14;
$info_opts .= ' ' if $checked && !is_empty($info_opts);
$info_opts .= strval(${$$arg_ref{OPTIONS}}{$key}, ',') if $checked;
}
}
chop($info_opts) if !is_empty($info_opts);
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => $info_opts }) if $$arg_ref{INFO} && length($info_opts);
# Required stylesheets:
Component__Header(ADD => 'CSS', RESOURCE => PACK_SELECT);
return strval('
<div class="', $form_group, ' form-group-', $ID_DEFAULT, ' form-selectpicker">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<select class="form-control input-lg selectpicker" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '" multiple="multiple"', !$$arg_ref{MULTIPLE} ? ' data-max-options="1"' : '', ' data-selected-text-format="count > 2"', $$arg_ref{REQUIRED} ? ' required="required">' : '>', $list_opts, '</select>
</div>
');
}
sub Control__js {
# Required javascripts:
Component__Header(ADD => 'JS', RESOURCE => PACK_SELECT);
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
return strval_join(',', cgiapp_param($id));
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,138 @@
=head1 NAME
Dam::Components::Controls::Option
=head1 SYNOPSIS
my $option = Component__Get(CONTROL_OPTION, ['option']);
=head1 DESCRIPTION
Control para seleccionar una opción de una lista sencilla.
=head1 ARGUMENTS
(
TYPE => 'Option',
ID => 'opt' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Options' (default),
LABEL_INFO => Same as LABEL (default),
REQUIRED => 1 (control required; default) or 0 (not required)
OPTIONS => { 'op1' => 'Option 1', 'op2' => 'Option 2' } (default)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Option;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'option';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = _t('Options') if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ],
OPTIONS => [ ARG_DEFAULT, { 'op1' => strval(_t('Option'), ' 1'), 'op2' => strval(_t('Option'), ' 2') } ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $opt_value = cgiapp_param($$arg_ref{ID});
my $list_opts = $$arg_ref{REQUIRED} ? '' : strval('<option value="" title=""></option>');
for my $key (sort keys(%{$$arg_ref{OPTIONS}})) {
my $selected = '">';
if (is_eq($opt_value, $key)) {
$selected = '" selected="selected">';
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => ${$$arg_ref{OPTIONS}}{$key} }) if $$arg_ref{INFO};
}
$list_opts .= strval('<option value="', $key, '" title="', ${$$arg_ref{OPTIONS}}{$key}, $selected, ${$$arg_ref{OPTIONS}}{$key}, '</option>');
}
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<select class="form-control input-lg" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '"', $$arg_ref{REQUIRED} ? ' required="required">' : '>', $list_opts, '</select>
</div>
');
}
sub Control__js {
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
return cgiapp_param($id);
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,184 @@
=head1 NAME
Dam::Components::Controls::Upload
=head1 SYNOPSIS
my ($filehandle, $filename, $filetype) = Component__Get(CONTROL_UPLOAD, ['upload']);
=head1 DESCRIPTION
Control para subir un archivo al servidor.
=head1 ARGUMENTS
(
TYPE => 'Upload',
ID => 'upload' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Upload file' (default),
LABEL_INFO => 'File' (default),
REQUIRED => 1 (control required; default) or 0 (not required)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Upload;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use File::Copy qw(copy);
use Dam::Util;
use Dam::Debug;
use Dam::DamLogic;
use Dam::Var;
my $ID_DEFAULT = 'upload';
sub __arguments {
my $arg_ref = shift;
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_DEFAULT, _t('Upload file') ],
LABEL_INFO => [ ARG_DEFAULT, _t('File') ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
# Previous file info:
my $filename = cgiapp_param(strval($$arg_ref{ID}, '_name')) || '';
my $filesafe = cgiapp_param(strval($$arg_ref{ID}, '_safe')) || '';
my $filetype = cgiapp_param(strval($$arg_ref{ID}, '_type')) || '';
if (!is_empty($filename)) {
report_info(_t('If no other file is selected, then <--file--> previously uploaded will be used.', file => strval('<strong>', $filename, '</strong>')));
}
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => $filename }) if $$arg_ref{INFO} && !is_empty($filename);
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<div class="form-div form-control-', $ID_DEFAULT, '">
<input type="file" class="form-control input-lg" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '"', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
<input type="hidden" id="', $$arg_ref{ID}, '_name" name="', $$arg_ref{ID}, '_name" value="', $filename, '" />
<input type="hidden" id="', $$arg_ref{ID}, '_safe" name="', $$arg_ref{ID}, '_safe" value="', $filesafe, '" />
<input type="hidden" id="', $$arg_ref{ID}, '_type" name="', $$arg_ref{ID}, '_type" value="', $filetype, '" />
</div>
</div>
');
}
sub Control__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
return strval('
$(function(){
if ($("#', $$arg_ref{ID}, '_name").val()) {
$("#', $$arg_ref{ID}, '").prop("required", false);
}
});
');
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
# File handler:
my $filehandle = cgiapp_upload($id);
# File name:
my $filename = cgiapp_param($id);
my $filesafe = $filename;
# File type:
my $filetype = !is_empty($filename) ? cgiapp_uploadInfo($filename)->{'Content-Type'} : undef;
if ($filehandle) {
my $safe_characters = "a-zA-Z0-9_.-";
$filesafe =~ tr/ /_/;
$filesafe =~ s/[^$safe_characters]//g;
if ($filesafe =~ /^([$safe_characters]+)$/) {
$filesafe = strval('file-', time(), '_', $1);
if (copy($filehandle, strval(CONFIG('DIR_UPLOADS'), '/', $filesafe)) && open($filehandle, '<', strval(CONFIG('DIR_UPLOADS'), '/', $filesafe))) {
cgiapp_param(strval($id, '_name'), $filename);
cgiapp_param(strval($id, '_safe'), $filesafe);
cgiapp_param(strval($id, '_type'), $filetype);
return ($filehandle, $filename, $filetype);
}
}
}
else {
$filename = cgiapp_param(strval($id, '_name'));
$filesafe = cgiapp_param(strval($id, '_safe'));
$filetype = cgiapp_param(strval($id, '_type'));
if (!is_empty($filesafe) && open($filehandle, '<', strval(CONFIG('DIR_UPLOADS'), '/', $filesafe))) {
return ($filehandle, $filename, $filetype);
}
}
return (undef, undef, undef);
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,160 @@
=head1 NAME
Dam::Components::Controls::Year
=head1 SYNOPSIS
my $year = Component__Get(CONTROL_YEAR, ['year']);
=head1 DESCRIPTION
Control para seleccionar un año.
=head1 ARGUMENTS
(
TYPE => 'Year',
ID => 'year' (default),
INFO => 1 (show info in header; default) or 0 (don't show),
LABEL => 'Year' (default),
LABEL_INFO => Same as LABEL (default),
REQUIRED => 1 (control required; default) or 0 (not required)
)
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Controls::Year;
use Exporter qw(import);
our @EXPORT = qw(
Control__html Control__js Get
);
use Date::Calc qw(Today);
use Dam::Util;
use Dam::DamLogic;
my $ID_DEFAULT = 'year';
sub __arguments {
my $arg_ref = shift;
$$arg_ref{LABEL} = _t('Year') if is_empty($$arg_ref{LABEL});
$$arg_ref{LABEL_INFO} = $$arg_ref{LABEL} if is_empty($$arg_ref{LABEL_INFO});
check_arguments($arg_ref,
TYPE => [ ARG_REQUIRED ],
ID => [ ARG_DEFAULT, $ID_DEFAULT ],
INFO => [ ARG_DEFAULT, 1, 0 ],
LABEL => [ ARG_REQUIRED ],
LABEL_INFO => [ ARG_REQUIRED ],
REQUIRED => [ ARG_DEFAULT, 1, 0 ]
);
}
sub Control__html {
my ($self, $arg_ref, $info_ref) = @_;
__arguments($arg_ref);
my $year_value = cgiapp_param($$arg_ref{ID});
if (!is_report()) {
my @today = Today();
$year_value = $today[0];
}
push(@{$info_ref}, { DATA => $$arg_ref{LABEL_INFO}, VALUE => $year_value }) if $$arg_ref{INFO};
# Required stylesheets:
Component__Header(ADD => 'CSS', RESOURCE => PACK_DATEPICKER);
return strval('
<div class="form-group form-group-', $ID_DEFAULT, '">
<label for="', $$arg_ref{ID}, $$arg_ref{REQUIRED} ? '" class="required">' : '">', $$arg_ref{LABEL}, '</label>
<input type="text" class="form-control input-lg input-date-year" data-provide="datepicker" id="', $$arg_ref{ID}, '" name="', $$arg_ref{ID}, '" value="', $year_value, '" autocomplete="off" size=4', $$arg_ref{REQUIRED} ? ' required="required"' : '', ' />
</div>
');
}
sub Control__js {
my ($self, $arg_ref) = @_;
__arguments($arg_ref);
my @today = Today();
# Required javascripts:
Component__Header(ADD => 'JS', RESOURCE => PACK_DATEPICKER);
return strval('
$(function(){
$("#', $$arg_ref{ID}, '").datepicker({
language: "', _t('LANGUAGE_CODE'), '",
autoclose: true,
todayHighlight: true,
disableTouchKeyboard: true,
minViewMode: "years",
format: "yyyy",
endDate: "', $today[0], '"
});
});
');
}
sub Get {
my ($self, $id) = @_;
$id = $ID_DEFAULT if is_empty($id);
my $value = cgiapp_param($id);
return !is_empty($value) ? $value : undef;
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

View file

@ -0,0 +1,46 @@
<div class="jumbotron hemotron">
<div style="float: right;"><TMPL_VAR VERSION></div>
<h1><TMPL_VAR APP_NAME></h1>
<p><TMPL_VAR APP_SLOGAN></p>
<TMPL_IF GLOBAL_WARNING>
<br /><div class="alert alert-warning" role="alert"><TMPL_VAR GLOBAL_WARNING></div>
</TMPL_IF>
<TMPL_IF CHANGELOG_LAST>
<br />
<div class="panel panel-default">
<!-- Nav tabs -->
<ul id="changelog" class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab"><TMPL_VAR T_VERSION_NEWS></a></li>
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab"><TMPL_VAR T_VERSION_PREV></a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade in active" id="home">
<div class="panel-body">
<ul class="changelog-list"><TMPL_LOOP CHANGELOG_LAST>
<TMPL_VAR ITEM></TMPL_LOOP>
</ul>
</div>
</div>
<TMPL_IF CHANGELOG_PREV>
<div role="tabpanel" class="tab-pane fade" id="profile">
<div class="panel-body">
<ul class="changelog-list"><TMPL_LOOP CHANGELOG_PREV>
<TMPL_VAR ITEM></TMPL_LOOP>
</ul>
</div>
</div>
</TMPL_IF>
</div>
<script type="text/javascript">
$('#changelog a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
</script>
</div>
</TMPL_IF>
</div>

View file

@ -0,0 +1,14 @@
<TMPL_IF DEBUG>
<div class="panel panel-default hidden-print">
<div class="panel-heading">
<h4 class="panel-title">
<strong><span class="glyphicon glyphicon-wrench"></span> <a data-toggle="collapse" data-target="#debug-info" onClick="return false;" style="cursor: pointer;">DEBUG</a></strong>
</h4>
</div>
<div id="debug-info" class="panel-collapse collapse">
<div class="panel-body">
<TMPL_VAR DEBUG>
</div>
</div>
</div>
</TMPL_IF>

View file

@ -0,0 +1,5 @@
<div class="jumbotron hemotron">
<h1><TMPL_VAR APP_NAME></h1>
<h2><span class="glyphicon glyphicon-eye-close"></span> <TMPL_VAR T_ATTENTION> <TMPL_VAR T_UNAUTHORIZED_ACCESS> </h2>
<p><TMPL_VAR T_REPORT_WITHOUT_ACCESS> <TMPL_VAR T_CONTACT_ADMINISTRATOR></p>
</div>

View file

@ -0,0 +1,5 @@
<div class="jumbotron hemotron">
<h1><TMPL_VAR APP_NAME></h1>
<h2><span class="glyphicon glyphicon-eye-close"></span> <TMPL_VAR T_ATTENTION> <TMPL_VAR T_UNEXPECTED_ERROR></h2>
<p><TMPL_VAR T_ERROR_DURING_EXECUTION> <TMPL_VAR T_CONTACT_ADMINISTRATOR></p>
</div>

View file

@ -0,0 +1,72 @@
<input type="hidden" name="xt" id="xt" value="<TMPL_VAR FIRSTTIME>" />
<input type="hidden" name="rm" id="rm" value="<TMPL_VAR RUN_MODE>" />
<input type="hidden" name="dm" id="dm" value="" />
<input type="hidden" name="nv" id="nv" value="" />
<div class="panel panel-info panel-filter hidden-print">
<div class="panel-heading"><TMPL_VAR FILTER_TITLE></div>
<div class="panel-body">
<div class="modal fade" tabindex="-1" id="filter-error">
<div class="modal-dialog"><div class="modal-content"><div class="modal-body">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h3><strong><TMPL_VAR T_WATCH_OUT></strong></h3><h4 id="filter-message"></h4>
<p class="text-right"><button type="button" class="btn btn-default" data-dismiss="modal"><TMPL_VAR T_CLOSE></button></p>
</div></div></div>
</div>
<TMPL_IF DESCRIPTION><p class="description"><TMPL_VAR DESCRIPTION></p></TMPL_IF>
<TMPL_LOOP FILTER_CONTROLS><TMPL_VAR CONTROL></TMPL_LOOP>
<div class="form-group filter-buttons"><TMPL_LOOP FILTER_ACTIONS><TMPL_VAR ACTION></TMPL_LOOP>
</div>
<TMPL_IF REPORT_ERROR>
<div class="alert alert-danger hidden-print" role="alert"><TMPL_VAR REPORT_ERROR></div>
</TMPL_IF>
<TMPL_IF REPORT_WARNING>
<div class="alert alert-warning hidden-print" role="alert"><TMPL_VAR REPORT_WARNING></div>
</TMPL_IF>
<TMPL_IF REPORT_INFO>
<div class="alert alert-info hidden-print" role="alert"><TMPL_VAR REPORT_INFO></div>
</TMPL_IF>
</div>
</div>
<script type="text/javascript">
$(function(){
$("#filter").validate();
});
<TMPL_LOOP FILTER_JS><TMPL_VAR JAVASCRIPT></TMPL_LOOP>
</script>
<div class="panel panel-default panel-informa visible-print-block">
<div class="panel-heading"><TMPL_VAR APP_NAME></div>
<table class="table table-bordered table-condensed">
<tbody>
<tr>
<td style="width: 20%;"> <TMPL_VAR T_REPORT>: </td>
<td> <TMPL_VAR REPORT> </td>
</tr><TMPL_IF DESCRIPTION><tr>
<td> <TMPL_VAR T_DESCRIPTION>: </td>
<td> <TMPL_VAR DESCRIPTION> </td>
</tr></TMPL_IF><tr>
<td> <TMPL_VAR T_EDITION_DATE>: </td>
<td> <TMPL_VAR TODAY> </td>
</tr><tr>
<td> <TMPL_VAR T_REQUESTED_BY>: </td>
<td> <TMPL_VAR USER> </td>
<TMPL_LOOP FILTER_OPTIONS>
</tr><tr>
<td> <TMPL_VAR DATA>: </td>
<td> <TMPL_VAR VALUE> </td>
</TMPL_LOOP>
</tr>
</tbody>
</table>
</div>

View file

@ -0,0 +1,39 @@
<TMPL_IF DEBUG_MODE>
<div class="container hidden-print">
<div class="alert alert-info" role="alert" style="text-align: center;">
<strong><TMPL_VAR T_ATTENTION> <TMPL_VAR T_WARNING_MODE></strong>
</div>
</div>
</TMPL_IF>
<a href="#" class="scrollup">Arriba</a>
<footer>
<div class="container">
<p class="copyright"> <TMPL_VAR FOOTER_COPYRIGHT> </p>
<p class="today"> <TMPL_VAR TODAY> </p>
</div>
</footer>
<TMPL_IF CHECK_BROWSER>
<div id="outdated">
<h6><TMPL_VAR T_OLD_BROWSER></h6>
<p><TMPL_VAR T_UPDATE_BROWSER> <a id="btnUpdateBrowser" href="http://outdatedbrowser.com/es"><TMPL_VAR T_UPDATE_NOW></a>.</p>
<p class="last"><a href="#" id="btnCloseUpdateBrowser" title="<TMPL_VAR T_CLOSE>">&times;</a></p>
</div>
<script type="text/javascript">
<!--
outdatedBrowser({
bgColor: '#f25648',
color: '#ffffff',
lowerThan: 'boxShadow',
languagePath: ''
});
// -->
</script>
</TMPL_IF>
</body>
</html>

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="<TMPL_VAR LANGUAGE_CODE>" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="shortcut icon" href="<TMPL_VAR ROOT_WWW>/favicon.ico" type="image/x-icon" />
<title><TMPL_VAR TITLE></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<TMPL_LOOP STYLESHEETS>
<link rel="stylesheet" href="<TMPL_VAR ROOT_WWW><TMPL_VAR RESOURCE><TMPL_IF VERSION>?v=<TMPL_VAR VERSION></TMPL_IF>" type="text/css" /><TMPL_IF DEBUG_MODE> <!-- Priority <TMPL_VAR PRIORITY> --></TMPL_IF></TMPL_LOOP>
<TMPL_LOOP JAVASCRIPTS>
<script src="<TMPL_VAR ROOT_WWW><TMPL_VAR RESOURCE><TMPL_IF VERSION>?v=<TMPL_VAR VERSION></TMPL_IF>"<TMPL_IF CHARSET> charset="<TMPL_VAR CHARSET>"</TMPL_IF>></script><TMPL_IF DEBUG_MODE> <!-- Priority <TMPL_VAR PRIORITY> --></TMPL_IF></TMPL_LOOP>
<TMPL_IF CHECK_BROWSER>
<link rel="stylesheet" href="<TMPL_VAR ROOT_WWW>/dam/css/outdatedbrowser.min.css?v=1.1.5" type="text/css" />
<script src="<TMPL_VAR ROOT_WWW>/dam/js/outdatedbrowser.min.js?v=1.1.5"></script>
</TMPL_IF>
<!--[if lt IE 9]>
<link rel="stylesheet" media="all" href="<TMPL_VAR ROOT_WWW>/dam/css/ie8.css" type="text/css" />
<script src="<TMPL_VAR ROOT_WWW>/dam/js/respond.min.js"></script>
<script src="<TMPL_VAR ROOT_WWW>/dam/js/html5shiv.min.js"></script>
<![endif]-->
</head>
<body<TMPL_IF BODY_CLASSES> class="<TMPL_VAR BODY_CLASSES>"</TMPL_IF>>
<div id="loading"></div>
<TMPL_IF GLOBAL_ERROR>
<div class="container fatal-error">
<div class="alert alert-danger" role="alert"><TMPL_VAR GLOBAL_ERROR></div>
</div>
</TMPL_IF>

View file

@ -0,0 +1,26 @@
<h1 class="login-heading"><TMPL_VAR APP_NAME></h1>
<form name="login" method="post" class="form-login" onsubmit="
document.login.pass.value=encrypt(document.login.hide.value,document.login.key.value);
document.login.hide.value=document.login.pass.value.substr(0,document.login.hide.value.length);
">
<input type="hidden" name="rm" value="APP_confirm" />
<input type="hidden" name="key" value="<TMPL_VAR KEY>" />
<input type="hidden" name="pass" value="" />
<label for="user" class="sr-only"> <TMPL_VAR T_USERNAME> </label>
<input type="text" class="form-control" name="user" placeholder="<TMPL_VAR T_USERNAME>" maxlength="20" value="<TMPL_VAR LOGIN>" required autofocus />
<label for="pass" class="sr-only"> <TMPL_VAR T_PASSWORD> </label>
<input type="password" class="form-control" name="hide" placeholder="<TMPL_VAR T_PASSWORD>" maxlength="20" required />
<button type="submit" class="btn btn-lg btn-primary btn-block"> <TMPL_VAR T_LOGIN> </button>
<TMPL_IF ERROR>
<br /><br /><div class="alert alert-danger" role="alert">
<h4> <TMPL_VAR ERROR_TITLE> </h4>
<p> <TMPL_VAR ERROR_MESSAGE> (Error <TMPL_VAR ERROR>). </p>
</div>
</TMPL_IF>
</form>
<TMPL_VAR CRYPT_TEA>

View file

@ -0,0 +1,23 @@
<TMPL_IF ROUTES>
<form id="naviga" method="post"><input type="hidden" name="rm" />
<nav class="navbar navbar-default navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only"><TMPL_VAR T_NAVIGATION></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="#" class="navbar-brand option" id="RUN_home" title="<TMPL_VAR APP_NAME>"><TMPL_VAR APP_MNEMO></a>
</div>
<TMPL_VAR ROUTES>
</div>
</nav>
</form>
</TMPL_IF>

View file

@ -0,0 +1,152 @@
=head1 NAME
Dam::Components::Translations::ES_es
=head1 DESCRIPTION
Traducciones a español.
=head1 FUNCTIONS
=cut
use strict;
use warnings;
use utf8;
package Dam::Components::Translations::ES_es;
use Exporter qw(import);
our @EXPORT = qw(
Get
);
my %T = (
LANGUAGE_CODE => 'es',
MONTHS => [ 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre' ],
'Username' => 'Nombre de usuario',
'Password' => 'Contraseña',
'Login' => 'Entrar',
'User not active!' => '¡Usuario no activo!',
'Consult with your systems manager to activate your user' => 'Consulte con el responsable de sistemas para activar su usuario',
'Access error!' => '¡Error de acceso!',
'Verify username and retype password' => 'Verifique el nombre de usuario y vuelva a teclear la contraseña',
'Close' => 'Cerrar',
'Close session' => 'Cerrar sesión',
'Navigation' => 'Navegación',
'alpha' => 'alfa',
'beta' => 'beta',
'Original access' => 'Acceso original',
'Assigned access' => 'Acceso asignado',
'ERROR!' => '¡ERROR!',
'ERRORS!' => '¡ERRORES!',
'WARNING!' => '¡ADVERTENCIA!',
'WARNINGS!' => '¡ADVERTENCIAS!',
'ATTENTION!' => '¡ATENCIÓN!',
'Unauthorized Access' => 'Acceso No Autorizado',
'You are trying to run a report without sufficient access privileges.' => 'Está intentando ejecutar un informe sin suficientes privilegios de acceso.',
'Unexpected Error' => 'Error Inesperado',
'An unexpected error occurred during execution.' => 'Se ha producido un error inesperado durante la ejecución.',
'Please contact the administrator to resolve it.' => 'Por favor, contacte con el administrador para resolverlo.',
'Report' => 'Informe',
'Description' => 'Descripción',
'Edition date' => 'Fecha de edición',
'Requested by' => 'Solicitado por',
'Reports in <--alpha--> status are under development and may show errors or not give the expected results.' => 'Los informes en estado <--alpha--> están en desarrollo y pueden mostrar errores o no dar los resultados esperados.',
'Reports in <--beta--> status are in validation process.' => 'Los informes en estado <--beta--> están en proceso de validación.',
'WATCH OUT!' => '¡CUIDADO!',
'NO DATA!' => '¡SIN DATOS!',
'There is no data to apply the selection form filter.' => 'No hay registros que cumplan los criterios del filtro de selección.',
'Check the filter conditions.' => 'Compruebe las condiciones del filtro.',
'Filter fields marked with <--required--> are required.' => 'Los campos del filtro marcados con <--required--> son obligatorios.',
'This browser is out of date' => 'Este navegador es antiguo',
'You must update to use <--app--> correctly.' => 'Hay que actualizar para usar <--app--> correctamente.',
'Update my browser now' => 'Actualizar mi navegador ahora',
'You are running <--app--> in <--mode-->.' => 'Está ejecutando <--app--> en <--mode-->.',
'develop mode' => 'modo de desarrollo',
'testing mode' => 'modo de pruebas',
'About <--app-->' => 'Sobre <--app-->',
'What\'s new' => 'Novedades',
'Previous version' => 'Versión anterior',
# ACTIONS:
'Run' => 'Calcular',
'Print' => 'Imprimir',
'Download current report in CSV format' => 'Descarga el informe actual en formato CSV',
'Order by' => 'Ordenado por',
'Sort by' => 'Ordenar por',
'ascendant' => 'ascendente',
'descendent' => 'descendente',
# CONTROLS:
'Date' => 'Fecha',
'Date range' => 'Período',
'Month' => 'Mes',
'Year' => 'Año',
'YEAR' => 'AÑO',
'FIRST SEMESTER' => 'PRIMER SEMESTRE',
'SECOND SEMESTER' => 'SEGUNDO SEMESTRE',
'FIRST QUARTER' => 'PRIMER CUATRIMESTRE',
'SECOND QUARTER' => 'SEGUNDO CUATRIMESTRE',
'THIRD QUARTER' => 'TERCER CUATRIMESTRE',
'FIRST TRIMESTER' => 'PRIMER TRIMESTRE',
'SECOND TRIMESTER' => 'SEGUNDO TRIMESTRE',
'THIRD TRIMESTER' => 'TERCER TRIMESTRE',
'FOURTH TRIMESTER' => 'CUARTO TRIMESTRE',
'Date ranges greater than <--max--> are not allowed.' => 'No se admiten rangos de fechas superiores a <--max-->.',
'day(s)' => 'día(s)',
'year(s)' => 'año(s)',
'Option' => 'Opción',
'Options' => 'Opciones',
'Enter text' => 'Introducir texto',
'Input text' => 'Texto de entrada',
'Upload file' => 'Subir archivo',
'File' => 'Archivo',
'If no other file is selected, then <--file--> previously uploaded will be used.' => 'Si no se selecciona otro archivo, entonces se usará el mismo <--file--> subido anteriormente.'
);
sub Get { return \%T; }
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

1107
Dam/DamLogic.pm Normal file

File diff suppressed because it is too large Load diff

652
Dam/Database.pm Normal file
View file

@ -0,0 +1,652 @@
=head1 NAME
Dam::Database
=head1 DESCRIPTION
API for database access.
=head1 FUNCTIONS
=cut
use strict;
use warnings;
use utf8;
package Dam::Database;
use Exporter qw(import);
our @EXPORT = qw(
UNKNOWN_COLUMN DUPLICATE_ENTRY SQL_SYNTAX_ERROR
database_connect database_disconnect database_last_err database_last_errstr
CREATE_TEMP_TABLE DROP_TEMP_TABLE DROP_TEMP_TABLES
QUERY UPDATE INSERT_INTO DELETE_FROM
SELECT COUNT SUM AVG UNION
FIELDS FROM JOINS WHERE GROUP_BY HAVING ORDER_BY LIMIT
CLOSED AND OR NOT
COMPARE COMPARE_STR COMPARE_DATE COMPARE_FIELDS
BETWEEN BETWEEN_STR BETWEEN_DATES
EXISTS IN_FIELD
);
use DBI;
use Dam::Util;
use Dam::Debug;
use constant {
UNKNOWN_COLUMN => 1054,
DUPLICATE_ENTRY => 1062,
SQL_SYNTAX_ERROR => 1064
};
my $DBH = undef; # Database handle
my @TEMP_TABLES = (); # Temporal tables created during execution
=head2 database_connect()
Open connection to the database.
=cut
sub database_connect {
my %arg = @_;
check_arguments(\%arg,
DB_DSN => [ ARG_REQUIRED ],
DB_USER => [ ARG_REQUIRED ],
DB_PASSWORD => [ ARG_REQUIRED ]
);
$DBH = DBI->connect($arg{DB_DSN}, $arg{DB_USER}, $arg{DB_PASSWORD}) if !is_eq($arg{DB_DSN}, 'DBI:mysql:database=dbname;host=hostname');
return $DBH;
}
=head2 database_disconnect()
Close the connection to the database.
=cut
sub database_disconnect {
$DBH->disconnect() if defined($DBH);
}
=head2 database_last_err()
Returns the exit code of the last database access statement executed.
=cut
sub database_last_err {
return defined($DBH->err) ? $DBH->err : 0;
}
=head2 database_last_errstr()
Returns the output text message of the last database access statement executed.
=cut
sub database_last_errstr {
return defined($DBH->errstr) ? $DBH->errstr : '';
}
=head2 CREATE_TEMP_TABLE($table, %SOURCE)
Create a temporary table in the database.
=head3 Arguments:
- B<$table> (required): Name of the new data table.
- B<%SOURCE> (required): Its structure. Can be:
- C<DEF> => a definition of the fields in the new table;
- C<LIKE> => the name of an existing table to copy its structure; or
- C<AS> => a SELECT statement from which to get fields and data.
=cut
sub CREATE_TEMP_TABLE {
my ($table, %SOURCE) = @_;
my $query = strval('
CREATE TEMPORARY TABLE ', $table,
!is_empty($SOURCE{DEF}) ? strval(' ( ', strval_join(', ', array($SOURCE{DEF})), ' )') : '',
!is_empty($SOURCE{LIKE}) ? strval(' LIKE ', $SOURCE{LIKE}) : '',
!is_empty($SOURCE{AS}) ? strval(' AS ( ', $SOURCE{AS}, ' )') : ''
);
$DBH->do($query);
debug_info('CREATE TABLE', '<samp style="color: #888;">', $query, '</samp>');
if ($DBH->err) {
debug_error($DBH->errstr);
}
else {
push(@TEMP_TABLES, $table);
}
}
=head2 DROP_TEMP_TABLE($table)
Drop a temporary table from the database.
=head3 Arguments:
- B<$table> (required): Table name.
=cut
sub DROP_TEMP_TABLE {
my $table = shift;
my $query = strval('
DROP TEMPORARY TABLE ', $table
);
$DBH->do($query);
debug_info('DROP TABLE', '<samp style="color: #888;">', $query, '</samp>');
if ($DBH->err) {
debug_error($DBH->errstr);
}
else {
my $index = index_in_array($table, \@TEMP_TABLES);
splice(@TEMP_TABLES, $index, 1) if $index != -1;
}
}
=head2 DROP_TEMP_TABLES()
Delete the temporary tables created up to the moment of execution.
=cut
sub DROP_TEMP_TABLES {
while (@TEMP_TABLES) {
my $query = strval('
DROP TEMPORARY TABLE ', pop(@TEMP_TABLES)
);
$DBH->do($query);
debug_info('DROP TABLE', '<samp style="color: #888;">', $query, '</samp>');
debug_error($DBH->errstr) if $DBH->err;
}
@TEMP_TABLES = ();
}
=head2 QUERY($query)
Execute a query on the database.
=head3 Arguments:
- B<$query> (required): SELECT statement to execute.
=cut
sub QUERY {
my $query = shift;
my $execute = $DBH->prepare($query);
$execute->execute();
my $rows = $execute->rows != -1 ? strval(' =&gt; <kbd>', $execute->rows, '</kbd> ', $execute->rows == 1 ? 'fila seleccionada.' : 'filas seleccionadas.') : '';
debug_info('QUERY', '<samp style="color: #888;">', $query, '</samp>', $rows);
debug_error($execute->errstr) if $execute->err;
return $execute;
}
=head2 UPDATE($table, %SET)
Run an update on the database.
=head3 Arguments:
- B<$table> (required): Data table to be updated.
- B<%SET>: Fields to update.
=cut
sub UPDATE {
my ($table, %SET) = @_;
my $query = strval('
UPDATE ', $table,
!is_empty($SET{SET}) ? strval(' SET ', strval_join(', ', array($SET{SET}))) : '',
WHERE(array($SET{WHERE}))
);
$DBH->do($query);
debug_info('UPDATE', '<samp style="color: #888;">', $query, '</samp>');
debug_error($DBH->errstr) if $DBH->err;
}
=head2 INSERT_INTO($table, %INTO)
Insert data into a table in the database and return the number of rows inserted
or -1 if any error occurred.
=head3 Arguments:
- B<$table> (required): Table in which the data will be inserted.
- B<%INTO>: Selection of data to insert.
=cut
sub INSERT_INTO {
my ($table, %INTO) = @_;
my $query = strval('
INSERT INTO ', $table,
!is_empty($INTO{FIELDS}) ? strval(' ( ', strval_join(', ', array($INTO{FIELDS})), ' )') : '',
!is_empty($INTO{VALUES}) ? strval(' VALUES ( ', strval_join(', ', array($INTO{VALUES})), ' )') : '',
is_eq(ref($INTO{SELECT}), 'HASH') ? SELECT(%{$INTO{SELECT}}) : strval(' ', $INTO{SELECT})
);
my $rows = $DBH->do($query);
$rows = -1 if $DBH->err;
$rows = 0 if is_eq($rows, '0E0');
my $inserted_rows = $rows != -1 ? strval(' =&gt; <kbd>', $rows, '</kbd> ', $rows == 1 ? 'fila insertada.' : 'filas insertadas.') : '';
debug_info('INSERT INTO', '<samp style="color: #888;">', $query, '</samp>', $inserted_rows);
debug_error($DBH->errstr) if $DBH->err;
return $rows;
}
=head2 DELETE_FROM($table, %WHERE)
Delete a set of records from the database.
=head3 Arguments:
- B<$table> (required): Table from which records will be deleted.
- B<%WHERE>: Conditions to delete.
=cut
sub DELETE_FROM {
my ($table, %WHERE) = @_;
my $query = strval('
DELETE FROM ', $table,
WHERE(array($WHERE{WHERE}))
);
$DBH->do($query);
debug_info('DELETE FROM', '<samp style="color: #888;">', $query, '</samp>');
debug_error($DBH->errstr) if $DBH->err;
}
=head2 SELECT(%SELECT)
Construct a valid SELECT statement with the input arguments.
=head3 Arguments:
- B<%SELECT>: SELECT statement elements.
=cut
sub SELECT {
my %SELECT = @_;
return strval('
SELECT ',
FIELDS(array($SELECT{FIELDS})),
FROM(array($SELECT{FROM})),
JOINS(array($SELECT{JOINS})),
WHERE(array($SELECT{WHERE})),
GROUP_BY(array($SELECT{GROUP_BY})),
HAVING($SELECT{HAVING}),
ORDER_BY(array($SELECT{ORDER_BY})),
LIMIT($SELECT{LIMIT})
);
}
=head2 COUNT(%SELECT)
Constructs and executes a valid SELECT COUNT(C<$SELECT{FIELDS}>) statement with
the input arguments and returns the number of records that apply.
=head3 Arguments:
- B<%SELECT>: SELECT statement elements.
=cut
sub COUNT {
my %SELECT = @_;
$SELECT{FIELDS} = strval('COUNT(', is_empty($SELECT{FIELDS}) ? '*' : $SELECT{FIELDS}, ')');
my $query = SELECT(%SELECT);
my $execute = $DBH->prepare($query);
$execute->execute();
my $count = numval(($execute->fetchrow_array())[0]);
debug_info('COUNT', '<samp style="color: #888;">', $query, '</samp> =&gt; Cuenta <kbd>', $count, '</kbd>');
debug_error($execute->errstr) if $execute->err;
return $count;
}
=head2 SUM(%SELECT)
Constructs and executes a valid SELECT SUM(C<$SELECT{FIELDS}>) statement with
the input arguments and returns the requested sum.
=head3 Arguments:
- B<%SELECT>: SELECT statement elements.
=cut
sub SUM {
my %SELECT = @_;
$SELECT{FIELDS} = !is_empty($SELECT{FIELDS}) ? strval('SUM(', $SELECT{FIELDS}, ')') : '';
my $query = SELECT(%SELECT);
my $execute = $DBH->prepare($query);
$execute->execute();
my $sum = numval(($execute->fetchrow_array())[0]);
debug_info('SUM', '<samp style="color: #888;">', $query, '</samp> =&gt; Suma <kbd>', $sum, '</kbd>');
debug_error($execute->errstr) if $execute->err;
return $sum;
}
=head2 AVG(%SELECT)
Constructs and executes a valid SELECT AVG(C<$SELECT{FIELDS}>) statement with
the input arguments and returns the requested average.
=head3 Arguments:
- B<%SELECT>: SELECT statement elements.
=cut
sub AVG {
my %SELECT = @_;
$SELECT{FIELDS} = !is_empty($SELECT{FIELDS}) ? strval('AVG(', $SELECT{FIELDS}, ')') : '';
my $query = SELECT(%SELECT);
my $execute = $DBH->prepare($query);
$execute->execute();
my $avg = numval(($execute->fetchrow_array())[0]);
debug_info('AVG', '<samp style="color: #888;">', $query, '</samp> =&gt; Media <kbd>', $avg, '</kbd>');
debug_error($execute->errstr) if $execute->err;
return $avg;
}
=head2 UNION(@SELECTS)
Concatenates a list of SELECT statements into a valid UNION sentence.
=head3 Arguments:
- B<@SELECTS>: SELECT statement list.
=cut
sub UNION {
return trim(strval_join(' UNION ', @_));
}
sub FIELDS {
my $fields = trim(strval_join(', ', @_));
return !is_empty($fields) ? strval(' ', $fields) : '';
}
sub FROM {
my $from = trim(strval_join(', ', @_));
return !is_empty($from) ? strval(' FROM ', $from) : '';
}
sub JOINS {
my $joins = trim(strval_join(' ', @_));
return !is_empty($joins) ? strval(' ', $joins) : '';
}
sub WHERE {
my $where = trim(AND(@_));
return !is_empty($where) ? strval(' WHERE ', $where) : '';
}
sub GROUP_BY {
my $group_by = trim(strval_join(', ', @_));
return !is_empty($group_by) ? strval(' GROUP BY ', $group_by) : '';
}
sub HAVING {
my $having = trim(shift);
return !is_empty($having) ? strval(' HAVING ', $having) : '';
}
sub ORDER_BY {
my $order_by = trim(strval_join(', ', @_));
return !is_empty($order_by) ? strval(' ORDER BY ', $order_by) : '';
}
sub LIMIT {
my $limit = trim(shift);
return !is_empty($limit) ? strval(' LIMIT ', $limit) : '';
}
sub CLOSED {
my $sentence = trim(shift);
return !is_empty($sentence) ? strval(' ( ', $sentence, ' )') : '';
}
sub AND {
my $conditions = trim(strval_join(' AND ', @_));
return !is_empty($conditions) ? strval(' ', $conditions) : '';
}
sub OR {
my $conditions = trim(strval_join(' OR ', @_));
return !is_empty($conditions) ? strval(' ', $conditions) : '';
}
sub NOT {
my $sentence = trim(shift);
return !is_empty($sentence) ? strval(' NOT ', $sentence) : '';
}
sub COMPARE {
my ($field, $op, $value) = @_;
$field = trim($field);
$op = trim(one_space(uc($op)));
return '' if is_empty($field) || is_empty($op);
$value = trim($value);
if (is_empty($value)) {
return is_eq($op, 'IS NULL') || is_eq($op, 'IS NOT NULL') ? strval(' ', $field, ' ', $op) : '';
}
return strval(' ', $field, ' IS NULL') if is_eq($op, '=') && is_eq(uc($value), 'NULL');
return strval(' ', $field, ' IS NOT NULL') if is_eq($op, '!=') && is_eq(uc($value), 'NULL');
return strval(' ', $field, ' ', $op, ' ', $value);
}
sub COMPARE_STR {
my ($field, $op, $string) = @_;
$field = trim($field);
$op = trim(one_space(uc($op)));
return '' if is_empty($field) || is_empty($op);
if (is_empty(trim($string))) {
return is_eq($op, 'IS NULL') || is_eq($op, 'IS NOT NULL') ? strval(' ', $field, ' ', $op) : '';
}
return strval(' ', $field, ' IS NULL') if is_eq($op, '=') && is_eq(uc($string), 'NULL');
return strval(' ', $field, ' IS NOT NULL') if is_eq($op, '!=') && is_eq(uc($string), 'NULL');
return strval(' ', $field, ' ', $op, " '", $string, "'");
}
sub COMPARE_DATE {
return COMPARE_STR(@_);
}
sub COMPARE_FIELDS {
return COMPARE(@_);
}
sub BETWEEN {
my ($field, $ini, $end) = @_;
my $c1 = COMPARE($field, '>=', $ini);
my $c2 = COMPARE($field, '<=', $end);
return is_empty($c1) || is_empty($c2) ? strval($c1, $c2) : strval(' (', $c1, ' AND', $c2, ' )');
}
sub BETWEEN_STR {
my ($field, $ini, $end) = @_;
my $c1 = COMPARE_STR($field, '>=', $ini);
my $c2 = COMPARE_STR($field, '<=', $end);
return is_empty($c1) || is_empty($c2) ? strval($c1, $c2) : strval(' (', $c1, ' AND', $c2, ' )');
}
sub BETWEEN_DATES {
return BETWEEN_STR(@_);
}
sub EXISTS {
my $sentence = shift;
return !is_empty($sentence) ? strval(' EXISTS ( ', $sentence, ' )') : '';
}
sub IN_FIELD {
my ($field, $values) = @_;
$field = trim($field);
$values = trim($values);
return '' if is_empty($field) || is_empty($values);
my $isnot = is_eq(substr($values, 0, 1), '!');
$values = substr($values, 1) if $isnot;
my @values = split(m/('[^']+'|"[^"]+"|[^,]+)(?:\s*,\s*)?/, $values);
my $isnull = '';
my @nulls = ("'NULL'", '"NULL"', 'NULL');
my @infield = ();
foreach my $value (@values) {
if (!is_empty($value)) {
if (in_array($value, \@nulls)) {
$isnull = $isnot ? "$field IS NOT NULL" : "$field IS NULL";
}
else {
push(@infield, $value);
}
}
}
return $isnull if scalar(@infield) == 0;
my $infield = strval($field, $isnot ? ' NOT IN ( ' : ' IN ( ', strval_join(', ', @infield), ' )');
return is_empty($isnull) ? $infield : strval("( $infield ", $isnot ? 'AND' : 'OR', " $isnull )");
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

189
Dam/Debug.pm Normal file
View file

@ -0,0 +1,189 @@
=head1 NAME
Dam::Debug
=head1 DESCRIPTION
API for handling error, warning, information and debug messages.
=head1 FUNCTIONS
=cut
use strict;
use warnings;
use utf8;
package Dam::Debug;
use Exporter qw(import);
our @EXPORT = qw(
report_error report_warning report_info
debug_error debug_info debug
);
use Date::Calc qw(Now);
use Dam::Util;
use Dam::Var;
=head2 report_error(@message)
Push an error message to display in report execution.
=head3 Arguments:
- B<@message> (must): Error message formed by one or more strings.
=cut
sub report_error {
my $error = strval(@_);
my $REPORT_ERROR = RESERVED('REF_REPORT_ERROR');
push(@$REPORT_ERROR, $error) if !is_empty($error);
}
=head2 report_warning(@message)
Push a warning message to display in report execution.
=head3 Arguments:
- B<@message> (must): Warning message formed by one or more strings.
=cut
sub report_warning {
my $warning = strval(@_);
my $REPORT_WARNING = RESERVED('REF_REPORT_WARNING');
push(@$REPORT_WARNING, $warning) if !is_empty($warning);
}
=head2 report_info(@message)
Push an information message to display in report execution.
=head3 Arguments:
- B<@message> (must): Information message formed by one or more strings.
=cut
sub report_info {
my $info = strval(@_);
my $REPORT_INFO = RESERVED('REF_REPORT_INFO');
push(@$REPORT_INFO, $info) if !is_empty($info);
}
=head2 debug_error(@message)
Prepares a message with current time to display (according to
B<CONFIG('DEBUG_MODE')>) with all code debug messages sorted at the beginning of
the current report.
=head3 Arguments:
- B<@message>: Error message formed by one or more strings.
=cut
sub debug_error {
my $DEBUG_INFO = RESERVED('REF_DEBUG_INFO');
push(@$DEBUG_INFO, strval('[', sprintf("%02d:%02d:%02d", Now()), '] <strong style="color: red;">ERROR!</strong> <samp style="color: navy;">', @_, '</samp>')) if CONFIG('DEBUG_MODE');
}
=head2 debug_info($title, @message)
Prepare a message with current time, a short title and data of the function and
the call files, to show (according to B<CONFIG('DEBUG_MODE')>) with all code
debugging messages sorted at the beginning of the current report.
=head3 Arguments:
- B<$title> (must): Short title to show.
- B<@message>: Message formed by one or more strings.
=cut
sub debug_info {
my ($title, @message) = @_;
if (CONFIG('DEBUG_MODE')) {
my ($p0, $filename0, $line0) = caller(1);
$filename0 = substr($filename0, 3);
my ($p1, $filename1, $line1, $subroutine1) = caller(2);
$filename1 = substr($filename1, 3);
my $DEBUG_INFO = RESERVED('REF_DEBUG_INFO');
push(@$DEBUG_INFO, strval(
'[', sprintf("%02d:%02d:%02d", Now()), '] <strong>', $title, '</strong>: ',
'<code>', $filename0, '</code> línea ', $line0, ' (<code>', substr($subroutine1, 9), '</code>)',
index($filename1, 'CGI/Application.pm') == -1 ? strval(', desde <code>', $filename1, '</code> línea ', $line1) : '',
'.<br />', @message
));
}
}
=head2 debug($title, @message)
Prepare a message with current time and a short title to display (according to
B<CONFIG('DEBUG_MODE')>) with all code debugging messages sorted at the
beginning of the current report.
=head3 Arguments:
- B<$title> (must): Short title to show.
- B<@message>: Message formed by one or more strings.
=cut
sub debug {
my ($title, @message) = @_;
my $DEBUG_INFO = RESERVED('REF_DEBUG_INFO');
push(@$DEBUG_INFO, strval('[', sprintf("%02d:%02d:%02d", Now()), '] <strong>', $title, '</strong>: ', @message)) if CONFIG('DEBUG_MODE');
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

21
Dam/LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

728
Dam/Util.pm Normal file
View file

@ -0,0 +1,728 @@
=head1 NAME
Dam::Util
=head1 DESCRIPTION
Global functions for applications created using the Dam framework.
=head1 FUNCTIONS
=cut
use strict;
use warnings;
use utf8;
package Dam::Util;
use Exporter qw(import);
our @EXPORT = qw(
TRUE FALSE
ARG_OPTIONAL ARG_REQUIRED ARG_DEFAULT
_DIVIDER_
is_true is_empty is_eq is_num
numval pctval sumval
strval strval_trio strval_join trim
one_space escape_quotes
array in_array index_in_array occurrences_in_array match_arrays array_uniq
fatal warning info
check_arguments
format_num format_pct format_date_dmy get_today_ymd
csv_header csv_line
);
use Date::Calc qw(Today);
use Scalar::Util qw(looks_like_number);
use constant {
TRUE => 1,
FALSE => 0,
ARG_OPTIONAL => 0,
ARG_REQUIRED => 1,
ARG_DEFAULT => 2,
_DIVIDER_ => '_DIVIDER_'
};
=head2 is_true($var)
Returns C<TRUE> if C<$var> is not C<undef> and has a value other than C<0>, or
C<'0'> or the empty string C<''>. Or C<FALSE> otherwise.
=head3 Arguments:
- B<$var> (must): Variable to check.
=cut
sub is_true {
my $var = shift;
return defined($var) && $var ne '' && $var ne '0' ? TRUE : FALSE;
}
=head2 is_empty($var)
Returns C<TRUE> if C<$var> is C<undef> or the empty string C<''>. Or C<FALSE>
otherwise.
=head3 Arguments:
- B<$var> (must): Variable to check.
=cut
sub is_empty {
my $var = shift;
return !defined($var) || $var eq '' ? TRUE : FALSE;
}
=head2 is_eq($var, $str)
Returns C<TRUE> if C<$var> is not C<undef>, C<$str> is not C<undef> and they are
equals. Or C<FALSE> otherwise.
=head3 Arguments:
- B<$var> (must): Variable to check.
- B<$str> (must): Value to compare.
=cut
sub is_eq {
my ($var, $str) = @_;
return defined($var) && defined($str) && $var eq $str ? TRUE : FALSE;
}
=head2 is_num($num)
Returns C<TRUE> if C<$num> is a number, or C<FALSE> otherwise.
=head3 Arguments:
- B<$num> (must): Number to check.
=cut
sub is_num {
return looks_like_number(shift) ? TRUE : FALSE;
}
=head2 numval($num)
Returns C<$num> when C<is_num($num)>. Returns C<0> when C<is_empty($num)>. And
C<undef> otherwise.
=head3 Arguments:
- B<$num> (must): Number to check.
=cut
sub numval {
my $num = shift;
return $num if is_num($num);
return 0 if is_empty($num);
warning('Invalid number');
return undef;
}
=head2 pctval($numerator, $denominator, $decimals, $byzero)
Returns (C<$numerator> * 100) / C<$denominator> if C<is_num($numerator)>,
C<is_num($denominator)> and C<$denominator> is not C<0>. Otherwise it will
return C<undef>.
=head3 Arguments:
- B<$numerator> (must): The numerator of the percentage to calculate.
- B<$denominator> (must): The denominator of the percentage to calculate.
- B<$decimals> (optional): Maximum number of decimal places for the result.
- B<$byzero> (optional): If C<is_true($byzero)> it will send a warning message
when C<$denominator> is zero.
=cut
sub pctval {
my ($numerator, $denominator, $decimals, $byzero) = @_;
$numerator = 0 if is_empty($numerator);
if (is_num($numerator) && is_num($denominator)) {
warning('Invalid number of decimals') if !is_empty($decimals) && (!is_num($decimals) || $decimals < 0 || !($decimals - int($decimals)));
if ($denominator != 0) {
my $pctval = $numerator * 100 / $denominator;
return is_empty($decimals) ? $pctval : sprintf("%.${decimals}f", $pctval);
}
warning('Division by zero') if is_true($byzero);
return undef;
}
warning('Invalid numerator') if !is_num($numerator);
warning('Invalid denominator') if !is_num($denominator);
return undef;
}
=head2 sumval(@items)
Sum all the items in C<@items> considering each one as C<numval($item)>.
=head3 Arguments:
- B<@items> (must): List of items to add.
=cut
sub sumval {
my $sum = 0;
foreach my $item (@_) {
$sum += numval($item);
}
return $sum;
}
=head2 strval(@str)
Returns the concatenation of all strings in C<@str>, considering C<undef> as the
empty string C<''>.
=head3 Arguments:
- B<@str> (must): Array of strings to concatenate.
=cut
sub strval {
my $strval = '';
foreach my $str (@_) {
$strval .= defined($str) ? $str : '';
}
return $strval;
}
=head2 strval_trio($str1, $separator, $str2)
Returns the concatenation of the strings C<$str1>, C<$separator> and C<$str2> if
not C<is_empty($str1)> and not C<is_empty($str2)>. Otherwise it returns the
string C<$str1> or C<$str2> that is not empty, or the empty string C<''> if
both are.
=head3 Arguments:
- B<$str1> (optional): First string.
- B<$separator> (must): Separation string.
- B<$str2> (optional): Second string.
=cut
sub strval_trio {
my ($str1, $separator, $str2) = @_;
return strval($str1, $separator, $str2) if !is_empty($str1) && !is_empty($str2);
return strval($str1) if is_empty($str2);
return strval($str2);
}
=head2 strval_join($separator, @str)
Returns the concatenation of the (not empty) strings of C<@str> or the
referenced array of strings instead, using the string C<$separator> as the
separation between each one.
=head3 Arguments:
- B<$separator> (must): Separation string.
- B<@str> (must): Strings or reference to the array of strings to concatenate.
=cut
sub strval_join {
my ($separator, @str) = @_;
return '' if !@str;
@str = @{$str[0]} if scalar(@str) == 1 && ref($str[0]) eq 'ARRAY';
$separator = '' if is_empty($separator);
return join($separator, grep { !is_empty($_) } @str);
}
=head2 trim($str)
Returns a string whose leading and trailing spaces have been removed from
C<$str>. Or the empty string C<''> if C<is_empty($str)>.
=head3 Arguments:
- B<$str> (must): String to process.
=cut
sub trim {
my $str = shift;
return '' if is_empty($str);
$str =~ s/^\s+|\s+$//g;
return $str;
}
=head2 one_space($str)
Returns a string that converts the sequences of two or more consecutive spaces
of C<$str> into a single space. Or the empty string C<''> if
C<is_empty($str)>.
=head3 Arguments:
- B<$str> (must): String to process.
=cut
sub one_space {
my $str = shift;
return '' if is_empty($str);
$str =~ s/\s+/ /g;
return $str;
}
=head2 escape_quotes($str)
Returns the same string C<$str> by putting an escape character in front of each
escape character, single quote or double quote. Or the empty string C<''> if
C<is_empty($str)>.
=head3 Arguments:
- B<$str> (must): String to process.
=cut
sub escape_quotes {
my $str = shift;
return '' if is_empty($str);
$str =~ s/('|"|\\)/\\$1/g;
return $str;
}
=head2 array($var)
If C<$var> is a reference to an array then it returns the array. If it is a
variable then it returns an array with that element. It returns an empty array
otherwise.
=head3 Arguments:
- B<$var> (must): Variable (or reference to the array) to check.
=cut
sub array {
my $var = shift;
return () if !defined($var);
return ref($var) eq 'ARRAY' ? @{$var} : ( $var );
}
=head2 in_array($element, @array)
Returns C<TRUE> if C<$element> is in array C<@array> or in the referenced array
instead. Or C<FALSE> otherwise.
=head3 Arguments:
- B<$element> (must): Element to search.
- B<@array> (must): Array (or reference to the array) in which the element is
searched.
=cut
sub in_array {
my ($element, @array) = @_;
return FALSE if !defined($element) || !@array;
@array = @{$array[0]} if scalar(@array) == 1 && ref($array[0]) eq 'ARRAY';
my %hash_array = map { $_ => 1 } @array;
return defined($hash_array{$element}) ? TRUE : FALSE;
}
=head2 index_in_array($element, @array)
Returns the position where C<$element> is in array C<@array> or in the array
referenced instead, with C<0> being the first position in the array. Or it
returns C<-1> if there are no arguments or the element is not found.
=head3 Arguments:
- B<$element> (must): Element to search.
- B<@array> (must): Array (or reference to the array) in which the element is
searched.
=cut
sub index_in_array {
my ($element, @array) = @_;
return -1 if !defined($element) || !@array;
@array = @{$array[0]} if scalar(@array) == 1 && ref($array[0]) eq 'ARRAY';
my $index = 0;
foreach my $current (@array) {
return $index if $current eq $element;
$index++;
}
return -1;
}
=head2 occurrences_in_array($element, @array)
Returns the number of occurrences of C<$element> in array C<@array> or in the
referenced array instead. Or it returns C<-1> if there are no arguments.
=head3 Arguments:
- B<$element> (must): Element to search.
- B<@array> (must): Array (or reference to the array) in which the element is
searched.
=cut
sub occurrences_in_array {
my ($element, @array) = @_;
return -1 if !defined($element) || !@array;
@array = @{$array[0]} if scalar(@array) == 1 && ref($array[0]) eq 'ARRAY';
return grep { $_ eq $element } @array;
}
=head2 match_arrays($array_ref1, $array_ref2)
Returns C<TRUE> if arrays C<@$array_ref1> and C<@$array_ref2> have one or more
equal elements. Or C<FALSE> otherwise.
=head3 Arguments:
- B<$array_ref1> (required): Reference to the first array.
- B<$array_ref2> (required): Reference to the second array.
=cut
sub match_arrays {
my ($array_ref1, $array_ref2) = @_;
foreach my $match (@{$array_ref1}) {
return TRUE if in_array($match, $array_ref2);
}
return FALSE;
}
=head2 array_uniq(@array)
Returns a new array without duplicate elements of array C<@array>.
=head3 Arguments:
- B<@array> (required): Array to process.
=cut
sub array_uniq {
my %seen;
grep !is_empty($_) && !$seen{$_}++, @_;
}
=head2 fatal(@message)
Sends error message C<strval(@message)> to STDERR and abort program execution.
=head3 Arguments:
- B<@message> (optional): Error message consisting of one or more strings.
=cut
sub fatal {
my $message = strval(@_);
$message .= '. ' if !is_empty($message);
$message .= 'Fatal error';
my @call1 = caller(2);
my @call2 = caller(1);
die strval($message, ' at ', $call1[1], ' line ', $call1[2], '. See ', $call1[3], ' at ', $call2[1], ' line ', $call2[2], '. Info');
}
=head2 warning(@message)
Sends error message C<strval(@message)> to STDERR but does not abort program
execution.
=head3 Arguments:
- B<@message> (optional): Warning message consisting of one or more strings.
=cut
sub warning {
my $message = strval(@_);
$message .= '. ' if !is_empty($message);
$message .= 'Warning';
my @call1 = caller(2);
my @call2 = caller(1);
print STDERR strval($message, ' at ', $call1[1], ' line ', $call1[2], '. See ', $call1[3], ' at ', $call2[1], ' line ', $call2[2], "\n");
}
=head2 info(@message)
Sends information message C<strval(@message)> to STDERR.
=head3 Arguments:
- B<@message> (optional): Information message consisting of one or more
strings.
=cut
sub info {
print STDERR strval(@_, "\n");
}
sub check_arguments {
my ($arg_ref, %ARGUMENTS) = @_;
my @valid_args = keys(%ARGUMENTS);
foreach my $arg (keys(%$arg_ref)) {
fatal('Invalid "', $arg, '" argument') if !in_array($arg, \@valid_args);
}
foreach my $arg (@valid_args) {
my @values = array($ARGUMENTS{$arg});
if (@values) {
my $required = shift(@values);
my $ref_value_0 = ref($values[0]);
if ($required == ARG_DEFAULT) {
if (is_empty($$arg_ref{$arg}) && @values) {
if (is_empty($ref_value_0)) {
$$arg_ref{$arg} = $values[0];
}
elsif (is_eq($ref_value_0, 'ARRAY')) {
$$arg_ref{$arg} = (@{$values[0]});
}
elsif (is_eq($ref_value_0, 'HASH')) {
$$arg_ref{$arg} = {%{$values[0]}};
}
else {
$$arg_ref{$arg} = ${$values[0]};
}
}
fatal('Default value for "', $arg, '" is required') if !defined($$arg_ref{$arg});
push(@values, $$arg_ref{$arg}) if !in_array($$arg_ref{$arg}, \@values);
}
elsif ($required == ARG_REQUIRED) {
fatal('Value for "', $arg, '" is required') if !defined($$arg_ref{$arg});
}
elsif ($required != ARG_OPTIONAL) {
fatal('Type of argument not recognized');
}
fatal('Invalid "', $arg, '" value "', $$arg_ref{$arg}, '"') if @values && !is_empty($$arg_ref{$arg}) && !in_array($$arg_ref{$arg}, \@values);
}
}
}
sub format_num {
my ($number, %arg) = @_;
check_arguments(\%arg,
FORMAT => [ ARG_OPTIONAL ],
ZERO => [ ARG_DEFAULT, FALSE, TRUE ],
DECIMALS => [ ARG_DEFAULT, 0 ],
DEC_POINT => [ ARG_DEFAULT, ',' ],
THOUSANDS_SEP => [ ARG_DEFAULT, '.', 'none' ]
);
$number = trim($number);
return '' if is_empty($number);
return $number if in_array($number, '&infin;', '~');
return '' if !is_num($number);
$number = numval($number);
return '' if $number == 0 && !is_true($arg{ZERO});
return sprintf($arg{FORMAT}, $number) if !is_empty($arg{FORMAT});
$number = sprintf("%.$arg{DECIMALS}f", $number);
eval "\$number =~ tr/./$arg{DEC_POINT}/";
eval "\$number =~ s/(\\d)(?=(\\d{3})+(\\D|\$))/\$1\$arg{THOUSANDS_SEP}/g" if !is_eq($arg{THOUSANDS_SEP}, 'none');
return $number;
}
sub format_pct {
my ($number, %arg) = @_;
check_arguments(\%arg,
ZERO => [ ARG_DEFAULT, FALSE, TRUE ],
DECIMALS => [ ARG_DEFAULT, 2 ]
);
$number = format_num($number, %arg);
return is_empty($number) ? '' : strval($number, '%');
}
sub format_date_dmy {
my $date = shift;
return '' if is_empty($date);
my @date = split('-', $date);
return '' if is_empty($date[2]) || is_empty($date[1]) || is_empty($date[0]);
return sprintf("%02d/%02d/%04d", $date[2], $date[1], $date[0]);
}
=head2 get_today_ymd()
Devuelve la fecha actual en el formato AAAA-MM-DD.
=cut
sub get_today_ymd {
my ($y, $m, $d) = Today();
return ($y, sprintf("%02d", $m), sprintf("%02d", $d));
}
sub csv_header {
my %arg = @_;
check_arguments(\%arg,
SEPARATOR => [ ARG_DEFAULT, ';' ],
REPLACE => [ ARG_DEFAULT, ',' ],
HEADER => [ ARG_OPTIONAL ]
);
return __csv_line($arg{SEPARATOR}, $arg{REPLACE}, $arg{HEADER});
}
sub csv_line {
my %arg = @_;
check_arguments(\%arg,
SEPARATOR => [ ARG_DEFAULT, ';' ],
REPLACE => [ ARG_DEFAULT, ',' ],
DATA => [ ARG_OPTIONAL ]
);
return __csv_line($arg{SEPARATOR}, $arg{REPLACE}, $arg{DATA});
}
sub __csv_line {
my ($separator, $replace, $data) = @_;
my $line = '';
foreach my $field (@$data) {
if (!is_empty($field)) {
$field =~ s/$separator/$replace/g;
$line .= trim($field);
}
$line .= $separator;
}
chop($line);
return strval($line, "\r\n");
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

299
Dam/Var.pm Normal file
View file

@ -0,0 +1,299 @@
=head1 NAME
Dam::Var
=head1 DESCRIPTION
API for global variables.
=head1 FUNCTIONS
=cut
use strict;
use warnings;
use utf8;
package Dam::Var;
use Exporter qw(import);
our @EXPORT = qw(
UNDEF_VAR
DELETE_VAR
CONFIG
GLOBAL
RESERVED
);
use constant {
UNDEF_VAR => 'K9a(fz=D>vFy3m485jE]:Jm2B@3Ou6',
DELETE_VAR => '5s<N0U{R_hNgQn@CyKoD(]rUWO)mbW'
};
use Cwd;
use Dam::Util;
# CONFIGURATION VARIABLES:
# Today:
my ($y, $m, $d) = get_today_ymd();
# Version news:
my @LAST_CHANGELOG = ();
my @PREV_CHANGELOG = ();
# No access groups:
my @ACCESS_GROUPS = ();
# Menus:
my %ROUTES = ();
my %USER_MENU = (
ID => 'Admin',
TEXT => 'User',
OPTIONS => {
M_01 => { RUN => 'RUN_close', ICON => 'off', ACCESS => [ 1 ], TEXT => 'Close session' }
}
);
my %CONFIG_VARS = (
# DEBUG MODE (0 = PRODUCTION, 1 = DEVELOPMENT, 2 = TESTING):
DEBUG_MODE => 1,
# APPLICATION NAME AND SLOGAN:
APP_NAME => 'My Application',
APP_MNEMO => 'App',
APP_SLOGAN => '',
# APP FOLDERS & RESOURCE URL:
DIR_APP => getcwd(),
DIR_TEMPLATES => strval(getcwd(), '/templates'),
DIR_UPLOADS => strval(getcwd(), '/uploads'),
ROOT_WWW => '/',
# DATABASE ACCESS CONFIGURATION:
DB_DSN => 'DBI:mysql:database=dbname;host=hostname',
DB_USER => 'user',
DB_PASSWORD => 'password',
# LDAP DOMAIN/SERVER:
LDAP_DOMAIN => '',
# MESSAGES TRANSLATION:
l10n => 'EN_en',
# MAX SIZE FOR UPLOADED FILES (5MB):
UPLOAD_MAX_FILESIZE => 5 * 1024 * 1024,
# FOOTER COPYRIGHT:
FOOTER_COPYRIGHT => strval('&copy; ', $y, ' Made with Dam Framework'),
# VERSION VARIABLES:
VERSION => '0.01',
REF_LAST_CHANGELOG => \@LAST_CHANGELOG,
REF_PREV_CHANGELOG => \@PREV_CHANGELOG,
# ACCESS GROUPS FOR USERS REFERENCE:
REF_ACCESS_GROUPS => \@ACCESS_GROUPS,
# MENU REFERENCES:
REF_ROUTES => \%ROUTES,
REF_USER_MENU => \%USER_MENU
);
# GLOBAL VARIABLES:
my %GLOBAL_VARS = ();
# RESERVED VARIABLES:
# Error, warning, information and debug messages:
my @REPORT_ERROR = ();
my @REPORT_WARNING = ();
my @REPORT_INFO = ();
my @DEBUG_INFO = ();
my %RESERVED_VARS = (
# CGI APP:
CGIAPP => undef,
# ERROR, WARNING, INFORMATION AND DEBUG MESSAGES:
REF_REPORT_ERROR => \@REPORT_ERROR,
REF_REPORT_WARNING => \@REPORT_WARNING,
REF_REPORT_INFO => \@REPORT_INFO,
REF_DEBUG_INFO => \@DEBUG_INFO,
# CURRENT REPORT:
REF_CURRENT_PACKAGE => undef
);
=head2 CONFIG($variable, $value)
Mantiene una estructura global de variables ($variable => $value).
=cut
sub CONFIG {
push(@_, undef) if @_ % 2;
my %variables = @_;
my @variables = keys(%variables);
if (!@variables) {
foreach my $var (keys(%CONFIG_VARS)) {
info($var, ' => ', strval($CONFIG_VARS{$var}));
}
fatal('Global configuration variable name is required');
}
my $variable;
foreach my $var (@variables) {
fatal('Global configuration variable "', $var, '" doesn\'t exist') if !exists($CONFIG_VARS{$var});
if (defined($variables{$var})) {
fatal('Global configuration variables cannot be deleted (see "', $var, '")') if is_eq($variables{$var}, DELETE_VAR);
$CONFIG_VARS{$var} = $variables{$var};
}
$variable = $var;
}
return $CONFIG_VARS{$variable};
}
=head2 GLOBAL($variable, $value)
Mantiene una estructura global de variables ($variable => $value).
=cut
sub GLOBAL {
push(@_, undef) if @_ % 2;
my %variables = @_;
my @variables = keys(%variables);
if (!@variables) {
foreach my $var (keys(%GLOBAL_VARS)) {
info($var, ' => ', strval($GLOBAL_VARS{$var}));
}
fatal('Global variable name is required');
}
my $variable;
foreach my $var (@variables) {
__assign(\%GLOBAL_VARS, \%variables, $var);
$variable = $var;
}
return exists($GLOBAL_VARS{$variable}) ? $GLOBAL_VARS{$variable} : undef;
}
=head2 RESERVED($variable, $value)
Mantiene una estructura global de variables ($variable => $value).
=cut
sub RESERVED {
my $caller = caller();
$caller = substr($caller, 0, index($caller, '::'));
fatal('Reserved variables can only be used by Dam framework') if !is_eq($caller, 'Dam');
push(@_, undef) if @_ % 2;
my %variables = @_;
my @variables = keys(%variables);
if (!@variables) {
foreach my $var (keys(%RESERVED_VARS)) {
info($var, ' => ', strval($RESERVED_VARS{$var}));
}
fatal('Reserved variable name is required');
}
my $variable;
foreach my $var (@variables) {
__assign(\%RESERVED_VARS, \%variables, $var);
$variable = $var;
}
return exists($RESERVED_VARS{$variable}) ? $RESERVED_VARS{$variable} : undef;
}
# PRIVATE FUNCTIONS:
sub __assign {
my ($VARS_ref, $variables_ref, $var) = @_;
if (defined($$variables_ref{$var})) {
if (is_eq($$variables_ref{$var}, UNDEF_VAR)) {
$$VARS_ref{$var} = undef;
}
elsif (is_eq($$variables_ref{$var}, DELETE_VAR)) {
delete($$VARS_ref{$var});
}
else {
$$VARS_ref{$var} = $$variables_ref{$var};
}
}
}
1;
=head1 AUTHOR
Manuel Cillero C<< <manuel@cillero.es> >>
=head1 COPYRIGHT
The MIT License (MIT)
Copyright (c) 2004-2020 Manuel Cillero. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut