4d0aa9492bec0302260a6db9d30d8e62e7f67e02
[kivitendo-erp.git] / SL / Controller / TopQuickSearch.pm
1 package SL::Controller::TopQuickSearch;
2
3 use strict;
4 use parent qw(SL::Controller::Base);
5
6 use SL::ClientJS;
7 use SL::JSON;
8 use SL::Locale::String qw(t8);
9 use SL::Helper::UserPreferences;
10
11 use Rose::Object::MakeMethods::Generic (
12  'scalar --get_set_init' => [ qw(module js) ],
13 );
14
15 my @available_modules = (
16   'SL::Controller::TopQuickSearch::Article',
17   'SL::Controller::TopQuickSearch::Part',
18   'SL::Controller::TopQuickSearch::Service',
19   'SL::Controller::TopQuickSearch::Assembly',
20   'SL::Controller::TopQuickSearch::Assortment',
21   'SL::Controller::TopQuickSearch::Contact',
22   'SL::Controller::TopQuickSearch::SalesQuotation',
23   'SL::Controller::TopQuickSearch::SalesOrder',
24   'SL::Controller::TopQuickSearch::SalesDeliveryOrder',
25   'SL::Controller::TopQuickSearch::RequestForQuotation',
26   'SL::Controller::TopQuickSearch::PurchaseOrder',
27   'SL::Controller::TopQuickSearch::PurchaseDeliveryOrder',
28   'SL::Controller::TopQuickSearch::GLTransaction',
29   'SL::Controller::TopQuickSearch::Customer',
30   'SL::Controller::TopQuickSearch::Vendor',
31   'SL::Controller::TopQuickSearch::PhoneNumber',
32 );
33 my %modules_by_name;
34
35 sub action_query_autocomplete {
36   my ($self) = @_;
37
38   my $hashes = $self->module->query_autocomplete;
39
40   $self->render(\ SL::JSON::to_json($hashes), { layout => 0, type => 'json', process => 0 });
41 }
42
43 sub action_select_autocomplete {
44   my ($self) = @_;
45
46   my $redirect_url = $self->module->select_autocomplete;
47
48   $self->js->redirect_to($redirect_url)->render;
49 }
50
51 sub action_do_search {
52   my ($self) = @_;
53
54   my $redirect_url = $self->module->do_search;
55
56   if ($redirect_url) {
57     $self->js->redirect_to($redirect_url)
58   }
59
60   $self->js->render;
61 }
62
63 sub available_modules {
64   my ($self) = @_;
65
66   $self->require_modules;
67
68   map { $_->new } @available_modules;
69 }
70
71 sub enabled_modules {
72   my $user_prefs = SL::Helper::UserPreferences->new(
73     namespace         => 'TopQuickSearch',
74   );
75
76   my @quick_search_modules;
77   if (my $prefs_val = $user_prefs->get('quick_search_modules')) {
78     @quick_search_modules = split ',', $prefs_val;
79   } else {
80     @quick_search_modules = @{ $::instance_conf->get_quick_search_modules };
81   }
82
83   my %enabled_names = map { $_ => 1 } @quick_search_modules;
84
85   grep {
86     $enabled_names{$_->name}
87   } $_[0]->available_modules
88 }
89
90 sub active_modules {
91   grep {
92     !$_->auth || $::auth->assert($_->auth, 1)
93   } $_[0]->enabled_modules
94 }
95
96 sub init_module {
97   my ($self) = @_;
98
99   $self->require_modules;
100
101   die 'Need module' unless $::form->{module};
102
103   die 'Unknown module ' . $::form->{module} unless my $class = $modules_by_name{$::form->{module}};
104
105   $::auth->assert($class->auth) if $class->auth;
106
107   return $class->new;
108 }
109
110 sub init_js {
111   SL::ClientJS->new(controller => $_[0])
112 }
113
114 sub require_modules {
115   my ($self) = @_;
116
117   if (!$self->{__modules_required}) {
118     for my $class (@available_modules) {
119       eval "require $class" or die $@;
120       $modules_by_name{ $class->name } = $class;
121     }
122     $self->{__modules_required} = 1;
123   }
124 }
125
126 1;
127
128 __END__
129
130 =encoding utf-8
131
132 =head1 NAME
133
134 SL::Controller::TopQuickSearch - Framework for pluggable quicksearch fields in the layout top header.
135
136 =head1 SYNOPSIS
137
138   use SL::Controller::TopQuickSearch;
139   my $search = SL::Controller::TopQuickSearch->new;
140   $::request->layout->add_javascripts('kivi.QuickSearch.js');
141
142   # in template
143   [%- FOREACH module = search.enabled_modules %]
144   <input type='text' id='top-search-[% module.name %]'>
145   [%- END %]
146
147 =head1 DESCRIPTION
148
149 This controller provides abstraction for different search plugins, and ensures
150 that all follow a common useability scheme.
151
152 Modules should be configurable per user, but currently are not. Disabling
153 modules can be done by removing them from available_modules or in client_config.
154
155 =head1 BEHAVIOUR REQUIREMENTS
156
157 =over 4
158
159 =item *
160
161 A single text input field with the html5 placeholder containing a small
162 description of the target will be rendered from the plugin information.
163
164 =item *
165
166 On typing, the autocompletion must be enabled.
167
168 =item *
169
170 On C<Enter>, the search should redirect to an appropriate listing of matching
171 results.
172
173 If only one item matches the result, the plugin should instead redirect
174 directly to the matched item.
175
176 =item *
177
178 Search terms should accept the broadest possible matching, and if possible with
179 C<multi> parsing.
180
181 =item *
182
183 In case nothing is found, a visual indicator should be given, but no actual
184 redirect should occur.
185
186 =item *
187
188 Each search must check rights and must not present a backdoor into data that
189 the user should not see.
190
191 =item *
192
193 By design the search must not try to guess C<exact matches>.
194
195 =back
196
197 =head1 INTERFACE
198
199 The full interface is described in L<SL::Controller::TopQuickSeach::Base>
200
201 =head1 TODO
202
203   * user configuration
204
205 =head1 BUGS
206
207 None yet :)
208
209 =head1 AUTHOR
210
211 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
212
213 =cut