|
1 <?php |
|
2 |
|
3 /** |
|
4 * @file |
|
5 * Administrative page callbacks for the Database Logging module. |
|
6 */ |
|
7 |
|
8 /** |
|
9 * Page callback: Displays a listing of database log messages. |
|
10 * |
|
11 * Messages are truncated at 56 chars. Full-length messages can be viewed on the |
|
12 * message details page. |
|
13 * |
|
14 * @see dblog_clear_log_form() |
|
15 * @see dblog_event() |
|
16 * @see dblog_filter_form() |
|
17 * @see dblog_menu() |
|
18 * |
|
19 * @ingroup logging_severity_levels |
|
20 */ |
|
21 function dblog_overview() { |
|
22 $filter = dblog_build_filter_query(); |
|
23 $rows = array(); |
|
24 $classes = array( |
|
25 WATCHDOG_DEBUG => 'dblog-debug', |
|
26 WATCHDOG_INFO => 'dblog-info', |
|
27 WATCHDOG_NOTICE => 'dblog-notice', |
|
28 WATCHDOG_WARNING => 'dblog-warning', |
|
29 WATCHDOG_ERROR => 'dblog-error', |
|
30 WATCHDOG_CRITICAL => 'dblog-critical', |
|
31 WATCHDOG_ALERT => 'dblog-alert', |
|
32 WATCHDOG_EMERGENCY => 'dblog-emerg', |
|
33 ); |
|
34 |
|
35 $build['dblog_filter_form'] = drupal_get_form('dblog_filter_form'); |
|
36 $build['dblog_clear_log_form'] = drupal_get_form('dblog_clear_log_form'); |
|
37 |
|
38 $header = array( |
|
39 '', // Icon column. |
|
40 array('data' => t('Type'), 'field' => 'w.type'), |
|
41 array('data' => t('Date'), 'field' => 'w.wid', 'sort' => 'desc'), |
|
42 t('Message'), |
|
43 array('data' => t('User'), 'field' => 'u.name'), |
|
44 array('data' => t('Operations')), |
|
45 ); |
|
46 |
|
47 $query = db_select('watchdog', 'w')->extend('PagerDefault')->extend('TableSort'); |
|
48 $query->leftJoin('users', 'u', 'w.uid = u.uid'); |
|
49 $query |
|
50 ->fields('w', array('wid', 'uid', 'severity', 'type', 'timestamp', 'message', 'variables', 'link')) |
|
51 ->addField('u', 'name'); |
|
52 if (!empty($filter['where'])) { |
|
53 $query->where($filter['where'], $filter['args']); |
|
54 } |
|
55 $result = $query |
|
56 ->limit(50) |
|
57 ->orderByHeader($header) |
|
58 ->execute(); |
|
59 |
|
60 foreach ($result as $dblog) { |
|
61 $rows[] = array('data' => |
|
62 array( |
|
63 // Cells |
|
64 array('class' => 'icon'), |
|
65 t($dblog->type), |
|
66 format_date($dblog->timestamp, 'short'), |
|
67 theme('dblog_message', array('event' => $dblog, 'link' => TRUE)), |
|
68 theme('username', array('account' => $dblog)), |
|
69 filter_xss($dblog->link), |
|
70 ), |
|
71 // Attributes for tr |
|
72 'class' => array(drupal_html_class('dblog-' . $dblog->type), $classes[$dblog->severity]), |
|
73 ); |
|
74 } |
|
75 |
|
76 $build['dblog_table'] = array( |
|
77 '#theme' => 'table', |
|
78 '#header' => $header, |
|
79 '#rows' => $rows, |
|
80 '#attributes' => array('id' => 'admin-dblog'), |
|
81 '#empty' => t('No log messages available.'), |
|
82 ); |
|
83 $build['dblog_pager'] = array('#theme' => 'pager'); |
|
84 |
|
85 return $build; |
|
86 } |
|
87 |
|
88 /** |
|
89 * Page callback: Shows the most frequent log messages of a given event type. |
|
90 * |
|
91 * Messages are not truncated on this page because events detailed herein do not |
|
92 * have links to a detailed view. |
|
93 * |
|
94 * @param string $type |
|
95 * Type of database log events to display (e.g., 'search'). |
|
96 * |
|
97 * @return array |
|
98 * A build array in the format expected by drupal_render(). |
|
99 * |
|
100 * @see dblog_menu() |
|
101 */ |
|
102 function dblog_top($type) { |
|
103 |
|
104 $header = array( |
|
105 array('data' => t('Count'), 'field' => 'count', 'sort' => 'desc'), |
|
106 array('data' => t('Message'), 'field' => 'message') |
|
107 ); |
|
108 $count_query = db_select('watchdog'); |
|
109 $count_query->addExpression('COUNT(DISTINCT(message))'); |
|
110 $count_query->condition('type', $type); |
|
111 |
|
112 $query = db_select('watchdog', 'w')->extend('PagerDefault')->extend('TableSort'); |
|
113 $query->addExpression('COUNT(wid)', 'count'); |
|
114 $query = $query |
|
115 ->fields('w', array('message', 'variables')) |
|
116 ->condition('w.type', $type) |
|
117 ->groupBy('message') |
|
118 ->groupBy('variables') |
|
119 ->limit(30) |
|
120 ->orderByHeader($header); |
|
121 $query->setCountQuery($count_query); |
|
122 $result = $query->execute(); |
|
123 |
|
124 $rows = array(); |
|
125 foreach ($result as $dblog) { |
|
126 $rows[] = array($dblog->count, theme('dblog_message', array('event' => $dblog))); |
|
127 } |
|
128 |
|
129 $build['dblog_top_table'] = array( |
|
130 '#theme' => 'table', |
|
131 '#header' => $header, |
|
132 '#rows' => $rows, |
|
133 '#empty' => t('No log messages available.'), |
|
134 ); |
|
135 $build['dblog_top_pager'] = array('#theme' => 'pager'); |
|
136 |
|
137 return $build; |
|
138 } |
|
139 |
|
140 /** |
|
141 * Page callback: Displays details about a specific database log message. |
|
142 * |
|
143 * @param int $id |
|
144 * Unique ID of the database log message. |
|
145 * |
|
146 * @return array|string |
|
147 * If the ID is located in the Database Logging table, a build array in the |
|
148 * format expected by drupal_render(); otherwise, an empty string. |
|
149 * |
|
150 * @see dblog_menu() |
|
151 */ |
|
152 function dblog_event($id) { |
|
153 $severity = watchdog_severity_levels(); |
|
154 $result = db_query('SELECT w.*, u.name, u.uid FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid WHERE w.wid = :id', array(':id' => $id))->fetchObject(); |
|
155 if ($dblog = $result) { |
|
156 $rows = array( |
|
157 array( |
|
158 array('data' => t('Type'), 'header' => TRUE), |
|
159 t($dblog->type), |
|
160 ), |
|
161 array( |
|
162 array('data' => t('Date'), 'header' => TRUE), |
|
163 format_date($dblog->timestamp, 'long'), |
|
164 ), |
|
165 array( |
|
166 array('data' => t('User'), 'header' => TRUE), |
|
167 theme('username', array('account' => $dblog)), |
|
168 ), |
|
169 array( |
|
170 array('data' => t('Location'), 'header' => TRUE), |
|
171 l($dblog->location, $dblog->location), |
|
172 ), |
|
173 array( |
|
174 array('data' => t('Referrer'), 'header' => TRUE), |
|
175 l($dblog->referer, $dblog->referer), |
|
176 ), |
|
177 array( |
|
178 array('data' => t('Message'), 'header' => TRUE), |
|
179 theme('dblog_message', array('event' => $dblog)), |
|
180 ), |
|
181 array( |
|
182 array('data' => t('Severity'), 'header' => TRUE), |
|
183 $severity[$dblog->severity], |
|
184 ), |
|
185 array( |
|
186 array('data' => t('Hostname'), 'header' => TRUE), |
|
187 check_plain($dblog->hostname), |
|
188 ), |
|
189 array( |
|
190 array('data' => t('Operations'), 'header' => TRUE), |
|
191 $dblog->link, |
|
192 ), |
|
193 ); |
|
194 $build['dblog_table'] = array( |
|
195 '#theme' => 'table', |
|
196 '#rows' => $rows, |
|
197 '#attributes' => array('class' => array('dblog-event')), |
|
198 ); |
|
199 return $build; |
|
200 } |
|
201 else { |
|
202 return ''; |
|
203 } |
|
204 } |
|
205 |
|
206 /** |
|
207 * Builds a query for database log administration filters based on session. |
|
208 * |
|
209 * @return array |
|
210 * An associative array with keys 'where' and 'args'. |
|
211 */ |
|
212 function dblog_build_filter_query() { |
|
213 if (empty($_SESSION['dblog_overview_filter'])) { |
|
214 return; |
|
215 } |
|
216 |
|
217 $filters = dblog_filters(); |
|
218 |
|
219 // Build query |
|
220 $where = $args = array(); |
|
221 foreach ($_SESSION['dblog_overview_filter'] as $key => $filter) { |
|
222 $filter_where = array(); |
|
223 foreach ($filter as $value) { |
|
224 $filter_where[] = $filters[$key]['where']; |
|
225 $args[] = $value; |
|
226 } |
|
227 if (!empty($filter_where)) { |
|
228 $where[] = '(' . implode(' OR ', $filter_where) . ')'; |
|
229 } |
|
230 } |
|
231 $where = !empty($where) ? implode(' AND ', $where) : ''; |
|
232 |
|
233 return array( |
|
234 'where' => $where, |
|
235 'args' => $args, |
|
236 ); |
|
237 } |
|
238 |
|
239 /** |
|
240 * Creates a list of database log administration filters that can be applied. |
|
241 * |
|
242 * @return array |
|
243 * Associative array of filters. The top-level keys are used as the form |
|
244 * element names for the filters, and the values are arrays with the following |
|
245 * elements: |
|
246 * - title: Title of the filter. |
|
247 * - where: The filter condition. |
|
248 * - options: Array of options for the select list for the filter. |
|
249 */ |
|
250 function dblog_filters() { |
|
251 $filters = array(); |
|
252 |
|
253 foreach (_dblog_get_message_types() as $type) { |
|
254 $types[$type] = t($type); |
|
255 } |
|
256 |
|
257 if (!empty($types)) { |
|
258 $filters['type'] = array( |
|
259 'title' => t('Type'), |
|
260 'where' => "w.type = ?", |
|
261 'options' => $types, |
|
262 ); |
|
263 } |
|
264 |
|
265 $filters['severity'] = array( |
|
266 'title' => t('Severity'), |
|
267 'where' => 'w.severity = ?', |
|
268 'options' => watchdog_severity_levels(), |
|
269 ); |
|
270 |
|
271 return $filters; |
|
272 } |
|
273 |
|
274 /** |
|
275 * Returns HTML for a log message. |
|
276 * |
|
277 * @param array $variables |
|
278 * An associative array containing: |
|
279 * - event: An object with at least the message and variables properties. |
|
280 * - link: (optional) Format message as link, event->wid is required. |
|
281 * |
|
282 * @ingroup themeable |
|
283 */ |
|
284 function theme_dblog_message($variables) { |
|
285 $output = ''; |
|
286 $event = $variables['event']; |
|
287 // Check for required properties. |
|
288 if (isset($event->message) && isset($event->variables)) { |
|
289 // Messages without variables or user specified text. |
|
290 if ($event->variables === 'N;') { |
|
291 $output = $event->message; |
|
292 } |
|
293 // Message to translate with injected variables. |
|
294 else { |
|
295 $output = t($event->message, unserialize($event->variables)); |
|
296 } |
|
297 // If the output is expected to be a link, strip all the tags and |
|
298 // special characters by using filter_xss() without any allowed tags. |
|
299 // If not, use filter_xss_admin() to allow some tags. |
|
300 if ($variables['link'] && isset($event->wid)) { |
|
301 // Truncate message to 56 chars after stripping all the tags. |
|
302 $output = truncate_utf8(filter_xss($output, array()), 56, TRUE, TRUE); |
|
303 $output = l($output, 'admin/reports/event/' . $event->wid, array('html' => TRUE)); |
|
304 } |
|
305 else { |
|
306 // Prevent XSS in log detail pages. |
|
307 $output = filter_xss_admin($output); |
|
308 } |
|
309 } |
|
310 return $output; |
|
311 } |
|
312 |
|
313 /** |
|
314 * Form constructor for the database logging filter form. |
|
315 * |
|
316 * @see dblog_filter_form_validate() |
|
317 * @see dblog_filter_form_submit() |
|
318 * @see dblog_overview() |
|
319 * |
|
320 * @ingroup forms |
|
321 */ |
|
322 function dblog_filter_form($form) { |
|
323 $filters = dblog_filters(); |
|
324 |
|
325 $form['filters'] = array( |
|
326 '#type' => 'fieldset', |
|
327 '#title' => t('Filter log messages'), |
|
328 '#collapsible' => TRUE, |
|
329 '#collapsed' => empty($_SESSION['dblog_overview_filter']), |
|
330 ); |
|
331 foreach ($filters as $key => $filter) { |
|
332 $form['filters']['status'][$key] = array( |
|
333 '#title' => $filter['title'], |
|
334 '#type' => 'select', |
|
335 '#multiple' => TRUE, |
|
336 '#size' => 8, |
|
337 '#options' => $filter['options'], |
|
338 ); |
|
339 if (!empty($_SESSION['dblog_overview_filter'][$key])) { |
|
340 $form['filters']['status'][$key]['#default_value'] = $_SESSION['dblog_overview_filter'][$key]; |
|
341 } |
|
342 } |
|
343 |
|
344 $form['filters']['actions'] = array( |
|
345 '#type' => 'actions', |
|
346 '#attributes' => array('class' => array('container-inline')), |
|
347 ); |
|
348 $form['filters']['actions']['submit'] = array( |
|
349 '#type' => 'submit', |
|
350 '#value' => t('Filter'), |
|
351 ); |
|
352 if (!empty($_SESSION['dblog_overview_filter'])) { |
|
353 $form['filters']['actions']['reset'] = array( |
|
354 '#type' => 'submit', |
|
355 '#value' => t('Reset') |
|
356 ); |
|
357 } |
|
358 return $form; |
|
359 } |
|
360 |
|
361 /** |
|
362 * Form validation handler for dblog_filter_form(). |
|
363 * |
|
364 * @see dblog_filter_form_submit() |
|
365 */ |
|
366 function dblog_filter_form_validate($form, &$form_state) { |
|
367 if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['type']) && empty($form_state['values']['severity'])) { |
|
368 form_set_error('type', t('You must select something to filter by.')); |
|
369 } |
|
370 } |
|
371 |
|
372 /** |
|
373 * Form submission handler for dblog_filter_form(). |
|
374 * |
|
375 * @see dblog_filter_form_validate() |
|
376 */ |
|
377 function dblog_filter_form_submit($form, &$form_state) { |
|
378 $op = $form_state['values']['op']; |
|
379 $filters = dblog_filters(); |
|
380 switch ($op) { |
|
381 case t('Filter'): |
|
382 foreach ($filters as $name => $filter) { |
|
383 if (isset($form_state['values'][$name])) { |
|
384 $_SESSION['dblog_overview_filter'][$name] = $form_state['values'][$name]; |
|
385 } |
|
386 } |
|
387 break; |
|
388 case t('Reset'): |
|
389 $_SESSION['dblog_overview_filter'] = array(); |
|
390 break; |
|
391 } |
|
392 return 'admin/reports/dblog'; |
|
393 } |
|
394 |
|
395 /** |
|
396 * Form constructor for the form that clears out the log. |
|
397 * |
|
398 * @see dblog_clear_log_submit() |
|
399 * @ingroup forms |
|
400 */ |
|
401 function dblog_clear_log_form($form) { |
|
402 $form['dblog_clear'] = array( |
|
403 '#type' => 'fieldset', |
|
404 '#title' => t('Clear log messages'), |
|
405 '#description' => t('This will permanently remove the log messages from the database.'), |
|
406 '#collapsible' => TRUE, |
|
407 '#collapsed' => TRUE, |
|
408 ); |
|
409 $form['dblog_clear']['clear'] = array( |
|
410 '#type' => 'submit', |
|
411 '#value' => t('Clear log messages'), |
|
412 '#submit' => array('dblog_clear_log_submit'), |
|
413 ); |
|
414 |
|
415 return $form; |
|
416 } |
|
417 |
|
418 /** |
|
419 * Form submission handler for dblog_clear_log_form(). |
|
420 */ |
|
421 function dblog_clear_log_submit() { |
|
422 $_SESSION['dblog_overview_filter'] = array(); |
|
423 db_truncate('watchdog')->execute(); |
|
424 drupal_set_message(t('Database log cleared.')); |
|
425 } |