diff --git a/metatag.module b/metatag.module index aef85fb..d479826 100644 --- a/metatag.module +++ b/metatag.module @@ -3285,3 +3285,10 @@ function i18n_string_metatag_config_delete($config) { $context = 'metatag_config:' . $config->instance; metatag_translations_delete($config->config, $context); } + +/** + * Implements hook_views_data(). + */ +function metatag_views_api() { + return array('api' => 3); +} diff --git a/metatag.views.inc b/metatag.views.inc new file mode 100644 index 0000000..6510829 --- /dev/null +++ b/metatag.views.inc @@ -0,0 +1,85 @@ + $entity_info) { + if (metatag_entity_supports_metatags($entity_type)) { + $entity_types[$entity_type] = $entity_info; + } + } + + foreach ($entity_types as $entity_type => $info) { + $support_revisions = !empty($info['entity keys']['revision']); + $data['metatag']['table']['join'][$info['base table']] = array( + 'field' => $support_revisions ? 'revision_id' : 'entity_id', + 'left_field' => $info['entity keys'][$support_revisions ? 'revision' : 'id'], + 'extra' => array( + array('field' => 'entity_type', 'value' => $entity_type), + // @todo Replace with real language support + # array('field' => 'language', 'value' => LANGUAGE_NONE) + ), + ); + } + + // Tracks how often each index is used to generate suffixes for duplicate + // names. + $blacklist = array_fill_keys(array_keys($data['metatag']), 1); + + $handler_default = 'metatag_handler_field_serialized'; + + // Assign a field handler based on the tag class. + $handlers = array( + 'DrupalListMetaTag' => 'metatag_handler_field_serialized_list' + ); + + // Add a field for each known meta tag. + $metatag_info = metatag_get_info(); + foreach ($metatag_info['tags'] as $name => $options) { + $field = array( + 'title' => $options['label'], + 'help' => !empty($options['description']) ? $options['description'] : ' ', + ); + if (isset($options['group'])) { + $group = isset($metatag_info['groups'][$options['group']]) ? $metatag_info['groups'][$options['group']]['label'] : $options['group']; + $field['group'] = $data['metatag']['table']['group'] . " ($group)"; + } + + $field['field'] = array( + 'real field' => 'data', + 'path' => array($name, 'value') + ); + $field['field']['handler'] = isset($handlers[$options['class']]) ? $handlers[$options['class']] : $handler_default; + $field['field']['metatag'] = $name; + + // Create a valid field name from the tag name. + // @todo Might have to check leading characters. + $safe_name = preg_replace('/[^a-z0-9_]/', '_', $name); + if (!isset($blacklist[$safe_name])) { + // Name not yet used, add to blacklist with a count of 1 + $blacklist[$safe_name] = 1; + } + else { + // Name already in use, append a suffix and increase count. + $safe_name .= '_' . $blacklist[$safe_name]++; + } + + $data['metatag'][$safe_name] = $field; + } + + return $data; +} diff --git a/views/metatag_handler_field_entity.inc b/views/metatag_handler_field_entity.inc new file mode 100644 index 0000000..a542bad --- /dev/null +++ b/views/metatag_handler_field_entity.inc @@ -0,0 +1,101 @@ + $info) { + $base_tables[$info['base table']] = $entity_type; + } + } + return isset($base_tables[$table]) ? $base_tables[$table] : NULL; +} + + +/** + * Class metatag_handler_entity. + * + * Uses modified code from views_handler_field_field to reliably provide the + * current field's base table as well as entity_type and entity_id. + */ +class metatag_handler_field_entity extends views_handler_field { + + var $entity_type_alias; + var $entity_id_alias; + + /** + * Set the base_table and base_table_alias. + * + * Copy of views_handler_field_field::get_base_table(). + * + * @return string + * The base table which is used in the current view "context". + */ + function get_base_table() { + $base_table = $this->view->base_table; + // If the current field is under a relationship you can't be sure that the + // base table of the view is the base table of the current field. + // For example a field from a node author on a node view does have users as base table. + if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') { + $relationships = $this->view->display_handler->get_option('relationships'); + if (!empty($relationships[$this->options['relationship']])) { + $options = $relationships[$this->options['relationship']]; + $data = views_fetch_data($options['table']); + $base_table = $data[$options['field']]['relationship']['base']; + } + } + return $base_table; + } + + /** + * Heavily modified code from views_hander_field_field::query(). + * + * By default, the only columns added to the query are entity_id and + * entity_type. This is because other needed data is fetched by entity_load(). + */ + function query() { + parent::query(); + + $base_table = $this->get_base_table(); + $base_table_alias = isset($this->relationship) ? $this->relationship : $base_table; + + $entity_type = _metatag_entity_type_from_table($base_table); + $entity_info = entity_get_info($entity_type); + + $this->entity_id_alias = $this->query->add_field($base_table_alias, $entity_info['entity keys']['id']); + + // The alias needs to be unique, so we use both the field table and the + // entity type. + // @todo Probably not necessary for this use case? + $entity_type_alias = $this->table_alias . '_' . $entity_type . '_entity_type'; + $this->entity_type_alias = $this->query->add_field(NULL, "'$entity_type'", $entity_type_alias); + } + + function get_entity_type($values) { + return isset($values->{$this->entity_type_alias}) ? $values->{$this->entity_type_alias} : NULL; + } + + function get_entity($values) { + $entity_type = $this->get_entity_type($values); + $entity_id = isset($values->{$this->entity_id_alias}) ? $values->{$this->entity_id_alias} : NULL; + if (!is_null($entity_id)) { + $entities = entity_load($entity_type, array($entity_id)); + return reset($entities); + } + return NULL; + } + +} diff --git a/views/metatag_handler_field_serialized.inc b/views/metatag_handler_field_serialized.inc new file mode 100644 index 0000000..b539d13 --- /dev/null +++ b/views/metatag_handler_field_serialized.inc @@ -0,0 +1,102 @@ + array( + 'use_default' => array('default' => FALSE, 'bool' => TRUE), + 'replace_tokens' => array('default' => FALSE, 'bool' => TRUE), + ), + ); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['metatag'] = array( + '#type' => 'fieldset', + '#title' => 'Metatag', + '#collapsible' => TRUE, + ); + $form['metatag']['use_default'] = array( + '#type' => 'checkbox', + '#title' => 'Use default metatag value', + '#description' => 'Display default value if no value has been defined on entity.', + '#default_value' => $this->options['metatag']['use_default'] + ); + + $form['metatag']['replace_tokens'] = array( + '#type' => 'checkbox', + '#title' => 'Replace tokens', + '#description' => 'Attempt to replace all tokens with their (entity) values. Note: Tokens in metatags will be replaced before Views tokens.', + '#default_value' => $this->options['metatag']['replace_tokens'] + ); + } + + function get_value($values, $field = NULL) { + $value = parent::get_value($values); + if (!is_null($value)) { + $value = @unserialize($value); + $value = $this->get_nested_value($value, $this->definition['path']); + } + $this->uses_default = FALSE; + if (is_null($value) && !empty($this->options['metatag']['use_default'])) { + $value = $this->get_default_value($values); + $this->uses_default = TRUE; + } + $this->raw_value = $value; + return $value; + } + + function get_default_value($values) { + $entity_type = $this->get_entity_type($values); + $instance = $entity_type; + $entity = $this->get_entity($values); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + if (!is_null($bundle)) { + $instance .= ":$bundle"; + } + $defaults = metatag_config_load_with_defaults($instance); + return $this->get_nested_value($defaults, $this->definition['path']); + } + + function replace_tokens($value, $values) { + $entity_type = $this->get_entity_type($values); + $entity = $this->get_entity($values); + // Reload the entity object from cache as it may have been altered. + $token_type = token_get_entity_mapping('entity', $entity_type); + $options['token data'][$token_type] = $entity; + $options['entity'] = $entity; + + $value = metatag_get_value($this->definition['metatag'], array('value' => $value), $options); + return $value; + } + + /** + * Retrieves a nested value from an array. + */ + function get_nested_value($value, $path) { + foreach ($path as $index) { + if (!isset($value[$index])) { + return NULL; + } + $value = $value[$index]; + } + return $value; + } + + function render($values) { + $value = (string) $this->get_value($values); + if (strlen($value) && !empty($this->options['metatag']['replace_tokens'])) { + $value = $this->replace_tokens($value, $values); + } + return $this->sanitize_value($value); + } + +} diff --git a/views/metatag_handler_field_serialized_list.inc b/views/metatag_handler_field_serialized_list.inc new file mode 100644 index 0000000..29f373d --- /dev/null +++ b/views/metatag_handler_field_serialized_list.inc @@ -0,0 +1,21 @@ +get_value($values); + return $this->sanitize_value(implode(',', $value)); + } + +}