package LIMS::Base; use parent 'LIMS'; use strict; use YAML::Tiny; use Data::Dumper; use Regexp::Common; use Sort::Naturally; use JavaScript::DataFormValidator; #------------------------------------------------------------------------------- # GLOBAL METHODS #------------------------------------------------------------------------------- sub model { my $self = shift; my $classname = shift || $self->error( 'Required classname attribute missing' ); # warn 'classname:'. $classname; # return stashed package object if it exists: if ( my $stashed = $self->stash->{"model::$classname"} ) { # $self->debug("model::$classname IS in stash"); return $stashed; } my $package = "LIMS::Model::$classname"; # $self->debug("model::$classname NOT in stash"); # load package via require: unless (eval "require $package") { $self->error("Unable to load module [$package]."); } my %args = ( _lims_db => $self->lims_db, # required by LIMS::Model::Base _session => $self->session, # required by LIMS::Model::Roles::SessionData ); # instantiate object my $model = $package->new(%args) || $self->error("unable to instatiate [$package]."); # $self->debug("instatiated $package."); $self->stash->{"model::$classname"} = $model; return $model; } #------------------------------------------------------------------------------- sub error { my ($self, $msg) = @_; # map { $self->debug("CALLER: $_") } ($package, $filename, $line); my ($package, $filename, $line) = caller; $self->tt_params( package => $package, line => $line, msg => $msg, title => 'Error', ); return $self->tt_process('site/error.tt'); } #------------------------------------------------------------------------------- # alternative to CAP::Flash - accepts args in same format as CAP::Flash, but # uses CAP::MessageStack instead: sub flash { # warn 'here'; warn Dumper caller(); my $self = shift; my ($type, $msg, $scope) = @_; # array of ( type => message ) # warn Dumper [ $type, $msg, $scope ]; $self->push_message( -message => $msg, -classification => $type, -scope => $scope, ); } #------------------------------------------------------------------------------- sub get_meta_data { my $self = shift; my $class = shift; unless ( $self->stash->{meta_data}->{$class} ) { my $meta_data = $self->model('Base')->get_meta($class); $self->stash->{meta_data}->{$class} = $meta_data; } return $self->stash->{meta_data}->{$class}; } #------------------------------------------------------------------------------- sub get_yaml_file { my ($self, $filename) = @_; # warn $filename; return $self->stash->{__yaml_file}->{$filename} if $self->stash->{__yaml_file}->{$filename}; # in case called in a loop my $src = $self->cfg('path_to_app_root') . "/config/.local/$filename.yml"; return 0 unless (-e $src); # eg function not configured =begin # could use 'eval', but probably want to preserve & output any error: my $yaml; eval { $yaml = YAML::Tiny->read($src); }; die @$ if @$; return $yaml->[0]; =cut my $yaml = YAML::Tiny->read($src)->[0]; # $self->debug($yaml); # stash in case we're called in loop (eg print_run, get_blood_tube_type): $self->stash->{__yaml_file}->{$filename} = $yaml; return $yaml; } #------------------------------------------------------------------------------- # uses CGI::Pager; accepts $args hashref (query_args and total_count); # makes cgi_pager object available to template and adds limit & offset params # to query_args; returns nothing sub pager { my $self = shift; my $args = shift; # hashref of query_args and total count my $args_for_query = $args->{query}; my $total_count = $args->{total}; my $entries_per_page = # config settings; can be overriden from form: $self->query->param('entries_per_page') || $self->cfg('settings')->{entries_per_page} || 10; my $pager = CGI::Pager->new( page_len => $entries_per_page, # default = 20 total_count => $total_count, ); $self->tt_params( cgi_pager => $pager ); # $self->debug($pager); # add limit & offset params to args_for_query hashref: $args_for_query->{limit} = $entries_per_page; $args_for_query->{offset} = $self->query->param('offset') || 0; } #------------------------------------------------------------------------------- # returns requested Data::FormValidator profile from LIMS::Validate sub validate { my $self = shift; my $profile_name = shift || return; # warn $profile_name; my $profile = $self->_get_validation_profile($profile_name); # return $profile if not a hashref (eg arrayref for FV::Simple or coderef): return $profile unless ref $profile eq 'HASH'; # remove 'ajax_methods' keys from hashref: my @keys = grep $_ ne 'ajax_methods', keys %$profile; # warn Dumper \@keys; my %dfv_profile = map { $_ => $profile->{$_} } @keys; # warn Dumper \%dfv_profile; return \%dfv_profile; } #------------------------------------------------------------------------------- # called from template with profile name to generate rules for jquery validation: # if used, need to load jquery.validate.js and # $(document).ready(function() { $("#id").validate({ rules: { } }) sub jquery_validation_profile { my $self = shift; my $profile_name = shift || return; # name of DFV profile to retrieve from $self->validate my $profile = $self->_get_validation_profile($profile_name); my $ajax_methods = $profile->{ajax_methods}; my $required = $profile->{required}; my $app_url = $self->query->url; my @rules; push @rules, ( qq!$_: "required"! ) for @$required; push @rules, ( qq!$_: { remote: "$app_url/ajax/$ajax_methods->{$_}" }! ) for keys %$ajax_methods; # warn Dumper \@rules; return \@rules; } #------------------------------------------------------------------------------- sub validation_models { my $self = shift; # warn 'creating validation_models'; # should only be called once my $_self = $self; weaken $_self; # or get circular refs inside the callbacks # validation models all return 1 if param being validated should 'pass': my %validation_models = ( validate_request_number => sub { my $request_number = shift; return $_self->model('Request')->get_requests_count($request_number); }, validate_specimen => sub { my $specimen = shift; return $_self->model('Specimen')->validate_specimen($specimen); }, # can have non-unique test_names, but not with same lab_section_id: # validate_lab_test => sub { # my $data = shift; # return $_self->model('LabTest')->check_lab_test_unique($data); # }, # check form field param is unique, or belongs to current record under edit: validate_param_ownership => sub { my $args = shift; # warn Dumper $args; return $_self->model('Validation')->validate_param_ownership($args); }, # just need to check nhs_number not already in use: validate_nhs_number_unique => sub { my $nhs_no = shift; return $_self->model('Patient')->check_patient_nhs_number_count($nhs_no); }, # how many lab_tests: get_lab_tests_count => sub { return $_self->model('Base')->get_objects_count('LabTest'); }, # hmrn_treatment_types: hmrn_treatment_types => sub { return $_self->model('HMRN')->get_tx_type_map; }, ); return \%validation_models; } #------------------------------------------------------------------------------- # sets tmpl vars & returns FormValidator::Simple::check result against supplied profile: sub form_validator { my $self = shift; my $profile = shift; # arrayref my $result = FormValidator::Simple->check( $self->query => $profile ); $self->tt_params( form_validator => $result ); return $result; } #------------------------------------------------------------------------------- # put requested JavaScript::DataFormValidator profile into tt_params # profile_name required - provides name for tmpl accessor and name of validation # profile to load (if not supplied direct) sub js_validation_profile { my $self = shift; my $profile_name = shift; # name of DFV profile to retrieve from $self->validate my $dfv_profile = shift; # optional; only C::Report::outreach() supplies it direct # get dfv validation profile from validate() if not supplied: if (! $dfv_profile) { $dfv_profile = $self->validate($profile_name) or die "cannot find validation profile for '$profile_name'"; } # grep methods compatible with JS::DFV: my %js_compatible_profile = map { $_ => $dfv_profile->{$_}, } grep $dfv_profile->{$_}, ( # only include methods supported in FormValidator.js v0.06 (not contraint_methods, # or contrainsts: named closures, sub-routine references or compiled regex): 'required', 'optional', 'dependencies', 'dependency_groups', 'constraints', 'msgs', ); # create js_validation_profile for template: $self->tt_params( # Javascript snippet for