You are here

composed_field.module in Composed Field 7

Defines composed field type.

File

composed_field.module
View source
<?php

/**
 * @file
 * Defines composed field type.
 */

/**
 * Implements hook_permission().
 */
function composed_field_permission() {
  return array(
    'enter PHP code in widget settings form' => array(
      'title' => t('Enter PHP code into the widget settings form'),
      'description' => t('Enter PHP code in the widget settings form. %warning', array(
        '%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'),
      )),
    ),
  );
}

/**
 * Permission callback.
 */
function composed_field_php_permission(&$element, &$form_state) {
  $element_name = $element['#parents'][4];
  $element_subfield = $element['#parents'][5];
  $element_value = $element['#default_value'];
  $submitted_values = $form_state['values']['instance']['widget']['settings']['composed_field'][$element_name];
  if (!user_access('enter PHP code in widget settings form')) {

    // Check if the current user has altered the element value saved by
    // another user that had permission to do so.
    foreach ($submitted_values as $subfield => $subfield_value) {
      if ($submitted_values[$element_subfield]['value'] != $element_value) {
        form_error($element, t('You do not have permission to either enter or change the value in %element-title.', array(
          '%element-title' => "#{$element_name} | " . $element['#title'],
        )));
      }
    }
  }
}

/**
 * Implements hook_field_info().
 */
function composed_field_field_info() {
  return array(
    'composed_field' => array(
      'label' => t('Composed field'),
      'description' => t('This field stores serialized array in the database.'),
      'default_widget' => 'composed_field_widget',
      'default_formatter' => 'composed_field_unformatted_list',
    ),
  );
}

/**
 * Implements hook_field_widget_info().
 */
function composed_field_field_widget_info() {
  return array(
    'composed_field_widget' => array(
      'label' => t('Composed field'),
      'field types' => array(
        'composed_field',
      ),
    ),
  );
}

/**
 * Implements hook_field_is_empty().
 */
function composed_field_field_is_empty($item, $field) {
  return empty($item['composed']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Updates the widget form on each Ajax callback.
 */
function composed_field_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {

  // Work only on field of type composed_field and when the form has been
  // submitted.
  if ($form['#field']['type'] == 'composed_field' && isset($form_state['values']['instance']['widget']['settings'])) {

    // Identify which element triggered the ajax callback.
    $tab_trigger = $form_state['triggering_element']['#parents'][4];
    $tab_subfield_trigger = $form_state['triggering_element']['#parents'][5];
    $form_controls = _composed_field_form_controls_api();
    if (isset($form_controls["#{$tab_trigger}"]['widget_form']['value']['#default_value'])) {

      // Set the default value for the attribute element related to the
      // triggering element.
      $form_state['values']['instance']['widget']['settings']['composed_field'][$tab_trigger][$tab_subfield_trigger]['value'] = $form_controls["#{$tab_trigger}"]['widget_form']['value']['#default_value'];
    }
    $form_widget = $form['instance']['widget']['settings'];
    $widget_form_state_values = $form_state['values']['instance']['widget']['settings'];
    $form_widget['#composed_field']['vertical_tab_settings']['number_of_subfields'] = $widget_form_state_values['number_of_subfields'];
    _composed_field_build_form_controls_in_vertical_tabs($form_widget, $widget_form_state_values);

    // Return the updated form.
    $form['instance']['widget']['settings'] = $form_widget;
  }
}

/**
 * Implements hook_field_widget_settings_form().
 */
function composed_field_field_widget_settings_form($field, $instance) {
  $widget_form_state_values = $instance['widget']['settings'];
  $number_of_subfields_title = t('Number of subfields');

  // Assume there is no value set for number_of_subfields as yet.
  $number_of_fields_not_set_message = t('You must enter a value into !number_of_subfields', array(
    '!number_of_subfields' => $number_of_subfields_title,
  ));
  if (isset($widget_form_state_values['number_of_subfields'])) {

    // The field is being edited rather than created.
    $number_of_subfields = $widget_form_state_values['number_of_subfields'];
  }
  else {
    $widget_form_state_values['number_of_subfields'] = '';
    $number_of_subfields = 1;
  }
  $form['inline'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display as inline element'),
    '#default_value' => empty($widget_form_state_values['inline']) ? FALSE : $widget_form_state_values['inline'],
  );

  // This element determines how many components our field will have.
  $form['number_of_subfields'] = array(
    '#type' => 'textfield',
    '#title' => $number_of_subfields_title,
    '#description' => t('Enter a numeric value and hit the tab key after that.') . '<br />' . t('This value determines how many subfields this field will have.'),
    '#default_value' => $widget_form_state_values['number_of_subfields'],
    '#maxlength' => 2,
    '#size' => 2,
    '#element_validate' => array(
      'element_validate_integer_positive',
    ),
    '#required' => TRUE,
    '#ajax' => array(
      'callback' => '_composed_field_vertical_tabs_ajax_callback',
    ),
  );
  $form['composed_field'] = array(
    '#type' => 'vertical_tabs',
    '#prefix' => "<div id='composed_field_tabs_ajax_wrapper'>",
    '#suffix' => '</div>',
  );

  // Build the form elements in each tab.
  $form['#composed_field'] = array(
    'vertical_tab_settings' => array(
      'subfield_start_from' => 1,
      'number_of_subfields' => $number_of_subfields,
      'number_of_fields_not_set_message' => $number_of_fields_not_set_message,
    ),
  );
  _composed_field_build_form_controls_in_vertical_tabs($form, $widget_form_state_values);
  return $form;
}

/**
 * AJAX callback. Updates the field widget form.
 */
function _composed_field_vertical_tabs_ajax_callback($form, $form_state) {
  $form_widget = $form['instance']['widget']['settings'];
  $trigger = $form_state['triggering_element']['#parents'];
  $wrapper = '#composed_field_tabs_ajax_wrapper';
  $render = $form_widget['composed_field'];

  // $trigger[4] is the tab element (form control attribute name).
  if (isset($trigger[4]) && $trigger[4] != 'type') {
    $wrapper = '#composed_field_' . $trigger[4] . '-' . $trigger[5] . '_ajax_wrapper';
    $render = $form_widget['composed_field'][$trigger[4]][$trigger[5]];
  }
  return array(
    '#type' => 'ajax',
    '#commands' => array(
      ajax_command_replace($wrapper, render($render)),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function composed_field_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $settings = $instance['widget']['settings'];
  $number_of_subfields = isset($settings['number_of_subfields']) ? $settings['number_of_subfields'] : 1;
  $element['composed']['#theme_wrappers'] = array(
    'form_element',
  );
  $default_value = isset($items[$delta]['composed']) ? $items[$delta]['composed'] : array();

  // Create as many form elements in each tab as there are subfields.
  for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
    $element['composed'][$subfield] = _composed_field_build_subfield_element($settings, $subfield, $default_value);
  }
  if (isset($settings['inline']) && $settings['inline']) {
    $prefix = isset($element['composed'][1]['#prefix']) ? $element['composed'][1]['#prefix'] : '';
    $suffix = isset($element['composed'][$number_of_subfields]['#suffix']) ? $element['composed'][$number_of_subfields]['#suffix'] : '';
    $element['composed'][1]['#prefix'] = '<div class="container-inline">' . $prefix;
    $element['composed'][$number_of_subfields]['#suffix'] = $suffix . '</div>';
  }
  return $element;
}

/**
 * Implements hook_field_presave().
 *
 * Lumps all the subfield values into an serialized array so it gets saved
 * as a single field value into the database.
 */
function composed_field_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  if ($field['type'] == 'composed_field') {
    foreach ($items as $delta => $item) {
      $all_subfields_have_value = FALSE;
      foreach ($item['composed'] as $subfield_value) {
        if (!empty($subfield_value)) {
          $all_subfields_have_value = TRUE;
          break;
        }
      }
      if ($all_subfields_have_value) {

        // Save it.
        $items[$delta]['composed'] = serialize($item['composed']);
      }
      else {

        // Do not save it.
        unset($items[$delta]);
      }
    }
  }
}

/**
 * Builds the form element for each subfield.
 *
 * @param Array $settings
 *   Field widget settings.
 * @param Int $subfield
 *   The subfield sequence number.
 * @param String $default_value
 *   The field's serialized value.
 * @param String $form_control_element
 *   The array key under widget_form array in $form_control.
 *
 * @return Array
 *   The subfield form element.
 */
function _composed_field_build_subfield_element($settings, $subfield, $default_value = array(), $form_control_element = 'value') {
  $form_controls = _composed_field_form_controls_api();
  $form_control_attributes = array_keys($form_controls);
  if (!empty($default_value)) {

    // Check if value is serialized.
    if (@unserialize($default_value) !== FALSE) {

      // Turns it into an associative array of subfield values.
      $default_value = unserialize($default_value);
    }
  }
  $default_value = isset($default_value[$subfield]) ? $default_value[$subfield] : '';
  $element = array();

  // Go through all attributes and check if a setting value has been entered
  // in each one of them.
  foreach ($form_control_attributes as $form_control_attributes_name) {
    $form_control_tab_name = str_replace('#', '', $form_control_attributes_name);

    // Is there a value for this attribute?
    if (!empty($settings['composed_field'][$form_control_tab_name][$subfield][$form_control_element])) {
      $value = $settings['composed_field'][$form_control_tab_name][$subfield][$form_control_element];

      // Does it need to be processed before using it?
      if (!empty($form_controls[$form_control_attributes_name]['value process'])) {
        $process_functions = $form_controls[$form_control_attributes_name]['value process'];

        // Call each function sending the attribute value as argument.
        foreach ($process_functions as $function_name) {
          $value = $function_name($value);
        }
      }
      $element[$form_control_attributes_name] = $value;
      $is_php = $form_controls[$form_control_attributes_name]['widget_form']['value'];
      $is_php = isset($is_php['#element_validate']) ? $is_php['#element_validate'] : array();

      // If the field element has the validation function
      // composed_field_php_permission set then its value must be php code.
      $is_php = in_array('composed_field_php_permission', $is_php) ? TRUE : FALSE;
      if ($is_php && !empty($value)) {

        // Evaluate the php code.
        $element[$form_control_attributes_name] = eval($value);
      }
    }
  }
  $element['#default_value'] = $default_value;
  return $element;
}

/**
 * Returns the Form Control Attributes.
 *
 * @see http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7
 *
 * @return Array
 *   Associative array of the form control's api structure.
 */
function _composed_field_form_controls_api() {
  include 'includes/composed_field.form.controls.inc';
  return $form_controls;
}

/**
 * Builds the widget form vertical tabs.
 *
 * @param Array $form
 *   The widget settings form.
 * @param Array $widget_form_state_values
 *   It is either the form saved values or the form submitted values.
 */
function _composed_field_build_form_controls_in_vertical_tabs(&$form, $widget_form_state_values) {
  foreach (_composed_field_form_controls_api() as $attribute_name => $form_control_structure) {

    // The Form API wont allow form element name starting with #, so we remove
    // it.
    $tab_name = str_replace('#', '', $attribute_name);
    $description = t('See !form_controls for more details.', array(
      '!form_controls' => l(t('Form Controls !tab_name', array(
        '!tab_name' => "#{$tab_name}",
      )), "http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#{$tab_name}", array(
        'attributes' => array(
          'target' => '_blank',
        ),
      )),
    ));
    if ($tab_name == 'default_value') {
      $description = t('The default value for this field, used when creating new content.');
    }

    // Set a vertical tab for each form control attribute.
    $form['composed_field'][$tab_name] = array(
      '#title' => $attribute_name,
      '#type' => 'fieldset',
      '#description' => $description,
    );
    $form['#composed_field']['vertical_tab_settings']['attribute_name'] = $attribute_name;
    $form['#composed_field']['vertical_tab_settings']['tab_name'] = $tab_name;

    // Build the tab elements.
    _composed_field_build_vertical_tab_elements($form, $widget_form_state_values, $form_control_structure);
  }
}

/**
 * Populates each widget form vertical tab with their form elements.
 *
 * @param Array $form
 *   The widget settings form.
 * @param Array $widget_form_state_values
 *   It is either the form saved values or the form submitted values.
 * @param Array $form_control_structure
 *   The form element's structure.
 */
function _composed_field_build_vertical_tab_elements(&$form, $widget_form_state_values, $form_control_structure) {
  foreach ($form['#composed_field']['vertical_tab_settings'] as $setting_name => $setting_value) {
    ${$setting_name} = $setting_value;
  }
  $form_controls = _composed_field_form_controls_api();

  // Create as many form elements in each tab as there are subfields.
  for ($subfield = $subfield_start_from; $subfield <= $number_of_subfields; $subfield++) {
    $form['composed_field'][$tab_name][$subfield] = array(
      '#type' => 'fieldset',
    );
    foreach ($form_control_structure['widget_form'] as $form_element => $form_element_properties) {
      $title = empty($form_element_properties['#title']) ? '' : $form_element_properties['#title'];
      $form_element_properties['#title'] = $title . t('Subfield !subfield', array(
        '!subfield' => $subfield,
      ));
      $enable_this_attribute_element_is_not_available = TRUE;
      $enable_this_attribute_element_value = FALSE;

      // Inicial state of all form elements in the vertical tab.
      $form_element_properties['#disabled'] = TRUE;
      $form_element_properties['#default_value'] = $number_of_fields_not_set_message;
      $number_of_subfields_has_value = FALSE;
      if (isset($widget_form_state_values['number_of_subfields']) && $widget_form_state_values['number_of_subfields'] > 0) {
        $form_element_properties['#default_value'] = '';
        $number_of_subfields_has_value = TRUE;
        $selected_value_for_type_attribute = $widget_form_state_values['composed_field']['type'][$subfield]['value'];

        // 'enable_this_attribute' element.
        if (in_array($selected_value_for_type_attribute, (array) $form_control_structure['supported_types'])) {
          $enable_this_attribute_element_is_not_available = FALSE;
          if (isset($widget_form_state_values['composed_field'][$tab_name][$subfield]['enable_this_attribute'])) {
            $enable_this_attribute_element_value = $widget_form_state_values['composed_field'][$tab_name][$subfield]['enable_this_attribute'];
          }
        }
      }
      if ($enable_this_attribute_element_value) {
        $form_element_properties['#disabled'] = FALSE;
        $default_value = $widget_form_state_values['composed_field'][$tab_name][$subfield][$form_element];
        $form_element_properties['#default_value'] = $default_value;
      }

      // Determine which tab are we in.
      switch ($attribute_name) {
        case '#type':
          if ($number_of_subfields_has_value) {

            // Make all elements in the #type attribute tab selectable.
            $form_element_properties['#disabled'] = FALSE;
            $form_element_properties['#default_value'] = $widget_form_state_values['composed_field'][$tab_name][$subfield][$form_element];
          }
          else {

            // Set a message advising the user to enter a value into
            // number_of_subfields.
            $form_element_properties['#default_value'] = '';
            $form_element_properties['#options'] = array(
              '' => $number_of_fields_not_set_message,
            );
          }
          break;
        case '#default_value':
          if (!empty($selected_value_for_type_attribute)) {
            $form_element_properties = _composed_field_build_subfield_element($widget_form_state_values, $subfield, array(), $form_element);
          }
          break;
        default:
          $form['composed_field'][$tab_name][$subfield]['enable_this_attribute'] = array(
            '#type' => 'checkbox',
            '#title' => t('Enable !attribute attribute for Subfield !subfield.', array(
              '!attribute' => $attribute_name,
              '!subfield' => $subfield,
            )),
            '#disabled' => $enable_this_attribute_element_is_not_available,
            '#default_value' => $enable_this_attribute_element_value,
            '#ajax' => array(
              'callback' => '_composed_field_vertical_tabs_ajax_callback',
            ),
          );
      }
      $form['composed_field'][$tab_name][$subfield] += array(
        '#prefix' => "<div id='composed_field_" . "{$tab_name}-{$subfield}" . "_ajax_wrapper'>",
        '#suffix' => '</div>',
      );
      $form['composed_field'][$tab_name][$subfield][$form_element] = $form_element_properties;
    }
  }
}

/**
 * Returns an array of allowed values for a list field.
 *
 * @param String $value
 *   The piped pair list of allowed values.
 *
 * @return Array
 *   The array of allowed values. Keys of the array are the raw stored values
 *   (number or text), values of the array are the display labels.
 */
function _composed_field_list_allowed_values_array($value) {

  // Break the lines.
  $array = explode("\n", $value);

  // Break the pipes.
  foreach ($array as $key => $line) {
    unset($array[$key]);
    if (!empty($line)) {
      $line = explode("|", $line);
      $array[check_plain($line[0])] = check_plain($line[1]);
    }
  }
  return $array;
}

/**
 * Returns an array of allowed values for a list field.
 *
 * @param String $value
 *   The piped pair list of allowed values.
 *
 * @return String
 *   The array of allowed values. Keys of the array are the raw stored values
 *   (number or text), values of the array are the display labels.
 */
function _composed_field_serialize($value) {

  // Break the lines.
  $array = explode("\n", $value);

  // Break the pipes.
  foreach ($array as $key => $line) {
    unset($array[$key]);
    if (!empty($line)) {
      $line = explode("|", $line);
      $array[check_plain($line[0])] = check_plain($line[1]);
    }
  }
  return $array;
}

/**
 * Implements hook_field_formatter_info().
 */
function composed_field_field_formatter_info() {
  $settings['display_options'] = array(
    'hidden' => 0,
    'format' => '_none',
    'prefix' => '',
    'suffix' => '',
  );
  return array(
    'composed_field_unformatted_list' => array(
      'label' => t('Unformatted list'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings + array(
        'style' => 'inline',
      ),
    ),
    'composed_field_fieldset' => array(
      'label' => t('Fieldset'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings + array(
        'collapsible' => TRUE,
        'collapsed' => FALSE,
      ),
    ),
    'composed_field_html_list' => array(
      'label' => t('HTML list'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings + array(
        'style' => 'inline',
        'list_type' => 'ul',
      ),
    ),
    'composed_field_table' => array(
      'label' => t('Table'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings + array(
        'table' => array(
          'number_column' => FALSE,
          'number_column_label' => '№',
          'column_label' => '',
        ),
      ),
    ),
    'composed_field_accordion' => array(
      'label' => t('Accordion'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings,
    ),
    'composed_field_tabs' => array(
      'label' => t('Tabs'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings,
    ),
    'composed_field_dialog' => array(
      'label' => t('Dialog'),
      'field types' => array(
        'composed_field',
      ),
      'settings' => $settings,
    ),
  );
}

/**
 * Builds the subfield values from the display options formatter info.
 *
 * @param Array $display_options
 *   An array keyed with hidden, format, prefix and suffix. This function
 *   checks if each value in those keys are serialized, if so it unserializes
 *   it resulting in a sub array of setting values for each subfield, if not it
 *   will then creates the sub array and repeat the same setting value for each
 *   subfield.
 * @param Int $number_of_subfields
 *   The total number of subfields for the field being displayed. This value is
 *   only used when the $display_options values are not serialized.
 *
 * @return Array
 *   The associative array setting values of hidden, format, prefix and
 *   suffix for each subfield.
 */
function _composed_field_formatter_info_unserialize_display_options($display_options, $number_of_subfields) {

  // Go through hidden, format, prefix and suffix.
  foreach ($display_options as $display_option => $display_option_value) {

    // Check if each value is serialized.
    if (@unserialize($display_option_value) !== FALSE) {

      // Turns it into an associative array of setting values for each subfield.
      $display_options[$display_option] = unserialize($display_option_value);
    }
    else {

      // The display options never got serialized so we gotta create an array
      // of default setting values for each subfield.
      $display_options[$display_option] = array();
      foreach (range(1, $number_of_subfields) as $subfield) {

        // Repeat the same setting value for each subfield.
        $display_options[$display_option][$subfield] = $display_option_value;
      }
    }
  }
  return $display_options;
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function composed_field_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $settings = $instance['display'][$view_mode]['settings'];
  $element = array(
    '#tree' => TRUE,
  );
  $widget_settings = $instance['widget']['settings'];
  $number_of_subfields = $widget_settings['number_of_subfields'];
  $element['number_of_subfields'] = array(
    '#type' => 'hidden',
    '#default_value' => $number_of_subfields,
  );
  $style_settings = array(
    '#type' => 'select',
    '#title' => t('Style'),
    '#options' => array(
      'inline' => t('Inline'),
      'block' => t('Block'),
    ),
    '#default_value' => $settings['style'],
  );
  switch ($instance['display'][$view_mode]['type']) {
    case 'composed_field_fieldset':
      $element['collapsible'] = array(
        '#type' => 'checkbox',
        '#title' => t('Collapsible'),
        '#default_value' => $settings['collapsible'],
      );
      $element['collapsed'] = array(
        '#type' => 'checkbox',
        '#title' => t('Collapsed'),
        '#default_value' => $settings['collapsed'],
      );
      break;
    case 'composed_field_unformatted_list':
      $element['style'] = $style_settings;
      break;
    case 'composed_field_html_list':
      $element['style'] = $style_settings;
      $element['list_type'] = array(
        '#type' => 'radios',
        '#title' => t('List type'),
        '#options' => array(
          'ul' => t('Unordered list'),
          'ol' => t('Ordered list'),
        ),
        '#default_value' => $settings['list_type'],
      );
      break;
    case 'composed_field_table':
      $element['table'] = array(
        '#title' => t('Table'),
        '#type' => 'fieldset',
        '#element_validate' => array(
          '_composed_field_formatter_info_serialize_table_column_label',
        ),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $element['table']['number_column'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable row number column'),
        '#default_value' => $settings['table']['number_column'],
        '#attributes' => array(
          'id' => 'number_column',
        ),
      );
      $element['table']['number_column_label'] = array(
        '#type' => 'textfield',
        '#title' => t('Number column label'),
        '#size' => 30,
        '#default_value' => $settings['table']['number_column_label'],
        '#states' => array(
          'visible' => array(
            '#number_column' => array(
              'checked' => TRUE,
            ),
          ),
        ),
      );
      $element['table']['column_label'] = array(
        '#type' => 'hidden',
      );
      $column_label = unserialize($settings['table']['column_label']);
      for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
        $element['table']['column_label'][$subfield] = array(
          '#type' => 'textfield',
          '#title' => t('Column label for Subfield !subfield', array(
            '!subfield' => $subfield,
          )),
          '#size' => 30,
          '#default_value' => isset($column_label[$subfield]) ? $column_label[$subfield] : '',
          '#tree' => TRUE,
        );
      }
      break;
  }
  $display_options = array(
    'hidden',
    'format',
    'prefix',
    'suffix',
  );
  foreach ($display_options as $display_option) {
    $element['display_options'][$display_option] = array(
      '#type' => 'hidden',
    );
  }

  // Get a list of formats.
  $options['_none'] = t('- None -');
  foreach (filter_formats() as $format) {
    $options[$format->format] = $format->name;
  }

  // Set the default value for hidden, format, $prefix and suffix.
  $display_options = _composed_field_formatter_info_unserialize_display_options($settings['display_options'], $number_of_subfields);

  // Common settings.
  for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
    if ($subfield == 1 && empty($display_options['suffix'][$subfield])) {
      $display_options['suffix'][$subfield] = ':&nbsp;';
    }
    $element[$subfield]['display_options'] = array(
      '#title' => t('Subfield !subfield', array(
        '!subfield' => $subfield,
      )),
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#element_validate' => array(
        '_composed_field_formatter_info_serialize_display_options',
      ),
    );
    $element[$subfield]['display_options']['hidden'] = array(
      '#type' => 'checkbox',
      '#title' => t('Hidden'),
      '#default_value' => $display_options['hidden'][$subfield],
    );
    $element[$subfield]['display_options']['format'] = array(
      '#type' => 'select',
      '#title' => t('Text format'),
      '#options' => $options,
      '#default_value' => $display_options['format'][$subfield],
      '#description' => t('Warning: This setting may have security implications.'),
      '#element_validate' => array(
        'composed_field_format_validate',
      ),
    );
    $element[$subfield]['display_options']['prefix'] = array(
      '#type' => 'textfield',
      '#title' => t('Prefix'),
      '#size' => 30,
      '#default_value' => $display_options['prefix'][$subfield],
    );
    $element[$subfield]['display_options']['suffix'] = array(
      '#type' => 'textfield',
      '#title' => t('Suffix'),
      '#size' => 30,
      '#default_value' => $display_options['suffix'][$subfield],
    );
  }
  return $element;
}

/**
 * Element validate callback.
 *
 * Serializes the column label values.
 */
function _composed_field_formatter_info_serialize_table_column_label($element, &$form_state, $form) {
  $field_name = $element['#parents'][1];
  $subfield = $element['#parents'][4];
  $submitted_values = $form_state['values']['fields'][$field_name]['settings_edit_form']['settings'];
  $submitted_column_labels = $submitted_values['table']['column_label'][$subfield];
  $number_of_subfields = $submitted_values['number_of_subfields'];
  foreach ($submitted_column_labels as $display_option => $display_option_value) {

    // $display_option][$subfield] = $display_option_value;
  }

  // $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']['table']['column_label']
  // Serialize the values for hidden, format, prefix and suffix.
  if ($number_of_subfields == $subfield) {
    $display_options = $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']['display_options'];
    foreach ($display_options as $display_option => $display_option_values) {
      $display_options[$display_option] = serialize($display_option_values);
    }

    // $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']['display_options'] = $display_options;
  }
}

/**
 * Element validate callback.
 *
 * Serializes the display option values of field_formatter_info.
 */
function _composed_field_formatter_info_serialize_display_options($element, &$form_state, $form) {
  $field_name = $element['#parents'][1];
  $subfield = $element['#parents'][4];
  $submitted_values = $form_state['values']['fields'][$field_name]['settings_edit_form']['settings'];
  $submitted_display_options = $submitted_values[$subfield]['display_options'];
  $number_of_subfields = $submitted_values['number_of_subfields'];
  foreach ($submitted_display_options as $display_option => $display_option_value) {
    $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']['display_options'][$display_option][$subfield] = $display_option_value;
  }

  // Serialize the values for hidden, format, prefix and suffix.
  if ($number_of_subfields == $subfield) {
    $display_options = $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']['display_options'];
    foreach ($display_options as $display_option => $display_option_values) {
      $display_options[$display_option] = serialize($display_option_values);
    }
    $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']['display_options'] = $display_options;
  }
}

/**
 * Element validate callback.
 */
function composed_field_format_validate($element, &$form_state, $form) {
  if ($element['#value'] == 'full_html') {
    drupal_set_message(t('Using the "Full HTML" format allows HTML to be posted unfiltered. This could represent a severe security risk.<br/>See !link for further information.', array(
      '!link' => '<a href="http://drupal.org/documentation/modules/filter">http://drupal.org/documentation/modules/filter</a>',
    )), 'warning', FALSE);
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function composed_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $settings = $display['settings'];
  $element = array();
  $widget_settings = $instance['widget']['settings'];
  $number_of_subfields = $widget_settings['number_of_subfields'];
  if (!$items) {
    return;
  }

  // Set the default value for hidden, format, prefix and suffix.
  $display_options = _composed_field_formatter_info_unserialize_display_options($settings['display_options'], $number_of_subfields);
  foreach ($items as $delta => $item) {
    $item = unserialize($item['composed']);
    for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
      if ($display_options['hidden'][$subfield]) {
        $item[$subfield] = FALSE;
      }
      else {

        // Show value pair of allowed values on instead of their key value.
        if (isset($instance['widget']['settings'][$subfield]['select']['allowed_values'])) {
          $allowed_values = $instance['widget']['settings'][$subfield]['select']['allowed_values'];
          $item[$subfield] = empty($allowed_values[$item[$subfield]]) ? $item[$subfield] : $allowed_values[$item[$subfield]];
        }
        $item[$subfield] = $display_options['format'][$subfield] == '_none' ? check_plain($item[$subfield]) : check_markup($item[$subfield], $display_options['format'][$subfield]);

        // Do not display prefix and suffix for empty subfields.
        $item[$subfield] = $display_options['prefix'][$subfield] . $item[$subfield] . $display_options['suffix'][$subfield];
      }
    }
    $items[$delta] = $item;
  }
  switch ($display['type']) {
    case 'composed_field_fieldset':
      foreach ($items as $delta => $item) {
        $value = '';
        foreach ($item as $subfield => $subfield_value) {
          if ($subfield !== 1) {
            $value .= $subfield_value;
          }
        }
        $element[$delta] = array(
          '#title' => $item[1],
          '#value' => $value,
          '#theme' => 'fieldset',
          '#collapsible' => $settings['collapsible'],
          '#collapsed' => $settings['collapsed'],
        );

        // theme_fieldset() doesn't handle '#collapsible', '#collapsed'
        // arguments as claimed. See http://drupal.org/node/1099132
        if ($settings['collapsible']) {
          $element[$delta]['#attached'] = array(
            'js' => array(
              'misc/form.js',
              'misc/collapse.js',
            ),
          );
          $element[$delta]['#attributes'] = array(
            'class' => $settings['collapsed'] ? array(
              'collapsible',
              'collapsed',
            ) : array(
              'collapsible',
            ),
          );
        }
      }
      break;
    case 'composed_field_unformatted_list':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#display' => $display,
          '#item' => $item,
          '#theme' => 'composed_field',
        );
      }
      break;
    case 'composed_field_html_list':
      foreach ($items as $item) {
        $list_items[] = theme('composed_field', array(
          '#display' => $display,
          '#item' => $item,
          '#theme' => 'composed_field',
        ));
      }
      $element[0] = array(
        '#theme' => 'item_list',
        '#type' => $settings['list_type'],
        '#items' => $list_items,
      );
      break;
    case 'composed_field_dialog':
      $element[0] = array(
        '#theme' => 'composed_field_dialog',
        '#items' => $items,
      );
      break;
    case 'composed_field_table':
      foreach ($items as $delta => $item) {
        $row = array();
        if ($settings['table']['number_column']) {
          $row[] = ++$delta;
        }
        foreach ($item as $subfield => $subfield_value) {
          $row[] = $item[$subfield];
        }
        $rows[] = $row;
      }
      $header = array();
      if (unserialize($settings['table']['column_label']) !== FALSE) {
        $settings['table']['column_label'] = unserialize($settings['table']['column_label']);
      }
      else {
        $settings['table']['column_label'] = array();
        for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
          $settings['table']['column_label'][$subfield] = '';
        }
      }
      foreach ($settings['table']['column_label'] as $subfield => $subfield_value) {
        if ($settings['table']['column_label'][$subfield]) {
          if ($settings['table']['number_column']) {
            $header[] = t('№');
          }
          $header[] = $settings['table']['column_label'][$subfield];
        }
      }

      // Display all values in a single element.
      $element[0] = array(
        '#theme' => 'table',
        '#rows' => $rows,
        '#header' => $header,
      );
      break;
    case 'composed_field_accordion':
      $element[0] = array(
        '#theme' => 'composed_field_accordion',
        '#items' => $items,
      );
      break;
    case 'composed_field_tabs':
      $element[0] = array(
        '#theme' => 'composed_field_tabs',
        '#items' => $items,
      );
      break;
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function composed_field_field_formatter_settings_summary($field, $instance, $view_mode) {
  $settings = $instance['display'][$view_mode]['settings'];
  $type = $instance['display'][$view_mode]['type'];
  $widget_settings = $instance['widget']['settings'];
  $number_of_subfields = $widget_settings['number_of_subfields'];
  $summary = '';
  switch ($type) {
    case 'composed_field_fieldset':
      $summary .= '<div>' . t('Collapsible: <em>@value</em>', array(
        '@value' => $settings['collapsible'] ? 'Yes' : 'No',
      )) . '</div>';
      $summary .= '<div>' . t('Collapsed: <em>@value</em>', array(
        '@value' => $settings['collapsed'] ? 'Yes' : 'No',
      )) . '</div>';
      break;
    case 'composed_field_unformatted_list':
    case 'composed_field_html_list':
      $summary .= '<div>' . t('Display style: <em>@value</em>', array(
        '@value' => t(ucfirst($settings['style'])),
      )) . '</div>';
      if ($type == 'composed_field_html_list') {
        $summary .= '<div>' . t('List type: <em>@value</em>', array(
          '@value' => $settings['list_type'] == 'ul' ? t('Unordered') : t('Ordered'),
        )) . '</div>';
      }
      break;
    case 'composed_field_table':
      if (unserialize($settings['table']['column_label']) !== FALSE) {
        $settings['table']['column_label'] = unserialize($settings['table']['column_label']);
      }
      else {
        $settings['table']['column_label'] = array();
        for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
          $settings['table']['column_label'][$subfield] = '';
        }
      }
      $summary .= '<div>' . t('Row number column: <em>@value</em>', array(
        '@value' => $settings['table']['number_column'] ? 'Yes' : 'No',
      )) . '</div>';
      foreach ($settings['table']['column_label'] as $subfield => $subfield_value) {
        $summary .= '<div>' . t("Subfield {$subfield} column label: <em>@value</em>", array(
          '@value' => $settings['table']['column_label'][$subfield],
        )) . '</div>';
      }
      break;
  }
  $filter_formats = filter_formats();

  // Set the default value for hidden, format, prefix and suffix.
  $display_options = _composed_field_formatter_info_unserialize_display_options($settings['display_options'], $number_of_subfields);
  for ($subfield = 1; $subfield <= $number_of_subfields; $subfield++) {
    $summary .= '<div><h5>' . t('Subfield !subfield', array(
      '!subfield' => $subfield,
    )) . '</h5>';
    $summary .= '<div>' . t('Hidden: <em>@value</em>', array(
      '@value' => $display_options['hidden'][$subfield] ? t('Yes') : t('No'),
    )) . '</div>';
    $format = isset($filter_formats[$display_options['format'][$subfield]]) ? $filter_formats[$display_options['format'][$subfield]]->name : '';
    $summary .= '<div style="color: ' . ($display_options['format'][$subfield] == 'full_html' ? 'red' : 'auto') . '">' . t('Format: <em>@value</em>', array(
      '@value' => $format,
    )) . '</div>';
    $summary .= '<div>' . t('Prefix: <em>@value</em>', array(
      '@value' => $display_options['prefix'][$subfield],
    )) . '</div>';
    $summary .= '<div>' . t('Suffix: <em>@value</em>', array(
      '@value' => $display_options['suffix'][$subfield],
    )) . '</div>';
    $summary .= '</div>';
  }
  return $summary;
}

/**
 * Implements hook_theme().
 */
function composed_field_theme() {
  return array(
    'composed_field' => array(
      'render element' => 'element',
    ),
    'composed_field_accordion' => array(
      'render element' => 'element',
    ),
    'composed_field_tabs' => array(
      'render element' => 'element',
    ),
    'composed_field_dialog' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Implements theme_composed_field().
 */
function theme_composed_field($vars) {
  $element = $vars['element'];
  $settings = $element['#display']['settings'];
  $class = $settings['style'] == 'block' ? 'clearfix' : 'container-inline';
  $output = '<div class="' . $class . '">';
  foreach ($element['#item'] as $subfield => $subfield_value) {
    $output .= "<div class='composed-field-{$subfield}'>{$subfield_value}</div>";
  }
  $output .= '</div>';
  return $output;
}

/**
 * Implements theme_composed_field_accordion().
 */
function theme_composed_field_accordion($vars) {
  $output = '<div class="composed-field-accordion">';
  $value = '';
  foreach ($vars['element']['#items'] as $item) {
    foreach ($item as $subfield => $subfield_value) {
      if ($subfield === 1) {
        $output .= '<h3><a href="#">' . $item[1] . '</a></h3>';
      }
      else {
        $value .= $item[$subfield];
      }
    }
  }
  $output .= '<div>' . $value . '</div>';
  $output .= '</div>';
  drupal_add_library('system', 'ui.accordion');
  drupal_add_js('jQuery(document).ready(function(){jQuery(".composed-field-accordion").accordion({ collapsible: true, active: false });});', 'inline');
  return $output;
}

/**
 * Implements theme_composed_field_tabs().
 */
function theme_composed_field_tabs($vars) {
  $output = '';
  $value = '';
  foreach ($vars['element']['#items'] as $delta => $item) {
    $vars['items'][] = '<a href="#tabs-' . $delta . '">' . $item[1] . '</a>';
    foreach ($item as $subfield => $subfield_value) {
      if ($subfield !== 1) {
        $value .= $item[$subfield];
      }
    }
    $output .= '<div id="tabs-' . $delta . '">' . $value . '</div>';
  }
  $output = '<div class="composed-field-tabs">' . theme('item_list', $vars) . $output . '</div>';
  drupal_add_library('system', 'ui.tabs');
  drupal_add_js('jQuery(document).ready(function(){jQuery(".composed-field-tabs").tabs();});', 'inline');
  return $output;
}

/**
 * Implements theme_composed_field_dialog().
 */
function theme_composed_field_dialog($vars) {
  $output = '';
  $value = '';
  foreach (array_reverse($vars['element']['#items']) as $delta => $item) {
    foreach ($item as $subfield => $subfield_value) {
      if ($subfield !== 1) {
        $value .= $item[$subfield];
      }
    }
    $output .= '<div class="composed-field-dialog" title="' . $item[1] . '">' . $value . '</div>';
  }
  drupal_add_library('system', 'ui.dialog');
  drupal_add_js('jQuery(document).ready(function(){jQuery(".composed-field-dialog").dialog();});', 'inline');
  return $output;
}

Functions

Namesort descending Description
composed_field_field_formatter_info Implements hook_field_formatter_info().
composed_field_field_formatter_settings_form Implements hook_field_formatter_settings_form().
composed_field_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
composed_field_field_formatter_view Implements hook_field_formatter_view().
composed_field_field_info Implements hook_field_info().
composed_field_field_is_empty Implements hook_field_is_empty().
composed_field_field_presave Implements hook_field_presave().
composed_field_field_widget_form Implements hook_field_widget_form().
composed_field_field_widget_info Implements hook_field_widget_info().
composed_field_field_widget_settings_form Implements hook_field_widget_settings_form().
composed_field_format_validate Element validate callback.
composed_field_form_field_ui_field_edit_form_alter Implements hook_form_FORM_ID_alter().
composed_field_permission Implements hook_permission().
composed_field_php_permission Permission callback.
composed_field_theme Implements hook_theme().
theme_composed_field Implements theme_composed_field().
theme_composed_field_accordion Implements theme_composed_field_accordion().
theme_composed_field_dialog Implements theme_composed_field_dialog().
theme_composed_field_tabs Implements theme_composed_field_tabs().
_composed_field_build_form_controls_in_vertical_tabs Builds the widget form vertical tabs.
_composed_field_build_subfield_element Builds the form element for each subfield.
_composed_field_build_vertical_tab_elements Populates each widget form vertical tab with their form elements.
_composed_field_formatter_info_serialize_display_options Element validate callback.
_composed_field_formatter_info_serialize_table_column_label Element validate callback.
_composed_field_formatter_info_unserialize_display_options Builds the subfield values from the display options formatter info.
_composed_field_form_controls_api Returns the Form Control Attributes.
_composed_field_list_allowed_values_array Returns an array of allowed values for a list field.
_composed_field_serialize Returns an array of allowed values for a list field.
_composed_field_vertical_tabs_ajax_callback AJAX callback. Updates the field widget form.