diff --git a/search_api_saved_searches.admin.inc b/search_api_saved_searches.admin.inc index 315f1a9..082f5e6 100644 --- a/search_api_saved_searches.admin.inc +++ b/search_api_saved_searches.admin.inc @@ -69,6 +69,7 @@ function search_api_saved_searches_index_edit(array $form, array &$form_state, S $settings->options['mail'] += array( 'activate' => array(), 'notify' => array(), + 'disable_mail' => FALSE, ); $settings->options['mail']['activate'] += array( 'send' => TRUE, @@ -304,12 +305,26 @@ You can configure your saved searches at the following address: '#collapsible' => TRUE, '#collapsed' => $settings->enabled, ); + + $form['options']['mail']['disable_mail'] = array( + '#type' => 'checkbox', + '#title' => t('Disable e-mail notifications'), + '#description' => t("Checking this will disable the e-mail notifications of saved searches on cron."), + '#default_value' => $options['mail']['disable_mail'], + '#parents' => array('options', 'mail', 'disable_mail'), + ); + $form['options']['mail']['activate_send'] = array( '#type' => 'checkbox', '#title' => t('Use activation mail for anonymous users'), '#description' => t("Will need saved searches created by anonymous users, or by normal users with an e-mail address that isn't their own, to be activated by clicking a link in an e-mail."), '#default_value' => $options['mail']['activate']['send'], '#parents' => array('options', 'mail', 'activate', 'send'), + '#states' => array( + 'invisible' => array( + ':input[name="options[mail][disable_mail]"]' => array('checked' => TRUE), + ), + ), ); $form['options']['mail']['activate'] = array( @@ -319,6 +334,7 @@ You can configure your saved searches at the following address: '#collapsed' => $settings->enabled, ); $form['options']['mail']['activate']['#states']['visible'][':input[name="options[mail][activate][send]"]']['checked'] = TRUE; + $form['options']['mail']['activate']['#states']['visible'][':input[name="options[mail][disable_mail]"]']['checked'] = FALSE; $form['options']['mail']['activate']['title'] = array( '#type' => 'textfield', '#title' => t('Subject'), @@ -346,6 +362,11 @@ You can configure your saved searches at the following address: '#title' => t('Notification mails'), '#collapsible' => TRUE, '#collapsed' => $settings->enabled, + '#states' => array( + 'visible' => array( + ':input[name="options[mail][disable_mail]"]' => array('checked' => FALSE) + ), + ), ); $form['options']['mail']['notify']['title'] = array( '#type' => 'textfield', diff --git a/search_api_saved_searches.module b/search_api_saved_searches.module index b9ac346..0dd722b 100644 --- a/search_api_saved_searches.module +++ b/search_api_saved_searches.module @@ -363,7 +363,137 @@ 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_rules_action_info(). + */ +function search_api_saved_searches_rules_action_info() { + return array( + 'search_api_saved_searches_rules_index_results' => array( + 'label' => t('Fetch the saved searches'), + 'parameter' => array( + 'index_id' => array( + 'type' => 'integer', + 'label' => t('Index for which to retrieve searches'), + 'description' => t('Select the search index for which saved searches should be retrieved.'), + 'options list' => '_search_api_saved_searches_settings_options_list', + ), + ), + 'provides' => array( + 'search_api_saved_search' => array( + 'type' => 'list', + 'label' => t('List of the IDs of the saved searches that require executing.'), + ), + ), + 'group' => t('Search API Saved Searches'), + ), + 'search_api_saved_searches_rules_get_saved_search_new_items' => array( + 'label' => t('Fetch the new results for a saved search'), + 'parameter' => array( + 'index_id' => array( + 'type' => 'integer', + 'label' => t('Saved search ID'), + 'description' => t('The ID of the saved search for which to retrieve new results.'), + ), + ), + 'provides' => array( + 'search' => array( + 'type' => 'search_api_saved_search', + 'label' => t('The executed search.'), + ), + 'result_count' => array( + 'type' => 'integer', + 'label' => t('The count of results that were found.'), + ), + 'results' => array( + 'type' => 'list', + 'label' => t('The list of new results for the saved search since it was last executed.'), + ), + ), + 'group' => t('Search API Saved Searches'), + ), + ); +} + +/** + * Retrieves the options list for selecting a saved search settings entity. + * + * @return string[] + * An associative array mapping saved search settings IDs to index names. + */ +function _search_api_saved_searches_settings_options_list() { + // Fetch the list of saved searches setting and make a list of values. + $entities = entity_load('search_api_saved_searches_settings'); + $ids = array(); + foreach ($entities as $entity) { + $ids[$entity->index_id][] = $entity->id; + } + + $indexes = search_api_index_load_multiple(array_keys($ids)); + $options = array(); + foreach ($indexes as $index_id => $index) { + foreach ($ids[$index_id] as $settings_id) { + $options[$settings_id] = $index->label(); + } + } + return $options; +} + +/** + * Callback: Implements the "Fetch the saved searches" rules action. + * + * @param int|null $settings_id + * (optional) The ID of the saved search settings entity for which to retrieve + * searches. NULL to retrieve for all. + * + * @return array + * An associative array with key "search_api_saved_search" containing the IDs + * of all searches that should be executed. + */ +function search_api_saved_searches_rules_index_results($settings_id) { + return array( + 'search_api_saved_search' => search_api_saved_searches_get_searches_to_be_executed($settings_id), + ); +} + +/** + * Retrieves the saved searches that need to be executed. + * + * @param int|null $settings_id + * (optional) The ID of the saved search settings entity for which to retrieve + * searches. NULL to retrieve for all. + * + * @return int[] + * The IDs of all searches that need to be executed. + */ +function search_api_saved_searches_get_searches_to_be_executed($settings_id = NULL) { + // 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. + $select = db_select('search_api_saved_search', 's'); + $select->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)); + if ($settings_id !== NULL) { + $select->condition('settings_id', $settings_id); + } + return $select->execute()->fetchCol(); +} + +/** + * Callback: Implements the "Fetch the new results for a search" rules action. + * + * @param int $search_id + * The ID of the saved search setting entity. + * + * @return array + * Array of the results count and the results list for the given search ID. + */ +function search_api_saved_searches_rules_get_saved_search_new_items($search_id) { + $search = search_api_saved_search_load($search_id); + return search_api_saved_search_fetch_search_results($search); +} /** * Implements hook_search_api_index_update(). @@ -1172,17 +1302,7 @@ function search_api_saved_searches_mail($key, array &$message, array $params) { * 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(); + $ids = search_api_saved_searches_get_searches_to_be_executed(); if (!$ids) { return; } @@ -1194,12 +1314,18 @@ function search_api_saved_searches_cron() { // 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(); + // Check the dissable mail notification value if not set then don't add the + // the item to the queue. + $settings = search_api_saved_searches_settings_load_multiple(FALSE, array('index_id' => $search->settings_id)); + $setting = reset($settings); + if (!$setting->options['mail']['disable_mail']) { + $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); @@ -1260,81 +1386,108 @@ function search_api_saved_searches_check_updates(array $search_ids) { $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(); + $results = search_api_saved_search_fetch_search_results($search); + if (!$results['result_count']) { + continue; } - 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)); + // Load the result items. + if ($results['results']) { + $results['results'] = $index->loadItems($results['results']); } + $mail_params['searches'][] = $results; } // 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); + 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); + array('@mail' => $search->mail), WATCHDOG_INFO); + } + } +} + +/** + * Fetches the results for a given search object. + * + * @param SearchApiSavedSearch $search + * The saved search to check for new results. + * + * @return array + * An associative array with the following keys: + * - search: The executed search. + * - result_count: The number of new results. + * - results: The IDs of the new results. + * + * @throws SearchApiException + * If an error occurred in the search. + */ +function search_api_saved_search_fetch_search_results(SearchApiSavedSearch $search) { + $return = array( + 'search' => $search, + 'result_count' => 0, + 'results' => array(), + ); + + $settings = $search->settings(); + 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']); + } + $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']); + } + $return['result_count'] = count($new_results); + $return['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)); } + + return $return; }