diff --git a/contrib/search_api_views/includes/handler_filter_entity.inc b/contrib/search_api_views/includes/handler_filter_entity.inc new file mode 100644 index 0000000..f095f16 --- /dev/null +++ b/contrib/search_api_views/includes/handler_filter_entity.inc @@ -0,0 +1,201 @@ + $this->isMultiValued() ? t('Is one of') : t('Is'), + 'all of' => t('Is all of'), + '<>' => $this->isMultiValued() ? t('Is not one of') : t('Is not'), + 'empty' => t('Is empty'), + 'not empty' => t('Is not empty'), + ); + if (!$this->isMultiValued()) { + unset($operators['all of']); + } + return $operators; + } + + /** + * {@inheritdoc} + */ + public function option_definition() { + $options = parent::option_definition(); + + $options['expose']['multiple']['default'] = TRUE; + + return $options; + } + + /** + * {@inheritdoc} + */ + public function value_form(&$form, &$form_state) { + parent::value_form($form, $form_state); + if (!is_array($this->value)) { + $this->value = $this->value ? array($this->value) : array(); + } + } + + /** + * {@inheritdoc} + */ + public function value_validate($form, &$form_state) { + if (!empty($form['value'])) { + $value = &$form_state['values']['options']['value']; + $values = $this->isMultiValued($form_state['values']['options']) ? drupal_explode_tags($value) : array($value); + $ids = $this->validate_entity_strings($form['value'], $values); + + if ($ids) { + $value = $ids; + } + } + } + + /** + * {@inheritdoc} + */ + public function accept_exposed_input($input) { + $rc = parent::accept_exposed_input($input); + + if ($rc) { + // If we have previously validated input, override. + if ($this->validated_exposed_input) { + $this->value = $this->validated_exposed_input; + } + } + + return $rc; + } + + /** + * {@inheritdoc} + */ + public function exposed_validate(&$form, &$form_state) { + if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) { + return; + } + + $identifier = $this->options['expose']['identifier']; + $input = $form_state['values'][$identifier]; + + if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) { + $this->operator = $this->options['group_info']['group_items'][$input]['operator']; + $input = $this->options['group_info']['group_items'][$input]['value']; + } + + $values = $this->isMultiValued() ? drupal_explode_tags($input) : array($input); + + if (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All'))) { + $this->validated_exposed_input = $this->validate_entity_strings($form[$identifier], $values); + } + else { + $this->validated_exposed_input = FALSE; + } + } + + /** + * Determines whether multiple user names can be entered into this filter. + * + * This is either the case if the form isn't exposed, or if the " Allow + * multiple selections" option is enabled. + * + * @param array $options + * (optional) The options array to use. If not supplied, the options set on + * this filter will be used. + * + * @return bool + * TRUE if multiple values can be entered for this filter, FALSE otherwise. + */ + protected function isMultiValued(array $options = array()) { + $options = $options ? $options : $this->options; + return empty($options['exposed']) || !empty($options['expose']['multiple']); + } + + /** + * {@inheritdoc} + */ + public function admin_summary() { + $value = $this->value; + $this->value = empty($value) ? '' : $this->ids_to_strings($value); + $ret = parent::admin_summary(); + $this->value = $value; + return $ret; + } + + /** + * {@inheritdoc} + */ + public function query() { + if ($this->operator === 'empty') { + $this->query->condition($this->real_field, NULL, '=', $this->options['group']); + } + elseif ($this->operator === 'not empty') { + $this->query->condition($this->real_field, NULL, '<>', $this->options['group']); + } + elseif (is_array($this->value)) { + if (count($this->value) == 1) { + $this->query->condition($this->real_field, reset($this->value), $this->operator, $this->options['group']); + } + else { + $filter = $this->query->createFilter($this->operator === '<>' || $this->operator === 'all of' ? 'AND' : 'OR'); + foreach ($this->value as $value) { + $filter->condition($this->real_field, $value, $this->operator === 'all of' ? '=' : $this->operator); + } + $this->query->filter($filter, $this->options['group']); + } + } + } + +} diff --git a/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc b/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc new file mode 100644 index 0000000..f295f81 --- /dev/null +++ b/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc @@ -0,0 +1,310 @@ + 'textfield'); + $options['limit'] = array('default' => TRUE, 'bool' => TRUE); + $options['vocabulary'] = array('default' => 0); + $options['hierarchy'] = array('default' => 0); + $options['error_message'] = array('default' => TRUE, 'bool' => TRUE); + + return $options; + } + + /** + * {@inheritdoc} + */ + public function extra_options_form(&$form, &$form_state) { + $vocabularies = taxonomy_get_vocabularies(); + $options = array(); + foreach ($vocabularies as $voc) { + $options[$voc->machine_name] = check_plain($voc->name); + } + + if ($this->options['limit']) { + // We only do this when the form is displayed. + if (empty($this->definition['vocabulary'])) { + $first_vocabulary = reset($vocabularies); + $this->definition['vocabulary'] = $first_vocabulary->machine_name; + } + + if (empty($this->definition['vocabulary'])) { + $form['vocabulary'] = array( + '#type' => 'radios', + '#title' => t('Vocabulary'), + '#options' => $options, + '#description' => t('Select which vocabulary to show terms for in the regular options.'), + '#default_value' => $this->definition['vocabulary'], + ); + } + } + + $form['type'] = array( + '#type' => 'radios', + '#title' => t('Selection type'), + '#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')), + '#default_value' => $this->options['type'], + ); + + $form['hierarchy'] = array( + '#type' => 'checkbox', + '#title' => t('Show hierarchy in dropdown'), + '#default_value' => !empty($this->options['hierarchy']), + '#dependency' => array('radio:options[type]' => array('select')), + ); + } + + /** + * {@inheritdoc} + */ + public function value_form(&$form, &$form_state) { + $vocabulary = taxonomy_vocabulary_machine_name_load($this->definition['vocabulary']); + if (empty($vocabulary) && $this->options['limit']) { + $form['markup'] = array( + '#markup' => '
' . t('An invalid vocabulary is selected. Please change it in the options.') . '
', + ); + return; + } + + parent::value_form($form, $form_state); + + if ($this->options['type'] == 'textfield') { + if ($this->value) { + $form['value']['#default_value'] = $this->ids_to_strings($this->value); + } + + $form['value']['#title'] = $this->options['limit'] ? t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)) : t('Select terms'); + + if ($this->options['limit']) { + $form['value']['#autocomplete_path'] = 'admin/views/ajax/autocomplete/taxonomy/' . $vocabulary->vid; + } + } + else { + if (!empty($this->options['hierarchy']) && $this->options['limit']) { + $tree = taxonomy_get_tree($vocabulary->vid); + $options = array(); + + if ($tree) { + foreach ($tree as $term) { + $choice = new stdClass(); + $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name); + $options[] = $choice; + } + } + } + else { + $options = array(); + $query = db_select('taxonomy_term_data', 'td'); + $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid'); + $query->fields('td'); + $query->orderby('tv.weight'); + $query->orderby('tv.name'); + $query->orderby('td.weight'); + $query->orderby('td.name'); + $query->addTag('term_access'); + if ($this->options['limit']) { + $query->condition('tv.machine_name', $vocabulary->machine_name); + } + $result = $query->execute(); + foreach ($result as $term) { + $options[$term->tid] = $term->name; + } + } + + $default_value = (array) $this->value; + + if (!empty($form_state['exposed'])) { + $identifier = $this->options['expose']['identifier']; + + if (!empty($this->options['expose']['reduce'])) { + $options = $this->reduce_value_options($options); + + if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) { + $default_value = array(); + } + } + + if (empty($this->options['expose']['multiple'])) { + if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) { + $default_value = 'All'; + } + elseif (empty($default_value)) { + $keys = array_keys($options); + $default_value = array_shift($keys); + } + // Due to #1464174 there is a chance that array('') was saved in the admin ui. + // Let's choose a safe default value. + elseif ($default_value == array('')) { + $default_value = 'All'; + } + else { + $copy = $default_value; + $default_value = array_shift($copy); + } + } + } + $form['value'] = array( + '#type' => 'select', + '#title' => $this->options['limit'] ? t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)) : t('Select terms'), + '#multiple' => TRUE, + '#options' => $options, + '#size' => min(9, count($options)), + '#default_value' => $default_value, + ); + + if (!empty($form_state['exposed']) && isset($identifier) && !isset($form_state['input'][$identifier])) { + $form_state['input'][$identifier] = $default_value; + } + } + } + + /** + * {@inheritdoc} + */ + public function value_validate($form, &$form_state) { + // We only validate if they've chosen the text field style. + if ($this->options['type'] != 'textfield') { + return; + } + + parent::value_validate($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function accept_exposed_input($input) { + if (empty($this->options['exposed'])) { + return TRUE; + } + + // If view is an attachment and is inheriting exposed filters, then assume + // exposed input has already been validated + if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) { + $this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']]; + } + + // If it's non-required and there's no value don't bother filtering. + if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) { + return FALSE; + } + + return parent::accept_exposed_input($input); + } + + /** + * {@inheritdoc} + */ + public function exposed_validate(&$form, &$form_state) { + if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) { + return; + } + + // We only validate if they've chosen the text field style. + if ($this->options['type'] != 'textfield') { + $input = $form_state['values'][$this->options['expose']['identifier']]; + if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) { + $input = $this->options['group_info']['group_items'][$input]['value']; + } + + if ($input != 'All') { + $this->validated_exposed_input = (array) $input; + } + return; + } + + parent::exposed_validate($form, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function validate_entity_strings(array &$form, array $values) { + if (empty($values)) { + return array(); + } + + $tids = array(); + $names = array(); + $missing = array(); + foreach ($values as $value) { + $missing[strtolower($value)] = TRUE; + $names[] = $value; + } + + if (!$names) { + return FALSE; + } + + $query = db_select('taxonomy_term_data', 'td'); + $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid'); + $query->fields('td'); + $query->condition('td.name', $names); + $query->condition('tv.machine_name', $this->definition['vocabulary']); + $query->addTag('term_access'); + $result = $query->execute(); + foreach ($result as $term) { + unset($missing[strtolower($term->name)]); + $tids[] = $term->tid; + } + + if ($missing && !empty($this->options['error_message'])) { + form_error($form, format_plural(count($missing), 'Unable to find term: @terms', 'Unable to find terms: @terms', array('@terms' => implode(', ', array_keys($missing))))); + } + elseif ($missing && empty($this->options['error_message'])) { + $tids = array(0); + } + + return $tids; + } + + /** + * {@inheritdoc} + */ + public function expose_form(&$form, &$form_state) { + parent::expose_form($form, $form_state); + if ($this->options['type'] != 'select') { + unset($form['expose']['reduce']); + } + $form['error_message'] = array( + '#type' => 'checkbox', + '#title' => t('Display error message'), + '#default_value' => !empty($this->options['error_message']), + ); + } + + /** + * {@inheritdoc} + */ + protected function ids_to_strings(array $ids) { + return implode(',', db_select('taxonomy_term_data', 'td') + ->fields('td', array('name')) + ->condition('td.tid', array_filter($ids)) + ->execute() + ->fetchCol()); + } + +} diff --git a/contrib/search_api_views/includes/handler_filter_user.inc b/contrib/search_api_views/includes/handler_filter_user.inc new file mode 100644 index 0000000..e3abd95 --- /dev/null +++ b/contrib/search_api_views/includes/handler_filter_user.inc @@ -0,0 +1,84 @@ +isMultiValued() ? 'admin/views/ajax/autocomplete/user' : 'user/autocomplete'; + $form['value']['#autocomplete_path'] = $path; + + // Set the correct default value in case the admin-set value is used (and a + // value is present). The value is used if the form is either not exposed, + // or the exposed form wasn't submitted yet (there is + if ($this->value && (empty($form_state['input']) || !empty($form_state['input']['live_preview']))) { + $form['value']['#default_value'] = $this->ids_to_strings($this->value); + } + } + + /** + * {@inheritdoc} + */ + protected function ids_to_strings(array $ids) { + $names = array(); + $args[':uids'] = array_filter($ids); + $result = db_query("SELECT uid, name FROM {users} u WHERE uid IN (:uids)", $args); + $result = $result->fetchAllKeyed(); + foreach ($ids as $uid) { + if (!$uid) { + $names[] = 'Anonymous'; + } + elseif (isset($result[$uid])) { + $names[] = $result[$uid]; + } + } + return implode(', ', $names); + } + + /** + * {@inheritdoc} + */ + protected function validate_entity_strings(array &$form, array $values) { + $uids = array(); + $missing = array(); + foreach ($values as $value) { + if (strtolower($value) == 'anonymous') { + $uids[] = 0; + } + else { + $missing[strtolower($value)] = $value; + } + } + + if (!$missing) { + return $uids; + } + + $result = db_query("SELECT * FROM {users} WHERE name IN (:names)", array(':names' => array_values($missing))); + foreach ($result as $account) { + unset($missing[strtolower($account->name)]); + $uids[] = $account->uid; + } + + if ($missing) { + form_error($form, format_plural(count($missing), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $missing)))); + } + + return $uids; + } + +} diff --git a/contrib/search_api_views/search_api_views.info b/contrib/search_api_views/search_api_views.info index 735ccfa..edef611 100644 --- a/contrib/search_api_views/search_api_views.info +++ b/contrib/search_api_views/search_api_views.info @@ -16,10 +16,13 @@ files[] = includes/handler_argument_taxonomy_term.inc files[] = includes/handler_filter.inc files[] = includes/handler_filter_boolean.inc files[] = includes/handler_filter_date.inc +files[] = includes/handler_filter_entity.inc files[] = includes/handler_filter_fulltext.inc files[] = includes/handler_filter_language.inc files[] = includes/handler_filter_options.inc +files[] = includes/handler_filter_taxonomy_term.inc files[] = includes/handler_filter_text.inc +files[] = includes/handler_filter_user.inc files[] = includes/handler_sort.inc files[] = includes/plugin_cache.inc files[] = includes/query.inc diff --git a/contrib/search_api_views/search_api_views.views.inc b/contrib/search_api_views/search_api_views.views.inc index 89b3c09..e782086 100644 --- a/contrib/search_api_views/search_api_views.views.inc +++ b/contrib/search_api_views/search_api_views.views.inc @@ -191,6 +191,17 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper elseif ($inner_type == 'date') { $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterDate'; } + elseif (isset($field['entity_type']) && $field['entity_type'] === 'user') { + $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterUser'; + } + elseif (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') { + $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterTaxonomyTerm'; + $info = $wrapper->info(); + $field_info = field_info_field($info['name']); + if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) { + $table[$id]['filter']['vocabulary'] = $field_info['settings']['allowed_values'][0]['vocabulary']; + } + } else { $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter'; }