1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288 |
- <?php
- /**
- * @file
- * Offers the ability to save searches and be notified of new results.
- */
- /**
- * Implements hook_menu().
- */
- function search_api_saved_searches_menu() {
- $items['admin/config/search/search_api/index/%search_api_index/saved_searches'] = array(
- 'title' => 'Saved searches',
- 'description' => 'Let users save searches on this index.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_api_saved_searches_index_edit', 5),
- 'access arguments' => array('administer search_api_saved_searches'),
- 'weight' => -1,
- 'type' => MENU_LOCAL_TASK,
- 'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
- 'file' => 'search_api_saved_searches.admin.inc',
- );
- $items['user/%user/saved-searches'] = array(
- 'title' => 'Saved searches',
- 'description' => 'View and edit your saved searches.',
- 'page callback' => 'search_api_saved_searches_user_listing',
- 'page arguments' => array(1),
- 'access callback' => 'search_api_saved_search_edit_access',
- 'access arguments' => array(1),
- 'weight' => 5,
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['user/%user/saved-searches/add'] = array(
- 'title' => 'Create saved search',
- 'description' => 'Create a new saved search.',
- 'page callback' => 'search_api_saved_searches_create_manual',
- 'access callback' => 'search_api_saved_search_create_access',
- 'access arguments' => array(NULL, TRUE),
- 'type' => MENU_LOCAL_ACTION,
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-searches/add'] = array(
- 'title' => 'Create saved search',
- 'description' => 'Create a new saved search.',
- 'page callback' => 'search_api_saved_searches_create_manual',
- 'access callback' => 'search_api_saved_search_create_access',
- 'access arguments' => array(NULL, TRUE),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-searches/add/%search_api_saved_searches_settings'] = array(
- 'title' => 'Create saved search',
- 'description' => 'Create a new saved search.',
- 'page callback' => 'search_api_saved_searches_create_manual',
- 'page arguments' => array(3),
- 'access callback' => 'search_api_saved_search_create_access',
- 'access arguments' => array(3, TRUE),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-search/%search_api_saved_search/activate/%'] = array(
- 'title' => 'Activate saved search',
- 'description' => 'Activate a new saved search.',
- 'page callback' => 'search_api_saved_searches_activate_page',
- 'page arguments' => array(2, 4),
- 'access callback' => 'search_api_saved_search_edit_access',
- 'access arguments' => array(NULL, 2, 4),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-search/%search_api_saved_search/enable'] = array(
- 'title' => 'Enable/Disable saved search',
- 'description' => 'Enable or disable a saved search.',
- 'page callback' => 'search_api_saved_searches_search_enable',
- 'page arguments' => array(2),
- 'access callback' => 'search_api_saved_search_edit_access',
- 'access arguments' => array(NULL, 2, 4),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-search/%search_api_saved_search/disable'] = array(
- 'title' => 'Enable/Disable saved search',
- 'description' => 'Enable or disable a saved search.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_api_saved_searches_search_disable_form', 2),
- 'access callback' => 'search_api_saved_search_edit_access',
- 'access arguments' => array(NULL, 2, 4),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-search/%search_api_saved_search/edit'] = array(
- 'title' => 'Edit saved search',
- 'description' => 'Edit a saved search.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_api_saved_searches_search_edit_form', 2),
- 'access callback' => 'search_api_saved_search_edit_access',
- 'access arguments' => array(NULL, 2, 4),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- $items['search-api/saved-search/%search_api_saved_search/delete'] = array(
- 'title' => 'Delete saved search',
- 'description' => 'Delete a saved search.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_api_saved_searches_search_delete_form', 2),
- 'access callback' => 'search_api_saved_search_edit_access',
- 'access arguments' => array(NULL, 2, 4),
- 'file' => 'search_api_saved_searches.pages.inc',
- );
- return $items;
- }
- /**
- * Implements hook_permission();
- */
- function search_api_saved_searches_permission() {
- $perms['administer search_api_saved_searches'] = array(
- 'title' => t('Administer saved searches'),
- 'description' => t('Enable and configure saved searches for search indexes.'),
- );
- $perms['use search_api_saved_searches'] = array(
- 'title' => t('Use saved searches'),
- 'description' => t('Save searches and receive e-mail notifications.'),
- );
- return $perms;
- }
- /**
- * Implements hook_entity_info().
- */
- function search_api_saved_searches_entity_info() {
- $info['search_api_saved_searches_settings'] = array(
- 'label' => t('Saved search settings'),
- 'controller class' => 'EntityAPIControllerExportable',
- 'entity class' => 'SearchApiSavedSearchesSettings',
- 'base table' => 'search_api_saved_searches_settings',
- 'uri callback' => 'search_api_saved_searches_settings_url',
- 'access callback' => 'search_api_saved_searches_settings_access',
- 'module' => 'search_api_saved_searches',
- 'exportable' => TRUE,
- 'entity keys' => array(
- 'id' => 'id',
- 'name' => 'delta',
- 'label' => 'delta',
- ),
- );
- $info['search_api_saved_search'] = array(
- 'label' => t('Saved search'),
- 'controller class' => 'EntityAPIController',
- 'entity class' => 'SearchApiSavedSearch',
- 'base table' => 'search_api_saved_search',
- 'access callback' => 'search_api_saved_search_access',
- 'module' => 'search_api_saved_searches',
- 'exportable' => FALSE,
- 'entity keys' => array(
- 'id' => 'id',
- 'label' => 'name',
- ),
- );
- return $info;
- }
- /**
- * Implements hook_entity_property_info_alter().
- *
- * Corrects the types which the Entity API automatically infers from the schema.
- * Otherwise, the "token" types would be "text", and "boolean" and "date" would
- * be "integer". Also, changes saved search results to be a list, not just a CSV
- * string.
- *
- * Fixing this here automatically also fixes the Views integration provided by
- * the Entity API, regarding these types.
- */
- function search_api_saved_searches_entity_property_info_alter(array &$info) {
- $settings = &$info['search_api_saved_searches_settings']['properties'];
- $settings['index_id']['type'] = 'token';
- $settings['enabled']['type'] = 'boolean';
- $settings['module']['type'] = 'token';
- $searches = &$info['search_api_saved_search']['properties'];
- $searches['settings_id']['type'] = 'token';
- $searches['enabled']['type'] = 'boolean';
- $searches['created']['type'] = 'date';
- $searches['last_queued']['type'] = 'date';
- $searches['last_execute']['type'] = 'date';
- // We can't assign "duration" until Entity API Views integration supports
- // this.
- //$searches['notify_interval']['type'] = 'duration';
- $searches['results']['type'] = 'list<token>';
- $searches['results']['getter callback'] = 'search_api_saved_searches_get_results_property';
- }
- /**
- * Getter callback for the saved search results property.
- *
- * @param SearchApiSavedSearch $search
- * The search whose results should be returned.
- * @param array $options
- * Options for the property. Are ignored.
- * @param string $property
- * The property to retrieve. Will always be "results".
- * @param string $entity_type
- * The entity type. Will always be "search_api_saved_search".
- *
- * @return array
- * An array with the IDs of all stored results.
- */
- function search_api_saved_searches_get_results_property(SearchApiSavedSearch $search, array $options, $property, $entity_type) {
- return $search->results ? explode(',', $search->results) : array();
- }
- /**
- * Implements hook_views_api().
- */
- function search_api_saved_searches_views_api() {
- return array(
- 'api' => 3,
- 'path' => drupal_get_path('module', 'search_api_saved_searches') . '/views',
- );
- }
- /**
- * URL callback for settings entities.
- */
- function search_api_saved_searches_settings_url(SearchApiSavedSearchesSettings $settings) {
- return array('path' => 'admin/config/search/search_api/index/' . $settings->index_id . '/saved_searches');
- }
- /**
- * Access callback for settings entities.
- *
- * @param string $op
- * The operation being performed. One of "view", "update", "create" or
- * "delete".
- * @param SearchApiSavedSearchesSettings|null $settings
- * (optional) The entity to check access for. If NULL is given, it will be
- * determined whether access is allowed for all settings.
- * @param object|null $account
- * The user to check for. NULL to check for the global user.
- *
- * @return bool
- * Whether access is allowed or not.
- *
- * @see entity_access
- */
- function search_api_saved_searches_settings_access($op, SearchApiSavedSearchesSettings $settings = NULL, $account = NULL) {
- return user_access('administer search_api_saved_searches', $account);
- }
- /**
- * Access callback for saved search entities.
- *
- * @param string $op
- * The operation being performed. One of "view", "update", "create" or
- * "delete".
- * @param SearchApiSavedSearch|null $search
- * (optional) The entity to check access for. If NULL is given, it will be
- * determined whether access is allowed for all searches.
- * @param object|null $account
- * The user to check for. NULL to check for the global user.
- *
- * @return bool
- * Whether access is allowed or not.
- *
- * @see entity_access
- */
- function search_api_saved_search_access($op, SearchApiSavedSearch $search = NULL, $account = NULL) {
- if (user_access('administer search_api_saved_searches', $account)) {
- return TRUE;
- }
- if (!$account) {
- global $user;
- $account = $user;
- }
- switch ($op) {
- case 'create':
- return user_access('use search_api_saved_searches', $account);
- default:
- // If the search was created by an anonymous user, there's no way we can
- // correctly determine access here.
- if (!$search || !$search->uid) {
- return FALSE;
- }
- return $search->uid == $account->uid;
- }
- }
- /**
- * Implements hook_user_insert().
- *
- * If a new user already has saved searches with the same mail address,
- * associate them with the new user. However, only do this if the user is
- * already active.
- */
- function search_api_saved_searches_user_insert(&$edit, $account, $category) {
- if (!empty($account->status)) {
- foreach (search_api_saved_search_load_multiple(FALSE, array('mail' => $account->mail, 'uid' => 0)) as $search) {
- $search->uid = $account->uid;
- if (empty($search->settings()->options['registered_user_delete_key'])) {
- unset($search->options['key']);
- }
- $search->save();
- }
- }
- }
- /**
- * Implements hook_user_update().
- *
- * If a user gets activated, associate saved searches with the same mail address
- * with them.
- *
- * If a user gets deactivated, disable all related saved searches.
- *
- * Also, change mail address of saved searches when the user mail address
- * changes.
- */
- function search_api_saved_searches_user_update(&$edit, $account, $category) {
- // For newly activated users, transfer all saved searches with their mail
- // address to them.
- if (!empty($account->status) && empty($account->original->status)) {
- foreach (search_api_saved_search_load_multiple(FALSE, array('mail' => $account->mail, 'uid' => 0)) as $search) {
- $search->uid = $account->uid;
- if (empty($search->settings()->options['registered_user_delete_key'])) {
- unset($search->options['key']);
- }
- $search->save();
- }
- }
- // If an account gets deactivated/banned, disable all associated searches.
- if (empty($account->status) && !empty($account->original->status)) {
- foreach (search_api_saved_search_load_multiple(FALSE, array('uid' => $account->uid)) as $search) {
- $search->enabled = FALSE;
- $search->save();
- }
- }
- // If the user's mail address changed, also change the mail address of the
- // user's saved searches.
- if ($account->mail != $account->original->mail) {
- foreach (search_api_saved_search_load_multiple(FALSE, array('mail' => $account->mail, 'uid' => $account->uid)) as $search) {
- $search->mail = $account->mail;
- $search->save();
- }
- }
- }
- /**
- * Implements hook_user_delete().
- *
- * If a user is deleted, delete their saved searches, too.
- */
- function search_api_saved_searches_user_delete($account) {
- entity_delete_multiple('search_api_saved_search', array_keys(search_api_saved_search_load_multiple(FALSE, array('uid' => $account->uid))));
- }
- // @todo Rules integration
- /**
- * Implements hook_search_api_index_update().
- *
- * If the index got disabled, do the same with its search settings.
- */
- function search_api_saved_searches_search_api_index_update(SearchApiIndex $index) {
- if (!$index->enabled && $index->original->enabled) {
- foreach (search_api_saved_searches_settings_load_multiple(FALSE, array('index_id' => $index->machine_name)) as $settings) {
- if ($settings->enabled) {
- $settings->enabled = FALSE;
- $settings->save();
- }
- }
- }
- }
- /**
- * Implements hook_search_api_index_delete().
- *
- * Deletes the settings associated with a search index.
- */
- function search_api_saved_searches_search_api_index_delete(SearchApiIndex $index) {
- // Only react on real delete, not revert.
- if ($index->status & ENTITY_IN_CODE) {
- return;
- }
- foreach (search_api_saved_searches_settings_load_multiple(FALSE, array('index_id' => $index->machine_name)) as $settings) {
- $settings->delete();
- }
- }
- /**
- * Implements hook_search_api_saved_searches_settings_insert().
- *
- * Clear block caches when new enabled saved search settings are saved.
- */
- function search_api_saved_searches_search_api_saved_searches_settings_insert(SearchApiSavedSearchesSettings $settings) {
- if ($settings->enabled) {
- block_flush_caches();
- cache_clear_all('*', 'cache_block', TRUE);
- }
- }
- /**
- * Implements hook_search_api_saved_searches_settings_update().
- *
- * Clear block caches when saved search settings are enabled or disabled.
- */
- function search_api_saved_searches_search_api_saved_searches_settings_update(SearchApiSavedSearchesSettings $settings) {
- if ($settings->enabled != $settings->original->enabled) {
- block_flush_caches();
- cache_clear_all('*', 'cache_block', TRUE);
- }
- // React if the new results determination method was switched to/from the
- // ID-based method.
- $options = $settings->options + array('date_field' => NULL);
- $orig_options = $settings->original->options + array('date_field' => NULL);
- if ($options['date_field'] != $orig_options['date_field']) {
- if (!$options['date_field']) {
- // When we switch to the ID-based method from another one, we need to save
- // the current results.
- foreach (search_api_saved_search_load_multiple(FALSE, array('settings_id' => $settings->delta)) as $search) {
- // This will automatically populate the results.
- $search->save();
- }
- }
- elseif (!$orig_options['date_field']) {
- // If we previously used the ID-based method and are now using a
- // field-based one, set the saved results for all searches to NULL.
- db_update('search_api_saved_search')
- ->fields(array(
- 'results' => NULL,
- ))
- ->condition('settings_id', $settings->delta)
- ->execute();
- }
- }
- }
- /**
- * Implements hook_search_api_saved_searches_settings_delete().
- *
- * Clear block caches when enabled saved search settings are deleted.
- */
- function search_api_saved_searches_search_api_saved_searches_settings_delete(SearchApiSavedSearchesSettings $settings) {
- // Only react on real delete, not revert.
- if ($settings->status & ENTITY_IN_CODE) {
- return;
- }
- foreach (search_api_saved_search_load_multiple(FALSE, array('settings_id' => $settings->delta)) as $search) {
- $search->delete();
- }
- if ($settings->enabled) {
- block_flush_caches();
- cache_clear_all('*', 'cache_block', TRUE);
- }
- }
- /**
- * Loads a single settings object.
- *
- * @param int|string $id
- * The settings' identifier or delta.
- * @param bool $reset
- * If TRUE, will reset the internal entity cache.
- *
- * @return SearchApiSavedSearchesSettings
- * The requested entity, or FALSE if no settings for that ID exist.
- */
- function search_api_saved_searches_settings_load($id, $reset = FALSE) {
- $ret = search_api_saved_searches_settings_load_multiple(array($id), array(), $reset);
- return $ret ? reset($ret) : FALSE;
- }
- /**
- * Loads multiple settings objects.
- *
- * @param array|false $ids
- * The settings' identifiers or deltas; or FALSE to load all settings objects.
- * @param array $conditions
- * Associative array of field => value conditions that returned objects must
- * satisfy.
- * @param bool $reset
- * If TRUE, will reset the internal entity cache.
- *
- * @return SearchApiSavedSearchesSettings[]
- * All saved search settings matching the conditions, keyed by delta.
- */
- function search_api_saved_searches_settings_load_multiple($ids = FALSE, array $conditions = array(), $reset = FALSE) {
- $settings = entity_load('search_api_saved_searches_settings', $ids, $conditions, $reset);
- return entity_key_array_by_property($settings, 'delta');
- }
- /**
- * Loads a single saved search object.
- *
- * @param $id
- * The saved search's ID.
- * @param $reset
- * If TRUE, will reset the internal entity cache.
- *
- * @return SearchApiSavedSearch
- * The requested entity, or FALSE if no settings for that ID exist.
- */
- function search_api_saved_search_load($id, $reset = FALSE) {
- $ret = entity_load('search_api_saved_search', array($id), array(), $reset);
- return $ret ? reset($ret) : FALSE;
- }
- /**
- * Loads multiple saved search objects.
- *
- * @param int[]|false $ids
- * The saved search's IDs; or FALSE to load all saved searches.
- * @param array $conditions
- * Associative array of field => value conditions that returned objects must
- * satisfy.
- * @param bool $reset
- * If TRUE, will reset the internal entity cache.
- *
- * @return SearchApiSavedSearch[]
- * All saved searches matching the conditions, keyed by their IDs.
- */
- function search_api_saved_search_load_multiple($ids = FALSE, array $conditions = array(), $reset = FALSE) {
- return entity_load('search_api_saved_search', $ids, $conditions, $reset);
- }
- /**
- * Determine whether the current user can create a saved search for specific settings.
- *
- * @param SearchApiSavedSearchesSettings $settings
- * The settings to check for. May be NULL, if $manual is TRUE, to check if any
- * saved searches can be created manually.
- * @param boolean $manual
- * (optional) If TRUE, check access for creating a saved search manually.
- *
- * @return boolean
- * TRUE iff the current user is allowed to create a new saved search.
- */
- function search_api_saved_search_create_access(SearchApiSavedSearchesSettings $settings = NULL, $manual = FALSE) {
- if ($manual) {
- if (isset($settings)) {
- if (!$settings->enabled || empty($settings->options['manual']['allow'])) {
- return FALSE;
- }
- }
- else {
- foreach (search_api_saved_searches_settings_load_multiple(FALSE, array('enabled' => TRUE)) as $settings) {
- if (!empty($settings->options['manual']['allow'])) {
- $found = TRUE;
- break;
- }
- }
- if (empty($found)) {
- return FALSE;
- }
- }
- }
- elseif (!$settings->enabled) {
- return FALSE;
- }
- if (user_access('administer search_api_saved_searches')) {
- return TRUE;
- }
- if (!user_access('use search_api_saved_searches')) {
- return FALSE;
- }
- if (!isset($settings)) {
- return TRUE;
- }
- // @todo Check settings-specific access rules, when there are any.
- return TRUE;
- }
- /**
- * Determine access to the edit interface for saved searches of a given user.
- *
- * This is both used to determine whether the current user can edit a specific
- * saved search, or whether she can display the overview of the user's saved
- * searches.
- * For anonymous users' searches an access key is generated that allows
- * accessing and editing the searches.
- *
- * @param $account
- * (optional) The user whose saved search(es) would be edited. NULL for guest.
- * @param SearchApiSavedSearch $search
- * (optional) The saved search involved, if there is just a single one.
- * @param string $key
- * (optional) The secret key to access the search.
- *
- * @return boolean
- * TRUE iff the current user is allowed to edit the saved search(es).
- */
- function search_api_saved_search_edit_access($account = NULL, SearchApiSavedSearch $search = NULL, $key = NULL) {
- global $user;
- if (empty($account)) {
- if (empty($search)) {
- return FALSE;
- }
- $account = (object) array('uid' => $search->uid);
- }
- if (user_access('administer search_api_saved_searches')) {
- return TRUE;
- }
- // Barring admins, the only way to edit anonymous users' saved searches is by
- // providing the access key. There is no overview of all saved searches.
- if (!empty($key) && !empty($search->options['key']) && $search->options['key'] == $key) {
- return TRUE;
- }
- if ($account->uid == 0) {
- return FALSE;
- }
- if ($account->uid != $user->uid || !user_access('use search_api_saved_searches')) {
- return FALSE;
- }
- if (isset($search)) {
- return $search->uid == $account->uid;
- }
- foreach (search_api_saved_searches_settings_load_multiple() as $settings) {
- // Allow access if users can manually create searches.
- if (!empty($settings->options['manual']['allow'])) {
- return TRUE;
- }
- // Allow access if the list should always be displayed.
- if (!empty($settings->options['show_empty_list'])) {
- return TRUE;
- }
- }
- // Let the user view the listing if there are any saved searches.
- $select = db_select('search_api_saved_search', 's')
- ->condition('uid', $account->uid);
- $select->addExpression('COUNT(1)');
- return (bool) $select->execute()->fetchField();
- }
- /**
- * Implements hook_block_info().
- */
- function search_api_saved_searches_block_info() {
- $blocks = array();
- foreach (search_api_saved_searches_settings_load_multiple(FALSE, array('enabled' => TRUE)) as $settings) {
- try {
- $blocks[$settings->delta] = array(
- 'info' => t('!index: Save search', array('!index' => $settings->index()->name)),
- // @todo Is this cache setting correct?
- 'cache' => DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE,
- );
- }
- catch (SearchApiException $e) {}
- }
- return $blocks;
- }
- /**
- * Implements hook_ctools_block_info().
- */
- function search_api_saved_searches_ctools_block_info($module, $delta, &$info) {
- $info['category'] = t('Search API Saved Searches');
- // Allow blocks to be used before the search results in Panels.
- $info['render last'] = TRUE;
- }
- /**
- * Implements hook_block_configure().
- */
- function search_api_saved_searches_block_configure($delta = '') {
- $settings = search_api_saved_searches_settings_load($delta);
- $form['settings_link'] = array(
- '#markup' => l(t('To saved search settings'), 'admin/config/search/search_api/index/' . $settings->index_id . '/saved_searches'),
- );
- return $form;
- }
- /**
- * Implements hook_block_view().
- */
- function search_api_saved_searches_block_view($delta = '') {
- $searches = search_api_current_search();
- if (!$searches) {
- return;
- }
- if (!user_access('use search_api_saved_searches')) {
- return;
- }
- $settings = search_api_saved_searches_settings_load($delta);
- if (!$settings || !search_api_saved_search_create_access($settings)) {
- return;
- }
- $index_id = $settings->index_id;
- $options = $settings->options;
- $ids_list = drupal_map_assoc($options['ids_list']);
- $search_ids = variable_get('search_api_saved_searches_search_ids', array());
- foreach ($searches as $id => $data) {
- if ($data[0]->getIndex()->machine_name == $index_id){
- if (!isset($search_ids[$index_id][$id])) {
- $search_ids[$index_id][$id] = $id;
- $search_ids_updated = TRUE;
- }
- if (isset($ids_list[$id]) != $options['default_true']) {
- if (isset($query)) {
- watchdog('search_api_saved_searches', 'Two matching searches on index %index for saved search block.',
- array('%index' => $settings->index()->name), WATCHDOG_WARNING,
- l(t('view page'), $_GET['q'], array('query' => drupal_get_query_parameters())));
- }
- else {
- list($query, $results) = $data;
- }
- }
- }
- }
- if (isset($search_ids_updated)) {
- variable_set('search_api_saved_searches_search_ids', $search_ids);
- }
- if (empty($query)) {
- return;
- }
- return array(
- 'subject' => t('Save search'),
- 'content' => array('form' => drupal_get_form('search_api_saved_searches_save_form', $settings, $query)),
- );
- }
- /**
- * Form builder for creating a new saved search.
- *
- * @param SearchApiSavedSearchesSettings $settings
- * The saved search settings with which to create a new saved search.
- * @param SearchApiQueryInterface $query
- * (optional) If creating a saved search for an already executed query, the
- * query.
- *
- * @see search_api_saved_searches_save_form_validate()
- * @see search_api_saved_searches_save_form_submit()
- * @ingroup forms
- */
- function search_api_saved_searches_save_form(array $form, array &$form_state, SearchApiSavedSearchesSettings $settings, SearchApiQueryInterface $query = NULL) {
- global $user;
- if (!isset($form_state['query']) && isset($query)) {
- $options = $query->getOptions();
- // When checking for new results, we need all results.
- // @todo Make this configurable?
- unset($options['offset'], $options['limit']);
- $options['search id'] = $settings->delta . ':' . 'saved-search';
- $form_state['query'] = array(
- 'index_id' => $query->getIndex()->machine_name,
- 'keys' => $query->getKeys(),
- 'original_keys' => $query->getOriginalKeys(),
- 'fields' => $query->getFields(),
- 'filters' => $query->getFilter()->getFilters(),
- 'options' => $options,
- );
- }
- $form_state['settings'] = $settings;
- $description = $settings->getTranslatedOption('description');
- if (!empty($description)) {
- $form['description'] = array(
- '#type' => 'item',
- '#description' => _filter_autop(check_plain($description)),
- );
- }
- if (empty($form_state['query'])) {
- $form['query'] = _search_api_saved_searches_create_search_form($settings);
- $form['name'] = array(
- '#type' => 'textfield',
- '#title' => t('Name'),
- '#description' => t('Enter the name that will be displayed for this saved search.'),
- '#maxlength' => 50,
- );
- }
- else {
- $form['#prefix'] = '<div id="search-api-saved-searches-save-form-wrapper">';
- $form['#suffix'] = '</div>';
- if (empty($settings->options['choose_name'])) {
- $form['name'] = array(
- '#type' => 'value',
- '#value' => _search_api_saved_searches_create_name($form_state['query']),
- );
- }
- else {
- $form['name'] = array(
- '#type' => 'textfield',
- '#title' => t('Name'),
- '#maxlength' => 50,
- '#size' => 16,
- '#required' => TRUE,
- '#default_value' => _search_api_saved_searches_create_name($form_state['query']),
- );
- }
- }
- if (empty($user->mail) || $settings->options['registered_choose_mail']) {
- $form['mail'] = array(
- '#type' => 'textfield',
- '#title' => t('E-mail address'),
- '#maxlength' => 100,
- '#size' => 16,
- '#default_value' => isset($user->mail) ? $user->mail : '',
- '#required' => TRUE,
- );
- }
- else {
- $form['mail'] = array(
- '#type' => 'value',
- '#value' => $user->mail,
- );
- }
- if ($settings->options['user_select_interval'] && count($settings->options['interval_options']) > 1) {
- $form['notify_interval'] = array(
- '#type' => 'select',
- '#title' => t('Notification interval'),
- '#options' => $settings->getTranslatedOption('interval_options'),
- '#required' => TRUE,
- );
- }
- else {
- $form['notify_interval'] = array(
- '#type' => 'value',
- '#value' => $settings->options['user_select_interval'] ? reset($settings->options['interval_options']) : $settings->options['set_interval'],
- );
- }
- if (!empty($form_state['query'])) {
- $form_state['page'] = array(
- 'path' => $_GET['q'],
- 'query' => drupal_get_query_parameters(),
- );
- }
- $form['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Save search'),
- '#ajax' => array(
- 'callback' => 'search_api_saved_searches_save_form_ajax',
- 'wrapper' => 'search-api-saved-searches-save-form-wrapper',
- 'effect' => 'fade',
- 'method' => 'replace',
- ),
- '#executes_submit_callback' => TRUE,
- );
- // For manual search creation we don't need AJAX functionality.
- if (empty($form_state['query'])) {
- unset($form['submit']['#ajax']);
- }
- return $form;
- }
- /**
- * Helper function for creating a form for manually creating a saved search.
- */
- function _search_api_saved_searches_create_search_form(SearchApiSavedSearchesSettings $settings) {
- $index = $settings->index();
- $wrapper = $index->entityWrapper();
- $options = isset($settings->options['manual']) ? $settings->options['manual'] : array();
- $form['#tree'] = TRUE;
- $form['fields'] = array(
- '#type' => 'fieldset',
- '#title' => t('Search'),
- );
- if (!empty($options['fulltext'])) {
- $form['fields']['search_api_saved_searches_fulltext'] = array(
- '#type' => 'textfield',
- '#title' => t('Keywords'),
- );
- }
- if (!empty($options['fields'])) {
- foreach ($options['fields'] as $field) {
- if (!empty($index->options['fields'][$field])) {
- // Extract the necessary field information out of the wrapper.
- $tmp = $wrapper;
- foreach (explode(':', $field) as $part) {
- if (!isset($tmp->$part)) {
- continue 2;
- }
- $tmp = $tmp->$part;
- }
- $info = $tmp->info();
- $form['fields'][$field]['#title'] = isset($info['label']) ? $info['label'] : $field;
- if ($optList = $tmp->optionsList('view')) {
- $optList = array(NULL => t('- Any -')) + $optList;
- $form['fields'][$field]['#type'] = 'select';
- $form['fields'][$field]['#options'] = $optList;
- }
- else {
- $form['fields'][$field]['#type'] = 'textfield';
- }
- }
- }
- }
- return $form;
- }
- /**
- * AJAX submit handler for search_api_saved_searches_save_form().
- */
- function search_api_saved_searches_save_form_ajax(array $form, array &$form_state) {
- return form_get_errors() ? $form : array('#theme' => 'status_messages');
- }
- /**
- * Form validation handler for search_api_saved_searches_save_form().
- *
- * @see search_api_saved_searches_save_form()
- * @see search_api_saved_searches_save_form_submit()
- */
- function search_api_saved_searches_save_form_validate(array $form, array &$form_state) {
- if ($msg = user_validate_mail($form_state['values']['mail'])) {
- form_error($form['mail'], $msg);
- }
- }
- /**
- * Form validation handler for search_api_saved_searches_save_form().
- *
- * @return boolean
- * TRUE iff the search was successfully saved.
- *
- * @see search_api_saved_searches_save_form()
- * @see search_api_saved_searches_save_form_validate()
- */
- function search_api_saved_searches_save_form_submit(array $form, array &$form_state) {
- global $user;
- $values = $form_state['values'];
- $settings = $form_state['settings'];
- if (empty($form_state['query'])) {
- $fields = $values['query']['fields'];
- $query = array(
- 'keys' => isset($fields['search_api_saved_searches_fulltext']) ? $fields['search_api_saved_searches_fulltext'] : NULL,
- 'fields' => NULL,
- 'filters' => array(),
- 'options' => array(
- 'search id' => $settings->delta . ':' . 'saved-search',
- ),
- );
- unset($fields['search_api_saved_searches_fulltext']);
- foreach ($fields as $field => $value) {
- if ($value || is_numeric($value)) {
- $query['filters'][] = array($field, $value, '=');
- }
- else {
- unset($fields[$field]);
- }
- }
- if (empty($values['name'])) {
- $query['original_keys'] = $query['keys'];
- $values['name'] = _search_api_saved_searches_create_name($query);
- unset($query['original_keys']);
- }
- if (empty($form_state['page']) && !empty($settings->options['manual']['page']['path'])) {
- $page_options = $settings->options['manual']['page'];
- $form_state['page'] = array(
- 'path' => $page_options['path'],
- 'query' => array(),
- );
- if (isset($query['keys'])) {
- if (empty($page_options['fulltext'])) {
- $form_state['page']['path'] .= '/' . $query['keys'];
- }
- else {
- $form_state['page']['query'][$page_options['fulltext']] = $query['keys'];
- }
- }
- foreach ($fields as $field => $value) {
- if (empty($page_options['direct_filter_params'])) {
- $form_state['page']['query']['filter'][$field][] = '"' . $value . '"';
- }
- else {
- $form_state['page']['query'][$field] = $value;
- }
- }
- }
- }
- else {
- $query = array_intersect_key($form_state['query'], drupal_map_assoc(array('keys', 'fields', 'filters', 'options')));
- }
- // Enable the saved search right away, if a logged-in user uses their own mail
- // address, or when they have admin privileges, or when activation mails are
- // generally deactivated, or if there are already active saved searches for
- // that user with that mail address. Otherwise, an activation mail will be
- // sent.
- $enabled = (!empty($user->mail) && $user->mail == $values['mail'])
- || user_access('administer search_api_saved_searches')
- || empty($settings->options['mail']['activate']['send'])
- || ($user->uid && search_api_saved_search_load_multiple(FALSE, array('enabled' => TRUE, 'uid' => $user->uid, 'mail' => $values['mail'])));
- // If an anonymous user uses an existing user's mail address to create a
- // saved search, file the saved search under that user right away.
- $uid = $user->uid;
- if (!$uid && ($users = user_load_multiple(FALSE, array('mail' => $values['mail'], 'status' => 1)))) {
- $uid = key($users);
- }
- $search = entity_create('search_api_saved_search', array(
- 'uid' => $uid,
- 'settings_id' => $settings->delta,
- 'enabled' => $enabled,
- 'name' => $values['name'],
- 'mail' => $values['mail'],
- 'created' => REQUEST_TIME,
- 'last_queued' => REQUEST_TIME,
- 'last_execute' => REQUEST_TIME,
- 'notify_interval' => $values['notify_interval'],
- 'query' => $query,
- 'options' => array(),
- ));
- // Choose where to redirect.
- if (!empty($form_state['page'])) {
- $search->options['page'] = $form_state['page'];
- $form_state['redirect'] = array($form_state['page']['path'], $form_state['page']);
- }
- elseif ($user->uid) {
- $form_state['redirect'] = 'user/' . $user->uid . '/saved-searches';
- }
- // Save saved search.
- $ret = $search->save();
- // Display success or error message.
- if (!$ret) {
- drupal_set_message(t('An error occurred while trying to save the search. Please contact the site administrator.'), 'error');
- $form_state['rebuild'] = TRUE;
- return FALSE;
- }
- else {
- if ($enabled) {
- if ($search->notify_interval < 0) {
- drupal_set_message(t('Your saved search was successfully created.'));
- }
- else {
- drupal_set_message(t('Your saved search was successfully created. You will receive e-mail notifications for new results in the future.'));
- }
- }
- else {
- drupal_set_message(t('Your saved search was successfully created. You will soon receive an e-mail with a confirmation link to activate it.'));
- }
- return TRUE;
- }
- }
- /**
- * Helper function for creating a name for a saved search with the given query.
- */
- function _search_api_saved_searches_create_name(array $query) {
- if (!empty($query['original_keys']) && is_scalar($query['original_keys'])) {
- $ret[] = $query['original_keys'];
- }
- $name = isset($ret) ? implode(' / ', $ret) : t('Saved search');
- drupal_alter('search_api_saved_search_create_name', $name, $query);
- return $name;
- }
- /**
- * Implements hook_mail().
- *
- * Two mails are provided, which expect the following values in the $params
- * array:
- * - activate:
- * - search: The SearchApiSavedSearch object that should be activated.
- * - user: The user object to which the saved search belongs.
- * - notify:
- * - user: The user to which the executed searches belong.
- * - settings: The settings with which the searches are associated.
- * - searches: An array containing arrays with the following keys:
- * - search: A SearchApiSavedSearch object that was checked.
- * - num_results: The number of new results for that saved search.
- * - results: An array of entities representing the new results for that
- * saved search.
- */
- function search_api_saved_searches_mail($key, array &$message, array $params) {
- $language = $message['language'];
- switch ($key) {
- case 'activate':
- $search = $params['search'];
- $settings = $search->settings();
- $data = array(
- 'user' => $params['user'],
- 'search_api_saved_search_info' => array(
- 'search' => $search,
- 'results' => array(),
- ),
- );
- $title = $settings->getTranslatedOption('mail.activate.title', $language->language);
- $message['subject'] .= token_replace($title, $data, array('language' => $language, 'sanitize' => FALSE));
- $body = $settings->getTranslatedOption('mail.activate.body', $language->language);
- $message['body'][] = token_replace($body, $data, array('language' => $language, 'sanitize' => FALSE));
- break;
- case 'notify':
- $settings = $params['settings'];
- $search = $params['searches'][0]['search'];
- $data = array(
- 'user' => $params['user'],
- 'search_api_saved_searches' => $params['searches'],
- 'search_api_saved_search_info' => array(
- 'search' => $search,
- 'results' => array(),
- ),
- );
- $title = $settings->getTranslatedOption('mail.notify.title', $language->language);
- $message['subject'] .= token_replace($title, $data, array('language' => $language, 'sanitize' => FALSE));
- $body = $settings->getTranslatedOption('mail.notify.body', $language->language);
- $message['body'][] = token_replace($body, $data, array('language' => $language, 'sanitize' => FALSE));
- break;
- }
- }
- /**
- * Implements hook_cron().
- *
- * Queue the saved searches that should be checked for new items.
- */
- function search_api_saved_searches_cron() {
- // Get all searches whose last execution lies more than the notify_interval
- // in the past. Add a small amount to the current time, so small differences
- // in execution time don't result in a delay until the next cron run.
- $ids = db_select('search_api_saved_search', 's')
- ->fields('s', array('id'))
- ->condition('enabled', 1)
- ->condition('notify_interval', 0, '>=')
- ->where('last_execute >= last_queued')
- ->where('last_queued + notify_interval < :time', array(':time' => REQUEST_TIME + 15))
- ->execute()
- ->fetchCol();
- if (!$ids) {
- return;
- }
- // Get the queue and load the queries.
- $queue = DrupalQueue::get('search_api_saved_searches_check_updates');
- $searches = search_api_saved_search_load_multiple($ids);
- // Group the search according to mail and settings. Grouping by mail prevents
- // a user from getting several mails at once, for different searches. Grouping
- // by settings is necessary since the mails can differ between settings.
- $user_searches = array();
- foreach ($searches as $search) {
- $user_searches[$search->mail . ' ' . $search->settings_id][] = $search->id;
- // Set the last execution timestamp now, so the interval doesn't move and we
- // don't get problems if the next cron run occurs before the queue is
- // completely executed.
- $search->last_queued = REQUEST_TIME;
- $search->save();
- }
- foreach ($user_searches as $searches) {
- $queue->createItem($searches);
- }
- }
- /**
- * Implements hook_cron_queue_info().
- *
- * Defines a queue for saved searches that should be checked for new items.
- */
- function search_api_saved_searches_cron_queue_info() {
- return array(
- 'search_api_saved_searches_check_updates' => array(
- 'worker callback' => 'search_api_saved_searches_check_updates',
- 'time' => 10,
- ),
- );
- }
- /**
- * Checks for new results for saved searches, and sends a mail if necessary.
- *
- * Used as a worker callback for the homonymous cron queue.
- *
- * @param int[] $search_ids
- * The IDs of the saved searches to check for new results. All of these should
- * have the same mail address and base settings.
- *
- * @throws SearchApiException
- * If an error occurred in one of the searches.
- *
- * @see search_api_saved_searches_cron_queue_info()
- */
- function search_api_saved_searches_check_updates(array $search_ids) {
- if (!$search_ids) {
- return;
- }
- // Since in earlier versions this function got the loaded searches passed
- // directly instead of just IDs, and there might still be some such items in
- // the queue when updating to the new style, we have to stay
- // backwards-compatible here. So, when an array of loaded searches is passed,
- // we first replace them with their IDs and only then load them again.
- if (!is_scalar(reset($search_ids))) {
- /** @var SearchApiSavedSearch[] $searches */
- $searches = $search_ids;
- $search_ids = array();
- foreach ($searches as $search) {
- $search_ids[] = $search->id;
- }
- }
- $searches = search_api_saved_search_load_multiple($search_ids, array('enabled' => 1));
- if (!$searches) {
- return;
- }
- $search = $searches[key($searches)];
- $settings = $search->settings();
- $index = $settings->index();
- $mail_params = array();
- foreach ($searches as $search) {
- try {
- // Make sure we run the query as the user who owns the saved search.
- // Otherwise node access will not work properly.
- $search->query['options']['search_api_access_account'] = $search->uid;
- // Get actual results for the query.
- $query = $search->query();
- // If a date field is set, use that to filter results.
- if (!empty($settings->options['date_field'])) {
- $query->condition($settings->options['date_field'], $search->last_execute, '>');
- }
- $response = $query->execute();
- if (!empty($response['results'])) {
- $old = array();
- $new = $results = drupal_map_assoc(array_keys($response['results']));
- if (empty($settings->options['date_field'])) {
- // ID-based method: Compare these results to the old ones.
- $old = drupal_map_assoc(explode(',', $search->results));
- $new = array_diff_key($results, $old);
- }
- if ($new) {
- // We have new results: send them to the user.
- // Only load those items that will be sent.
- $sent_new = $new;
- if (!empty($settings->options['mail']['notify']['max_results'])) {
- $sent_new = array_slice($new, 0, $settings->options['mail']['notify']['max_results']);
- }
- $sent_new = $index->loadItems($sent_new);
- $new_results = $sent_new + $new;
- // Let other modules alter these results.
- drupal_alter('search_api_saved_searches_new_results', $new_results, $search);
- if ($new_results) {
- // We have to slice again in case some items were moved around or
- // removed by alter hooks.
- $sent_new = $new_results;
- if (!empty($settings->options['mail']['notify']['max_results'])) {
- $sent_new = array_slice($new_results, 0, $settings->options['mail']['notify']['max_results']);
- // Now some of the top results might still be unloaded.
- if ($unloaded = array_filter($sent_new, 'is_scalar')) {
- $sent_new = $index->loadItems($unloaded) + $sent_new;
- }
- }
- $num_results = count($new_results);
- $mail_params['searches'][] = array(
- 'search' => $search,
- 'num_results' => $num_results,
- 'results' => $sent_new,
- );
- }
- }
- if (empty($settings->options['date_field']) && ($new || array_diff($old, $results))) {
- // The results changed in some way: store the latest version.
- $search->results = implode(',', $results);
- }
- }
- // Use time() instead of REQUEST_TIME to minimize the potential of sending
- // duplicate results due to longer-running cron queue workers.
- $search->last_execute = time();
- $search->save();
- }
- catch (SearchApiException $e) {
- $args = _drupal_decode_exception($e);
- $args['@id'] = $search->id;
- throw new SearchApiException(t('%type while trying to check for new results on saved search @id: !message in %function (line %line of %file).', $args));
- }
- }
- // If we set any searches in the mail parameters, send the mail.
- if ($mail_params) {
- $mail_params['user'] = user_load($search->uid);
- $mail_params['settings'] = $settings;
- $message = drupal_mail('search_api_saved_searches', 'notify', $search->mail,
- user_preferred_language($mail_params['user']), $mail_params);
- if ($message['result']) {
- watchdog('search_api_saved_searches', 'A mail with new saved search results was sent to @mail.',
- array('@mail' => $search->mail), WATCHDOG_INFO);
- }
- }
- }
|