Merge branch 'maint/7.0'
[ninja.git] / application / controllers / search.php
blobbe25a6dd7ba0453b6aa085908d9467b1a50609aa
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
2 /**
3 * Search controller
5 * op5, and the op5 logo are trademarks, servicemarks, registered servicemarks
6 * or registered trademarks of op5 AB.
7 * All other trademarks, servicemarks, registered trademarks, and registered
8 * servicemarks mentioned herein may be the property of their respective owner(s).
9 * The information contained herein is provided AS IS with NO WARRANTY OF ANY
10 * KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY, AND FITNESS FOR A
11 * PARTICULAR PURPOSE.
13 class Search_Controller extends Authenticated_Controller {
14 /**
15 * Contains a list of columns to search in, depending on table.
17 * @var array of arrays.
19 protected $search_columns = array(
20 'hosts' => array( 'name', 'display_name', 'address', 'alias', 'notes' ),
21 'services' => array( 'description', 'display_name', 'notes' ),
22 'hostgroups' => array( 'name', 'alias' ),
23 'servicegroups' => array( 'name', 'alias' ),
24 'comments' => array( 'author', 'comment' ),
25 '_si' => array('plugin_output', 'long_plugin_output')
28 protected $search_columns_matchall = array(
29 'hosts' => array( 'name', 'display_name', 'address', 'alias', 'plugin_output', 'long_plugin_output', 'notes' ),
30 'services' => array( 'description', 'display_name', 'host.name', 'host.address', 'host.alias', 'plugin_output', 'long_plugin_output', 'notes' ),
31 'hostgroups' => array( 'name', 'alias' ),
32 'servicegroups' => array( 'name', 'alias' ),
33 'comments' => array( 'author', 'comment' )
36 /**
37 * Do a search of a string
38 * (actually, call index...)
40 * @param $query search string
42 public function lookup($query=false) {
43 return $this->index($query);
46 /**
47 * Do a search of a string
49 * @param $query search string
51 public function index($query=false) {
52 $original_query = $query = trim($this->input->get('query', $query));
54 /* Is the query a complete search filter? */
55 if(preg_match('/^\[[a-zA-Z]+\]/', $query)) {
56 return url::redirect('listview?'.http_build_query(array('q'=>$query)));
59 /* Is the query a saved filter name? */
60 $filters = LSFilter_Saved_Queries_Model::get_query($query);
61 if($filters !== false) {
62 return url::redirect('listview?'.http_build_query(array('q'=>$filters)));
65 /* Is the query a oldschool search filter? h:kaka or boll */
66 $filters = $this->queryToLSFilter( $query );
68 /* Fallback on match everything */
69 if($filters === false) {
70 $filters = $this->queryToLSFilter_MatchAll( $query );
73 if(count($filters)==1) {
74 return url::redirect('listview?'.http_build_query(array('q'=>reset($filters))));
77 $limit = false;
78 if(isset($filters['limit'])) {
79 $limit = $filters['limit'];
80 unset($filters['limit']);
83 $this->render_queries( $filters, $original_query, $limit );
86 /**
87 * Render a list of queries as a page containing listview widgets
89 * @param $queries list of queries
91 private function render_queries($queries, $original_query, $limit=false) {
92 if( !is_array($queries) ) {
93 $queries = array($queries);
96 $this->template->content = $this->add_view('search/result');
98 $content = $this->template->content;
99 $content->date_format_str = nagstat::date_format();
101 $this->template->content->widgets = array();
103 $this->template->js[] = $this->add_path('/js/widgets.js');
105 $username = Auth::instance()->get_user()->username;
107 if( $limit === false ) {
108 $limit = config::get('pagination.default.items_per_page', '*');
110 foreach( $queries as $table => $query ) {
111 $setting = array('query'=>$query);
112 $setting['limit'] = $limit;
113 $model = new Ninja_widget_Model(array(
114 'page' => Router::$controller,
115 'name' => 'listview',
116 'widget' => 'listview',
117 'username' => $username,
118 'friendly_name' => ucfirst($table),
119 'setting' => $setting
122 $widget = widget::get($model, $this);
123 widget::set_resources($widget, $this);
125 $widget->set_fixed($query);
126 // abuse the fact that ls-tables are pluralized
127 $widget->extra_data_attributes['text-if-empty'] = _("No $table found, searching for ".htmlspecialchars($original_query));
129 $this->template->content->widgets[] = $widget->render();
132 $this->template->inline_js = $this->inline_js;
136 * This is an internal function to generate a livestatus query from a filter string.
138 * This method is public so it can be accessed from tests.
140 * @param $query Search query for string
141 * @return Livstatus query as string
143 public function queryToLSFilter($query)
145 $parser = new ExpParser_SearchFilter();
146 try {
147 $filter = $parser->parse( $query );
148 } catch( ExpParserException $e ) {
149 return false;
152 $query = array();
154 /* Map default tables to queries */
155 foreach($filter['filters'] as $table => $q ) {
156 $query[$table] = array($this->andOrToQuery($q, $this->search_columns[$table]));
159 if( isset( $filter['filters']['_si'] ) ) {
160 /* Map status information table to hosts and services */
161 $query['hosts'] = array_merge(isset($query['hosts'])?$query['hosts']:array(), $query['_si']);
162 $query['services'] = array_merge(isset($query['services'])?$query['services']:array(), $query['_si']);
163 unset( $query['_si'] );
164 } else if( isset( $filter['filters']['comments'] ) ) {
165 /* Map subtables for comments (hosts and servies) */
166 if( isset( $filter['filters']['services'] ) ) {
167 $query['comments'][] = $this->andOrToQuery( $filter['filters']['services'],
168 array_map( function($col){
169 return 'service.'.$col;
170 }, $this->search_columns['services'] ) );
172 if( isset( $filter['filters']['hosts'] ) ) {
173 $query['comments'][] = $this->andOrToQuery( $filter['filters']['hosts'],
174 array_map( function($col){
175 return 'host.'.$col;
176 }, $this->search_columns['hosts'] ) );
178 /* Don't search in hosts or servies if searching in comments */
179 unset( $query['hosts'] );
180 unset( $query['services'] );
182 else if( isset( $filter['filters']['services'] ) ) {
183 if( isset( $filter['filters']['hosts'] ) )
184 $query['services'][] = $this->andOrToQuery( $filter['filters']['hosts'],
185 array_map( function($col){
186 return 'host.'.$col;
187 }, $this->search_columns['hosts'] ) );
188 /* Don't search in hosts if searching for services, just filter on hosts... */
189 unset( $query['hosts'] );
192 $result = array();
193 foreach( $query as $table => $filters ) {
194 $result[$table] = '['.$table.'] '.implode(' and ',$filters);
197 if( isset($filter['limit']) ) {
198 $result['limit'] = intval($filter['limit']);
201 return $result;
204 private function andOrToQuery( $matches, $columns ) {
205 $result = array();
206 foreach( $matches as $and ) {
207 $orresult = array();
208 foreach( $and as $or ) {
209 $or = trim($or);
210 $or = str_replace('%','.*',$or);
211 $or = addslashes($or);
212 foreach( $columns as $col ) {
213 $orresult[] = "$col ~~ \"$or\"";
216 $result[] = '(' . implode(' or ', $orresult) . ')';
219 return implode(' and ',$result);
223 * This is an internal function to generate a livestatus query from a filter string.
225 * This method is public so it can be accessed from tests.
227 * @param $query Search query for string
228 * @return Livstatus query as string
230 public function queryToLSFilter_MatchAll($query)
233 $query = str_replace('%','.*',$query);
235 $filters = array();
236 foreach( $this->search_columns_matchall as $table => $cols ) {
237 $subfilters = array();
238 foreach( $cols as $col ) {
239 $subfilters[] = "$col ~~ \"".addslashes($query)."\"";
241 $filters[$table] = "[$table] ".implode(' or ', $subfilters);
244 return $filters;
248 * Translated helptexts for this controller
250 public static function _helptexts($id)
252 # Tag unfinished helptexts with @@@HELPTEXT:<key> to make it
253 # easier to find those later
254 $helptexts = array(
255 'search_help' => sprintf(_("You may perform an AND search on hosts and services: 'h:web AND s:ping' will search for all services called something like ping on hosts called something like web.<br /><br />
256 Furthermore, it's possible to make OR searches: 'h:web OR mail' to search for hosts with web or mail in any of the searchable fields.<br /><br />
257 Combine AND with OR: 'h:web OR mail AND s:ping OR http'<br /><br />
258 Use si:critical to search for status information like critical<br /><br />
259 Read the manual for more tips on searching.<br /><br />
261 The search result is currently limited to %s rows (for each object type).<br /><br />
262 To temporarily change this for your search, use limit=&lt;number&gt; (e.g limit=100) or limit=0 to disable the limit entirely."), config::get('pagination.default.items_per_page', '*')
264 'saved_search_help' => _('Click to save this search for later use. Your saved searches will be available by clicking on the icon just below the search field at the top of the page.'),
265 'filterbox' => _('When you start to type, the visible content gets filtered immediately.<br /><br />If you press <kbd>enter</kbd> or the button "Search through all result pages", you filter all result pages but <strong>only through its primary column</strong> (<em>host name</em> for host objects, etc).')
267 if (array_key_exists($id, $helptexts)) {
268 echo $helptexts[$id];
270 else
271 echo sprintf(_("This helptext ('%s') is not translated yet"), $id);