callback_add_hierarchy.inc 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <?php
  2. /**
  3. * @file
  4. * Contains SearchApiAlterAddHierarchy.
  5. */
  6. /**
  7. * Adds all ancestors for hierarchical fields.
  8. */
  9. class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
  10. /**
  11. * Cached value for the hierarchical field options.
  12. *
  13. * @var array
  14. *
  15. * @see getHierarchicalFields()
  16. */
  17. protected $field_options;
  18. /**
  19. * Overrides SearchApiAbstractAlterCallback::supportsIndex().
  20. *
  21. * Returns TRUE only if any hierarchical fields are available.
  22. */
  23. public function supportsIndex(SearchApiIndex $index) {
  24. return (bool) $this->getHierarchicalFields();
  25. }
  26. /**
  27. * {@inheritdoc}
  28. */
  29. public function configurationForm() {
  30. $options = $this->getHierarchicalFields();
  31. $this->options += array('fields' => array());
  32. $form['fields'] = array(
  33. '#title' => t('Hierarchical fields'),
  34. '#description' => t('Select the fields which should be supplemented with their ancestors. ' .
  35. 'Each field is listed along with its children of the same type. ' .
  36. 'When selecting several child properties of a field, all those properties will be recursively added to that field. ' .
  37. 'Please note that you should de-select all fields before disabling this data alteration.'),
  38. '#type' => 'select',
  39. '#multiple' => TRUE,
  40. '#size' => min(6, count($options, COUNT_RECURSIVE)),
  41. '#options' => $options,
  42. '#default_value' => $this->options['fields'],
  43. );
  44. return $form;
  45. }
  46. /**
  47. * {@inheritdoc}
  48. */
  49. public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
  50. // Change the saved type of fields in the index, if necessary.
  51. if (!empty($this->index->options['fields'])) {
  52. $fields = &$this->index->options['fields'];
  53. $previous = drupal_map_assoc($this->options['fields']);
  54. foreach ($values['fields'] as $field) {
  55. list($key) = explode(':', $field);
  56. if (empty($previous[$field]) && isset($fields[$key]['type'])) {
  57. $fields[$key]['type'] = 'list<' . search_api_extract_inner_type($fields[$key]['type']) . '>';
  58. $change = TRUE;
  59. }
  60. }
  61. $new = drupal_map_assoc($values['fields']);
  62. foreach ($previous as $field) {
  63. list($key) = explode(':', $field);
  64. if (empty($new[$field]) && isset($fields[$key]['type'])) {
  65. $w = $this->index->entityWrapper(NULL, FALSE);
  66. if (isset($w->$key)) {
  67. $type = $w->$key->type();
  68. $inner = search_api_extract_inner_type($fields[$key]['type']);
  69. $fields[$key]['type'] = search_api_nest_type($inner, $type);
  70. $change = TRUE;
  71. }
  72. }
  73. }
  74. if (isset($change)) {
  75. $this->index->save();
  76. }
  77. }
  78. return parent::configurationFormSubmit($form, $values, $form_state);
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. public function alterItems(array &$items) {
  84. if (empty($this->options['fields'])) {
  85. return;
  86. }
  87. foreach ($items as $item) {
  88. $wrapper = $this->index->entityWrapper($item, FALSE);
  89. $values = array();
  90. foreach ($this->options['fields'] as $field) {
  91. list($key, $prop) = explode(':', $field);
  92. if (!isset($wrapper->$key)) {
  93. continue;
  94. }
  95. $child = $wrapper->$key;
  96. $values += array($key => array());
  97. $this->extractHierarchy($child, $prop, $values[$key]);
  98. }
  99. foreach ($values as $key => $value) {
  100. $item->$key = array_values($value);
  101. }
  102. }
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function propertyInfo() {
  108. if (empty($this->options['fields'])) {
  109. return array();
  110. }
  111. $ret = array();
  112. $wrapper = $this->index->entityWrapper(NULL, FALSE);
  113. foreach ($this->options['fields'] as $field) {
  114. list($key, $prop) = explode(':', $field);
  115. if (!isset($wrapper->$key)) {
  116. continue;
  117. }
  118. $child = $wrapper->$key;
  119. while (search_api_is_list_type($child->type())) {
  120. $child = $child[0];
  121. }
  122. if (!isset($child->$prop)) {
  123. continue;
  124. }
  125. if (!isset($ret[$key])) {
  126. $ret[$key] = $child->info();
  127. $type = search_api_extract_inner_type($ret[$key]['type']);
  128. $ret[$key]['type'] = "list<$type>";
  129. $ret[$key]['getter callback'] = 'entity_property_verbatim_get';
  130. // The return value of info() has some additional internal values set,
  131. // which we have to unset for the use here.
  132. unset($ret[$key]['name'], $ret[$key]['parent'], $ret[$key]['langcode'], $ret[$key]['clear'],
  133. $ret[$key]['property info alter'], $ret[$key]['property defaults']);
  134. }
  135. if (isset($ret[$key]['bundle'])) {
  136. $info = $child->$prop->info();
  137. if (empty($info['bundle']) || $ret[$key]['bundle'] != $info['bundle']) {
  138. unset($ret[$key]['bundle']);
  139. }
  140. }
  141. }
  142. return $ret;
  143. }
  144. /**
  145. * Finds all hierarchical fields for the current index.
  146. *
  147. * @return array
  148. * An array containing all hierarchical fields of the index, structured as
  149. * an options array grouped by primary field.
  150. */
  151. protected function getHierarchicalFields() {
  152. if (!isset($this->field_options)) {
  153. $this->field_options = array();
  154. $wrapper = $this->index->entityWrapper(NULL, FALSE);
  155. // Only entities can be indexed in hierarchies, as other properties don't
  156. // have IDs that we can extract and store.
  157. $entity_info = entity_get_info();
  158. foreach ($wrapper as $key1 => $child) {
  159. while (search_api_is_list_type($child->type())) {
  160. $child = $child[0];
  161. }
  162. $info = $child->info();
  163. $type = $child->type();
  164. if (empty($entity_info[$type])) {
  165. continue;
  166. }
  167. foreach ($child as $key2 => $prop) {
  168. if (search_api_extract_inner_type($prop->type()) == $type) {
  169. $prop_info = $prop->info();
  170. $this->field_options[$info['label']]["$key1:$key2"] = $prop_info['label'];
  171. }
  172. }
  173. }
  174. }
  175. return $this->field_options;
  176. }
  177. /**
  178. * Extracts a hierarchy from a metadata wrapper by modifying $values.
  179. */
  180. public function extractHierarchy(EntityMetadataWrapper $wrapper, $property, array &$values) {
  181. if (search_api_is_list_type($wrapper->type())) {
  182. foreach ($wrapper as $w) {
  183. $this->extractHierarchy($w, $property, $values);
  184. }
  185. return;
  186. }
  187. try {
  188. $v = $wrapper->value(array('identifier' => TRUE));
  189. if ($v && !isset($values[$v])) {
  190. $values[$v] = $v;
  191. if (isset($wrapper->$property) && $wrapper->value() && $wrapper->$property->value()) {
  192. $this->extractHierarchy($wrapper->$property, $property, $values);
  193. }
  194. }
  195. }
  196. catch (EntityMetadataWrapperException $e) {
  197. // Some properties like entity_metadata_book_get_properties() throw
  198. // exceptions, so we catch them here and ignore the property.
  199. }
  200. }
  201. }