X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FHelper%2FGetModels.pm;h=cb73ab35435f49ebdac2dba489e8429e6e129ab2;hb=96f8eef08701d2296bebee75db99557bffb92278;hp=ae9810b5c55d965cdd4f6f360fb3440ba4d2b371;hpb=ec3a4636c1d58339915614120cd82759150d7641;p=kivitendo-erp.git diff --git a/SL/Controller/Helper/GetModels.pm b/SL/Controller/Helper/GetModels.pm index ae9810b5c..cb73ab354 100644 --- a/SL/Controller/Helper/GetModels.pm +++ b/SL/Controller/Helper/GetModels.pm @@ -7,50 +7,146 @@ use SL::Controller::Helper::GetModels::Filtered; use SL::Controller::Helper::GetModels::Sorted; use SL::Controller::Helper::GetModels::Paginated; +use Scalar::Util qw(weaken); + use Rose::Object::MakeMethods::Generic ( - scalar => [ qw(controller model query with_objects filtered sorted paginated) ], - 'scalar --get_set_init' => [ qw(handlers) ], + scalar => [ qw(controller model query with_objects filtered sorted paginated finalized final_params) ], + 'scalar --get_set_init' => [ qw(handlers source additional_url_params) ], + array => [ qw(plugins) ], ); use constant PRIV => '__getmodelshelperpriv'; -#my $registered_handlers = {}; + +# official interface + +sub get { + my ($self) = @_; + my %params = $self->finalize; + + return $self->manager->get_all(%params); +} + +sub count { + my ($self) = @_; + my %params = $self->finalize; + + return $self->manager->get_all_count(%params); +} + +sub disable_plugin { + my ($self, $plugin) = @_; + die 'cannot change internal state after finalize was called' if $self->finalized; + die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base'); + + $self->$plugin->disabled(1); +} + +sub enable_plugin { + my ($self, $plugin) = @_; + die 'cannot change internal state after finalize was called' if $self->finalized; + die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base'); + $self->$plugin->disabled(0); +} + +sub is_enabled_plugin { + my ($self, $plugin) = @_; + die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base'); + $self->$plugin->is_enabled; +} + +# TODO: get better delegation +sub set_report_generator_sort_options { + my ($self, %params) = @_; + $self->finalize; + + $self->sorted->set_report_generator_sort_options(%params); +} + +sub get_paginate_args { + my ($self) = @_; + my %params = $self->finalize; + + $self->paginated->get_current_paginate_params(%params); +} + +sub get_sort_spec { + my ($self) = @_; + + $self->sorted->specs; +} + +sub get_current_sort_params { + my ($self) = @_; + + $self->sorted->read_params; +} sub init { my ($self, %params) = @_; -# for my $plugin (qw(filtered sorted paginated)) { -# next unless $params{$plugin}; -# $self->${ \"make_$plugin" }(%{ delete $params{$plugin} || {} }); -# } -# - # TODO: default model - $self->model(delete $params{model}); + my $model = delete $params{model}; + if (!$model && $params{controller} && ref $params{controller}) { + $model = ref $params{controller}; + $model =~ s/.*:://; + die 'Need a valid model' unless $model; + } + $self->model($model); + my @plugins; for my $plugin (qw(filtered sorted paginated)) { - next unless my $spec = delete $params{$plugin} // {}; + next if exists($params{$plugin}) && !$params{$plugin}; + + my $spec = delete $params{$plugin} // {}; my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin; - $self->$plugin($plugin_class->new(%$spec, get_models => $self)); + push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self)); } + $self->plugins(@plugins); $self->SUPER::init(%params); + + $_->read_params for $self->plugins; + + weaken $self->controller if $self->controller; +} + +sub finalize { + my ($self, %params) = @_; + + return %{ $self->final_params } if $self->finalized; + + $self->register_handlers(callback => sub { shift; (@_, %{ $self->additional_url_params }) }) if %{ $self->additional_url_params }; + + push @{ $params{query} ||= [] }, @{ $self->query || [] }; + push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] }; + + %params = $_->finalize(%params) for $self->plugins; + + $self->finalized(1); + $self->final_params(\%params); + + return %params; } sub register_handlers { my ($self, %additional_handlers) = @_; -# my $only = delete($additional_handlers{ONLY}) || []; -# $only = [ $only ] if !ref $only; -# my %hook_params = @{ $only } ? ( only => $only ) : (); - my $handlers = $self->handlers; map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers; } +sub add_additional_url_params { + my ($self, %params) = @_; + + $self->additional_url_params({ %{ $self->additional_url_params }, %params }); + + return $self; +} + sub get_models_url_params { - my ($class, $sub_name_or_code) = @_; + my ($self, $sub_name_or_code) = @_; - my $code = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->$sub_name_or_code(@_) }; + my $code = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) }; my $callback = sub { my ($self, %params) = @_; my @additional_params = $code->($self); @@ -60,35 +156,21 @@ sub get_models_url_params { ); }; - push @{ _registered_handlers($class)->{callback} }, $callback; + $self->register_handlers('callback' => $callback); } -sub get_callback { +sub get_callback_params { my ($self, %override_params) = @_; my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name); - - return $self->controller->url_for(%default_params, %override_params); } -sub get { - my ($self, %params) = @_; - - push @{ $params{query} ||= [] }, @{ $self->query || [] }; - push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] }; - - %params = $self->_run_handlers('get_models', %params); - - return $self->manager->get_all(%params); -} - -sub get_paginate_args { - my ($self, %params) = @_; +sub get_callback { + my ($self, %override_params) = @_; - push @{ $params{query} ||= [] }, @{ $self->query || [] }; - push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] }; + my %default_params = $self->get_callback_params(%override_params); - $self->paginated->get_current_paginate_params(%params); + return $self->controller->url_for(%default_params, %override_params); } sub manager { @@ -119,10 +201,15 @@ sub _run_handlers { sub init_handlers { { callback => [], - get_models => [], } } +sub init_source { + $::form +} + +sub init_additional_url_params { +{} } + 1; __END__ @@ -132,84 +219,71 @@ __END__ =head1 NAME -SL::Controller::Helper::GetModels - Base mixin for controller helpers -dealing with semi-automatic handling of sorting and paginating lists +SL::Controller::Helper::GetModels - Base class for the GetModels system. =head1 SYNOPSIS -For a proper synopsis see L. +In controller: -=head1 OVERVIEW + use SL::Controller::Helper::GetModels; -For a generic overview see L. + my $get_models = SL::Controller::Helper::GetModels->new( + controller => $self, + ); -This base module is the interface between a controller and specialized -helper modules that handle things like sorting and paginating. The -specialized helpers register themselves with this module via a call to -L during compilation time (e.g. in the -case of C this happens when the controller calls -L). + my $models = $self->get_models->get; -A controller will later usually call the L -function. Templates will call the L function. Both -functions run the registered handlers handing over control to the -specialized helpers so that they may inject their parameters into the -call chain. +=head1 OVERVIEW -The C helper hooks into the controller call to the action -via a C hook. This is done so that it can remember the -action called by the user. This is used for constructing the callback -in L. +Building a CRUD controller would be easy, were it not for those stupid +list actions. People unreasonably expect stuff like filtering, sorting, +paginating, exporting etc simply to work. Well, lets try to make it simply work +a little. -=head1 PACKAGE FUNCTIONS +This class is a proxy between a controller and specialized +helper modules that handle these things (sorting, paginating etc) and gives you +the means to retrieve the information when needed to display sort headers or +paginating footers. -=over 4 +Information about the requested data query can be stored in the object up to +a certain point, from which on the object becomes locked and can only be +accessed for information. (See C). -=item C +=head1 INTERFACE METHODS -Register one of the controller's subs to be called whenever an URL has -to be generated (e.g. for sort and pagination links). This is a way -for the controller to add additional parameters to the URL (e.g. for -filter parameters). +=over 4 -The C<$sub> parameter can be either a code reference or the name of -one of the controller's functions. +=item new PARAMS -The value returned by this C<$sub> must be either a single hash -reference or a hash of key/value pairs to add to the URL. +Create a new GetModels object. Params must have at least an entry +C, other than that, see C for options. -=item C +=item get -This function should only be called from other controller helpers like -C or C. It is not exported and must therefore be -called its full name. The first parameter C<$class> must be the actual -controller's class name. +Retrieve all models for the current configuration. Will finalize the object. -If C<%handlers> contains a key C then it is passed to the hook -registration in L. +=item get_models_url_params SUB -The C<%handlers> register callback functions in the specialized -controller helpers that are called during invocation of -L or L. Possible keys are C and -C. +Register a sub to be called whenever an URL has to be generated (e.g. for sort +and pagination links). This is a way for the controller to add additional +parameters to the URL (e.g. for filter parameters). -Each handler (the value in the hash) can be either a code reference -(in which case it is called directly) or the name of an instance -function callable on a controller instance. In both cases the handler -receives a hash of parameters built during this very call to -L or L respectively. The handler's return -value must be the new hash to be used in calls to further handlers and -to the actual database model functions later on. +The parameter can be either a code reference or the name of +one of the controller's functions. -=back +The value returned by C must be either a single hash +reference or a hash of key/value pairs to add to the URL. -=head1 INSTANCE FUNCTIONS +=item add_additional_url_params C<%params> -=over 4 +Sets additional parameters that will be added to each URL generated by +this model (e.g. for pagination/sorting). This is just sugar for a +proper call to L with an anonymous sub adding +those parameters. -=item C +=item get_callback -Return an URL suitable for use as a callback parameter. It maps to the +Returns a URL suitable for use as a callback parameter. It maps to the current controller and action. All registered handlers of type 'callback' (e.g. the ones by C and C) can inject the parameters they need so that the same list view as is currently @@ -218,26 +292,155 @@ visible can be re-rendered. Optional C<%params> passed to this function may override any parameter set by the registered handlers. -=item C +=item enable_plugin PLUGIN -Query the model manager via C and return its result. The -parameters to C are constructed by calling all registered -handlers of type 'models' (e.g. the ones by C and -C). +=item disable_plugin PLUGIN -Optional C<%params> passed to this function may override any parameter -set by the registered handlers. +=item is_enabled_plugin PLUGIN + +Enable or disable the specified plugin. Useful to disable paginating for +exports for example. C can be used to check the current +state fo a plugin. + +Must not be finalized to use this. + +=item finalize + +Forces finalized state. Can be used on finalized objects without error. + +Note that most higher functions will call this themselves to force a finalized +state. If you do use it it must come before any other finalizing methods, and +will most likely function as a reminder for maintainers where your code +switches from configuration to finalized state. + +=item source HASHREF + +The source for user supplied information. Defaults to $::form. Changing it +after C phase has no effect. + +=item controller CONTROLLER -The return value is the an array reference of C models. +A weakened link to the controller that created the GetModels object. Needed for +certain plugin methods. =back -=head1 BUGS +=head1 DELEGATION METHODS -Nothing here yet. +All of these finalize. -=head1 AUTHOR +Methods delegating to C: + +=over 4 + +=item * + +set_report_generator_sort_options + +=item * + +get_sort_spec + +=item * + +get_current_sort_params + +=back + +Methods delegating to C: + +=over 4 + +=item * + +get_paginate_args + +=back + +=head1 STATES + +A GetModels object is in one of 3 states at any given time. Their purpose is to +make a class of bugs impossible that orginated from changing the configuration +of a GetModels object halfway during the request. This was a huge problem in +the old implementation. + +=over 4 + +=item Base + +This is the state after creating a new object. + +=item Init + +In this state all the information needed from the source ($::form) has been read +and subsequent changes to the source have no effect. In the current +implementation this will happen during creation, so that the return value of +C is already in state C. + +=item Finalized + +In this state no new configuration will be accepted so that information gotten +through the various methods is consistent. Every information retrieval method +will trigger finalize. + +=back + + +=head1 CONFIGURATION + +Most of the configuration will be handed to GetModels on creation via C. +This is a list of accepted params. + +=over 4 + +=item controller SELF + +The creating controller. Currently this is mandatory. + +=item model MODEL + +The name of the model for this GetModels instance. If none is given, the model +is inferred from the name of the controller class. + +=item sorted PARAMS + +=item paginated PARAMS + +=item filtered PARAMS + +Configuration for plugins. If the option for any plugin is omitted, it defaults +to enabled and is configured by default. Giving a falsish value as first argument +will disable the plugin. + +If the value is a hashref, it will be passed to the plugin's C method. + +=item query + +=item with_objects + +Additional static parts for Rose to include into the final query. + +=item source + +Source for plugins to pull their data from. Defaults to $::form. + +=back + +=head1 BUGS AND CAVEATS + +=over 4 + +=item * + +Delegation is not as clean as it should be. Most of the methods rely on action +at a distance and should be moved out. + +=back + +=head1 AUTHORS Moritz Bunkus Em.bunkus@linet-services.deE +Sven Schöling Es.schoeling@linet-services.deE + =cut