/[drupal]/contributions/modules/views_savedsearches/views_savedsearches.module
ViewVC logotype

Contents of /contributions/modules/views_savedsearches/views_savedsearches.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.4 - (show annotations) (download) (as text)
Tue Jan 27 08:57:08 2009 UTC (10 months ago) by quicksketch
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +10 -13 lines
File MIME type: text/x-php
Updating view analysis to take into account query aliases.
1 <?php
2 // $Id: views_savedsearches.module,v 1.3 2009/01/27 07:11:52 quicksketch Exp $
3
4 /**
5 * @file
6 * This module allows you to save your current configuration of exposed
7 * filters for later use. This can be especially useful for Views with a lot
8 * of exposed filters.
9 *
10 * Whenever a View is changed, all saved searches for that View are deleted,
11 * to prevent corruption.
12 * If the user has JavaScript enabled, the saving and deleting process happens
13 * through AHAH, and the new section is displayed below the exposed filters
14 * section. When JavaScript is disabled, the new section is displayed above
15 * the exposed filters section.
16 */
17
18
19 define('AHAH_PATH_DELETE', 'views_savedsearches_ahah/delete');
20 define('AHAH_PATH_SAVE', 'views_savedsearches_ahah/save');
21
22
23 //----------------------------------------------------------------------------
24 // Drupal core hooks.
25
26 /**
27 * Implementation of hook_help().
28 */
29 /**
30 * Implementation of hook_help().
31 */
32 function views_savedsearches_help($path, $arg) {
33 $output = '';
34
35 switch ($path) {
36 case 'admin/help#views_savedsearches':
37 $output = '<p>'. t("The Views Saved Searches module allows users to save their current configuration of exposed filters for later use.") .'</p>';
38 break;
39 case 'admin/build/views/tools/saved-searches':
40 $output = '<p>'. t('Note: Apologies for the bizarre location of this settings form. We\'re working to place the options for saving exposed filter searches in the main view configuration screen.') .'</p>';
41 break;
42 }
43
44 return $output;
45 }
46
47 /**
48 * Implementation of hook_menu().
49 */
50 function views_savedsearches_menu() {
51 $items = array();
52 $items[AHAH_PATH_SAVE] = array(
53 'page callback' => 'views_savedsearches_ahah',
54 'page arguments' => array('save'),
55 'access arguments' => array('access content'),
56 'type' => MENU_CALLBACK,
57 );
58 $items[AHAH_PATH_DELETE] = array(
59 'page callback' => 'views_savedsearches_ahah',
60 'page arguments' => array('delete'),
61 'access arguments' => array('access content'),
62 'type' => MENU_CALLBACK,
63 );
64 $items['admin/build/views/tools/saved-searches'] = array(
65 'title' => t('Saved Searches'),
66 'description' => t('Configure which views support the saved searches functionality.'),
67 'page callback' => 'drupal_get_form',
68 'page arguments' => array('views_savedsearches_settings'),
69 'access arguments' => array('administer site configuration'),
70 'weight' => 5,
71 'type' => MENU_LOCAL_TASK
72 );
73
74 return $items;
75 }
76
77 function views_savedsearches_theme() {
78 return array(
79 'views_savedsearches_container' => array(
80 'arguments' => array('view', 'views_filters_form_values'),
81 ),
82 'views_savedsearches_list' => array(
83 'arguments' => array('view', 'current_view_matches_sid'),
84 ),
85 );
86 }
87
88 /**
89 * Per-view enable of the form
90 */
91 function views_savedsearches_settings() {
92 $views = views_get_all_views();
93 $options = array();
94 foreach ($views as $view) {
95 $options[$view->name] = $view->name;
96 }
97
98 $form['views_savedsearches'] = array(
99 '#title' => t('Enable the saved searches form for the following views'),
100 '#type' => 'checkboxes',
101 '#options' => $options,
102 '#default_value' => variable_get('views_savedsearches', array()),
103 );
104
105 return system_settings_form($form);
106 }
107
108 /**
109 * Implementation of hook_form_alter().
110 */
111 function views_savedsearches_form_alter(&$form, $form_state, $form_id) {
112 // Delete all saved searches whenever an edited View is saved.
113 if ($form_id == 'views_edit_view') {
114 $form['#submit'] = '_views_savedsearches_views_edit_form_submit';
115 }
116 }
117
118
119 //----------------------------------------------------------------------------
120 // Views hooks.
121
122 /**
123 * Implementation of hook_views_pre_view().
124 */
125 function views_savedsearches_views_pre_view(&$view) {
126 $enabled_views = variable_get('views_savedsearches', array());
127 if (empty($enabled_views[$view->name])) {
128 return;
129 }
130
131 $output = '';
132 $output .= '<div id="view-'. form_clean_id($view->name) .'-savedsearches-container" class="views-savedsearches-container clear-block">';
133 $output .= theme('views_savedsearches_container', drupal_clone($view), $_GET);
134 $output .= '</div>';
135
136 $view->attachment_before = $output . $view->attachment_before;
137 }
138
139
140 //----------------------------------------------------------------------------
141 // Form functions.
142
143 /**
144 * Form building callback; the form used to save a search.
145 */
146 function views_savedsearches_save_search_form($form_state, $view) {
147 // Save some properties about the current view.
148 $form['variables']['#tree'] = TRUE;
149 $form['variables']['vid'] = array('#type' => 'hidden', '#value' => $view->vid);
150 // This is needed for the non-AHAH form submit.
151 $form['variables']['exposed_filters'] = array('#type' => 'value', '#value' => serialize(_views_savedsearches_params_to_filters($view, $_GET)));
152
153 $form['name'] = array(
154 '#type' => 'textfield',
155 '#required' => TRUE,
156 '#title' => t('Name'),
157 '#size' => 30,
158 '#maxlength' => 30,
159 );
160
161 $form['submit'] = array(
162 '#type' => 'submit',
163 '#value' => t('Save this search'),
164 );
165
166 // Make sure we redirect the user to the current view after he has saved the
167 // current search. Without this, he would be redirected to the default view,
168 // i.e. without any exposed filters configured.
169 $form['#redirect'] = array(
170 $_GET['q'],
171 '',
172 );
173 $params = $_GET;
174 unset($params['q']);
175 foreach($params as $k => $v) {
176 if (!empty($k) && !empty($v)) {
177 $query[] = $k .'='. $v;
178 }
179 else {
180 $query[]= $v;
181 }
182 }
183 $form['#redirect'][1] = implode('&', $query);
184
185 return $form;
186 }
187
188 /**
189 * Form validation callback; for views_savedsearches_save_search_form.
190 */
191 function views_savedsearches_save_search_form_validate($form, &$form_state) {
192 if (strlen($form_state['values']['name']) == 0) {
193 form_set_error('name', t('You must enter a name for this saved search.'));
194 }
195 elseif (strlen($form_state['values']['name']) > 30) {
196 form_set_error('name', t('Your name for this saved search is too long. The name for a saved search can not be longer than 128 characters.'));
197 }
198 }
199
200 /**
201 * Form submission callback; for views_savedsearches_save_search_form.
202 */
203 function views_savedsearches_save_search_form_submit($form, &$form_state) {
204 global $user;
205
206 // Create a new saved search object.
207 $saved_search = new StdClass();
208 $saved_search->sid = 'new';
209 $saved_search->uid = $user->uid;
210 $saved_search->vid = $form_state['values']['variables']['vid'];
211 $saved_search->name = $form_state['values']['name'];
212 $saved_search->filters = $form_state['values']['variables']['exposed_filters'];
213
214 views_savedsearches_save($saved_search);
215
216 drupal_set_message(t("Your %name search was saved!", array('%name' => $saved_search->name)));
217 }
218
219 /**
220 * Form building callback; the form to delete a search.
221 */
222 function views_savedsearches_delete_search_form($form_state, $view, $options) {
223 // Save some properties about the current view.
224 $form['variables']['#tree'] = TRUE;
225 $form['variables']['vid'] = array('#type' => 'hidden', '#value' => $view->vid);
226
227 $form['saved_collapse'] = array(
228 '#type' => 'fieldset',
229 '#collapsible' => TRUE,
230 '#collapsed' => TRUE,
231 '#title' => t('Saved searches'),
232 );
233 $form['saved_collapse']['saved_searches'] = array(
234 '#type' => 'checkboxes',
235 '#options' => $options,
236 '#attributes' => array(
237 'class' => 'form-checkboxes-horizontal',
238 ),
239 );
240
241 $form['saved_collapse']['delete'] = array(
242 '#type' => 'submit',
243 '#value' => t('Delete'),
244 );
245
246 // Make sure we redirect the user to the current view after he has saved the
247 // current search. Without this, he would be redirected to the default view,
248 // i.e. without any exposed filters configured.
249 $form['#redirect'] = $_GET['q'];
250
251 return $form;
252 }
253
254 /**
255 * Form submission callback; for views_savedsearches_delete_search_form.
256 */
257 function views_savedsearches_delete_search_form_submit($form, &$form_state) {
258 foreach ($form_state['values']['saved_searches'] as $sid => $value) {
259 if ($sid == $value) {
260 $saved_search = views_savedsearches_load($sid);
261 views_savedsearches_delete($sid);
262 drupal_set_message(t('Saved search %name deleted.', array('%name' => $saved_search->name)));
263 }
264 }
265 }
266
267
268 //----------------------------------------------------------------------------
269 // AHAH functions.
270
271 /**
272 * AHAH callback; for both the save search and delete saved search form.
273 */
274 function views_savedsearches_ahah($action) {
275 $vid = $_POST['variables']['vid'];
276 $views_filters_form_values = array();
277
278 parse_str($_POST['views_filters_form'], $views_filters_form_values);
279 $_POST['variables']['exposed_filters'] = serialize($views_filters_form_values);
280
281 // Submit the save form with $_POST instead of $form_values.
282 $form_state = array(
283 'submitted' => TRUE,
284 'values' => $_POST,
285 );
286 call_user_func("views_savedsearches_{$action}_search_form_submit", NULL, $form_state);
287
288 // Reset $_POST(), to prevent drupal_get_form() from calling the validate
289 // and submit hooks. drupal_get_form will be called at least once for the
290 // list (which is also the delete form).
291 $_POST = array();
292
293 // Output.
294 $view = views_get_view($vid);
295
296 $output = '';
297 $output .= theme('status_messages');
298 $output .= theme('views_savedsearches_container', $view, $views_filters_form_values);
299
300 print $output;
301 exit;
302 }
303
304
305 //----------------------------------------------------------------------------
306 // Database functions.
307
308 /**
309 * Load a saved search
310 *
311 * @param sid
312 * A saved search id.
313 * @return
314 * A saved search object.
315 */
316 function views_savedsearches_load($sid) {
317 $saved_search = new StdClass();
318 $saved_search->sid = $sid;
319
320 $result = db_query("SELECT * FROM {views_savedsearches} WHERE sid = %d", $sid);
321 $obj = db_fetch_object($result);
322 if (is_object($obj)) {
323 $saved_search = $obj;
324 $saved_search->filters = unserialize($saved_search->filters);
325 }
326 return $saved_search;
327 }
328
329 /**
330 * Load multiple saved searches.
331 *
332 * @param $uid
333 * A user id.
334 * @param $vid
335 * A view id.
336 * @return
337 * An array of saved search objects.
338 */
339 function views_savedsearches_load_multiple($uid = NULL, $vid = NULL) {
340 $saved_searches = array();
341
342 $result = db_query("SELECT sid FROM {views_savedsearches} WHERE uid = %d AND vid = %d ORDER BY name", $uid, $vid);
343 while ($row = db_fetch_object($result)) {
344 $saved_searches[$row->sid] = views_savedsearches_load($row->sid);
345 }
346 return $saved_searches;
347 }
348
349 /**
350 * Get the number of saved searches, by uid, vid, both or none of both (this
351 * is the equivalent of getting the total count).
352 *
353 * @param $uid
354 * A user id.
355 * @param $vid
356 * View id.
357 * @return
358 * The count of saved searches, depending on the passed user id and view id.
359 */
360 function views_savedsearches_count($uid = NULL, $vid = NULL) {
361 $query = 'SELECT COUNT(sid) FROM {views_savedsearches}';
362
363 if ($uid > 0 && $vid > 0) {
364 $result = db_query("$query WHERE uid = %d AND vid = %d", $uid, $vid);
365 }
366 elseif ($uid > 0) {
367 $result = db_query("$query WHERE uid = %d", $uid);
368 }
369 elseif ($vid > 0) {
370 $result = db_query("$query WHERE vid = %d", $vid);
371 }
372 else {
373 $result = db_query($query);
374 }
375
376 return db_result($result);
377 }
378
379 /**
380 * Delete a saved search.
381 *
382 * @param $sid
383 * A saved search id.
384 */
385 function views_savedsearches_delete($sid) {
386 db_query("DELETE FROM {views_savedsearches} WHERE sid = %d", $sid);
387 }
388
389 /**
390 * Save a saved search.
391 *
392 * @param $saved_search
393 * A saved search object.
394 * @return
395 * A saved search id.
396 */
397 function views_savedsearches_save($saved_search) {
398 if ($saved_search->sid && $saved_search->sid != 'new') {
399 db_query("UPDATE {views_savedsearches} SET uid = %d, vid = %d, name = '%s', filters = '%s' WHERE sid = %d",
400 $saved_search->uid,
401 $saved_search->vid,
402 $saved_search->name,
403 $saved_search->sid
404 );
405 }
406 else {
407 $saved_search->sid = db_result(db_query('select MAX(sid) + 1 from {views_savedsearches}'));
408 db_query("INSERT INTO {views_savedsearches} (sid, uid, vid, name, filters) VALUES(%d, %d, %d, '%s', '%s')",
409 $saved_search->sid,
410 $saved_search->uid,
411 $saved_search->vid,
412 $saved_search->name,
413 $saved_search->filters
414 );
415 }
416 return $saved_search->sid;
417 }
418
419 /**
420 * Get a saved search id by a set of filters.
421 *
422 * @param $filters
423 * An array container filter name - filter value(s) pairs.
424 * @return
425 * A saved search id.
426 */
427 function views_savedsearches_sid_by_filters($filters) {
428 $sid = 0;
429
430 if (count($filters)) {
431 $sid = db_result(db_query("SELECT sid FROM {views_savedsearches} WHERE filters = '%s'", serialize($filters)));
432 }
433 return $sid;
434 }
435
436
437 //----------------------------------------------------------------------------
438 // Private functions.
439
440 /**
441 * Form submission callback; for the views_edit_view form.
442 */
443 function _views_savedsearches_views_edit_form_submit(&$form, &$form_values) {
444 dsm($form_values);
445 db_query("DELETE FROM {views_savedsearches} WHERE vid = %d", $form_values['vid']);
446 }
447
448 /**
449 * Helper function; constructs a params string from an array of filters (can
450 * be used as a GET query, similar to the "params" method of jQuery).
451 *
452 * @param $filters
453 * An array container filter name - filter value(s) pairs.
454 * @return
455 * A GET query.
456 */
457 function _views_savedsearches_filters_to_params($filters) {
458 $query = '';
459
460 if (is_array($filters)) {
461 foreach ($filters as $filter_name => $filter_value) {
462 // Distinction between multiple-value filters and single-value filters.
463 if (is_array($filter_value)) {
464 foreach ($filter_value as $value) {
465 if (strlen($query) > 0) {
466 $query .= '&';
467 }
468 $query .= drupal_urlencode($filter_name) . drupal_urlencode('[]') .'='. drupal_urlencode($value);
469 }
470 }
471 else {
472 if (strlen($query) > 0) {
473 $query .= '&';
474 }
475 $query .= drupal_urlencode($filter_name) .'='. drupal_urlencode($filter_value);
476 }
477 }
478 }
479 return $query;
480 }
481
482 /**
483 * Helper function; deconstructs a params string from to an array of filters.
484 *
485 * @param $params
486 * Am array of current parameters in the query string.
487 * @return
488 * An array containing filter name - filter value(s) pairs.
489 */
490 function _views_savedsearches_params_to_filters($view, $params) {
491 $filters = array();
492
493 $display = $view->display[$view->current_display];
494 if (isset($display->display_options['filters'])) {
495 foreach ($display->display_options['filters'] as $filter) {
496 if (isset($filter['expose']) && isset($params[$filter['expose']['identifier']])) {
497 $query_id = $filter['expose']['identifier'];
498 $filters[$query_id] = $params[$query_id];
499 }
500 }
501 }
502
503 return $filters;
504 }
505
506 /**
507 * Helper function; configures some JS settings to make the AHAH forms
508 * possible.
509 *
510 * @param $view_name
511 * Name of a view.
512 */
513 function _views_savedsearches_configure_ahah($view_name) {
514 static $required_things_added;
515
516 if (!isset($required_things_added)) {
517 drupal_add_js('misc/jquery.form.js');
518 drupal_add_js('misc/collapse.js', 'theme');
519 drupal_add_js(drupal_get_path('module', 'views_savedsearches') .'/views_savedsearches.js', 'module');
520 drupal_add_js(
521 array(
522 'views_savedsearches' => array(
523 'paths' => array(
524 'deletePath' => AHAH_PATH_DELETE,
525 'savePath' => AHAH_PATH_SAVE,
526 ),
527 ),
528 ),
529 'setting'
530 );
531
532 $required_things_added = TRUE;
533 }
534
535 // Add the current view's name to the JS settings.
536 drupal_add_js(
537 array(
538 'views_savedsearches' => array(
539 'view_names' => array(str_replace('_', '-', $view_name)),
540 ),
541 ),
542 'setting'
543 );
544 }
545
546 /**
547 * Helper function; transforms an array of saved search objects into an array
548 * of form options that can be used for a select or checkboxes form item. The
549 * key will be the sid (saved search id) and the value a link to the saved
550 * search.
551 *
552 * @param $saved_searches
553 * An array of saved search objects.
554 * @param $view_url
555 * The view URL that applies to all saved search objects passed in param 1.
556 * @param $current_view_matches_sid
557 * The saved search id that matches the current view (if any).
558 * @return
559 * An array of form options.
560 */
561 function _views_savedsearches_saved_searches_to_form_options($saved_searches, $view_url, $current_view_matches_sid = 0) {
562 $options = array();
563
564 if (is_array($saved_searches)) {
565 foreach ($saved_searches as $sid => $saved_search) {
566 $query = _views_savedsearches_filters_to_params($saved_search->filters);
567
568 // If this saved search is active, mark it with a class.
569 $class = ($sid == $current_view_matches_sid) ? 'views-savedsearches-is-active' : '';
570
571 $options[$sid] = l($saved_search->name, $view_url, array('attributes' => array('class' => $class), 'query' => $query));
572 }
573 }
574 return $options;
575 }
576
577
578 //----------------------------------------------------------------------------
579 // Theming functions.
580
581 /**
582 * Theming callback; controls the entire output.
583 *
584 * @param $view
585 * A view oject.
586 * @param $views_filters_form_values
587 * An array consisting of key-value pairs, where the keys are the names of
588 * the filters or operators (ops) of a views form. This data can either be
589 * found in $_GET (normal forms) or in $_POST['views_filters_form'] (AHAH
590 * forms).
591 */
592 function theme_views_savedsearches_container($view, $views_filters_form_values) {
593 $output = '';
594
595 // Add JS necessary for adding AHAH.
596 _views_savedsearches_configure_ahah($view->name);
597
598 // Add the CSS and see if the current view matches a sid.
599 drupal_add_css(drupal_get_path('module', 'views_savedsearches') .'/views_savedsearches.css', 'module');
600 $view_filters = _views_savedsearches_params_to_filters($view, $views_filters_form_values);
601 $current_view_matches_sid = views_savedsearches_sid_by_filters($view_filters);
602
603 // The list (also delete saved search form).
604 $output .= '<div class="views-savedsearches-list-ahah">';
605 $output .= theme('views_savedsearches_list', $view, $current_view_matches_sid);
606 $output .= '</div>';
607
608 // The save search form.
609 $output .= '<div class="views-savedsearches-save-ahah">';
610 if (!$current_view_matches_sid && count($view_filters)) {
611 $output .= drupal_get_form('views_savedsearches_save_search_form', $view);
612 }
613 $output .= '</div>';
614
615 return $output;
616 }
617
618 /**
619 * Theming callback; themes the list of saved searches.
620 *
621 * @param $view
622 * A view object.
623 * @param $current_view_matches_sid
624 * The saved search id that matches the current view.
625 */
626 function theme_views_savedsearches_list($view, $current_view_matches_sid) {
627 global $user;
628
629 $output = '';
630
631 // Load saved searches for the current user, to use them as form options.
632 $saved_searches = views_savedsearches_load_multiple($user->uid, $view->vid);
633 $options = _views_savedsearches_saved_searches_to_form_options($saved_searches, $view->get_path(), $current_view_matches_sid);
634
635 // Display a list of saved searches (if there are any) and allow the user to
636 // delete any of them.
637 if (count($options)) {
638 $output .= drupal_get_form('views_savedsearches_delete_search_form', $view, $options, $current_view_matches_sid);
639 }
640 else {
641 $output .= '<p>'. t('No saved searches found for this view.') .'</p>';
642 }
643
644 return $output;
645 }

  ViewVC Help
Powered by ViewVC 1.1.2