diff --git a/includes/index_entity.inc b/includes/index_entity.inc index 7042b82..a5e2c93 100644 --- includes/index_entity.inc +++ includes/index_entity.inc @@ -106,6 +106,11 @@ class SearchApiIndex extends Entity { public $enabled; /** + * @var integer + */ + public $read_only; + + /** * Constructor as a helper to the parent constructor. */ public function __construct(array $values = array()) { @@ -117,33 +122,35 @@ class SearchApiIndex extends Entity { * database, or for the first time loaded from code). */ public function postCreate() { - // Remember items to index. - $entity_info = entity_get_info($this->entity_type); - if (!empty($entity_info['base table'])) { - // Use a subselect, which will probably be much faster than entity_load(). - // We just assume that no module/entity type will be stupid enough to use "base table" and - // "entity_keys[id]" in a different way than the default controller. - $id_field = $entity_info['entity keys']['id']; - $table = $entity_info['base table']; - $query = db_select($table, 't'); - $query->addField('t', $id_field, 'item_id'); - $query->addExpression(':index_id', 'index_id', array(':index_id' => $this->id)); - $query->addExpression('1', 'changed'); - - db_insert('search_api_item')->from($query)->execute(); - } - else { - // We have to use the slow entity_load(). - $entities = entity_load($this->entity_type, FALSE); - $query = db_insert('search_api_item')->fields(array('item_id', 'index_id', 'changed')); - foreach ($entities as $item_id => $entity) { - $query->values(array( - 'item_id' => $item_id, - 'index_id' => $this->id, - 'changed' => 1, - )); + if (!$this->read_only) { + // Remember items to index. + $entity_info = entity_get_info($this->entity_type); + if (!empty($entity_info['base table'])) { + // Use a subselect, which will probably be much faster than entity_load(). + // We just assume that no module/entity type will be stupid enough to use "base table" and + // "entity_keys[id]" in a different way than the default controller. + $id_field = $entity_info['entity keys']['id']; + $table = $entity_info['base table']; + $query = db_select($table, 't'); + $query->addField('t', $id_field, 'item_id'); + $query->addExpression(':index_id', 'index_id', array(':index_id' => $this->id)); + $query->addExpression('1', 'changed'); + + db_insert('search_api_item')->from($query)->execute(); + } + else { + // We have to use the slow entity_load(). + $entities = entity_load($this->entity_type, FALSE); + $query = db_insert('search_api_item')->fields(array('item_id', 'index_id', 'changed')); + foreach ($entities as $item_id => $entity) { + $query->values(array( + 'item_id' => $item_id, + 'index_id' => $this->id, + 'changed' => 1, + )); + } + $query->execute(); } - $query->execute(); } $server = $this->server(); @@ -166,7 +173,7 @@ class SearchApiIndex extends Entity { * not defined in code anymore. */ public function postDelete() { - if ($server = $this->server()) { + if (($server = $this->server()) && !$this->read_only) { if ($server->enabled) { $server->removeIndex($this); } @@ -177,6 +184,8 @@ class SearchApiIndex extends Entity { } } + // This should be executed even when the index is read only, in case at some + // point it was not read only. db_delete('search_api_item') ->condition('index_id', $this->id) ->execute(); @@ -221,7 +230,7 @@ class SearchApiIndex extends Entity { * the specified values. */ public function update(array $fields) { - $changeable = array('name' => 1, 'enabled' => 1, 'description' => 1, 'server' => 1, 'options' => 1); + $changeable = array('name' => 1, 'enabled' => 1, 'description' => 1, 'server' => 1, 'options' => 1, 'read_only' => 1); $changed = FALSE; foreach ($fields as $field => $value) { if (isset($changeable[$field]) && $value !== $this->$field) { @@ -243,7 +252,7 @@ class SearchApiIndex extends Entity { * TRUE on success, FALSE on failure. */ public function reindex() { - if (!$this->server) { + if (!$this->server || $this->read_only) { return TRUE; } $ret = _search_api_index_reindex($this->id); @@ -260,7 +269,7 @@ class SearchApiIndex extends Entity { * TRUE on success, FALSE on failure. */ public function clear() { - if (!$this->server) { + if (!$this->server || $this->read_only) { return TRUE; } @@ -359,6 +368,9 @@ class SearchApiIndex extends Entity { * An array of the IDs of all items that should be marked as indexed. */ public function index(array $items) { + if ($this->read_only) { + return array(); + } if (!$this->enabled) { throw new SearchApiException(t("Couldn't index values on '!name' index (index is disabled)", array('!name' => $this->name))); } @@ -624,6 +636,10 @@ class SearchApiIndex extends Entity { * @return array * An array containing all (or all indexed) fulltext fields defined for this * index. + * + * @TODO What does this mean when we're using a read-only index? I suspect it + * will not return anything, and I suspect that will be just fine. But I'm not + * sure. */ public function getFulltextFields($only_indexed = TRUE) { $i = $only_indexed ? 1 : 0; diff --git a/search_api.admin.inc b/search_api.admin.inc index 0f46bd8..6fc8429 100644 --- search_api.admin.inc +++ search_api.admin.inc @@ -661,6 +661,7 @@ function search_api_admin_index_view(SearchApiIndex $index = NULL, $action = NUL '#server' => $index->server(), '#options' => $index->options, '#status' => $index->status, + '#read_only' => $index->read_only, ); return $ret; @@ -684,9 +685,11 @@ function search_api_admin_index_view(SearchApiIndex $index = NULL, $action = NUL * - total_items: The total number of items that have to be indexed for this * index. * - status: The entity configuration status (in database, in code, etc.). + * - read_only: Boolean indicating whether this index is read only. */ function theme_search_api_index(array $variables) { extract($variables); + $output = ''; $output .= '

' . check_plain($name) . '

' . "\n"; @@ -728,7 +731,7 @@ function theme_search_api_index(array $variables) { $output .= '' . "\n"; } - if (!empty($options)) { + if (!$read_only && !empty($options)) { $output .= '
' . t('Index options') . '
' . "\n"; $output .= '
' . "\n"; $output .= '
' . t('Cron limit') . '
' . "\n"; @@ -762,6 +765,10 @@ function theme_search_api_index(array $variables) { $output .= '
' . "\n"; } + elseif ($read_only) { + $output .= '
' . t('Read only') . '
' . "\n"; + $output .= '
' . t('This index is read-only.') . '
' . "\n"; + } $output .= '
' . t('Configuration status') . '
' . "\n"; $output .= '
' . "\n"; @@ -1002,6 +1009,12 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI $form['server']['#options'][$server->machine_name] = t('@server_name (disabled)', array('@server_name' => $server->name)); } } + $form['read_only'] = array( + '#type' => 'checkbox', + '#title' => t('Read only'), + '#description' => t('Do not write to this index or track ids of entities in this index.'), + '#default_value' => $index->read_only, + ); $form['cron_limit'] = array( '#type' => 'textfield', '#title' => t('Cron limit'), @@ -1010,6 +1023,10 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI '#default_value' => isset($index->options['cron_limit']) ? $index->options['cron_limit'] : SEARCH_API_DEFAULT_CRON_LIMIT, '#size' => 4, '#attributes' => array('class' => array('search-api-cron-limit')), + '#element_validate' => array('_element_validate_integer'), + '#states' => array( + 'invisible' => array(':input[name="read_only"]' => array('checked' => TRUE)), + ), ); $form['submit'] = array( @@ -1021,17 +1038,6 @@ function search_api_admin_index_edit(array $form, array &$form_state, SearchApiI } /** - * Validation callback for search_api_admin_index_edit. - */ -function search_api_admin_index_edit_validate(array $form, array &$form_state) { - $cron_limit = $form_state['values']['cron_limit']; - if ($cron_limit != '' . ((int) $cron_limit)) { - // We don't enforce stricter rules and treat all negative values as -1. - form_set_error('cron_limit', t('The cron limit must be a number.')); - } -} - -/** * Submit callback for search_api_admin_index_edit. */ function search_api_admin_index_edit_submit(array $form, array &$form_state) { diff --git a/search_api.install b/search_api.install index 3b180ca..ef24bba 100644 --- search_api.install +++ search_api.install @@ -115,6 +115,13 @@ function search_api_schema() { 'not null' => TRUE, 'default' => 1, ), + 'read_only' => array( + 'description' => 'A flag indicating whether to write to this index.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), ) + entity_exportable_schema_fields(), 'indexes' => array( 'entity_type' => array('entity_type'), @@ -678,3 +685,18 @@ function search_api_update_7106() { } } } + +/** + * Add "read only" property to Search API index entities. + */ +function search_api_update_7107() { + $db_field = array( + 'description' => 'A flag indicating whether to write to this index.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('search_api_index', 'read_only', $db_field); + return t('Added a "read only" property to index entities.'); +} diff --git a/search_api.module b/search_api.module index cfda65e..2c8377a 100644 --- search_api.module +++ search_api.module @@ -98,7 +98,8 @@ function search_api_menu() { 'description' => 'Display and work on index status.', 'page callback' => 'drupal_get_form', 'page arguments' => array('search_api_admin_index_status_form', 5), - 'access arguments' => array('administer search_api'), + 'access callback' => '_search_api_access_index_indexer_config', + 'access arguments' => array(5), 'file' => 'search_api.admin.inc', 'weight' => -8, 'type' => MENU_LOCAL_TASK, @@ -120,7 +121,8 @@ function search_api_menu() { 'description' => 'Select indexed fields.', 'page callback' => 'drupal_get_form', 'page arguments' => array('search_api_admin_index_fields', 5), - 'access arguments' => array('administer search_api'), + 'access callback' => '_search_api_access_index_indexer_config', + 'access arguments' => array(5), 'file' => 'search_api.admin.inc', 'weight' => -4, 'type' => MENU_LOCAL_TASK, @@ -131,7 +133,8 @@ function search_api_menu() { 'description' => 'Edit index workflow.', 'page callback' => 'drupal_get_form', 'page arguments' => array('search_api_admin_index_workflow', 5), - 'access arguments' => array('administer search_api'), + 'access callback' => '_search_api_access_index_indexer_config', + 'access arguments' => array(5), 'file' => 'search_api.admin.inc', 'weight' => -2, 'type' => MENU_LOCAL_TASK, @@ -154,6 +157,15 @@ function search_api_menu() { } /** + * Menu access callback for index configuration pages relating to indexing + * operations. If a Search API index is read only, the configuration tasks for + * indexing (write operations) should not be available. + */ +function _search_api_access_index_indexer_config($index) { + return user_access('administer search_api') && !$index->read_only; +} + +/** * Implements hook_theme(). */ function search_api_theme() { @@ -184,6 +196,7 @@ function search_api_theme() { 'indexed_items' => 0, 'total_items' => 0, 'status' => ENTITY_CUSTOM, + 'read_only' => 0, ), 'file' => 'search_api.admin.inc', ); @@ -543,7 +556,7 @@ function search_api_entity_insert($entity, $type) { $id = $info['entity keys']['id']; $id = $entity->$id; - foreach (search_api_index_load_multiple(FALSE, array('entity_type' => $type)) as $index) { + foreach (search_api_index_load_multiple(FALSE, array('entity_type' => $type, 'read_only' => 0)) as $index) { db_insert('search_api_item') ->fields(array( 'index_id' => $index->id, @@ -569,7 +582,17 @@ function search_api_entity_update($entity, $type) { $id = $info['entity keys']['id']; $id = $entity->$id; - search_api_mark_dirty($type, array($id)); + foreach (search_api_index_load_multiple(FALSE, array('entity_type' => $type, 'read_only' => 0)) as $index) { + // Mark index records as changed, but leave records that are already "dirty" + // untouched so that the indexing order doesn't change. + db_merge('search_api_item') + ->key(array( + 'item_id' => $id, + 'index_id' => $index->id, + )) + ->expression('changed', 'IF(changed = 0, :timestamp, changed)', array(':timestamp' => REQUEST_TIME)) + ->execute(); + } } /** @@ -663,29 +686,6 @@ function search_api_search_api_processor_info() { } /** - * Mark the entities with the specified IDs as "dirty", i.e., as needing to be - * reindexed. - * - * @param $entity_type - * The type of entity, e.g., 'node'. - * @param array $ids - * The entity IDs of the entities to be marked dirty. - */ -function search_api_mark_dirty($entity_type, array $ids) { - $query = db_select('search_api_index', 'i') - ->fields('i', array('id')) - ->condition('entity_type' , $entity_type); - db_update('search_api_item') - ->fields(array( - 'changed' => REQUEST_TIME, - )) - ->condition('item_id', $ids, 'IN') - ->condition('index_id', $query, 'IN') - ->condition('changed', 0) - ->execute(); -} - -/** * Indexes items for the specified index. Only items marked as changed are * indexed, in their order of change (if known). * @@ -707,6 +707,11 @@ function search_api_index_items(SearchApiIndex $index, $limit = -1) { throw new SearchApiException(t("Couldn't index values for '!name' index (unknown entity type '!type')", array('!name' => $index->name, '!type' => $index->entity_type))); } + // Don't try to index read-only indexes. + if ($index->read_only) { + return 0; + } + $items = search_api_get_items_to_index($index, $limit); if (!$items) { return 0;