250 lines
8.3 KiB
PHP
250 lines
8.3 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Search API data alteration callback that adds an URL field for all items.
|
|
*/
|
|
class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
|
|
|
/**
|
|
* Cached value for the hierarchical field options.
|
|
*
|
|
* @var array
|
|
*
|
|
* @see getHierarchicalFields()
|
|
*/
|
|
protected $field_options;
|
|
|
|
/**
|
|
* Enable this data alteration only if any hierarchical fields are available.
|
|
*
|
|
* @param SearchApiIndex $index
|
|
* The index to check for.
|
|
*
|
|
* @return boolean
|
|
* TRUE if the callback can run on the given index; FALSE otherwise.
|
|
*/
|
|
public function supportsIndex(SearchApiIndex $index) {
|
|
return (bool) $this->getHierarchicalFields();
|
|
}
|
|
|
|
/**
|
|
* Display a form for configuring this callback.
|
|
*
|
|
* @return array
|
|
* A form array for configuring this callback, or FALSE if no configuration
|
|
* is possible.
|
|
*/
|
|
public function configurationForm() {
|
|
$options = $this->getHierarchicalFields();
|
|
$this->options += array('fields' => array());
|
|
$form['fields'] = array(
|
|
'#title' => t('Hierarchical fields'),
|
|
'#description' => t('Select the fields which should be supplemented with their ancestors. ' .
|
|
'Each field is listed along with its children of the same type. ' .
|
|
'When selecting several child properties of a field, all those properties will be recursively added to that field. ' .
|
|
'Please note that you should de-select all fields before disabling this data alteration.'),
|
|
'#type' => 'select',
|
|
'#multiple' => TRUE,
|
|
'#size' => min(6, count($options, COUNT_RECURSIVE)),
|
|
'#options' => $options,
|
|
'#default_value' => $this->options['fields'],
|
|
);
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Submit callback for the form returned by configurationForm().
|
|
*
|
|
* This method should both return the new options and set them internally.
|
|
*
|
|
* @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.
|
|
*
|
|
* @return array
|
|
* The new options array for this callback.
|
|
*/
|
|
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
|
// Change the saved type of fields in the index, if necessary.
|
|
if (!empty($this->index->options['fields'])) {
|
|
$fields = &$this->index->options['fields'];
|
|
$previous = drupal_map_assoc($this->options['fields']);
|
|
foreach ($values['fields'] as $field) {
|
|
list($key, $prop) = explode(':', $field);
|
|
if (empty($previous[$field]) && isset($fields[$key]['type'])) {
|
|
$fields[$key]['type'] = 'list<' . search_api_extract_inner_type($fields[$key]['type']) . '>';
|
|
$change = TRUE;
|
|
}
|
|
}
|
|
$new = drupal_map_assoc($values['fields']);
|
|
foreach ($previous as $field) {
|
|
list($key, $prop) = explode(':', $field);
|
|
if (empty($new[$field]) && isset($fields[$key]['type'])) {
|
|
$w = $this->index->entityWrapper(NULL, FALSE);
|
|
if (isset($w->$key)) {
|
|
$type = $w->$key->type();
|
|
$inner = search_api_extract_inner_type($fields[$key]['type']);
|
|
$fields[$key]['type'] = search_api_nest_type($inner, $type);
|
|
$change = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (isset($change)) {
|
|
$this->index->save();
|
|
}
|
|
}
|
|
|
|
return parent::configurationFormSubmit($form, $values, $form_state);
|
|
}
|
|
|
|
/**
|
|
* Alter items before indexing.
|
|
*
|
|
* Items which are removed from the array won't be indexed, but will be marked
|
|
* as clean for future indexing. This could for instance be used to implement
|
|
* some sort of access filter for security purposes (e.g., don't index
|
|
* unpublished nodes or comments).
|
|
*
|
|
* @param array $items
|
|
* An array of items to be altered, keyed by item IDs.
|
|
*/
|
|
public function alterItems(array &$items) {
|
|
if (empty($this->options['fields'])) {
|
|
return array();
|
|
}
|
|
foreach ($items as $item) {
|
|
$wrapper = $this->index->entityWrapper($item, FALSE);
|
|
|
|
$values = array();
|
|
foreach ($this->options['fields'] as $field) {
|
|
list($key, $prop) = explode(':', $field);
|
|
if (!isset($wrapper->$key)) {
|
|
continue;
|
|
}
|
|
$child = $wrapper->$key;
|
|
|
|
$values += array($key => array());
|
|
$this->extractHierarchy($child, $prop, $values[$key]);
|
|
}
|
|
foreach ($values as $key => $value) {
|
|
$item->$key = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Declare the properties that are (or can be) added to items with this
|
|
* callback. If a property with this name already exists for an entity it
|
|
* will be overridden, so keep a clear namespace by prefixing the properties
|
|
* with the module name if this is not desired.
|
|
*
|
|
* @see hook_entity_property_info()
|
|
*
|
|
* @return array
|
|
* Information about all additional properties, as specified by
|
|
* hook_entity_property_info() (only the inner "properties" array).
|
|
*/
|
|
public function propertyInfo() {
|
|
if (empty($this->options['fields'])) {
|
|
return array();
|
|
}
|
|
|
|
$ret = array();
|
|
$wrapper = $this->index->entityWrapper(NULL, FALSE);
|
|
foreach ($this->options['fields'] as $field) {
|
|
list($key, $prop) = explode(':', $field);
|
|
if (!isset($wrapper->$key)) {
|
|
continue;
|
|
}
|
|
$child = $wrapper->$key;
|
|
while (search_api_is_list_type($child->type())) {
|
|
$child = $child[0];
|
|
}
|
|
if (!isset($child->$prop)) {
|
|
continue;
|
|
}
|
|
if (!isset($ret[$key])) {
|
|
$ret[$key] = $child->info();
|
|
$type = search_api_extract_inner_type($ret[$key]['type']);
|
|
$ret[$key]['type'] = "list<$type>";
|
|
$ret[$key]['getter callback'] = 'entity_property_verbatim_get';
|
|
// The return value of info() has some additional internal values set,
|
|
// which we have to unset for the use here.
|
|
unset($ret[$key]['name'], $ret[$key]['parent'], $ret[$key]['langcode'], $ret[$key]['clear'],
|
|
$ret[$key]['property info alter'], $ret[$key]['property defaults']);
|
|
}
|
|
if (isset($ret[$key]['bundle'])) {
|
|
$info = $child->$prop->info();
|
|
if (empty($info['bundle']) || $ret[$key]['bundle'] != $info['bundle']) {
|
|
unset($ret[$key]['bundle']);
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Helper method for finding all hierarchical fields of an index's type.
|
|
*
|
|
* @return array
|
|
* An array containing all hierarchical fields of the index, structured as
|
|
* an options array grouped by primary field.
|
|
*/
|
|
protected function getHierarchicalFields() {
|
|
if (!isset($this->field_options)) {
|
|
$this->field_options = array();
|
|
$wrapper = $this->index->entityWrapper(NULL, FALSE);
|
|
// Only entities can be indexed in hierarchies, as other properties don't
|
|
// have IDs that we can extract and store.
|
|
$entity_info = entity_get_info();
|
|
foreach ($wrapper as $key1 => $child) {
|
|
while (search_api_is_list_type($child->type())) {
|
|
$child = $child[0];
|
|
}
|
|
$info = $child->info();
|
|
$type = $child->type();
|
|
if (empty($entity_info[$type])) {
|
|
continue;
|
|
}
|
|
foreach ($child as $key2 => $prop) {
|
|
if (search_api_extract_inner_type($prop->type()) == $type) {
|
|
$prop_info = $prop->info();
|
|
$this->field_options[$info['label']]["$key1:$key2"] = $prop_info['label'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $this->field_options;
|
|
}
|
|
|
|
/**
|
|
* Extracts a hierarchy from a metadata wrapper by modifying $values.
|
|
*/
|
|
public function extractHierarchy(EntityMetadataWrapper $wrapper, $property, array &$values) {
|
|
if (search_api_is_list_type($wrapper->type())) {
|
|
foreach ($wrapper as $w) {
|
|
$this->extractHierarchy($w, $property, $values);
|
|
}
|
|
return;
|
|
}
|
|
try {
|
|
$v = $wrapper->value(array('identifier' => TRUE));
|
|
if ($v && !isset($values[$v])) {
|
|
$values[$v] = $v;
|
|
if (isset($wrapper->$property) && $wrapper->value() && $wrapper->$property->value()) {
|
|
$this->extractHierarchy($wrapper->$property, $property, $values);
|
|
}
|
|
}
|
|
}
|
|
catch (EntityMetadataWrapperException $e) {
|
|
// Some properties like entity_metadata_book_get_properties() throw
|
|
// exceptions, so we catch them here and ignore the property.
|
|
}
|
|
}
|
|
|
|
}
|