callback_add_aggregation.inc 11 KB

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