diff --git a/contrib/search_api_views/includes/handler_argument_date.inc b/contrib/search_api_views/includes/handler_argument_date.inc index 4d74b39..a92c896 100644 --- a/contrib/search_api_views/includes/handler_argument_date.inc +++ b/contrib/search_api_views/includes/handler_argument_date.inc @@ -1,5 +1,9 @@ argument. + * {@inheritdoc} */ public function query($group_by = FALSE) { if (empty($this->value)) { $this->fillValue(); + if ($this->value === FALSE) { + $this->abort(); + return; + } } + $outer_conjunction = strtoupper($this->operator); + if (empty($this->options['not'])) { $operator = '='; - $conjunction = 'OR'; + $inner_conjunction = 'OR'; } else { $operator = '<>'; - $conjunction = 'AND'; + $inner_conjunction = 'AND'; } - if (!empty($this->argument)) { - $dates = preg_split('/[, ]/', $this->argument); - - if (!empty($dates)) { - $filter = $this->query->createFilter($conjunction); - foreach ($dates as $date) { - $values = explode(';', $date); - + if (!empty($this->value)) { + if (!empty($this->value)) { + $outer_filter = $this->query->createFilter($outer_conjunction); + foreach ($this->value as $value) { + $value_filter = $this->query->createFilter($inner_conjunction); + $values = explode(';', $value); + $values = array_map(array($this, 'getTimestamp'), $values); + if (in_array(FALSE, $values, TRUE)) { + $this->abort(); + return; + } $is_range = (count($values) > 1); - $my_filter = ($is_range ? $this->query->createFilter(empty($this->options['not']) ? 'AND' : 'OR') : $filter); + + $inner_filter = ($is_range ? $this->query->createFilter('AND') : $value_filter); $range_op = (empty($this->options['not']) ? '>=' : '<'); - $my_filter->condition($this->real_field, $this->getTimestamp($values[0]), $is_range ? $range_op : $operator); + $inner_filter->condition($this->real_field, $values[0], $is_range ? $range_op : $operator); if ($is_range) { $range_op = (empty($this->options['not']) ? '<=' : '>'); - $my_filter->condition($this->real_field, strtotime('+1 day', $this->getTimestamp($values[1]))-1, $range_op); - $filter->filter($my_filter); + $inner_filter->condition($this->real_field, $values[1], $range_op); + $value_filter->filter($inner_filter); } + $outer_filter->filter($value_filter); } - $this->query->filter($filter); + $this->query->filter($outer_filter); } } } - protected function getTimestamp($value) { + /** + * Converts a value to a timestamp, if it isn't one already. + * + * @param string|int $value + * The value to convert. Either a timestamp, or a date/time string as + * recognized by strtotime(). + * + * @return int|false + * The parsed timestamp, or FALSE if an illegal string was passed. + */ + public function getTimestamp($value) { if (is_numeric($value)) { return $value; } - $date = new DateTime($value, date_default_timezone_object()); - $date->setTime(0, 0, 0); - return $date->format('U'); + return strtotime($value); } /** - * Get the title this argument will assign the view, given the argument. + * Fills $this->value with data from the argument. */ - public function title() { - if (!empty($this->argument)) { - if (empty($this->value)) { - $this->fillValue(); + protected function fillValue() { + if (!empty($this->options['break_phrase'])) { + // Set up defaults: + if (!isset($this->value)) { + $this->value = array(); } - $terms = array(); - foreach ($this->value as $tid) { - $taxonomy_term = taxonomy_term_load($tid); - if ($taxonomy_term) { - $terms[] = check_plain($taxonomy_term->name); - } + + if (!isset($this->operator)) { + $this->operator = 'OR'; + } + + if (empty($this->argument)) { + return; + } + + if (preg_match('/^([-\d;:\s]+\+)*[-\d;:\s]+$/', $this->argument)) { + // The '+' character in a query string may be parsed as ' '. + $this->value = explode('+', $this->argument); + } + elseif (preg_match('/^([-\d;:\s]+,)*[-\d;:\s]+$/', $this->argument)) { + $this->operator = 'AND'; + $this->value = explode(',', $this->argument); } - return $terms ? implode(', ', $terms) : check_plain($this->argument); + // Keep an 'error' value if invalid strings were given. + if (!empty($this->argument) && (empty($this->value) || !is_array($this->value))) { + $this->value = FALSE; + } } else { - return check_plain($this->argument); + $this->value = array($this->argument); } } /** - * Fill $this->value with data from the argument. + * Aborts the associated query due to an illegal argument. + */ + protected function abort() { + $variables['!field'] = $this->definition['group'] . ': ' . $this->definition['title']; + $this->query->abort(t('Illegal argument passed to !field contextual filter.', $variables)); + } + + /** + * Computes the title this argument will assign the view, given the argument. * - * Uses views_break_phrase(), if appropriate. + * @return string + * A title fitting for the passed argument. */ - protected function fillValue() { - if (!empty($this->options['break_phrase'])) { - views_break_phrase($this->argument, $this); - } - else { - $this->value = array($this->argument); + public function title() { + if (!empty($this->argument)) { + if (empty($this->value)) { + $this->fillValue(); + } + $dates = array(); + foreach ($this->value as $date) { + $date_parts = explode(';', $date); + + $ts = $this->getTimestamp($date_parts[0]); + $datestr = format_date($ts, 'short'); + if (count($date_parts) > 1) { + $ts = $this->getTimestamp($date_parts[1]); + $datestr .= ' - ' . format_date($ts, 'short'); + } + + if ($datestr) { + $dates[] = $datestr; + } + } + + return $dates ? implode(', ', $dates) : check_plain($this->argument); } + + return check_plain($this->argument); } } diff --git a/contrib/search_api_views/includes/query.inc b/contrib/search_api_views/includes/query.inc index e6b41a0..3063338 100644 --- a/contrib/search_api_views/includes/query.inc +++ b/contrib/search_api_views/includes/query.inc @@ -329,8 +329,14 @@ class SearchApiViewsQuery extends views_plugin_query { * * Used by handlers to flag a fatal error which shouldn't be displayed but * still lead to the view returning empty and the search not being executed. + * + * @param string|null $msg + * Optionally, a translated, unescaped error message to display. */ - public function abort() { + public function abort($msg = NULL) { + if ($msg) { + $this->errors[] = $msg; + } $this->abort = TRUE; } diff --git a/contrib/search_api_views/search_api_views.info b/contrib/search_api_views/search_api_views.info index 735ccfa..0baa3d1 100644 --- a/contrib/search_api_views/search_api_views.info +++ b/contrib/search_api_views/search_api_views.info @@ -12,6 +12,7 @@ files[] = includes/handler_argument.inc files[] = includes/handler_argument_fulltext.inc files[] = includes/handler_argument_more_like_this.inc files[] = includes/handler_argument_string.inc +files[] = includes/handler_argument_date.inc files[] = includes/handler_argument_taxonomy_term.inc files[] = includes/handler_filter.inc files[] = includes/handler_filter_boolean.inc diff --git a/contrib/search_api_views/search_api_views.views.inc b/contrib/search_api_views/search_api_views.views.inc index 1ee9927..540dd58 100644 --- a/contrib/search_api_views/search_api_views.views.inc +++ b/contrib/search_api_views/search_api_views.views.inc @@ -188,6 +188,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper if ($inner_type == 'string' || $inner_type == 'uri') { $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentString'; } + elseif ($inner_type == 'date') { + $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentDate'; + } else { $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument'; }