1 <?php
defined('SYSPATH') OR die('No direct access allowed.');
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
13 class Search_Controller
extends Authenticated_Controller
{
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' )
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);
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))));
78 if(isset($filters['limit'])) {
79 $limit = $filters['limit'];
80 unset($filters['limit']);
83 $this->render_queries( $filters, $original_query, $limit );
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');
97 $this->template
->css_header
= $this->add_view('css_header');
98 $this->template
->js_header
= $this->add_view('js_header');
100 $content = $this->template
->content
;
101 $content->date_format_str
= nagstat
::date_format();
103 $this->xtra_js
= array();
104 $this->xtra_css
= array();
105 $this->template
->content
->widgets
= array();
107 $this->xtra_js
[] = $this->add_path('/js/widgets.js');
109 $username = Auth
::instance()->get_user()->username
;
111 if( $limit === false ) {
112 $limit = config
::get('pagination.default.items_per_page', '*');
114 foreach( $queries as $table => $query ) {
115 $setting = array('query'=>$query);
116 $setting['limit'] = $limit;
117 $model = new Ninja_widget_Model(array(
118 'page' => Router
::$controller,
119 'name' => 'listview',
120 'widget' => 'listview',
121 'username' => $username,
122 'friendly_name' => ucfirst($table),
123 'setting' => $setting
126 $widget = widget
::get($model, $this);
127 widget
::set_resources($widget, $this);
129 $widget->set_fixed($query);
130 // abuse the fact that ls-tables are pluralized
131 $widget->extra_data_attributes
['text-if-empty'] = _("No $table found, searching for ".htmlspecialchars($original_query));
133 $this->template
->content
->widgets
[] = $widget->render();
136 $this->template
->js_header
->js
= $this->xtra_js
;
137 $this->template
->css_header
->css
= $this->xtra_css
;
138 $this->template
->inline_js
= $this->inline_js
;
142 * This is an internal function to generate a livestatus query from a filter string.
144 * This method is public so it can be accessed from tests.
146 * @param $query Search query for string
147 * @return Livstatus query as string
149 public function queryToLSFilter($query)
151 $parser = new ExpParser_SearchFilter();
153 $filter = $parser->parse( $query );
154 } catch( ExpParserException
$e ) {
160 /* Map default tables to queries */
161 foreach($filter['filters'] as $table => $q ) {
162 $query[$table] = array($this->andOrToQuery($q, $this->search_columns
[$table]));
165 if( isset( $filter['filters']['_si'] ) ) {
166 /* Map status information table to hosts and services */
167 $query['hosts'] = array_merge(isset($query['hosts'])?
$query['hosts']:array(), $query['_si']);
168 $query['services'] = array_merge(isset($query['services'])?
$query['services']:array(), $query['_si']);
169 unset( $query['_si'] );
170 } else if( isset( $filter['filters']['comments'] ) ) {
171 /* Map subtables for comments (hosts and servies) */
172 if( isset( $filter['filters']['services'] ) ) {
173 $query['comments'][] = $this->andOrToQuery( $filter['filters']['services'],
174 array_map( function($col){
175 return 'service.'.$col;
176 }, $this->search_columns
['services'] ) );
178 if( isset( $filter['filters']['hosts'] ) ) {
179 $query['comments'][] = $this->andOrToQuery( $filter['filters']['hosts'],
180 array_map( function($col){
182 }, $this->search_columns
['hosts'] ) );
184 /* Don't search in hosts or servies if searching in comments */
185 unset( $query['hosts'] );
186 unset( $query['services'] );
188 else if( isset( $filter['filters']['services'] ) ) {
189 if( isset( $filter['filters']['hosts'] ) )
190 $query['services'][] = $this->andOrToQuery( $filter['filters']['hosts'],
191 array_map( function($col){
193 }, $this->search_columns
['hosts'] ) );
194 /* Don't search in hosts if searching for services, just filter on hosts... */
195 unset( $query['hosts'] );
199 foreach( $query as $table => $filters ) {
200 $result[$table] = '['.$table.'] '.implode(' and ',$filters);
203 if( isset($filter['limit']) ) {
204 $result['limit'] = intval($filter['limit']);
210 private function andOrToQuery( $matches, $columns ) {
212 foreach( $matches as $and ) {
214 foreach( $and as $or ) {
216 $or = str_replace('%','.*',$or);
217 $or = addslashes($or);
218 foreach( $columns as $col ) {
219 $orresult[] = "$col ~~ \"$or\"";
222 $result[] = '(' . implode(' or ', $orresult) . ')';
225 return implode(' and ',$result);
229 * This is an internal function to generate a livestatus query from a filter string.
231 * This method is public so it can be accessed from tests.
233 * @param $query Search query for string
234 * @return Livstatus query as string
236 public function queryToLSFilter_MatchAll($query)
239 $query = str_replace('%','.*',$query);
242 foreach( $this->search_columns_matchall
as $table => $cols ) {
243 $subfilters = array();
244 foreach( $cols as $col ) {
245 $subfilters[] = "$col ~~ \"".addslashes($query)."\"";
247 $filters[$table] = "[$table] ".implode(' or ', $subfilters);
254 * Translated helptexts for this controller
256 public static function _helptexts($id)
258 # Tag unfinished helptexts with @@@HELPTEXT:<key> to make it
259 # easier to find those later
261 '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 />
262 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 />
263 Combine AND with OR: 'h:web OR mail AND s:ping OR http'<br /><br />
264 Use si:critical to search for status information like critical<br /><br />
265 Read the manual for more tips on searching.<br /><br />
267 The search result is currently limited to %s rows (for each object type).<br /><br />
268 To temporarily change this for your search, use limit=<number> (e.g limit=100) or limit=0 to disable the limit entirely."), config
::get('pagination.default.items_per_page', '*')
270 '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.'),
271 '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).')
273 if (array_key_exists($id, $helptexts)) {
274 echo $helptexts[$id];
277 echo sprintf(_("This helptext ('%s') is not translated yet"), $id);