updated search_api and search_api_solr
This commit is contained in:
@@ -10,6 +10,16 @@
|
||||
*/
|
||||
class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* The type of aggregation currently performed.
|
||||
*
|
||||
* Used to temporarily store the current aggregation type for use of
|
||||
* SearchApiAlterAddAggregation::reduce() with array_reduce().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reductionType;
|
||||
|
||||
public function configurationForm() {
|
||||
$form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
|
||||
|
||||
@@ -193,6 +203,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
return isset($a) ? min($a, $b) : $b;
|
||||
case 'first':
|
||||
return isset($a) ? $a : $b;
|
||||
case 'first_char':
|
||||
$b = "$b";
|
||||
if (isset($a) || $b === '') {
|
||||
return $a;
|
||||
}
|
||||
return drupal_substr($b, 0, 1);
|
||||
case 'list':
|
||||
if (!isset($a)) {
|
||||
$a = array();
|
||||
@@ -200,6 +216,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
$a[] = $b;
|
||||
return $a;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,10 +269,13 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
/**
|
||||
* Helper method for getting all available aggregation types.
|
||||
*
|
||||
* @param $info (optional)
|
||||
* One of "name", "type" or "description", to indicate what values should be
|
||||
* returned for the types. Defaults to "name".
|
||||
* @param string $info
|
||||
* (optional) One of "name", "type" or "description", to indicate what
|
||||
* information should be returned for the types.
|
||||
*
|
||||
* @return string[]
|
||||
* An associative array of aggregation type identifiers mapped to their
|
||||
* names, data types or descriptions, as requested.
|
||||
*/
|
||||
protected function getTypes($info = 'name') {
|
||||
switch ($info) {
|
||||
@@ -267,6 +287,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'max' => t('Maximum'),
|
||||
'min' => t('Minimum'),
|
||||
'first' => t('First'),
|
||||
'first_char' => t('First letter'),
|
||||
'list' => t('List'),
|
||||
);
|
||||
case 'type':
|
||||
@@ -277,6 +298,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'max' => 'integer',
|
||||
'min' => 'integer',
|
||||
'first' => 'string',
|
||||
'first_char' => 'string',
|
||||
'list' => 'list<string>',
|
||||
);
|
||||
case 'description':
|
||||
@@ -287,9 +309,11 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'max' => t('The Maximum aggregation computes the numerically largest contained field value.'),
|
||||
'min' => t('The Minimum aggregation computes the numerically smallest contained field value.'),
|
||||
'first' => t('The First aggregation will simply keep the first encountered field value. This is helpful foremost when you know that a list field will only have a single value.'),
|
||||
'first_char' => t('The "First letter" aggregation uses just the first letter of the first encountered field value as the aggregated value. This can, for example, be used to build a Glossary view.'),
|
||||
'list' => t('The List aggregation collects all field values into a multi-valued field containing all values.'),
|
||||
);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -74,7 +74,7 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
|
||||
// we use try/catch. This will at least prevent some errors, even though
|
||||
// it's no protection against fatal errors and the like.
|
||||
try {
|
||||
$render = entity_view($type, array(entity_id($type, $item) => $item), $mode);
|
||||
$render = entity_view($type, array(entity_id($type, $item) => $item), $mode, $item->search_api_language);
|
||||
$text = render($render);
|
||||
if (!$text) {
|
||||
$item->search_api_viewed = NULL;
|
||||
|
@@ -14,6 +14,15 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
if ($this->isMultiEntityIndex($index)) {
|
||||
$info = entity_get_info();
|
||||
foreach ($index->options['datasource']['types'] as $type) {
|
||||
if (isset($info[$type]) && self::hasBundles($info[$type])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
return $index->getEntityType() && ($info = entity_get_info($index->getEntityType())) && self::hasBundles($info);
|
||||
}
|
||||
|
||||
@@ -21,15 +30,24 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
$info = entity_get_info($this->index->getEntityType());
|
||||
if (self::hasBundles($info) && isset($this->options['bundles'])) {
|
||||
$bundles = array_flip($this->options['bundles']);
|
||||
$default = (bool) $this->options['default'];
|
||||
if (!$this->supportsIndex($this->index) || !isset($this->options['bundles'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isMultiEntityIndex()) {
|
||||
$bundle_prop = 'item_bundle';
|
||||
}
|
||||
else {
|
||||
$info = entity_get_info($this->index->getEntityType());
|
||||
$bundle_prop = $info['entity keys']['bundle'];
|
||||
foreach ($items as $id => $item) {
|
||||
if (isset($bundles[$item->$bundle_prop]) == $default) {
|
||||
unset($items[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
$bundles = array_flip($this->options['bundles']);
|
||||
$default = (bool) $this->options['default'];
|
||||
|
||||
foreach ($items as $id => $item) {
|
||||
if (isset($bundles[$item->$bundle_prop]) == $default) {
|
||||
unset($items[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,31 +56,53 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm() {
|
||||
$info = entity_get_info($this->index->getEntityType());
|
||||
if (self::hasBundles($info)) {
|
||||
if ($this->supportsIndex($this->index)) {
|
||||
$options = array();
|
||||
foreach ($info['bundles'] as $bundle => $bundle_info) {
|
||||
$options[$bundle] = isset($bundle_info['label']) ? $bundle_info['label'] : $bundle;
|
||||
if ($this->isMultiEntityIndex()) {
|
||||
$info = entity_get_info();
|
||||
$unsupported_types = array();
|
||||
foreach ($this->index->options['datasource']['types'] as $type) {
|
||||
if (isset($info[$type]) && self::hasBundles($info[$type])) {
|
||||
foreach ($info[$type]['bundles'] as $bundle => $bundle_info) {
|
||||
$options["$type:$bundle"] = $info[$type]['label'] . ' » ' . $bundle_info['label'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$unsupported_types[] = isset($info[$type]['label']) ? $info[$type]['label'] : $type;
|
||||
}
|
||||
}
|
||||
if ($unsupported_types) {
|
||||
$form['unsupported_types']['#markup'] = '<p>' . t('The following entity types do not contain any bundles: @types. All items of those types will therefore be included in the index.', array('@types' => implode(', ', $unsupported_types))) . '</p>';
|
||||
}
|
||||
}
|
||||
$form = array(
|
||||
'default' => array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Which items should be indexed?'),
|
||||
'#default_value' => isset($this->options['default']) ? $this->options['default'] : 1,
|
||||
'#options' => array(
|
||||
1 => t('All but those from one of the selected bundles'),
|
||||
0 => t('Only those from the selected bundles'),
|
||||
),
|
||||
),
|
||||
'bundles' => array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Bundles'),
|
||||
'#default_value' => isset($this->options['bundles']) ? $this->options['bundles'] : array(),
|
||||
'#options' => $options,
|
||||
'#size' => min(4, count($options)),
|
||||
'#multiple' => TRUE,
|
||||
else {
|
||||
$info = entity_get_info($this->index->getEntityType());
|
||||
foreach ($info['bundles'] as $bundle => $bundle_info) {
|
||||
$options[$bundle] = isset($bundle_info['label']) ? $bundle_info['label'] : $bundle;
|
||||
}
|
||||
}
|
||||
if (!empty($this->index->options['datasource']['bundles'])) {
|
||||
$form['message']['#markup'] = '<p>' . t("<strong>Note:</strong> This index is already restricted to certain bundles. If you use this data alteration, those will be reduced further. However, the index setting is better supported in the user interface and should therefore be prefered. For example, using this data alteration will not reduce the displayed total number of items to index (even though some of them will not be indexed). Consider creating a new index with appropriate bundle settings instead.") . '</p>';
|
||||
$included_bundles = array_flip($this->index->options['datasource']['bundles']);
|
||||
$options = array_intersect_key($options, $included_bundles);
|
||||
}
|
||||
$form['default'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Which items should be indexed?'),
|
||||
'#default_value' => isset($this->options['default']) ? $this->options['default'] : 1,
|
||||
'#options' => array(
|
||||
1 => t('All but those from one of the selected bundles'),
|
||||
0 => t('Only those from the selected bundles'),
|
||||
),
|
||||
);
|
||||
$form['bundles'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Bundles'),
|
||||
'#default_value' => isset($this->options['bundles']) ? $this->options['bundles'] : array(),
|
||||
'#options' => $options,
|
||||
'#size' => min(4, count($options)),
|
||||
'#multiple' => TRUE,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form = array(
|
||||
@@ -81,10 +121,25 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
* The entity type's entity_get_info() array.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity type has bundles, FASLE otherwise.
|
||||
* TRUE if the entity type has bundles, FALSE otherwise.
|
||||
*/
|
||||
protected static function hasBundles(array $entity_info) {
|
||||
return !empty($entity_info['entity keys']['bundle']) && !empty($entity_info['bundles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given index contains multiple types of entities.
|
||||
*
|
||||
* @param SearchApiIndex|null $index
|
||||
* (optional) The index to examine. Defaults to the index set for this
|
||||
* plugin.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the index is a multi-entity index, FALSE otherwise.
|
||||
*/
|
||||
protected function isMultiEntityIndex(SearchApiIndex $index = NULL) {
|
||||
$index = $index ? $index : $this->index;
|
||||
return $index->datasource() instanceof SearchApiCombinedEntityDataSourceController;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@ class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
|
||||
foreach ($list as $lang) {
|
||||
$name = t($lang->name);
|
||||
$native = $lang->native;
|
||||
$languages[$lang->language] = ($name == $native) ? $name : "$name ($native)";
|
||||
$languages[$lang->language] = check_plain(($name == $native) ? $name : "$name ($native)");
|
||||
if (!$lang->enabled) {
|
||||
$languages[$lang->language] .= ' [' . t('disabled') . ']';
|
||||
}
|
||||
|
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the SearchApiAlterUserStatus class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Filters out blocked user accounts.
|
||||
*/
|
||||
class SearchApiAlterUserStatus extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
return $index->getEntityType() == 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
foreach ($items as $id => $account) {
|
||||
if (empty($account->status)) {
|
||||
unset($items[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -22,7 +22,7 @@
|
||||
interface SearchApiDataSourceControllerInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new data source controller.
|
||||
* Constructs an SearchApiDataSourceControllerInterface object.
|
||||
*
|
||||
* @param string $type
|
||||
* The item type for which this controller is created.
|
||||
@@ -47,7 +47,7 @@ interface SearchApiDataSourceControllerInterface {
|
||||
* Loads items of the type of this data source controller.
|
||||
*
|
||||
* @param array $ids
|
||||
* The IDs of the items to laod.
|
||||
* The IDs of the items to load.
|
||||
*
|
||||
* @return array
|
||||
* The loaded items, keyed by ID.
|
||||
@@ -158,6 +158,10 @@ interface SearchApiDataSourceControllerInterface {
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which items should be tracked.
|
||||
*
|
||||
* @return SearchApiIndex[]|null
|
||||
* All indexes for which any items were added; or NULL if items were added
|
||||
* for all of them.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
@@ -179,6 +183,10 @@ interface SearchApiDataSourceControllerInterface {
|
||||
* The concept of queued items will be removed in the Drupal 8 version of
|
||||
* this module.
|
||||
*
|
||||
* @return SearchApiIndex[]|null
|
||||
* All indexes for which any items were updated; or NULL if items were
|
||||
* updated for all of them.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
@@ -227,6 +235,10 @@ interface SearchApiDataSourceControllerInterface {
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which the deletions should be tracked.
|
||||
*
|
||||
* @return SearchApiIndex[]|null
|
||||
* All indexes for which any items were deleted; or NULL if items were
|
||||
* deleted for all of them.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
@@ -281,6 +293,69 @@ interface SearchApiDataSourceControllerInterface {
|
||||
*/
|
||||
public function getEntityType();
|
||||
|
||||
/**
|
||||
* Form constructor for configuring the datasource for a given index.
|
||||
*
|
||||
* @param array $form
|
||||
* The form returned by configurationForm().
|
||||
* @param array $form_state
|
||||
* The form state. $form_state['index'] will contain the edited index. If
|
||||
* this key is empty, then a new index is being created. In case of an edit,
|
||||
* $form_state['index']->options['datasource'] contains the previous
|
||||
* settings for the datasource.
|
||||
*
|
||||
* @return array|false
|
||||
* A form array for configuring this callback, or FALSE if no configuration
|
||||
* is possible.
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state);
|
||||
|
||||
/**
|
||||
* Validation callback for the form returned by configurationForm().
|
||||
*
|
||||
* This method will only be called if that form was non-empty.
|
||||
*
|
||||
* @param array $form
|
||||
* The form returned by configurationForm().
|
||||
* @param array $values
|
||||
* The part of the $form_state['values'] array corresponding to this form.
|
||||
* @param array $form_state
|
||||
* The complete form state.
|
||||
*/
|
||||
public function configurationFormValidate(array $form, array &$values, array &$form_state);
|
||||
|
||||
/**
|
||||
* Submit callback for the form returned by configurationForm().
|
||||
*
|
||||
* This method will only be called if that form was non-empty.
|
||||
*
|
||||
* Any necessary changes to the submitted values should be made, afterwards
|
||||
* they will automatically be stored as the index's "datasource" options. The
|
||||
* method can also be used by the datasource controller to react to the
|
||||
* possible change in its settings.
|
||||
*
|
||||
* @param array $form
|
||||
* The form returned by configurationForm().
|
||||
* @param array $values
|
||||
* The part of the $form_state['values'] array corresponding to this form.
|
||||
* @param array $form_state
|
||||
* The complete form state.
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state);
|
||||
|
||||
/**
|
||||
* Returns a summary of an index's current datasource configuration.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index whose datasource configuration should be summarized.
|
||||
*
|
||||
* @return string|null
|
||||
* A translated string describing the index's current datasource
|
||||
* configuration. Or NULL, if there is no configuration (or no description
|
||||
* is available).
|
||||
*/
|
||||
public function getConfigurationSummary(SearchApiIndex $index);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,10 +592,14 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemInsert(array $item_ids, array $indexes) {
|
||||
if (!$this->table) {
|
||||
if (!$this->table || $item_ids === array()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
$this->checkIndex($index);
|
||||
}
|
||||
|
||||
// Since large amounts of items can overstrain the database, only add items
|
||||
// in chunks.
|
||||
foreach (array_chunk($item_ids, 1000) as $chunk) {
|
||||
@@ -528,7 +607,6 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
->fields(array($this->itemIdColumn, $this->indexIdColumn, $this->changedColumn));
|
||||
foreach ($chunk as $item_id) {
|
||||
foreach ($indexes as $index) {
|
||||
$this->checkIndex($index);
|
||||
$insert->values(array(
|
||||
$this->itemIdColumn => $item_id,
|
||||
$this->indexIdColumn => $index->id,
|
||||
@@ -544,24 +622,29 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
|
||||
if (!$this->table) {
|
||||
return;
|
||||
if (!$this->table || $item_ids === array()) {
|
||||
return NULL;
|
||||
}
|
||||
$index_ids = array();
|
||||
|
||||
$ret = array();
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
$this->checkIndex($index);
|
||||
$index_ids[] = $index->id;
|
||||
$update = db_update($this->table)
|
||||
->fields(array(
|
||||
$this->changedColumn => REQUEST_TIME,
|
||||
))
|
||||
->condition($this->indexIdColumn, $index->id)
|
||||
->condition($this->changedColumn, 0, $dequeue ? '<=' : '=');
|
||||
if ($item_ids !== FALSE) {
|
||||
$update->condition($this->itemIdColumn, $item_ids, 'IN');
|
||||
}
|
||||
if ($update->execute()) {
|
||||
$ret[] = $index;
|
||||
}
|
||||
}
|
||||
$update = db_update($this->table)
|
||||
->fields(array(
|
||||
$this->changedColumn => REQUEST_TIME,
|
||||
))
|
||||
->condition($this->indexIdColumn, $index_ids, 'IN')
|
||||
->condition($this->changedColumn, 0, $dequeue ? '<=' : '=');
|
||||
if ($item_ids !== FALSE) {
|
||||
$update->condition($this->itemIdColumn, $item_ids, 'IN');
|
||||
}
|
||||
$update->execute();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -569,7 +652,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
*/
|
||||
public function trackItemQueued($item_ids, SearchApiIndex $index) {
|
||||
$this->checkIndex($index);
|
||||
if (!$this->table) {
|
||||
if (!$this->table || $item_ids === array()) {
|
||||
return;
|
||||
}
|
||||
$update = db_update($this->table)
|
||||
@@ -587,7 +670,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemIndexed(array $item_ids, SearchApiIndex $index) {
|
||||
if (!$this->table) {
|
||||
if (!$this->table || $item_ids === array()) {
|
||||
return;
|
||||
}
|
||||
$this->checkIndex($index);
|
||||
@@ -604,18 +687,23 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemDelete(array $item_ids, array $indexes) {
|
||||
if (!$this->table) {
|
||||
return;
|
||||
if (!$this->table || $item_ids === array()) {
|
||||
return NULL;
|
||||
}
|
||||
$index_ids = array();
|
||||
|
||||
$ret = array();
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
$this->checkIndex($index);
|
||||
$index_ids[] = $index->id;
|
||||
$delete = db_delete($this->table)
|
||||
->condition($this->indexIdColumn, $index->id)
|
||||
->condition($this->itemIdColumn, $item_ids, 'IN');
|
||||
if ($delete->execute()) {
|
||||
$ret[] = $index;
|
||||
}
|
||||
}
|
||||
db_delete($this->table)
|
||||
->condition($this->itemIdColumn, $item_ids, 'IN')
|
||||
->condition($this->indexIdColumn, $index_ids, 'IN')
|
||||
->execute();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,6 +747,32 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
return array('indexed' => $indexed, 'total' => $total);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigurationSummary(SearchApiIndex $index) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given index is valid for this datasource controller.
|
||||
*
|
||||
|
@@ -10,28 +10,69 @@
|
||||
*/
|
||||
class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceController {
|
||||
|
||||
/**
|
||||
* Entity type info for this type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityInfo;
|
||||
|
||||
/**
|
||||
* The ID key of this entity type, if any.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $idKey;
|
||||
|
||||
/**
|
||||
* The bundle key of this entity type, if any.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $bundleKey;
|
||||
|
||||
/**
|
||||
* Cached return values for getBundles(), keyed by index machine name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bundles = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($type) {
|
||||
parent::__construct($type);
|
||||
|
||||
$this->entityInfo = entity_get_info($this->entityType);
|
||||
if (!empty($this->entityInfo['entity keys']['id'])) {
|
||||
$this->idKey = $this->entityInfo['entity keys']['id'];
|
||||
}
|
||||
if (!empty($this->entityInfo['entity keys']['bundle'])) {
|
||||
$this->bundleKey = $this->entityInfo['entity keys']['bundle'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIdFieldInfo() {
|
||||
$info = entity_get_info($this->entityType);
|
||||
$properties = entity_get_property_info($this->entityType);
|
||||
if (empty($info['entity keys']['id'])) {
|
||||
throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $info['label'])));
|
||||
if (!$this->idKey) {
|
||||
throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $this->entityInfo['label'])));
|
||||
}
|
||||
$field = $info['entity keys']['id'];
|
||||
if (empty($properties['properties'][$field]['type'])) {
|
||||
throw new SearchApiDataSourceException(t("Entity type @type doesn't specify a type for the @prop property.", array('@type' => $info['label'], '@prop' => $field)));
|
||||
if (empty($properties['properties'][$this->idKey]['type'])) {
|
||||
throw new SearchApiDataSourceException(t("Entity type @type doesn't specify a type for the @prop property.", array('@type' => $this->entityInfo['label'], '@prop' => $this->idKey)));
|
||||
}
|
||||
$type = $properties['properties'][$field]['type'];
|
||||
$type = $properties['properties'][$this->idKey]['type'];
|
||||
if (search_api_is_list_type($type)) {
|
||||
throw new SearchApiDataSourceException(t("Entity type @type uses list field @prop as its ID.", array('@type' => $info['label'], '@prop' => $field)));
|
||||
throw new SearchApiDataSourceException(t("Entity type @type uses list field @prop as its ID.", array('@type' => $this->entityInfo['label'], '@prop' => $this->idKey)));
|
||||
}
|
||||
if ($type == 'token') {
|
||||
$type = 'string';
|
||||
}
|
||||
return array(
|
||||
'key' => $field,
|
||||
'key' => $this->idKey,
|
||||
'type' => $type,
|
||||
);
|
||||
}
|
||||
@@ -103,24 +144,53 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
// all items again without any key conflicts.
|
||||
$this->stopTracking($indexes);
|
||||
|
||||
$entity_info = entity_get_info($this->entityType);
|
||||
|
||||
if (!empty($entity_info['base table'])) {
|
||||
if (!empty($this->entityInfo['base table']) && $this->idKey) {
|
||||
// Use a subselect, which will probably be much faster than entity_load().
|
||||
|
||||
// Assumes that all entities use the "base table" property and the
|
||||
// "entity keys[id]" in the same way as the default controller.
|
||||
$id_field = $entity_info['entity keys']['id'];
|
||||
$table = $entity_info['base table'];
|
||||
$table = $this->entityInfo['base table'];
|
||||
|
||||
// We could also use a single insert (with a JOIN in the nested query),
|
||||
// We could also use a single insert (with a UNION in the nested query),
|
||||
// but this method will be mostly called with a single index, anyways.
|
||||
foreach ($indexes as $index) {
|
||||
// Select all entity ids.
|
||||
$query = db_select($table, 't');
|
||||
$query->addField('t', $id_field, 'item_id');
|
||||
$query->addField('t', $this->idKey, 'item_id');
|
||||
$query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
|
||||
$query->addExpression('1', 'changed');
|
||||
if ($bundles = $this->getIndexBundles($index)) {
|
||||
$bundle_column = $this->bundleKey;
|
||||
if (!db_field_exists($table, $bundle_column)) {
|
||||
if ($this->entityType == 'taxonomy_term') {
|
||||
$bundle_column = 'vid';
|
||||
$bundles = db_query('SELECT vid FROM {taxonomy_vocabulary} WHERE machine_name IN (:bundles)', array(':bundles' => $bundles))->fetchCol();
|
||||
}
|
||||
elseif ($this->entityType == 'comment') {
|
||||
// Comments are significantly more complicated, since they don't
|
||||
// store their bundle explicitly in their database table. Instead,
|
||||
// we need to get all the nodes from the enabled types and filter
|
||||
// by those.
|
||||
$bundle_column = 'nid';
|
||||
$node_types = array();
|
||||
foreach ($bundles as $bundle) {
|
||||
if (substr($bundle, 0, 13) === 'comment_node_') {
|
||||
$node_types[] = substr($bundle, 13);
|
||||
}
|
||||
}
|
||||
if ($node_types) {
|
||||
$bundles = db_query('SELECT nid FROM {node} WHERE type IN (:bundles)', array(':bundles' => $node_types))->fetchCol();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->startTrackingFallback(array($index->machine_name => $index));
|
||||
}
|
||||
}
|
||||
$query->condition($bundle_column, $bundles);
|
||||
}
|
||||
|
||||
// INSERT ... SELECT ...
|
||||
db_insert($this->table)
|
||||
@@ -129,16 +199,165 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
}
|
||||
else {
|
||||
// In the absence of a 'base table', use the slow entity_load().
|
||||
parent::startTracking($indexes);
|
||||
$this->startTrackingFallback($indexes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes tracking of the index status of items for the given indexes.
|
||||
*
|
||||
* Fallback for when the items cannot directly be loaded into
|
||||
* {search_api_item} via "INSERT INTO … SELECT …".
|
||||
*
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which item tracking should be initialized.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* Thrown if any error state was encountered.
|
||||
*
|
||||
* @see SearchApiEntityDataSourceController::startTracking()
|
||||
*/
|
||||
protected function startTrackingFallback(array $indexes) {
|
||||
// In the absence of a 'base table', use the slower way of retrieving the
|
||||
// items and inserting them "manually". For each index we get the item IDs
|
||||
// (since selected bundles might differ) and insert all of them as new.
|
||||
foreach ($indexes as $index) {
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', $this->entityType);
|
||||
if ($bundles = $this->getIndexBundles($index)) {
|
||||
$query->entityCondition('bundle', $bundles);
|
||||
}
|
||||
$result = $query->execute();
|
||||
$ids = !empty($result[$this->entityType]) ? array_keys($result[$this->entityType]) : array();
|
||||
if ($ids) {
|
||||
$this->trackItemInsert($ids, array($index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAllItemIds() {
|
||||
return array_keys(entity_load($this->entityType));
|
||||
public function trackItemInsert(array $item_ids, array $indexes) {
|
||||
$ret = array();
|
||||
|
||||
foreach ($indexes as $index_id => $index) {
|
||||
$ids = $item_ids;
|
||||
if ($bundles = $this->getIndexBundles($index)) {
|
||||
$ids = drupal_map_assoc($ids);
|
||||
foreach (entity_load($this->entityType, $ids) as $id => $entity) {
|
||||
if (empty($bundles[$entity->{$this->bundleKey}])) {
|
||||
unset($ids[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($ids) {
|
||||
parent::trackItemInsert($ids, array($index));
|
||||
$ret[$index_id] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state) {
|
||||
$options = $this->getAvailableBundles();
|
||||
if (!$options) {
|
||||
return FALSE;
|
||||
}
|
||||
$form['bundles'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Bundles'),
|
||||
'#description' => t('Restrict the entity bundles that will be included in this index. Leave blank to include all bundles. This setting cannot be changed for existing indexes.'),
|
||||
'#options' => array_map('check_plain', $options),
|
||||
'#attributes' => array('class' => array('search-api-checkboxes-list')),
|
||||
'#disabled' => !empty($form_state['index']),
|
||||
);
|
||||
if (!empty($form_state['index']->options['datasource'])) {
|
||||
$form['bundles']['#default_value'] = drupal_map_assoc($form_state['index']->options['datasource']['bundles']);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
if (!empty($values['bundles'])) {
|
||||
$values['bundles'] = array_keys(array_filter($values['bundles']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigurationSummary(SearchApiIndex $index) {
|
||||
if ($bundles = $this->getIndexBundles($index)) {
|
||||
$args['!bundles'] = implode(', ', array_intersect_key($this->getAvailableBundles(), $bundles));
|
||||
return format_plural(count($bundles), 'Indexed bundle: !bundles.', 'Indexed bundles: !bundles.', $args);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the available bundles for this entity type.
|
||||
*
|
||||
* @return array
|
||||
* An array (which might be empty) mapping this entity type's bundle keys to
|
||||
* their labels.
|
||||
*/
|
||||
protected function getAvailableBundles() {
|
||||
if (!$this->bundleKey || empty($this->entityInfo['bundles'])) {
|
||||
return array();
|
||||
}
|
||||
$bundles = array();
|
||||
foreach ($this->entityInfo['bundles'] as $bundle => $bundle_info) {
|
||||
$bundles[$bundle] = isset($bundle_info['label']) ? $bundle_info['label'] : $bundle;
|
||||
}
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the bundles that should be indexed for an index.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index for which to check.
|
||||
*
|
||||
* @return array
|
||||
* An array containing all bundles that should be included in this index, as
|
||||
* both the keys and values. An empty array means all current bundles should
|
||||
* be included.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If the index doesn't belong to this datasource controller.
|
||||
*/
|
||||
protected function getIndexBundles(SearchApiIndex $index) {
|
||||
$this->checkIndex($index);
|
||||
|
||||
if (!isset($this->bundles[$index->machine_name])) {
|
||||
$this->bundles[$index->machine_name] = array();
|
||||
if (!empty($index->options['datasource']['bundles'])) {
|
||||
// We retrieve the available bundles here to check whether all of them
|
||||
// are included by the index's setting. In this case, we return an empty
|
||||
// array, too, to save on complexity.
|
||||
// On the other hand, we still want to return deleted bundles since we
|
||||
// do not want to suddenly include all bundles when all selected bundles
|
||||
// were deleted.
|
||||
$available = $this->getAvailableBundles();
|
||||
foreach ($index->options['datasource']['bundles'] as $bundle) {
|
||||
$this->bundles[$index->machine_name][$bundle] = $bundle;
|
||||
unset($available[$bundle]);
|
||||
}
|
||||
if (!$available) {
|
||||
$this->bundles[$index->machine_name] = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->bundles[$index->machine_name];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiCombinedEntityDataSourceController.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a datasource for indexing multiple types of entities.
|
||||
*/
|
||||
class SearchApiCombinedEntityDataSourceController extends SearchApiAbstractDataSourceController {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $table = 'search_api_item_string_id';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIdFieldInfo() {
|
||||
return array(
|
||||
'key' => 'item_id',
|
||||
'type' => 'string',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadItems(array $ids) {
|
||||
$ids_by_type = array();
|
||||
foreach ($ids as $id) {
|
||||
list($type, $entity_id) = explode('/', $id);
|
||||
$ids_by_type[$type][$entity_id] = $id;
|
||||
}
|
||||
|
||||
$items = array();
|
||||
foreach ($ids_by_type as $type => $type_ids) {
|
||||
foreach (entity_load($type, array_keys($type_ids)) as $entity_id => $entity) {
|
||||
$id = $type_ids[$entity_id];
|
||||
$item = (object) array($type => $entity);
|
||||
$item->item_id = $id;
|
||||
$item->item_type = $type;
|
||||
$item->item_entity_id = $entity_id;
|
||||
$item->item_bundle = NULL;
|
||||
try {
|
||||
list(, , $bundle) = entity_extract_ids($type, $entity);
|
||||
$item->item_bundle = $bundle ? "$type:$bundle" : NULL;
|
||||
}
|
||||
catch (EntityMalformedException $e) {
|
||||
// Will probably make problems at some other place, but for extracting
|
||||
// the bundle it is really not critical enough to fail on – just
|
||||
// ignore this exception.
|
||||
}
|
||||
$items[$id] = $item;
|
||||
unset($type_ids[$entity_id]);
|
||||
}
|
||||
if ($type_ids) {
|
||||
search_api_track_item_delete($type, array_keys($type_ids));
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getPropertyInfo() {
|
||||
$info = array(
|
||||
'item_id' => array(
|
||||
'label' => t('ID'),
|
||||
'description' => t('The combined ID of the item, containing both entity type and entity ID.'),
|
||||
'type' => 'token',
|
||||
),
|
||||
'item_type' => array(
|
||||
'label' => t('Entity type'),
|
||||
'description' => t('The entity type of the item.'),
|
||||
'type' => 'token',
|
||||
'options list' => 'search_api_entity_type_options_list',
|
||||
),
|
||||
'item_entity_id' => array(
|
||||
'label' => t('Entity ID'),
|
||||
'description' => t('The entity ID of the item.'),
|
||||
'type' => 'token',
|
||||
),
|
||||
'item_bundle' => array(
|
||||
'label' => t('Bundle'),
|
||||
'description' => t('The bundle of the item, if applicable.'),
|
||||
'type' => 'token',
|
||||
'options list' => 'search_api_combined_bundle_options_list',
|
||||
),
|
||||
'item_label' => array(
|
||||
'label' => t('Label'),
|
||||
'description' => t('The label of the item.'),
|
||||
'type' => 'text',
|
||||
// Since this needs a bit more computation than the others, we don't
|
||||
// include it always when loading the item but use a getter callback.
|
||||
'getter callback' => 'search_api_get_multi_type_item_label',
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($this->getSelectedEntityTypeOptions() as $type => $label) {
|
||||
$info[$type] = array(
|
||||
'label' => $label,
|
||||
'description' => t('The indexed entity, if it is of type %type.', array('%type' => $label)),
|
||||
'type' => $type,
|
||||
);
|
||||
}
|
||||
|
||||
return array('property info' => $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemId($item) {
|
||||
return isset($item->item_id) ? $item->item_id : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemLabel($item) {
|
||||
return search_api_get_multi_type_item_label($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemUrl($item) {
|
||||
if ($item->item_type == 'file') {
|
||||
return array(
|
||||
'path' => file_create_url($item->file->uri),
|
||||
'options' => array(
|
||||
'entity_type' => 'file',
|
||||
'entity' => $item,
|
||||
),
|
||||
);
|
||||
}
|
||||
$url = entity_uri($item->item_type, $item->{$item->item_type});
|
||||
return $url ? $url : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function startTracking(array $indexes) {
|
||||
if (!$this->table) {
|
||||
return;
|
||||
}
|
||||
// We first clear the tracking table for all indexes, so we can just insert
|
||||
// all items again without any key conflicts.
|
||||
$this->stopTracking($indexes);
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
$types = $this->getEntityTypes($index);
|
||||
|
||||
// Wherever possible, use a sub-select instead of the much slower
|
||||
// entity_load().
|
||||
foreach ($types as $type) {
|
||||
$entity_info = entity_get_info($type);
|
||||
|
||||
if (!empty($entity_info['base table'])) {
|
||||
// Assumes that all entities use the "base table" property and the
|
||||
// "entity keys[id]" in the same way as the default controller.
|
||||
$id_field = $entity_info['entity keys']['id'];
|
||||
$table = $entity_info['base table'];
|
||||
|
||||
// Select all entity ids.
|
||||
$query = db_select($table, 't');
|
||||
$query->addExpression("CONCAT(:prefix, t.$id_field)", 'item_id', array(':prefix' => $type . '/'));
|
||||
$query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
|
||||
$query->addExpression('1', 'changed');
|
||||
|
||||
// INSERT ... SELECT ...
|
||||
db_insert($this->table)
|
||||
->from($query)
|
||||
->execute();
|
||||
|
||||
unset($types[$type]);
|
||||
}
|
||||
}
|
||||
|
||||
// In the absence of a "base table", use the slow entity_load().
|
||||
if ($types) {
|
||||
foreach ($types as $type) {
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', $type);
|
||||
$result = $query->execute();
|
||||
$ids = !empty($result[$type]) ? array_keys($result[$type]) : array();
|
||||
if ($ids) {
|
||||
foreach ($ids as $i => $id) {
|
||||
$ids[$i] = $type . '/' . $id;
|
||||
}
|
||||
$this->trackItemInsert($ids, array($index), TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts tracking the index status for the given items on the given indexes.
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of new items to track.
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which items should be tracked.
|
||||
* @param bool $skip_type_check
|
||||
* (optional) If TRUE, don't check whether the type matches the index's
|
||||
* datasource configuration. Internal use only.
|
||||
*
|
||||
* @return SearchApiIndex[]|null
|
||||
* All indexes for which any items were added; or NULL if items were added
|
||||
* for all of them.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function trackItemInsert(array $item_ids, array $indexes, $skip_type_check = FALSE) {
|
||||
$ret = array();
|
||||
|
||||
foreach ($indexes as $index_id => $index) {
|
||||
$ids = drupal_map_assoc($item_ids);
|
||||
|
||||
if (!$skip_type_check) {
|
||||
$types = $this->getEntityTypes($index);
|
||||
foreach ($ids as $id) {
|
||||
list($type) = explode('/', $id);
|
||||
if (!isset($types[$type])) {
|
||||
unset($ids[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($ids) {
|
||||
parent::trackItemInsert($ids, array($index));
|
||||
$ret[$index_id] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state) {
|
||||
$form['types'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Entity types'),
|
||||
'#description' => t('Select the entity types which should be included in this index.'),
|
||||
'#options' => array_map('check_plain', search_api_entity_type_options_list()),
|
||||
'#attributes' => array('class' => array('search-api-checkboxes-list')),
|
||||
'#disabled' => !empty($form_state['index']),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
if (!empty($form_state['index']->options['datasource']['types'])) {
|
||||
$form['types']['#default_value'] = $this->getEntityTypes($form_state['index']);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
if (!empty($values['types'])) {
|
||||
$values['types'] = array_keys(array_filter($values['types']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigurationSummary(SearchApiIndex $index) {
|
||||
if ($type_labels = $this->getSelectedEntityTypeOptions($index)) {
|
||||
$args['!types'] = implode(', ', $type_labels);
|
||||
return format_plural(count($type_labels), 'Indexed entity types: !types.', 'Indexed entity types: !types.', $args);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the index for which the current method was called.
|
||||
*
|
||||
* Very ugly method which uses the stack trace to find the right object.
|
||||
*
|
||||
* @return SearchApiIndex
|
||||
* The active index.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* Thrown if the active index could not be determined.
|
||||
*/
|
||||
protected function getCallingIndex() {
|
||||
foreach (debug_backtrace() as $trace) {
|
||||
if (isset($trace['object']) && $trace['object'] instanceof SearchApiIndex) {
|
||||
return $trace['object'];
|
||||
}
|
||||
}
|
||||
// If there's only a single index on the site, it's also easy.
|
||||
$indexes = search_api_index_load_multiple(FALSE);
|
||||
if (count($indexes) === 1) {
|
||||
return reset($indexes);
|
||||
}
|
||||
throw new SearchApiException('Could not determine the active index of the datasource.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity types for which this datasource is configured.
|
||||
*
|
||||
* Depends on the index from which this method is (indirectly) called.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* (optional) The index for which to get the enabled entity types. If not
|
||||
* given, will be determined automatically.
|
||||
*
|
||||
* @return string[]
|
||||
* The machine names of the datasource's enabled entity types, as both keys
|
||||
* and values.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* Thrown if the active index could not be determined.
|
||||
*/
|
||||
protected function getEntityTypes(SearchApiIndex $index = NULL) {
|
||||
if (!$index) {
|
||||
$index = $this->getCallingIndex();
|
||||
}
|
||||
if (isset($index->options['datasource']['types'])) {
|
||||
return drupal_map_assoc($index->options['datasource']['types']);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected entity type options for this datasource.
|
||||
*
|
||||
* Depends on the index from which this method is (indirectly) called.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* (optional) The index for which to get the enabled entity types. If not
|
||||
* given, will be determined automatically.
|
||||
*
|
||||
* @return string[]
|
||||
* An associative array, mapping the machine names of the enabled entity
|
||||
* types to their labels.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* Thrown if the active index could not be determined.
|
||||
*/
|
||||
protected function getSelectedEntityTypeOptions(SearchApiIndex $index = NULL) {
|
||||
return array_intersect_key(search_api_entity_type_options_list(), $this->getEntityTypes($index));
|
||||
}
|
||||
|
||||
}
|
@@ -19,7 +19,7 @@ class SearchApiException extends Exception {
|
||||
*/
|
||||
public function __construct($message = NULL) {
|
||||
if (!$message) {
|
||||
$message = t('An error occcurred in the Search API framework.');
|
||||
$message = t('An error occurred in the Search API framework.');
|
||||
}
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
@@ -115,7 +115,8 @@ class SearchApiIndex extends Entity {
|
||||
public $item_type;
|
||||
|
||||
/**
|
||||
* An array of options for configuring this index. The layout is as follows:
|
||||
* An array of options for configuring this index. The layout is as follows
|
||||
* (with all keys being optional):
|
||||
* - cron_limit: The maximum number of items to be indexed per cron batch.
|
||||
* - index_directly: Boolean setting whether entities are indexed immediately
|
||||
* after they are created or updated.
|
||||
@@ -150,6 +151,8 @@ class SearchApiIndex extends Entity {
|
||||
* - weight: Used for sorting the processors.
|
||||
* - settings: Processor-specific settings, configured via the processor's
|
||||
* configuration form.
|
||||
* - datasource: Datasource-specific settings, configured via the datasource's
|
||||
* configuration form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -436,7 +439,6 @@ class SearchApiIndex extends Entity {
|
||||
return $this->server()->query($this, $options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indexes items on this index.
|
||||
*
|
||||
@@ -932,7 +934,12 @@ class SearchApiIndex extends Entity {
|
||||
$i = $only_indexed ? 1 : 0;
|
||||
if (!isset($this->fulltext_fields[$i])) {
|
||||
$this->fulltext_fields[$i] = array();
|
||||
$fields = $only_indexed ? $this->options['fields'] : $this->getFields(FALSE);
|
||||
if ($only_indexed) {
|
||||
$fields = isset($this->options['fields']) ? $this->options['fields'] : array();
|
||||
}
|
||||
else {
|
||||
$fields = $this->getFields(FALSE);
|
||||
}
|
||||
foreach ($fields as $key => $field) {
|
||||
if (search_api_is_text_type($field['type'])) {
|
||||
$this->fulltext_fields[$i][] = $key;
|
||||
|
@@ -172,7 +172,7 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
|
||||
$default_fields = drupal_map_assoc(array_keys($this->options['fields']));
|
||||
}
|
||||
foreach ($fields as $name => $field) {
|
||||
$field_options[$name] = $field['name'];
|
||||
$field_options[$name] = check_plain($field['name']);
|
||||
if (!empty($default_fields[$name]) || (!isset($this->options['fields']) && $this->testField($name, $field))) {
|
||||
$default_fields[$name] = $name;
|
||||
}
|
||||
@@ -390,13 +390,15 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to process data from the given field.
|
||||
*
|
||||
* @param $name
|
||||
* The field's machine name.
|
||||
* @param array $field
|
||||
* The field's information.
|
||||
*
|
||||
* @return
|
||||
* TRUE, iff the field should be processed.
|
||||
* @return bool
|
||||
* TRUE, if the field should be processed, FALSE otherwise.
|
||||
*/
|
||||
protected function testField($name, array $field) {
|
||||
if (empty($this->options['fields'])) {
|
||||
@@ -406,8 +408,13 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* TRUE, iff the type should be processed.
|
||||
* Determines whether fields of the given type should normally be processed.
|
||||
*
|
||||
* Defaults to processing text types, but can easily be overridden by
|
||||
* subclasses.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE, if the type should be processed, FALSE otherwise.
|
||||
*/
|
||||
protected function testType($type) {
|
||||
return search_api_is_text_type($type, array('text', 'tokens'));
|
||||
|
@@ -86,12 +86,12 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
),
|
||||
),
|
||||
);
|
||||
// Exclude certain fulltextfields
|
||||
// Exclude certain fulltext fields.
|
||||
$fields = $this->index->getFields();
|
||||
$fulltext_fields = array();
|
||||
foreach ($this->index->getFulltextFields() as $field) {
|
||||
if (isset($fields[$field])) {
|
||||
$fulltext_fields[$field] = $fields[$field]['name'] . ' (' . $field . ')';
|
||||
$fulltext_fields[$field] = check_plain($fields[$field]['name'] . ' (' . $field . ')');
|
||||
}
|
||||
}
|
||||
$form['exclude_fields'] = array(
|
||||
|
@@ -120,7 +120,9 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
|
||||
);
|
||||
}
|
||||
$text = substr($text, $pos + 1);
|
||||
preg_match('#^(/?)([-:_a-zA-Z]+)#', $text, $m);
|
||||
if (!preg_match('#^(/?)([-:_a-zA-Z]+)#', $text, $m)) {
|
||||
continue;
|
||||
}
|
||||
$text = substr($text, strpos($text, '>') + 1);
|
||||
if ($m[1]) {
|
||||
// Closing tag.
|
||||
|
@@ -86,7 +86,9 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* Retrieves the processor's configured stopwords.
|
||||
*
|
||||
* @return array
|
||||
* An array whose keys are the stopwords set in either the file or the text
|
||||
* field.
|
||||
*/
|
||||
|
@@ -153,7 +153,9 @@ interface SearchApiQueryInterface {
|
||||
*
|
||||
* @param string $field
|
||||
* The field to sort by. The special fields 'search_api_relevance' (sort by
|
||||
* relevance) and 'search_api_id' (sort by item id) may be used.
|
||||
* relevance) and 'search_api_id' (sort by item id) may be used. Also, if
|
||||
* the search server supports the "search_api_random_sort" feature, the
|
||||
* "search_api_random" special field can be used to sort randomly.
|
||||
* @param string $order
|
||||
* The order to sort items in - either 'ASC' or 'DESC'.
|
||||
*
|
||||
@@ -217,7 +219,10 @@ interface SearchApiQueryInterface {
|
||||
* - execution: The actual query to the search server, in whatever form.
|
||||
* - postprocessing: Preparing the results for returning.
|
||||
* Additional metadata may be returned in other keys. Only 'result count'
|
||||
* and 'result' always have to be set, all other entries are optional.
|
||||
* and 'results' always have to be set, all other entries are optional.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If an error prevented the search from completing.
|
||||
*/
|
||||
public function execute();
|
||||
|
||||
@@ -466,8 +471,8 @@ class SearchApiQuery implements SearchApiQueryInterface {
|
||||
);
|
||||
$modes['terms'] = array(
|
||||
'name' => t('Multiple terms'),
|
||||
'description' => t('The query is interpreted as multiple keywords seperated by spaces. ' .
|
||||
'Keywords containing spaces may be "quoted". Quoted keywords must still be seperated by spaces.'),
|
||||
'description' => t('The query is interpreted as multiple keywords separated by spaces. ' .
|
||||
'Keywords containing spaces may be "quoted". Quoted keywords must still be separated by spaces.'),
|
||||
);
|
||||
// @todo Add fourth mode for complicated expressions, e.g.: »"vanilla ice" OR (love NOT hate)«
|
||||
return $modes;
|
||||
@@ -586,6 +591,10 @@ class SearchApiQuery implements SearchApiQueryInterface {
|
||||
'search_api_relevance' => array('type' => 'decimal'),
|
||||
'search_api_id' => array('type' => 'integer'),
|
||||
);
|
||||
if ($this->getIndex()->server()->supportsFeature('search_api_random_sort')) {
|
||||
$fields['search_api_random'] = array('type' => 'integer');
|
||||
}
|
||||
|
||||
if (empty($fields[$field])) {
|
||||
throw new SearchApiException(t('Trying to sort on unknown field @field.', array('@field' => $field)));
|
||||
}
|
||||
@@ -720,6 +729,9 @@ class SearchApiQuery implements SearchApiQueryInterface {
|
||||
public function postExecute(array &$results) {
|
||||
// Postprocess results.
|
||||
$this->index->postprocessSearchResults($results, $this);
|
||||
|
||||
// Let modules alter the results.
|
||||
drupal_alter('search_api_results', $results, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -241,7 +241,7 @@ class SearchApiServer extends Entity {
|
||||
/**
|
||||
* Adds a new index to this server.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* If an exception in the service class implementation of this method occurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::addIndex()
|
||||
@@ -268,7 +268,7 @@ class SearchApiServer extends Entity {
|
||||
* If the service class implementation of the method returns TRUE, this will
|
||||
* automatically take care of marking the items on the index for re-indexing.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* If an exception in the service class implementation of this method occurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::fieldsUpdated()
|
||||
@@ -296,7 +296,7 @@ class SearchApiServer extends Entity {
|
||||
/**
|
||||
* Removes an index from this server.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* If an exception in the service class implementation of this method occurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::removeIndex()
|
||||
@@ -334,7 +334,7 @@ class SearchApiServer extends Entity {
|
||||
/**
|
||||
* Deletes indexed items from this server.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* If an exception in the service class implementation of this method occurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::deleteItems()
|
||||
|
@@ -420,7 +420,15 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
|
||||
public function preDelete() {
|
||||
$indexes = search_api_index_load_multiple(FALSE, array('server' => $this->server->machine_name));
|
||||
foreach ($indexes as $index) {
|
||||
$this->removeIndex($index);
|
||||
// removeIndex() might throw exceptions, but this method mustn't.
|
||||
try {
|
||||
$this->removeIndex($index);
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$variables['%index'] = $index->name;
|
||||
$variables['%server'] = $this->server->name;
|
||||
watchdog_exception('search_api', $e, '%type while trying to remove index %index from deleted server %server: !message in %function (line %line of %file).', $variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user