$formatter) { foreach ($formatter['field types'] as $formatter_field_type) { // Check that the field type exists. if (isset($field_types[$formatter_field_type])) { $options[$formatter_field_type][$name] = $formatter['label']; } } } } if ($field_type) { return !empty($options[$field_type]) ? $options[$field_type] : array(); } return $options; } /** * A field that displays fieldapi fields. * * @ingroup views_field_handlers */ class views_handler_field_field extends views_handler_field { /** * An array to store field renderable arrays for use by render_items. * * @var array */ public $items = array(); /** * Store the field information. * * @var array */ public $field_info = array(); /** * Does the field supports multiple field values. * * @var bool */ public $multiple; /** * Does the rendered fields get limited. * * @var bool */ public $limit_values; /** * A shortcut for $view->base_table. * * @var string */ public $base_table; /** * Store the field instance. * * @var array */ public $instance; function init(&$view, &$options) { parent::init($view, $options); $this->field_info = $field = field_info_field($this->definition['field_name']); $this->multiple = FALSE; $this->limit_values = FALSE; if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) { $this->multiple = TRUE; // If "Display all values in the same row" is FALSE, then we always limit // in order to show a single unique value per row. if (!$this->options['group_rows']) { $this->limit_values = TRUE; } // If "First and last only" is chosen, limit the values if (!empty($this->options['delta_first_last'])) { $this->limit_values = TRUE; } // Otherwise, we only limit values if the user hasn't selected "all", 0, or // the value matching field cardinality. if ((intval($this->options['delta_limit']) && ($this->options['delta_limit'] != $field['cardinality'])) || intval($this->options['delta_offset'])) { $this->limit_values = TRUE; } } // Convert old style entity id group column to new format. // @todo Remove for next major version. if ($this->options['group_column'] == 'entity id') { $this->options['group_column'] = 'entity_id'; } } /** * Check whether current user has access to this handler. * * @return bool * Return TRUE if the user has access to view this field. */ function access() { $base_table = $this->get_base_table(); return field_access('view', $this->field_info, $this->definition['entity_tables'][$base_table]); } /** * Set the base_table and base_table_alias. * * @return string * The base table which is used in the current view "context". */ function get_base_table() { if (!isset($this->base_table)) { // This base_table is coming from the entity not the field. $this->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']); $this->base_table = $data[$options['field']]['relationship']['base']; } } } return $this->base_table; } /** * Called to add the field to a 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(). * Other columns are added only if they are used in groupings, or if * 'add fields to query' is specifically set to TRUE in the field definition. * * The 'add fields to query' switch is used by modules which need all data * present in the query itself (such as "sphinx"). */ function query($use_groupby = FALSE) { $this->get_base_table(); $params = array(); if ($use_groupby) { // When grouping on a "field API" field (whose "real_field" is set to // entity_id), retrieve the minimum entity_id to have a valid entity_id to // pass to field_view_field(). $params = array( 'function' => 'min', ); $this->ensure_my_table(); } // Get the entity type according to the base table of the field. // Then add it to the query as a formula. That way we can avoid joining // the field table if all we need is entity_id and entity_type. $entity_type = $this->definition['entity_tables'][$this->base_table]; $entity_info = entity_get_info($entity_type); if (isset($this->relationship)) { $this->base_table_alias = $this->relationship; } else { $this->base_table_alias = $this->base_table; } // We always need the base field (entity_id / revision_id). if (empty($this->definition['is revision'])) { $this->field_alias = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['id'], '', $params); } else { $this->field_alias = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['revision'], '', $params); $this->aliases['entity_id'] = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['id'], '', $params); } // The alias needs to be unique, so we use both the field table and the entity type. $entity_type_alias = $this->definition['table'] . '_' . $entity_type . '_entity_type'; $this->aliases['entity_type'] = $this->query->add_field(NULL, "'$entity_type'", $entity_type_alias); $fields = $this->additional_fields; // We've already added entity_type, so we can remove it from the list. $entity_type_key = array_search('entity_type', $fields); if ($entity_type_key !== FALSE) { unset($fields[$entity_type_key]); } if ($use_groupby) { // Add the fields that we're actually grouping on. $options = array(); if ($this->options['group_column'] != 'entity_id') { $options = array($this->options['group_column'] => $this->options['group_column']); } $options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : array(); $fields = array(); $rkey = $this->definition['is revision'] ? 'FIELD_LOAD_REVISION' : 'FIELD_LOAD_CURRENT'; // Go through the list and determine the actual column name from field api. foreach ($options as $column) { $name = $column; if (isset($this->field_info['storage']['details']['sql'][$rkey][$this->table][$column])) { $name = $this->field_info['storage']['details']['sql'][$rkey][$this->table][$column]; } $fields[$column] = $name; } $this->group_fields = $fields; } // Add additional fields (and the table join itself) if needed. if ($this->add_field_table($use_groupby)) { $this->ensure_my_table(); $this->add_additional_fields($fields); // Filter by language, if field translation is enabled. $field = $this->field_info; if (field_is_translatable($entity_type, $field) && !empty($this->view->display_handler->options['field_language_add_to_query'])) { $column = $this->table_alias . '.language'; // By the same reason as field_language the field might be LANGUAGE_NONE in reality so allow it as well. // @see this::field_language() global $language_content; $default_language = language_default('language'); $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'), array($language_content->language, $default_language), $this->view->display_handler->options['field_language']); $placeholder = $this->placeholder(); $language_fallback_candidates = array($language); if (variable_get('locale_field_language_fallback', TRUE)) { require_once DRUPAL_ROOT . '/includes/language.inc'; $language_fallback_candidates = array_merge($language_fallback_candidates, language_fallback_get_candidates()); } else { $language_fallback_candidates[] = LANGUAGE_NONE; } $this->query->add_where_expression(0, "$column IN($placeholder) OR $column IS NULL", array($placeholder => $language_fallback_candidates)); } } // The revision id inhibits grouping. // So, stop here if we're using grouping, or if aren't adding all columns to // the query. if ($use_groupby || empty($this->definition['add fields to query'])) { return; } $this->add_additional_fields(array('revision_id')); } /** * Determine if the field table should be added to the query. */ function add_field_table($use_groupby) { // Grouping is enabled, or we are explicitly required to do this. if ($use_groupby || !empty($this->definition['add fields to query'])) { return TRUE; } // This a multiple value field, but "group multiple values" is not checked. if ($this->multiple && !$this->options['group_rows']) { return TRUE; } return FALSE; } /** * Determine if this field is click sortable. */ function click_sortable() { // Not click sortable in any case. if (empty($this->definition['click sortable'])) { return FALSE; } // A field is not click sortable if it's a multiple field with // "group multiple values" checked, since a click sort in that case would // add a join to the field table, which would produce unwanted duplicates. if ($this->multiple && $this->options['group_rows']) { return FALSE; } return TRUE; } /** * Called to determine what to tell the clicksorter. */ function click_sort($order) { // No column selected, can't continue. if (empty($this->options['click_sort_column'])) { return; } $this->ensure_my_table(); $column = _field_sql_storage_columnname($this->definition['field_name'], $this->options['click_sort_column']); if (!isset($this->aliases[$column])) { // Column is not in query; add a sort on it (without adding the column). $this->aliases[$column] = $this->table_alias . '.' . $column; } $this->query->add_orderby(NULL, NULL, $order, $this->aliases[$column]); } function option_definition() { $options = parent::option_definition(); // option_definition runs before init/construct, so no $this->field_info $field = field_info_field($this->definition['field_name']); $field_type = field_info_field_types($field['type']); $column_names = array_keys($field['columns']); $default_column = ''; // Try to determine a sensible default. if (count($column_names) == 1) { $default_column = $column_names[0]; } elseif (in_array('value', $column_names)) { $default_column = 'value'; } // If the field has a "value" column, we probably need that one. $options['click_sort_column'] = array( 'default' => $default_column, ); $options['type'] = array( 'default' => $field_type['default_formatter'], ); $options['settings'] = array( 'default' => array(), ); $options['group_column'] = array( 'default' => $default_column, ); $options['group_columns'] = array( 'default' => array(), ); // Options used for multiple value fields. $options['group_rows'] = array( 'default' => TRUE, 'bool' => TRUE, ); // If we know the exact number of allowed values, then that can be // the default. Otherwise, default to 'all'. $options['delta_limit'] = array( 'default' => ($field['cardinality'] > 1) ? $field['cardinality'] : 'all', ); $options['delta_offset'] = array( 'default' => 0, ); $options['delta_reversed'] = array( 'default' => FALSE, 'bool' => TRUE, ); $options['delta_first_last'] = array( 'default' => FALSE, 'bool' => TRUE, ); $options['multi_type'] = array( 'default' => 'separator' ); $options['separator'] = array( 'default' => ', ' ); $options['field_api_classes'] = array( 'default' => FALSE, 'bool' => TRUE, ); return $options; } function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $field = $this->field_info; $formatters = _field_view_formatter_options($field['type']); $column_names = array_keys($field['columns']); // If this is a multiple value field, add its options. if ($this->multiple) { $this->multiple_options_form($form, $form_state); } // No need to ask the user anything if the field has only one column. if (count($field['columns']) == 1) { $form['click_sort_column'] = array( '#type' => 'value', '#value' => isset($column_names[0]) ? $column_names[0] : '', ); } else { $form['click_sort_column'] = array( '#type' => 'select', '#title' => t('Column used for click sorting'), '#options' => drupal_map_assoc($column_names), '#default_value' => $this->options['click_sort_column'], '#description' => t('Used by Style: Table to determine the actual column to click sort the field on. The default is usually fine.'), '#fieldset' => 'more', ); } $form['type'] = array( '#type' => 'select', '#title' => t('Formatter'), '#options' => $formatters, '#default_value' => $this->options['type'], '#ajax' => array( 'path' => views_ui_build_form_url($form_state), ), '#submit' => array('views_ui_config_item_form_submit_temporary'), '#executes_submit_callback' => TRUE, ); $form['field_api_classes'] = array( '#title' => t('Use field template'), '#type' => 'checkbox', '#default_value' => $this->options['field_api_classes'], '#description' => t('If checked, field api classes will be added using field.tpl.php (or equivalent). This is not recommended unless your CSS depends upon these classes. If not checked, template will not be used.'), '#fieldset' => 'style_settings', '#weight' => 20, ); if ($this->multiple) { $form['field_api_classes']['#description'] .= ' ' . t('Checking this option will cause the group Display Type and Separator values to be ignored.'); } // Get the currently selected formatter. $format = $this->options['type']; $formatter = field_info_formatter_types($format); $settings = $this->options['settings'] + field_info_formatter_settings($format); // Provide an instance array for hook_field_formatter_settings_form(). ctools_include('fields'); $this->instance = ctools_fields_fake_field_instance($this->definition['field_name'], '_custom', $formatter, $settings); // Store the settings in a '_custom' view mode. $this->instance['display']['_custom'] = array( 'type' => $format, 'settings' => $settings, ); // Get the settings form. $settings_form = array('#value' => array()); $function = $formatter['module'] . '_field_formatter_settings_form'; if (function_exists($function)) { $settings_form = $function($field, $this->instance, '_custom', $form, $form_state); } $form['settings'] = $settings_form; } /** * Provide options for multiple value fields. */ function multiple_options_form(&$form, &$form_state) { $field = $this->field_info; $form['multiple_field_settings'] = array( '#type' => 'fieldset', '#title' => t('Multiple field settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 5, ); $form['group_rows'] = array( '#title' => t('Display all values in the same row'), '#type' => 'checkbox', '#default_value' => $this->options['group_rows'], '#description' => t('If checked, multiple values for this field will be shown in the same row. If not checked, each value in this field will create a new row. If using group by, please make sure to group by "Entity ID" for this setting to have any effect.'), '#fieldset' => 'multiple_field_settings', ); // Make the string translatable by keeping it as a whole rather than // translating prefix and suffix separately. list($prefix, $suffix) = explode('@count', t('Display @count value(s)')); if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) { $type = 'textfield'; $options = NULL; $size = 5; } else { $type = 'select'; $options = drupal_map_assoc(range(1, $field['cardinality'])); $size = 1; } $form['multi_type'] = array( '#type' => 'radios', '#title' => t('Display type'), '#options' => array( 'ul' => t('Unordered list'), 'ol' => t('Ordered list'), 'separator' => t('Simple separator'), ), '#dependency' => array('edit-options-group-rows' => array(TRUE)), '#default_value' => $this->options['multi_type'], '#fieldset' => 'multiple_field_settings', ); $form['separator'] = array( '#type' => 'textfield', '#title' => t('Separator'), '#default_value' => $this->options['separator'], '#dependency' => array( 'radio:options[multi_type]' => array('separator'), 'edit-options-group-rows' => array(TRUE), ), '#dependency_count' => 2, '#fieldset' => 'multiple_field_settings', ); $form['delta_limit'] = array( '#type' => $type, '#size' => $size, '#field_prefix' => $prefix, '#field_suffix' => $suffix, '#options' => $options, '#default_value' => $this->options['delta_limit'], '#prefix' => '