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');
98 $content = $this->template
->content
;
99 $content->date_format_str
= nagstat
::date_format();
101 $this->xtra_js
= array();
102 $this->xtra_css
= array();
103 $this->template
->content
->widgets
= array();
105 $this->xtra_js
[] = $this->add_path('/js/widgets.js');
107 $username = Auth
::instance()->get_user()->username
;
109 if( $limit === false ) {
110 $limit = config
::get('pagination.default.items_per_page', '*');
112 foreach( $queries as $table => $query ) {
113 $setting = array('query'=>$query);
114 $setting['limit'] = $limit;
115 $model = new Ninja_widget_Model(array(
116 'page' => Router
::$controller,
117 'name' => 'listview',
118 'widget' => 'listview',
119 'username' => $username,
120 'friendly_name' => ucfirst($table),
121 'setting' => $setting
124 $widget = widget
::get($model, $this);
125 widget
::set_resources($widget, $this);
127 $widget->set_fixed($query);
128 // abuse the fact that ls-tables are pluralized
129 $widget->extra_data_attributes
['text-if-empty'] = _("No $table found, searching for ".htmlspecialchars($original_query));
131 $this->template
->content
->widgets
[] = $widget->render();
134 $this->template
->inline_js
= $this->inline_js
;
138 * This is an internal function to generate a livestatus query from a filter string.
140 * This method is public so it can be accessed from tests.
142 * @param $query Search query for string
143 * @return Livstatus query as string
145 public function queryToLSFilter($query)
147 $parser = new ExpParser_SearchFilter();
149 $filter = $parser->parse( $query );
150 } catch( ExpParserException
$e ) {
156 /* Map default tables to queries */
157 foreach($filter['filters'] as $table => $q ) {
158 $query[$table] = array($this->andOrToQuery($q, $this->search_columns
[$table]));
161 if( isset( $filter['filters']['_si'] ) ) {
162 /* Map status information table to hosts and services */
163 $query['hosts'] = array_merge(isset($query['hosts'])?
$query['hosts']:array(), $query['_si']);
164 $query['services'] = array_merge(isset($query['services'])?
$query['services']:array(), $query['_si']);
165 unset( $query['_si'] );
166 } else if( isset( $filter['filters']['comments'] ) ) {
167 /* Map subtables for comments (hosts and servies) */
168 if( isset( $filter['filters']['services'] ) ) {
169 $query['comments'][] = $this->andOrToQuery( $filter['filters']['services'],
170 array_map( function($col){
171 return 'service.'.$col;
172 }, $this->search_columns
['services'] ) );
174 if( isset( $filter['filters']['hosts'] ) ) {
175 $query['comments'][] = $this->andOrToQuery( $filter['filters']['hosts'],
176 array_map( function($col){
178 }, $this->search_columns
['hosts'] ) );
180 /* Don't search in hosts or servies if searching in comments */
181 unset( $query['hosts'] );
182 unset( $query['services'] );
184 else if( isset( $filter['filters']['services'] ) ) {
185 if( isset( $filter['filters']['hosts'] ) )
186 $query['services'][] = $this->andOrToQuery( $filter['filters']['hosts'],
187 array_map( function($col){
189 }, $this->search_columns
['hosts'] ) );
190 /* Don't search in hosts if searching for services, just filter on hosts... */
191 unset( $query['hosts'] );
195 foreach( $query as $table => $filters ) {
196 $result[$table] = '['.$table.'] '.implode(' and ',$filters);
199 if( isset($filter['limit']) ) {
200 $result['limit'] = intval($filter['limit']);
206 private function andOrToQuery( $matches, $columns ) {
208 foreach( $matches as $and ) {
210 foreach( $and as $or ) {
212 $or = str_replace('%','.*',$or);
213 $or = addslashes($or);
214 foreach( $columns as $col ) {
215 $orresult[] = "$col ~~ \"$or\"";
218 $result[] = '(' . implode(' or ', $orresult) . ')';
221 return implode(' and ',$result);
225 * This is an internal function to generate a livestatus query from a filter string.
227 * This method is public so it can be accessed from tests.
229 * @param $query Search query for string
230 * @return Livstatus query as string
232 public function queryToLSFilter_MatchAll($query)
235 $query = str_replace('%','.*',$query);
238 foreach( $this->search_columns_matchall
as $table => $cols ) {
239 $subfilters = array();
240 foreach( $cols as $col ) {
241 $subfilters[] = "$col ~~ \"".addslashes($query)."\"";
243 $filters[$table] = "[$table] ".implode(' or ', $subfilters);
250 * Translated helptexts for this controller
252 public static function _helptexts($id)
254 # Tag unfinished helptexts with @@@HELPTEXT:<key> to make it
255 # easier to find those later
257 '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 />
258 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 />
259 Combine AND with OR: 'h:web OR mail AND s:ping OR http'<br /><br />
260 Use si:critical to search for status information like critical<br /><br />
261 Read the manual for more tips on searching.<br /><br />
263 The search result is currently limited to %s rows (for each object type).<br /><br />
264 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', '*')
266 '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.'),
267 '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).')
269 if (array_key_exists($id, $helptexts)) {
270 echo $helptexts[$id];
273 echo sprintf(_("This helptext ('%s') is not translated yet"), $id);