callback_add_aggregation.inc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <?php
  2. /**
  3. * Search API data alteration callback that adds an URL field for all items.
  4. */
  5. class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
  6. public function configurationForm() {
  7. $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
  8. $fields = $this->index->getFields(FALSE);
  9. $field_options = array();
  10. foreach ($fields as $name => $field) {
  11. $field_options[$name] = $field['name'];
  12. }
  13. $additional = empty($this->options['fields']) ? array() : $this->options['fields'];
  14. $types = $this->getTypes();
  15. $type_descriptions = $this->getTypes('description');
  16. $tmp = array();
  17. foreach ($types as $type => $name) {
  18. $tmp[$type] = array(
  19. '#type' => 'item',
  20. '#description' => $type_descriptions[$type],
  21. );
  22. }
  23. $type_descriptions = $tmp;
  24. $form['#id'] = 'edit-callbacks-search-api-alter-add-aggregation-settings';
  25. $form['description'] = array(
  26. '#markup' => t('<p>This data alteration lets you define additional fields that will be added to this index. ' .
  27. 'Each of these new fields will be an aggregation of one or more existing fields.</p>' .
  28. '<p>To add a new aggregated field, click the "Add new field" button and then fill out the form.</p>' .
  29. '<p>To remove a previously defined field, click the "Remove field" button.</p>' .
  30. '<p>You can also change the names or contained fields of existing aggregated fields.</p>'),
  31. );
  32. $form['fields']['#prefix'] = '<div id="search-api-alter-add-aggregation-field-settings">';
  33. $form['fields']['#suffix'] = '</div>';
  34. if (isset($this->changes)) {
  35. $form['fields']['#prefix'] .= '<div class="messages warning">All changes in the form will not be saved until the <em>Save configuration</em> button at the form bottom is clicked.</div>';
  36. }
  37. foreach ($additional as $name => $field) {
  38. $form['fields'][$name] = array(
  39. '#type' => 'fieldset',
  40. '#title' => $field['name'] ? $field['name'] : t('New field'),
  41. '#collapsible' => TRUE,
  42. '#collapsed' => (boolean) $field['name'],
  43. );
  44. $form['fields'][$name]['name'] = array(
  45. '#type' => 'textfield',
  46. '#title' => t('New field name'),
  47. '#default_value' => $field['name'],
  48. '#required' => TRUE,
  49. );
  50. $form['fields'][$name]['type'] = array(
  51. '#type' => 'select',
  52. '#title' => t('Aggregation type'),
  53. '#options' => $types,
  54. '#default_value' => $field['type'],
  55. '#required' => TRUE,
  56. );
  57. $form['fields'][$name]['type_descriptions'] = $type_descriptions;
  58. foreach (array_keys($types) as $type) {
  59. $form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]']['value'] = $type;
  60. }
  61. $form['fields'][$name]['fields'] = array(
  62. '#type' => 'checkboxes',
  63. '#title' => t('Contained fields'),
  64. '#options' => $field_options,
  65. '#default_value' => drupal_map_assoc($field['fields']),
  66. '#attributes' => array('class' => array('search-api-alter-add-aggregation-fields')),
  67. '#required' => TRUE,
  68. );
  69. $form['fields'][$name]['actions'] = array(
  70. '#type' => 'actions',
  71. 'remove' => array(
  72. '#type' => 'submit',
  73. '#value' => t('Remove field'),
  74. '#submit' => array('_search_api_add_aggregation_field_submit'),
  75. '#limit_validation_errors' => array(),
  76. '#name' => 'search_api_add_aggregation_remove_' . $name,
  77. '#ajax' => array(
  78. 'callback' => '_search_api_add_aggregation_field_ajax',
  79. 'wrapper' => 'search-api-alter-add-aggregation-field-settings',
  80. ),
  81. ),
  82. );
  83. }
  84. $form['actions']['#type'] = 'actions';
  85. $form['actions']['add_field'] = array(
  86. '#type' => 'submit',
  87. '#value' => t('Add new field'),
  88. '#submit' => array('_search_api_add_aggregation_field_submit'),
  89. '#limit_validation_errors' => array(),
  90. '#ajax' => array(
  91. 'callback' => '_search_api_add_aggregation_field_ajax',
  92. 'wrapper' => 'search-api-alter-add-aggregation-field-settings',
  93. ),
  94. );
  95. return $form;
  96. }
  97. public function configurationFormValidate(array $form, array &$values, array &$form_state) {
  98. unset($values['actions']);
  99. if (empty($values['fields'])) {
  100. return;
  101. }
  102. foreach ($values['fields'] as $name => $field) {
  103. $fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
  104. unset($values['fields'][$name]['actions']);
  105. if ($field['name'] && !$fields) {
  106. form_error($form['fields'][$name]['fields'], t('You have to select at least one field to aggregate. If you want to remove an aggregated field, please delete its name.'));
  107. }
  108. }
  109. }
  110. public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
  111. if (empty($values['fields'])) {
  112. return array();
  113. }
  114. $index_fields = $this->index->getFields(FALSE);
  115. foreach ($values['fields'] as $name => $field) {
  116. if (!$field['name']) {
  117. unset($values['fields'][$name]);
  118. }
  119. else {
  120. $values['fields'][$name]['description'] = $this->fieldDescription($field, $index_fields);
  121. }
  122. }
  123. $this->options = $values;
  124. return $values;
  125. }
  126. public function alterItems(array &$items) {
  127. if (!$items) {
  128. return;
  129. }
  130. if (isset($this->options['fields'])) {
  131. $types = $this->getTypes('type');
  132. foreach ($items as $item) {
  133. $wrapper = $this->index->entityWrapper($item);
  134. foreach ($this->options['fields'] as $name => $field) {
  135. if ($field['name']) {
  136. $required_fields = array();
  137. foreach ($field['fields'] as $f) {
  138. if (!isset($required_fields[$f])) {
  139. $required_fields[$f]['type'] = $types[$field['type']];
  140. }
  141. }
  142. $fields = search_api_extract_fields($wrapper, $required_fields);
  143. $values = array();
  144. foreach ($fields as $f) {
  145. if (isset($f['value'])) {
  146. $values[] = $f['value'];
  147. }
  148. }
  149. $values = $this->flattenArray($values);
  150. $this->reductionType = $field['type'];
  151. $item->$name = array_reduce($values, array($this, 'reduce'), NULL);
  152. if ($field['type'] == 'count' && !$item->$name) {
  153. $item->$name = 0;
  154. }
  155. }
  156. }
  157. }
  158. }
  159. }
  160. /**
  161. * Helper method for reducing an array to a single value.
  162. */
  163. public function reduce($a, $b) {
  164. switch ($this->reductionType) {
  165. case 'fulltext':
  166. return isset($a) ? $a . "\n\n" . $b : $b;
  167. case 'sum':
  168. return $a + $b;
  169. case 'count':
  170. return $a + 1;
  171. case 'max':
  172. return isset($a) ? max($a, $b) : $b;
  173. case 'min':
  174. return isset($a) ? min($a, $b) : $b;
  175. case 'first':
  176. return isset($a) ? $a : $b;
  177. }
  178. }
  179. /**
  180. * Helper method for flattening a multi-dimensional array.
  181. */
  182. protected function flattenArray(array $data) {
  183. $ret = array();
  184. foreach ($data as $item) {
  185. if (!isset($item)) {
  186. continue;
  187. }
  188. if (is_scalar($item)) {
  189. $ret[] = $item;
  190. }
  191. else {
  192. $ret = array_merge($ret, $this->flattenArray($item));
  193. }
  194. }
  195. return $ret;
  196. }
  197. public function propertyInfo() {
  198. $types = $this->getTypes('type');
  199. $ret = array();
  200. if (isset($this->options['fields'])) {
  201. foreach ($this->options['fields'] as $name => $field) {
  202. $ret[$name] = array(
  203. 'label' => $field['name'],
  204. 'description' => empty($field['description']) ? '' : $field['description'],
  205. 'type' => $types[$field['type']],
  206. );
  207. }
  208. }
  209. return $ret;
  210. }
  211. /**
  212. * Helper method for creating a field description.
  213. */
  214. protected function fieldDescription(array $field, array $index_fields) {
  215. $fields = array();
  216. foreach ($field['fields'] as $f) {
  217. $fields[] = isset($index_fields[$f]) ? $index_fields[$f]['name'] : $f;
  218. }
  219. $type = $this->getTypes();
  220. $type = $type[$field['type']];
  221. return t('A @type aggregation of the following fields: @fields.', array('@type' => $type, '@fields' => implode(', ', $fields)));
  222. }
  223. /**
  224. * Helper method for getting all available aggregation types.
  225. *
  226. * @param $info (optional)
  227. * One of "name", "type" or "description", to indicate what values should be
  228. * returned for the types. Defaults to "name".
  229. *
  230. */
  231. protected function getTypes($info = 'name') {
  232. switch ($info) {
  233. case 'name':
  234. return array(
  235. 'fulltext' => t('Fulltext'),
  236. 'sum' => t('Sum'),
  237. 'count' => t('Count'),
  238. 'max' => t('Maximum'),
  239. 'min' => t('Minimum'),
  240. 'first' => t('First'),
  241. );
  242. case 'type':
  243. return array(
  244. 'fulltext' => 'text',
  245. 'sum' => 'integer',
  246. 'count' => 'integer',
  247. 'max' => 'integer',
  248. 'min' => 'integer',
  249. 'first' => 'string',
  250. );
  251. case 'description':
  252. return array(
  253. 'fulltext' => t('The Fulltext aggregation concatenates the text data of all contained fields.'),
  254. 'sum' => t('The Sum aggregation adds the values of all contained fields numerically.'),
  255. 'count' => t('The Count aggregation takes the total number of contained field values as the aggregated field value.'),
  256. 'max' => t('The Maximum aggregation computes the numerically largest contained field value.'),
  257. 'min' => t('The Minimum aggregation computes the numerically smallest contained field value.'),
  258. '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.'),
  259. );
  260. }
  261. }
  262. /**
  263. * Submit helper callback for buttons in the callback's configuration form.
  264. */
  265. public function formButtonSubmit(array $form, array &$form_state) {
  266. $button_name = $form_state['triggering_element']['#name'];
  267. if ($button_name == 'op') {
  268. for ($i = 1; isset($this->options['fields']['search_api_aggregation_' . $i]); ++$i) {
  269. }
  270. $this->options['fields']['search_api_aggregation_' . $i] = array(
  271. 'name' => '',
  272. 'type' => 'fulltext',
  273. 'fields' => array(),
  274. );
  275. }
  276. else {
  277. $field = substr($button_name, 34);
  278. unset($this->options['fields'][$field]);
  279. }
  280. $form_state['rebuild'] = TRUE;
  281. $this->changes = TRUE;
  282. }
  283. }
  284. /**
  285. * Submit function for buttons in the callback's configuration form.
  286. */
  287. function _search_api_add_aggregation_field_submit(array $form, array &$form_state) {
  288. $form_state['callbacks']['search_api_alter_add_aggregation']->formButtonSubmit($form, $form_state);
  289. }
  290. /**
  291. * AJAX submit function for buttons in the callback's configuration form.
  292. */
  293. function _search_api_add_aggregation_field_ajax(array $form, array &$form_state) {
  294. return $form['callbacks']['settings']['search_api_alter_add_aggregation']['fields'];
  295. }