updated search_api modules to last version
i will try to dont patch them anymore
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,6 +284,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 +295,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'max' => 'integer',
|
||||
'min' => 'integer',
|
||||
'first' => 'string',
|
||||
'first_char' => 'string',
|
||||
'list' => 'list<string>',
|
||||
);
|
||||
case 'description':
|
||||
@@ -287,6 +306,7 @@ 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.'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,6 +596,10 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
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,
|
||||
@@ -545,23 +623,28 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
*/
|
||||
public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
|
||||
if (!$this->table) {
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,17 +688,22 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
*/
|
||||
public function trackItemDelete(array $item_ids, array $indexes) {
|
||||
if (!$this->table) {
|
||||
return;
|
||||
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,24 @@ 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)) {
|
||||
$query->condition($this->bundleKey, $bundles);
|
||||
}
|
||||
|
||||
// INSERT ... SELECT ...
|
||||
db_insert($this->table)
|
||||
@@ -129,16 +170,147 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
}
|
||||
else {
|
||||
// In the absence of a 'base table', use the slow entity_load().
|
||||
parent::startTracking($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' => $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' => 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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_sort" special field can be used to sort randomly.
|
||||
* @param string $order
|
||||
* The order to sort items in - either 'ASC' or 'DESC'.
|
||||
*
|
||||
@@ -586,6 +588,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 +726,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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