You are here

mvf.module in Measured Value Field 7

Same filename and directory in other branches
  1. 6 mvf.module

Define a field type of measured value.

Define a Field API field type that composes of a value and a unit of measurement in which value is measured.

File

mvf.module
View source
<?php

/**
 * @file
 * Define a field type of measured value.
 *
 * Define a Field API field type that composes of a value and a unit of
 * measurement in which value is measured.
 */

/**
 * Module that defines field type used for sub field 'value'.
 *
 * @var string
 */
define('MVF_MODULE_VALUE', 'number');

/**
 * Module that defines field type used for sub field 'unit'.
 *
 * @var string
 */
define('MVF_MODULE_UNIT', 'entityreference');

/**
 * Constant denotes outputting the entered value in the originally entered unit
 * measure.
 *
 * @var int
 */
define('MVF_UNIT_ORIGINAL', -1);

/**
 * Constant should be used in Unit Suggesters, when desired output unit cannot
 * be determined.
 *
 * @var int
 */
define('MVF_UNIT_UNKNOWN', -2);

/**
 * Implements hook_menu().
 */
function mvf_menu() {
  $items = array();
  $items['mvf/ajax'] = array(
    'page callback' => 'mvf_ajax',
    'access arguments' => array(
      'administer site configuration',
    ),
    'delivery callback' => 'ajax_deliver',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_admin_paths().
 */
function mvf_admin_paths() {
  return array(
    'mvf/ajax/*' => TRUE,
  );
}

/**
 * Implements hook_field_info().
 */
function mvf_field_info() {
  $field_info = array();

  // Collecting data from modules that define our sub fields.
  $value = module_invoke(MVF_MODULE_VALUE, 'field_info');
  $unit = module_invoke(MVF_MODULE_UNIT, 'field_info');

  // We narrow down a list of field types eligible for units sub field. As we
  // currently only support entityreference field type.
  $unit = array(
    'entityreference' => $unit['entityreference'],
  );
  foreach ($value as $value_field_type => $value_settings) {
    foreach ($unit as $unit_field_type => $unit_settings) {

      // We hard code 'target_type' setting of unit subfield to be 'units_unit'.
      $unit_settings['settings']['target_type'] = 'units_unit';
      $field_info['mvf_' . $value_field_type . '_' . $unit_field_type] = array(
        'label' => t('Measured @type', array(
          '@type' => $value_settings['label'],
        )),
        'settings' => array(
          'value' => isset($value_settings['settings']) ? $value_settings['settings'] : array(),
          'unit' => isset($unit_settings['settings']) ? $unit_settings['settings'] : array(),
          'meta_info' => array(
            'value' => array(
              'field_type' => $value_field_type,
              'widget' => $value_settings['default_widget'],
              'formatter' => $value_settings['default_formatter'],
              'module' => MVF_MODULE_VALUE,
              'label' => t('Value'),
              'not_supported_widgets' => array(),
            ),
            'unit' => array(
              'field_type' => $unit_field_type,
              'widget' => $unit_settings['default_widget'],
              'formatter' => $unit_settings['default_formatter'],
              'module' => MVF_MODULE_UNIT,
              'label' => t('Unit Measure'),
              // We can't support these widgets because entityreference module
              // tries to pull up field settings by field name, in our case
              // it won't be able to find its settings in the structure how we
              // keep it in MVF.
              'not_supported_widgets' => array(
                'entityreference_autocomplete',
                'entityreference_autocomplete_tags',
              ),
            ),
          ),
        ),
        'instance_settings' => array(
          'value' => isset($value_settings['instance_settings']) ? $value_settings['instance_settings'] : array(),
          'unit' => isset($unit_settings['instance_settings']) ? $unit_settings['instance_settings'] : array(),
          'mvf' => array(
            'min' => array(
              mvf_subfield_to_column('value') => NULL,
              mvf_subfield_to_column('unit') => NULL,
            ),
            'max' => array(
              mvf_subfield_to_column('value') => NULL,
              mvf_subfield_to_column('unit') => NULL,
            ),
            'unit_suggesters_settings' => array(),
          ),
        ),
        'default_widget' => 'mvf_widget_default',
        'default_formatter' => 'mvf_formatter_default',
        'property_type' => 'mvf_' . $value_field_type . '_' . $unit_field_type,
        'property_callbacks' => array(
          'mvf_property_info_callback',
        ),
      );
    }
  }
  return $field_info;
}

/**
 * Callback to alter the property info of mvf field.
 *
 * @see mvf_field_info().
 */
function mvf_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $name = $field['field_name'];
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];

  // We will expand the structure with additional properties for extracting
  // converted value. Since the possible targets of unit conversions depend on
  // conditions of a specific MVF field, we will populate that data through
  // 'property info alter' on-the-fly.
  $property['property info alter'] = 'mvf_property_info_alter';
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  $property['property info'] = mvf_data_property_info();
  unset($property['query callback']);
}

/**
 * Defines info for the properties of the MVF field data structure.
 */
function mvf_data_property_info() {
  return array(
    'value' => array(
      'type' => 'decimal',
      'label' => t('Value'),
      'schema field' => mvf_subfield_to_column('value'),
      'setter callback' => 'entity_property_verbatim_set',
    ),
    'unit' => array(
      'type' => 'units_unit',
      'label' => t('Unit'),
      'schema field' => mvf_subfield_to_column('unit'),
      'setter callback' => 'entity_property_verbatim_set',
    ),
  );
}

/**
 * Implements hook_field_widget_info().
 */
function mvf_field_widget_info() {

  // We need info about units_unit entity type.
  $units_entity_info = entity_get_info('units_unit');
  return array(
    'mvf_widget_default' => array(
      'label' => t('Configurable sub widgets'),
      'description' => t('Default widget for unit measured field. Allows you to choose widgets independently for value and unit measure sub fields.'),
      'field types' => mvf_field_types(),
      'settings' => array(
        'meta_info' => array(
          'value' => array(
            'weight' => 0,
          ),
          'unit' => array(
            'weight' => 1,
            // What property of units_unit entity to use for human readable
            // label, when generating options array for the widget.
            'label_property' => $units_entity_info['entity keys']['label'],
          ),
        ),
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function mvf_field_formatter_info() {
  $field_types = mvf_field_types();
  $settings = array(
    'unit' => array(),
    'value' => array(),
    'mvf' => array(
      'unit_suggesters_settings' => array(),
    ),
  );
  return array(
    'mvf_formatter_default' => array(
      'label' => t('Default'),
      'description' => t('Default formatter for unit measured field. Allows you to choose formatters independently for value and unit measure sub fields.'),
      'field types' => $field_types,
      'settings' => $settings,
    ),
    'mvf_formatter_symbol' => array(
      'label' => t('Configurable digits, units as symbol'),
      'description' => t('Formatter for unit measured field. Allows you to choose formatter for the numerical part from available numerical formatters, while rendering output unit as a symbol.'),
      'field types' => $field_types,
      'settings' => $settings,
    ),
  );
}

/**
 * Implements hook_element_info().
 *
 */
function mvf_element_info() {
  $type = array();

  // This one is a MVF widget - an element for entering value and unit
  // measurement that corresponds to that value.
  $type['mvf_widget'] = array(
    '#input' => TRUE,
    '#delta' => 0,
    '#field' => array(),
    '#instance' => array(),
    '#language' => LANGUAGE_NONE,
    '#field_parents' => array(),
    '#items' => array(),
    '#process' => array(
      '_mvf_widget_process',
    ),
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'mvf') . '/mvf.css',
      ),
    ),
    '#theme_wrappers' => array(
      'form_element',
    ),
  );

  // Form element of unit suggester settings.
  $type['mvf_unit_suggester'] = array(
    '#input' => TRUE,
    '#field' => array(),
    '#instance' => array(),
    '#view_mode' => NULL,
    '#theme' => 'mvf_unit_suggesters_settings',
    '#process' => array(
      '_mvf_unit_suggester_process',
    ),
    '#theme_wrappers' => array(
      'container',
    ),
  );
  return $type;
}

/**
 * Implements hook_field_settings_form().
 */
function mvf_field_settings_form($field, $instance, $has_data) {
  $form = array();

  // Firstly we recursively merge field settings forms for each of our
  // sub-fields.
  foreach ($field['settings'] as $subfield => $settings) {
    switch ($subfield) {
      case 'value':
      case 'unit':

        // Mocking up field.
        $mock_field = mvf_field_mockup($field, $subfield);

        // Mocking up instance.
        $mock_instance = mvf_instance_mockup($field, $instance, $subfield);
        $extra = module_invoke($field['settings']['meta_info'][$subfield]['module'], 'field_settings_form', $mock_field, $mock_instance, $has_data);

        // Doing any customizations in the output of a sub field hook.
        switch ($subfield) {
          case 'unit':

            // We have to add our custom validate function that will "repair"
            // what brakes entity reference native validate function.
            $extra['#element_validate'][] = 'mvf_entityreference_field_settings_validate';

            // We hardcore entity type to be 'units_unit'. It would be only
            // confusing to end user letting him see this setting. We have to do
            // it in a process function, because entityreference module defines
            // its actual form elements in its own process function. We have no
            // other choice.
            $extra['#process'][] = 'mvf_entityreference_field_settings_process';
            break;
        }
        $form[$subfield] = array(
          '#tree' => TRUE,
        );
        if (is_array($extra) && !empty($extra)) {
          $form[$subfield] += array(
            '#type' => 'fieldset',
            '#title' => t('@label Settings', array(
              '@label' => $field['settings']['meta_info'][$subfield]['label'],
            )),
            '#collapsible' => TRUE,
          ) + $extra;
        }
        break;
      case 'meta_info':
        $form['meta_info'] = array(
          '#type' => 'fieldset',
          '#title' => t('Sub Widgets'),
          '#tree' => TRUE,
          '#collapsible' => TRUE,
        );

        // Get a list of widgets for each of the sub fields.
        $info = _field_info_collate_types();
        foreach ($settings as $subfield2 => $meta_info) {

          // Filtering out only those widgets that apply to our sub field.
          $widgets = array();
          foreach ($info['widget types'] as $widget_type => $widget) {
            if (in_array($meta_info['field_type'], $widget['field types']) && !in_array($widget_type, $meta_info['not_supported_widgets'])) {
              $widgets[$widget_type] = $widget;
            }
          }
          $options = array();
          foreach ($widgets as $widget_type => $widget) {
            $options[$widget_type] = $widget['label'];
          }
          $form['meta_info'][$subfield2] = array();
          $form['meta_info'][$subfield2]['widget'] = array(
            '#type' => 'select',
            '#title' => t('@label Widget', array(
              '@label' => $meta_info['label'],
            )),
            '#description' => t('Please, choose the widget for @label part of the field.', array(
              '@label' => $meta_info['label'],
            )),
            '#required' => TRUE,
            '#options' => $options,
            '#default_value' => $meta_info['widget'],
          );

          // Adding another hidden info.
          foreach ($meta_info as $k => $v) {
            if (!isset($form['meta_info'][$subfield2][$k])) {
              $form['meta_info'][$subfield2][$k] = array(
                '#type' => 'value',
                '#value' => $v,
              );
            }
          }
        }
        break;
    }
  }
  return $form;
}

/**
 * Supportive validation function.
 *
 * In fact function does not validate anything, however, it hooks into the
 * logic of entityreference module and makes it work as a subfield for MVF.
 */
function mvf_entityreference_field_settings_validate($form, &$form_state) {

  // Entityreference module holds here field array definition. Since in our
  // case it's just a sub field, we have to pass it through mocking up
  // sub field function.
  $form_state['entityreference']['field'] = mvf_field_mockup($form_state['entityreference']['field'], 'unit');
  unset($form_state['values']['field']['settings']['unit']['handler_submit']);
}

/**
 * Implements hook_field_load().
 */
function mvf_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) {
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instances = array();
    foreach ($instances as $k => $v) {
      $mocked_instances[$k] = mvf_instance_mockup($field, $v, $subfield);
    }
    $function = $mocked_field['module'] . '_field_load';
    if (function_exists($function)) {
      $function($entity_type, $entities, $mocked_field, $mocked_instances, $langcode, $items);
    }
  }
}

/**
 * Implements hook_field_validate().
 */
function mvf_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
    $function = $mocked_field['module'] . '_field_validate';
    if (function_exists($function)) {
      $function($entity_type, $entity, $mocked_field, $mocked_instance, $langcode, $items, $errors);
    }
  }

  // Validating min and max values on our own.
  foreach ($items as $delta => $item) {

    // If any reasonable value has been submitted.
    if (!module_invoke('mvf', 'field_is_empty', $item, $field)) {
      $item_unit = units_unit_load($item[mvf_subfield_to_column('unit')]);
      $item_value = $item[mvf_subfield_to_column('value')];

      // Checking for minimal value condition.
      if (!module_invoke('mvf', 'field_is_empty', $instance['settings']['mvf']['min'], $field)) {
        $min = $instance['settings']['mvf']['min'];
        $min_unit = units_unit_load($min[mvf_subfield_to_column('unit')]);
        $min_value = units_convert($min[mvf_subfield_to_column('value')], $min_unit->machine_name, $item_unit->machine_name);
        if ($item_value < $min_value) {
          $errors[$field['field_name']][$langcode][$delta][] = array(
            'error' => 'mvf_min',
            'message' => t('%name: the value may be no less than %min_value %min_unit.', array(
              '%name' => $instance['label'],
              '%min_value' => $min[mvf_subfield_to_column('value')],
              '%min_unit' => entity_label('units_unit', $min_unit),
            )),
          );
        }
      }

      // Checking for maximum value condition.
      if (!module_invoke('mvf', 'field_is_empty', $instance['settings']['mvf']['max'], $field)) {
        $max = $instance['settings']['mvf']['max'];
        $max_unit = units_unit_load($max[mvf_subfield_to_column('unit')]);
        $max_value = units_convert($max[mvf_subfield_to_column('value')], $max_unit->machine_name, $item_unit->machine_name);
        if ($item_value > $max_value) {
          $errors[$field['field_name']][$langcode][$delta][] = array(
            'error' => 'mvf_max',
            'message' => t('%name: the value may be no greater than %max_value %max_unit.', array(
              '%name' => $instance['label'],
              '%max_value' => $max[mvf_subfield_to_column('value')],
              '%max_unit' => entity_label('units_unit', $max_unit),
            )),
          );
        }
      }
    }
  }
}

/**
 * Implements hook_field_presave().
 */
function mvf_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
    $function = $mocked_field['module'] . '_field_presave';
    if (function_exists($function)) {
      $function($entity_type, $entity, $mocked_field, $mocked_instance, $langcode, $items);
    }
  }
}

/**
 * Implements hook_field_insert().
 */
function mvf_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
    $function = $mocked_field['module'] . '_field_insert';
    if (function_exists($function)) {
      $function($entity_type, $entity, $mocked_field, $mocked_instance, $langcode, $items);
    }
  }
}

/**
 * Implements hook_field_update().
 */
function mvf_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
    $function = $mocked_field['module'] . '_field_update';
    if (function_exists($function)) {
      $function($entity_type, $entity, $mocked_field, $mocked_instance, $langcode, $items);
    }
  }
}

/**
 * Implements hook_field_delete().
 */
function mvf_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
    $function = $mocked_field['module'] . '_field_delete';
    if (function_exists($function)) {
      $function($entity_type, $entity, $mocked_field, $mocked_instance, $langcode, $items);
    }
  }
}

/**
 * Implements hook_field_instance_settings_form().
 */
function mvf_field_instance_settings_form($field, $instance) {
  $form = array();
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);

    // Checking if the module that defines a subfield wants to define any
    // instance settings.
    $extra = module_invoke($meta_info['module'], 'field_instance_settings_form', $mocked_field, $mocked_instance);
    if (is_array($extra) && !empty($extra)) {

      // Doing any customizations in the output of a sub field hook.
      switch ($subfield) {
        case 'unit':

          // We have to add our custom validate function that will "repair"
          // what brakes entity reference native validate function.
          $extra['#element_validate'][] = 'mvf_entityreference_field_instance_settings_validate';

          // We have to "save" original field definition array for
          // our submit validation function.
          $extra['#mvf'] = array(
            'field' => $field,
          );
          $extra['#pre_render'][] = 'mvf_entityreference_field_instance_settings_pre_render';
          break;
        case 'value':

          // Number module native 'min' and 'max' don't make sense in MVF,
          // because min and max should be defined considering unit measure, and
          // not only numeric value.
          $extra['min']['#access'] = FALSE;
          $extra['max']['#access'] = FALSE;
          break;
      }
      $form[$subfield] = array(
        '#type' => 'fieldset',
        '#title' => t('@label Instance Settings', array(
          '@label' => $meta_info['label'],
        )),
        '#collapsible' => TRUE,
      ) + $extra;
    }
  }

  // Also defining some our proper MVF instance settings.
  $form['mvf'] = array(
    '#tree' => TRUE,
  );
  $form['mvf']['min'] = array(
    '#type' => 'mvf_widget',
    '#title' => t('Min'),
    '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.'),
    '#field' => $field,
    '#instance' => $instance,
    '#entity_type' => $instance['entity_type'],
    '#default_value' => $instance['settings']['mvf']['min'],
  );
  $form['mvf']['max'] = array(
    '#type' => 'mvf_widget',
    '#title' => t('Max'),
    '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.'),
    '#field' => $field,
    '#instance' => $instance,
    '#entity_type' => $instance['entity_type'],
    '#default_value' => $instance['settings']['mvf']['max'],
  );
  $form['mvf']['unit_suggesters_settings'] = array(
    '#type' => 'mvf_unit_suggester',
    '#title' => t('Unit suggestion'),
    '#field' => $field,
    '#instance' => $instance,
  );
  return $form;
}

/**
 * Supportive validation function.
 *
 * In fact function does not validate anything, however, it hooks into the
 * logic of entityreference module and makes it work as a subfield for MVF.
 */
function mvf_entityreference_field_instance_settings_validate($form, &$form_state) {

  // Entityreference module holds here instance array definition. Since in our
  // case it's just a sub field, we have to pass it through mocking up
  // sub field function.
  $field = $form['#mvf']['field'];
  $instance = $form_state['entityreference']['instance'];
  $form_state['entityreference']['instance'] = mvf_instance_mockup($field, $instance, 'unit');
}

/**
 * Form element process function.
 *
 * Hide entity reference instance settings form element if it is empty.
 */
function mvf_entityreference_field_instance_settings_pre_render($element) {
  if (count(element_children($element)) == 1 && isset($element['settings']) && count(element_children($element['settings'])) == 0) {

    // That's an empty construction produced by entityreference module.
    $element['#printed'] = TRUE;
  }
  return $element;
}

/**
 * Implements hook_field_widget_settings_form().
 */
function mvf_field_widget_settings_form($field, $instance) {
  $form = array();
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);

    // Checking if the module that defines a subfield wants to define any
    // settings.
    $extra = module_invoke($mocked_instance['widget']['module'], 'field_widget_settings_form', $mocked_field, $mocked_instance);
    if (is_array($extra) && !empty($extra)) {

      // Doing any customizations before inserting output of the module that
      // defines a sub field into our widget settings form.
      switch ($subfield) {
        case 'value':
          break;
        case 'unit':
          break;
      }
      $form[$subfield] = array(
        '#type' => 'fieldset',
        '#title' => t('@label Widget Settings', array(
          '@label' => $meta_info['label'],
        )),
        '#collapsible' => TRUE,
      ) + $extra;
    }
  }
  $form['meta_info'] = array(
    '#theme' => 'mvf_column_order',
  );

  // Sort by weight the columns.
  uasort($instance['widget']['settings']['meta_info'], 'drupal_sort_weight');
  foreach ($instance['widget']['settings']['meta_info'] as $subfield => $meta_info) {
    $form['meta_info'][$subfield]['column'] = array(
      '#markup' => $field['settings']['meta_info'][$subfield]['label'],
    );

    // Any extra per sub field customizations, adjustments.
    switch ($subfield) {
      case 'unit':
        $unit_entity_info = entity_get_info('units_unit');
        $form['meta_info'][$subfield]['label_property'] = array(
          '#type' => 'select',
          '#title' => t('Label Property'),
          '#required' => TRUE,
          '#options' => array(
            $unit_entity_info['entity keys']['label'] => t('Label'),
            'symbol' => t('Symbol'),
          ),
          '#default_value' => isset($instance['widget']['settings']['meta_info'][$subfield]['label_property']) ? $instance['widget']['settings']['meta_info'][$subfield]['label_property'] : NULL,
        );
        break;
    }
    $form['meta_info'][$subfield]['weight'] = array(
      '#type' => 'weight',
      '#title' => t('Weight'),
      '#title_display' => 'invisible',
      '#default_value' => isset($instance['widget']['settings']['meta_info'][$subfield]['weight']) ? $instance['widget']['settings']['meta_info'][$subfield]['weight'] : 0,
    );
  }
  return $form;
}

/**
 * Implements hook_field_widget_form().
 */
function mvf_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $element = array(
    '#type' => 'mvf_widget',
    '#field' => $field,
    '#instance' => $instance,
    '#delta' => $delta,
    '#langcode' => $langcode,
    '#items' => $items,
  ) + $element;
  return $element;
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function mvf_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $element = array();

  // We are able to do configurable formatters in 2 steps. In the 1st step
  // user chooses sub formatters and in the 2nd step user defines any settings
  // for the chosen sub formatters.
  $info = _field_info_collate_types();
  $superior_formatter = $instance['display'][$view_mode];
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    switch ($superior_formatter['type']) {
      case 'mvf_formatter_default':

        // Default formatter implies delegating formatting to both sub
        // formatters, as to value subformatter, as to unit subformatter.
        $is_delegating_unit = TRUE;
        break;
      case 'mvf_formatter_symbol':

        // Symbol formatter already knows how to render the unit part - it
        // should be rendered as symbol, so only the value part should be
        // delegated to a sub formatter.
        $is_delegating_unit = FALSE;
        break;
      default:

        // We are not supposed to be here, let's fallback on delegation.
        $is_delegating_unit = TRUE;
        break;
    }
    if ($subfield == 'unit' && !$is_delegating_unit) {

      // We do not want to show the sub formatter options for unit.
    }
    else {

      // Looking for formatters that support our sub field type.
      $formatters = array();
      foreach ($info['formatter types'] as $formatter_type => $formatter) {
        if (in_array($meta_info['field_type'], $formatter['field types'])) {
          $formatters[$formatter_type] = $formatter;
        }
      }
      $fieldset_id = 'mvf-formatter-' . $subfield;
      $element[$subfield] = array(
        '#type' => 'fieldset',
        '#title' => $meta_info['label'],
        '#collapsible' => TRUE,
        '#prefix' => '<div id="' . $fieldset_id . '">',
        '#suffix' => '</div>',
      );
      $options = array();
      foreach ($formatters as $formatter_type => $formatter) {
        $options[$formatter_type] = $formatter['label'];
      }

      // Since we update values via #ajax, the chosen value in
      // $superior_formatter can be overriden in $form_state by the current
      // unsaved (yet) value.
      if (isset($form_state['values']['fields'][$field['field_name']]['settings_edit_form']['settings'][$subfield]['formatter'])) {
        $formatter = $form_state['values']['fields'][$field['field_name']]['settings_edit_form']['settings'][$subfield]['formatter'];
      }
      elseif (isset($superior_formatter['settings'][$subfield]['formatter'])) {

        // Then we check superior formatter to see if there are any stored
        // settings there.
        $formatter = $superior_formatter['settings'][$subfield]['formatter'];
      }
      else {

        // If we end up here, it's the 1st time formatter settings form is opened,
        // since there is no stored settings in superior formatter. So lastly we
        // fallback on sub field default formatter.
        $formatter = $meta_info['formatter'];
      }

      // After we have let current $form_state values to override currently
      // saved formatter for the subfield, we are now able to mock $field and
      // $instance so that in the mocked results the overriden formatter will be
      // reflected.
      $instance['display'][$view_mode]['settings'][$subfield]['formatter'] = $formatter;
      $mocked_field = mvf_field_mockup($field, $subfield);
      $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
      $element[$subfield]['formatter'] = array(
        '#type' => 'select',
        '#title' => t('Formatter'),
        '#required' => TRUE,
        '#description' => t('Please, choose formatter for the sub field.'),
        '#options' => $options,
        '#default_value' => $formatter,
        '#ajax' => array(
          'path' => 'mvf/ajax/formatter/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'] . '/' . $subfield,
          'wrapper' => $fieldset_id,
          'event' => 'change',
          'effect' => 'fade',
        ),
      );
      if (isset($formatters[$formatter])) {
        $formatter = $formatters[$formatter];

        // Since the sub formatter has been chosen, now we can check whether
        // the module that defines sub formatter desires to define some settings
        // for its sub formatter too.
        $function = $formatter['module'] . '_field_formatter_settings_form';
        $extra = NULL;
        if (function_exists($function)) {
          $extra = $function($mocked_field, $mocked_instance, $view_mode, $form, $form_state);
        }
        if (is_array($extra)) {

          // Doing any customizations after collecting data from the module that
          // defines a sub formatter.
          switch ($subfield) {
            case 'value':
              if ($field['settings']['meta_info'][$subfield]['field_type'] == 'number_integer') {

                // For integer we have to define 'scale' and 'decimal_separator'
                // because number module keeps these 2 parameters behind the
                // scenes for integer (since they don't make sense to integer)
                // in order to use the same formatter for all numbers.
                $extra['scale'] = array(
                  '#type' => 'value',
                  '#value' => $formatter['settings']['scale'],
                );
                $extra['decimal_separator'] = array(
                  '#type' => 'value',
                  '#value' => $formatter['settings']['decimal_separator'],
                );
              }
              break;
          }
          $element[$subfield] += $extra;
        }
      }
    }
  }
  $element['mvf'] = array(
    '#type' => 'fieldset',
    '#title' => t('Unit output'),
    '#collapsible' => TRUE,
  );
  $element['mvf']['override'] = array(
    '#type' => 'checkbox',
    '#title' => t('Override instance default unit suggestion?'),
    '#default_value' => isset($superior_formatter['settings']['mvf']['override']) ? $superior_formatter['settings']['mvf']['override'] : FALSE,
    '#attributes' => array(
      'class' => array(
        'mvf-formatter-settings-unit-suggestion-override',
      ),
    ),
  );
  $element['mvf']['unit_suggesters_settings'] = array(
    '#type' => 'mvf_unit_suggester',
    '#title' => t('Unit suggestion'),
    '#field' => $field,
    '#instance' => $instance,
    '#view_mode' => $view_mode,
    '#states' => array(
      'visible' => array(
        ':input.mvf-formatter-settings-unit-suggestion-override' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function mvf_field_formatter_settings_summary($field, $instance, $view_mode) {
  $summary = array();
  $superior_formatter = $instance['display'][$view_mode];
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    if ($superior_formatter['type'] == 'mvf_formatter_symbol' && $subfield == 'unit') {

      // For symbol formatter for unit sub field we do not have to outsource the
      // task of generation of summary to the sub formatter.
      $subsummary = 'Units as symbol';
    }
    else {

      // We collect what subformatter wants to output into the summary for the
      // the sub field.
      $mocked_field = mvf_field_mockup($field, $subfield);
      $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
      $formatter = $mocked_instance['display'][$view_mode];
      $subsummary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $mocked_field, $mocked_instance, $view_mode);
    }
    if ($subsummary) {
      $summary[$subfield] = $subsummary;
    }
  }
  $summary = implode('; ', $summary);
  if (empty($summary)) {

    // We have to output at least some string in order to make the "settings"
    // button for format appear.
    $summary = 'no format';
  }
  return $summary;
}

/**
 * Implements hook_field_formatter_view().
 */
function mvf_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();

  // In order to successfully mock up instance for a sub field, we have to know
  // view mode that corresponds to the supplied $display. We could compare each
  // display in $instance trying to find the one that equals to $display.
  // It seems easier just to extend $instance['display'] with $display
  // and then catch it out in $mocked_instance
  $mocked_view_mode = 'mvf_dummy_view_mode';
  $instance['display'][$mocked_view_mode] = $display;

  // Sort by weight the subfields.
  uasort($instance['widget']['settings']['meta_info'], 'drupal_sort_weight');
  foreach ($items as $delta => $item) {
    $element[$delta] = array();
    foreach ($instance['widget']['settings']['meta_info'] as $subfield => $meta_info) {
      if ($subfield == 'unit' && $display['type'] == 'mvf_formatter_symbol') {

        // We were requested to render the unit as symbol, so we do not delegate
        // rendering of unit to the sub formatter, we do it ourselves.
        $extra = array(
          '#markup' => $item['entity']->symbol ? filter_xss_admin($item['entity']->symbol) : entity_label('units_unit', $item['entity']),
        );
      }
      else {

        // We are supposed to delegate formatting of this subfield to the sub
        // formatter, whatever sub formatter was chosen in the settings of our
        // superior formatter.
        $mocked_field = mvf_field_mockup($field, $subfield);
        $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);
        $mocked_display = $mocked_instance['display'][$mocked_view_mode];
        unset($mocked_instance['display'][$mocked_view_mode]);
        $mocked_delta = 0;
        $mocked_items = array(
          $mocked_delta => $item,
        );
        $extra = module_invoke($mocked_display['module'], 'field_formatter_view', $entity_type, $entity, $mocked_field, $mocked_instance, $langcode, $mocked_items, $mocked_display);
      }
      if (is_array($extra)) {
        if (!isset($extra['#prefix'])) {
          $extra['#prefix'] = '';
        }
        if (!isset($extra['#suffix'])) {
          $extra['#suffix'] = '';
        }

        // Wrapping subfield in a separate <div> for easy theming.
        $extra['#prefix'] = '<div class="mvf-subfield mvf-' . $subfield . '">' . $extra['#prefix'];
        $extra['#suffix'] .= '</div>';
        $element[$delta][$subfield] = $extra;

        // Adding extra CSS classes for easier theming.
        list(, , $bundle) = entity_extract_ids('units_unit', $item['entity']);
        $element[$delta]['#prefix'] = '<span class="mvf-unit-' . entity_id('units_unit', $item['entity']) . ' mvf-measure-' . $bundle . '">';
        $element[$delta]['#suffix'] = '</span>';
      }
    }
  }

  // Only add CSS if there is item to prevent rendering empty field.
  if (!empty($element)) {
    $element['#attached']['css'] = array();
    $element['#attached']['css'][drupal_get_path('module', 'mvf') . '/mvf.css'] = array();
  }
  return $element;
}

/**
 * Implements hook_field_formatter_prepare_view().
 */
function mvf_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
  $mocked_view_mode = 'mvf_mocked_view_mode';

  // Before we proceed we have to convert all $items to necessary output unit
  // measure. Necessary output unit measure should be suggested by MVF Unit
  // suggesters.
  foreach ($entities as $entity_id => $entity) {
    $instance = $instances[$entity_id];
    $instance['display'][$mocked_view_mode] = $displays[$entity_id];
    $output_unit = mvf_unit_suggest($items[$entity_id], $field, $instance, $entity, $mocked_view_mode);
    $items[$entity_id] = mvf_items_convert($field, $items[$entity_id], $output_unit);
  }
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    foreach ($instances as $k => $v) {
      $mocked_instances = array(
        $k => array(),
      );
      $mocked_displays = array(
        $k => array(),
      );
      $v['display'][$mocked_view_mode] = $displays[$k];
      $mocked_instances[$k] = mvf_instance_mockup($field, $v, $subfield);
      $displays[$k] = $mocked_instances[$k]['display'][$mocked_view_mode];
      unset($mocked_instances[$k]['display'][$mocked_view_mode]);
      $function = $displays[$k]['module'] . '_field_formatter_prepare_view';
      if (function_exists($function)) {
        $function($entity_type, $entities, $mocked_field, $mocked_instances, $langcode, $items, $mocked_displays);
      }
    }
  }
}

/**
 * Implements hook_field_is_empty().
 */
function mvf_field_is_empty($item, $field) {

  // If at least one sub field is empty, the entire field is considered empty.
  $is_empty = FALSE;
  foreach ($field['settings']['meta_info'] as $subfield => $meta_info) {
    $mocked_field = mvf_field_mockup($field, $subfield);
    $is_empty = $is_empty || (bool) module_invoke($mocked_field['module'], 'field_is_empty', $item, $mocked_field);
  }
  return $is_empty;
}

/**
 * Implements hook_theme().
 */
function mvf_theme() {
  return array(
    'mvf_column_order' => array(
      'render element' => 'element',
    ),
    'mvf_unit_suggesters_settings' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Implements hook_field_update_instance().
 */
function mvf_field_update_instance($instance, $prior_instance) {
  $field_map = field_info_field_map();
  if (in_array($field_map[$instance['field_name']]['type'], mvf_field_types())) {

    // Notify each unit suggester if its status has changed.
    $field = field_info_field($instance['field_name']);
    $measure = mvf_measure_extract($field);
    $unit_suggesters = mvf_get_unit_suggester();
    $view_modes = array_unique(array_merge(array_keys($instance['display']), array_keys($prior_instance['display'])));
    $view_modes[] = NULL;
    foreach ($view_modes as $view_mode) {
      foreach ($unit_suggesters as $unit_suggester) {
        $statuses = array(
          'prior' => array(
            'instance' => $prior_instance,
            'status' => FALSE,
          ),
          'current' => array(
            'instance' => $instance,
            'status' => FALSE,
          ),
        );
        foreach ($statuses as $k => $v) {
          if ($view_mode && !isset($v['instance']['display'][$view_mode])) {
            $statuses[$k]['status'] = FALSE;
          }
          else {
            $statuses[$k]['status'] = mvf_unit_suggester_info($unit_suggester, $field, $v['instance'], $view_mode);
            $statuses[$k]['status'] = $statuses[$k]['status']['enable'];
          }
        }
        if ($statuses['prior']['status'] != $statuses['current']['status']) {
          $callback_name = $statuses['current']['status'] ? 'enabled callback' : 'disabled callback';
          $function = ctools_plugin_get_function($unit_suggester, $callback_name);
          if ($function) {
            $settings = $statuses['current']['status'] ? mvf_unit_suggester_info($unit_suggester, $field, $statuses['current']['instance'], $view_mode) : mvf_unit_suggester_info($unit_suggester, $field, $statuses['prior']['instance'], $view_mode);
            $function($measure, $field, $instance, $view_mode, $settings, $unit_suggester);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_field_delete_instance().
 */
function mvf_field_delete_instance($instance) {
  $field = field_read_fields(array(
    'field_name' => $instance['field_name'],
  ), array(
    'include_deleted' => TRUE,
  ));
  $field = reset($field);
  if (in_array($field['type'], mvf_field_types())) {
    $measure = mvf_measure_extract($field);
    $view_modes = array_keys($instance['display']);
    $view_modes[] = NULL;
    foreach ($view_modes as $view_mode) {
      $unit_suggesters = mvf_unit_suggesters_info($field, $instance, $view_mode);
      foreach ($unit_suggesters as $unit_suggester) {
        if ($unit_suggester['settings']['enable']) {
          $function = ctools_plugin_get_function($unit_suggester['plugin'], 'disabled callback');
          if ($function) {
            $function($measure, $field, $instance, $view_mode, $unit_suggester['settings'], $unit_suggester['plugin']);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_ctools_plugin_type().
 */
function mvf_ctools_plugin_type() {
  $plugins = array();
  $plugins['unit_suggesters'] = array(
    'defaults' => array(
      'title' => NULL,
      'description' => NULL,
      'applicable callback' => NULL,
      'enabled callback' => NULL,
      'disabled callback' => NULL,
      'settings form callback' => NULL,
      'suggest unit callback' => NULL,
    ),
  );
  return $plugins;
}

/**
 * Implements hook_ctools_plugin_directory().
 */
function mvf_ctools_plugin_directory($owner, $plugin_type) {
  switch ($owner) {
    case 'mvf':
      switch ($plugin_type) {
        case 'unit_suggesters':
          return 'plugins/' . $plugin_type;
      }
      break;
  }
}

/**
 * Process function for form element type 'mvf_widget'.
 *
 * Expand for element into an input for value and an input for unit measure
 * in accordance with field settings for which form element is generated.
 */
function _mvf_widget_process($element, &$form_state, $form) {

  // Additionally values may be provided in #default_value property. So we
  // check it too.
  if (empty($element['#items']) && isset($element['#default_value'])) {
    $element['#delta'] = 0;
    $element['#items'] = array(
      $element['#default_value'],
    );
  }
  $form_fields = array();
  $field = $element['#field'];
  $instance = $element['#instance'];
  $items = $element['#items'];
  if (!isset($element['#field_name'])) {
    $element['#field_name'] = $element['#field']['field_name'];
  }
  if (!isset($element['#bundle'])) {
    $element['#bundle'] = $instance['bundle'];
  }
  uasort($instance['widget']['settings']['meta_info'], 'drupal_sort_weight');
  foreach ($instance['widget']['settings']['meta_info'] as $subfield => $meta_info) {
    $meta_info = $field['settings']['meta_info'][$subfield];
    $mocked_field = mvf_field_mockup($field, $subfield);
    $mocked_instance = mvf_instance_mockup($field, $instance, $subfield);

    // We have to extra process $mocked_field in order to make it work with
    // options module. It gets default value based on looking into the key
    // of first element in $field['columns'] array. Thus we have to make the
    // current subfield column first in columns array.
    $column_name = mvf_subfield_to_column($subfield);
    $column = array(
      $column_name => $mocked_field['columns'][$column_name],
    );
    $mocked_field['columns'] = $column + $mocked_field['columns'];
    $function = $mocked_instance['widget']['module'] . '_field_widget_form';
    $extra = FALSE;
    if (function_exists($function)) {

      // Since we mock sub fields as if the cardinality is 1 (we handle real
      // cardinality in our own field), we have to mock $items array to
      // represent "truth" for the subfield, i.e. $items array should only
      // include the current item and nothing else, this way we are able to mock
      // the cardinality 1 for our sub fields.
      $mocked_delta = 0;
      $mocked_items = isset($items[$element['#delta']]) ? array(
        $mocked_delta => $items[$element['#delta']],
      ) : array();
      switch ($subfield) {
        case 'value':
          break;
        case 'unit':
          if (empty($mocked_items)) {
            $mocked_items[] = array(
              mvf_subfield_to_column('unit') => mvf_unit_suggest($mocked_items, $field, $instance, $element['#entity'], NULL, array(
                MVF_UNIT_ORIGINAL,
              )),
            );
          }
          break;
      }
      $tmp = array(
        '#entity_type' => $element['#entity_type'],
        '#bundle' => $element['#bundle'],
        '#field_name' => $element['#field_name'],
        '#language' => $element['#language'],
        '#field_parents' => $element['#field_parents'],
        // @todo: #columns.
        '#description' => '',
        '#required' => $element['#required'],
        '#delta' => $mocked_delta,
        '#entity' => (object) array(),
      );
      if (isset($element['#title'])) {
        $tmp['#title'] = $element['#title'];
      }
      if (isset($element['#entity'])) {
        $tmp['#entity'] = $element['#entity'];
      }
      $extra = $function($form, $form_state, $mocked_field, $mocked_instance, $element['#language'], $mocked_items, $mocked_delta, $tmp);
    }
    if (is_array($extra) && !empty($extra)) {
      $extra = isset($extra[$subfield]) ? $extra[$subfield] : $extra;
      unset($extra['#description']);
      $extra['#title'] = isset($extra['#title']) ? $extra['#title'] . ' ' . $meta_info['label'] : $meta_info['label'];
      $extra['#title_display'] = 'invisible';

      // Doing any sub field specific customizations of subfield output.
      switch ($subfield) {
        case 'unit':
          if ($mocked_instance['widget']['module'] == 'options') {

            // Options module messes things up for us in its element validate
            // function. So we have to clean things up in our own element
            // validate function that will be run after the options' one.
            $extra['#element_validate'][] = 'mvf_field_widget_unit_options_validate';

            // Additionally we have to make sure the labeling is consistent with
            // what we have in widget settings. By default options module
            // generate options for EntityReference fields based on 'label'
            // property of each entity. MVF needs to support labeling by
            // 'symbol' property too. Thus if our widget is set up to generate
            // options based on 'symbol' property, we have to slightly adjust
            // the already generated #options array.
            switch ($instance['widget']['settings']['meta_info']['unit']['label_property']) {
              case 'symbol':

                // Let's substitute labels of entities by their symbols.
                if (!empty($extra['#options'])) {
                  $umids = $extra['#options'];
                  unset($umids['_none']);
                  $umids = array_keys($umids);
                  $units = units_unit_load_multiple($umids);
                  $options = array();
                  foreach ($units as $k => $unit) {
                    $label = $unit->{$instance['widget']['settings']['meta_info']['unit']['label_property']} ? $unit->{$instance['widget']['settings']['meta_info']['unit']['label_property']} : entity_label('units_unit', $unit);
                    $options[$k] = $label;
                  }

                  // Resort the new options array, because alphabetical order
                  // probably has changed.
                  asort($options);

                  // Putting on top of the new options array the 'None' option
                  // if it was originally present.
                  if (isset($extra['#options']['_none'])) {
                    $options = array(
                      '_none' => $extra['#options']['_none'],
                    ) + $options;
                  }
                  $extra['#options'] = $options;
                }
                break;
            }
          }
          break;
        case 'value':

          // Number module uses FAPI element validate function
          // number_field_widget_validate() for validating user input. That
          // function retrieves info about field and instance from $form_state.
          // Apparently in our case things go bad, because it retrieves info
          // about MVF field, instead of expected number sub field. We have to
          // to override $form_state before that validation function and then
          // return $form_state back to how it was after running Number's module
          // validation function.
          array_unshift($extra['#element_validate'], 'mvf_field_widget_validate_value_form_state_mockup_override');
          $extra['#element_validate'][] = 'mvf_field_widget_validate_value_form_state_mockup_revert';
          break;
      }
      if (isset($element['#dependency'])) {
        $extra['#dependency'] = $element['#dependency'];
      }
      if (isset($element['#states'])) {
        $extra['#states'] = $element['#states'];
      }
      $form_fields[$column_name] = $extra;
    }
  }
  $element += $form_fields;
  $element['label'] = array(
    '#type' => 'item',
    '#title' => isset($element['#title']) ? $element['#title'] : $instance['label'],
    '#title_display' => $element['#title_display'],
    '#required' => $element['#required'],
    '#weight' => -10,
  );
  $element['#title_display'] = 'invisible';
  if (isset($element['#dependency'])) {
    $element['label']['#dependency'] = $element['#dependency'];
  }
  if (isset($element['#states'])) {
    $element['label']['#states'] = $element['#states'];
  }
  return $element;
}

/**
 * Supportive function.
 *
 * Mock up field array of a subfield of the supplied $field array based on the
 * parameter $subfield.
 *
 * @param array $field
 *   Field that includes sub fields one of which needs to be mocked up
 * @param string $subfield
 *   Subfield name for which the sub field array should be mocked up
 *
 * @return array
 *   Array of mocked up subfield
 */
function mvf_field_mockup($field, $subfield) {
  $mocked_field = array(
    'field_name' => $field['field_name'],
    'translatable' => $field['translatable'],
    'entity_types' => $field['entity_types'],
    'type' => $field['settings']['meta_info'][$subfield]['field_type'],
    'module' => $field['settings']['meta_info'][$subfield]['module'],
    'active' => $field['active'],
    'locked' => $field['locked'],
    // For subfields the cardinality is always 1, we handle cardinality in the
    // real field.
    'cardinality' => 1,
    'deleted' => $field['deleted'],
    'settings' => isset($field['settings'][$subfield]) ? $field['settings'][$subfield] : array(),
    'storage' => $field['storage'],
  );
  if (isset($field['columns'])) {
    $mocked_field['columns'] = $field['columns'];
  }
  if (isset($field['bundles'])) {
    $mocked_field['bundles'] = $field['bundles'];
  }
  return $mocked_field;
}

/**
 * Supportive function.
 *
 * Mock up instance array of a subfield instance of the supplied $instance
 * array based on the parameter $subfield.
 *
 * @param array $field
 *   Field array. It is used to extract info and mock up the instance array
 * @param array $instance
 *   Instance array. It is used to extract info and mock up the instance array
 * @param string $subfield
 *   Subfield name for which the sub field instance should be mocked up
 *
 * @return array
 *   Array of mocked up subfield instance
 */
function mvf_instance_mockup($field, $instance, $subfield) {

  // We need to figure out what module supplies the widget selected for this
  // instance.
  $info = _field_info_collate_types();
  $widget = $info['widget types'][$field['settings']['meta_info'][$subfield]['widget']];
  $mocked_instance = array(
    'label' => $instance['label'],
    'widget' => array(
      'type' => $field['settings']['meta_info'][$subfield]['widget'],
      'weight' => $instance['widget']['weight'],
      'settings' => isset($instance['widget']['settings'][$subfield]) ? $instance['widget']['settings'][$subfield] : $widget['settings'],
      'module' => $widget['module'],
    ),
    'settings' => isset($instance['settings'][$subfield]) ? $instance['settings'][$subfield] : array(),
    // Display will be defined later on.
    'display' => array(),
    'required' => $instance['required'],
    'description' => $instance['description'],
    'entity_type' => $instance['entity_type'],
    'bundle' => $instance['bundle'],
  );
  if (isset($instance['deleted'])) {
    $mocked_instance['deleted'] = $instance['deleted'];
  }
  if (isset($instance['display']) && is_array($instance['display'])) {
    foreach ($instance['display'] as $view_mode => $display) {
      $formatter_type = isset($display['settings'][$subfield]['formatter']) ? $display['settings'][$subfield]['formatter'] : $field['settings']['meta_info'][$subfield]['formatter'];
      $formatter = $info['formatter types'][$formatter_type];
      unset($display['settings'][$subfield]['formatter']);
      $mocked_instance['display'][$view_mode] = array(
        'label' => $display['label'],
        'type' => $formatter_type,
        'settings' => isset($display['settings'][$subfield]) && !empty($display['settings'][$subfield]) ? $display['settings'][$subfield] : $formatter['settings'],
        'module' => $formatter['module'],
        'weight' => $display['weight'],
      );
    }
  }
  return $mocked_instance;
}

/**
 * Supportive function.
 *
 * Convert values between sub field names and columns they represent.
 *
 * @param string $value
 *   Either column name or sub field name
 * @param string $target
 *   Expected converted value. Allowed values are:
 *     column - $value should hold sub field name and returned will be column
 *       name of the supplied sub field
 *     subfield - $value should hold column name and returned will be sub field
 *       name of the supplied column
 *
 * @return string
 *   See description of $target parameter for details on returned value
 */
function mvf_subfield_to_column($value, $target = 'column') {
  $map = array(
    'value' => 'value',
    'unit' => 'target_id',
  );
  if ($target == 'subfield') {
    $map = array_flip($map);
  }
  return $map[$value];
}

/**
 * Supportive function, normally should be used in formatters of MVF field.
 *
 * Convert $items array of MVF field instance from the original unit measures
 * into $destination_unit
 *
 * @param array $field
 *   Field definition array of MVF field $items of which are supplied for
 *   conversion
 * @param array $items
 *   $items array of MVF field instance
 * @param object|string|int $destination_unit
 *   Into what unit measure $items should be converted to. You may supply a
 *   fully loaded entity 'units_unit', or string, which will be considered to be
 *   machine name of destination unit, or int, which will be considered to be
 *   umid of destination unit. You may also provide constant
 *   MVF_FORMATTER_ORIGINAL_UNIT if want to keep the units in which values were
 *   originally entered
 *
 * @return array
 *   Converted $items array
 */
function mvf_items_convert($field, $items, $destination_unit) {
  if (in_array($destination_unit, array(
    MVF_UNIT_ORIGINAL,
    MVF_UNIT_UNKNOWN,
  ))) {

    // We were either asked not to do any conversion with $items or we do not
    // know into what units it should be converted. So we just return it as it
    // is.
    return $items;
  }
  if (is_numeric($destination_unit)) {
    $destination_unit = units_unit_load($destination_unit);
  }
  elseif (!is_object($destination_unit)) {
    $destination_unit = units_unit_machine_name_load($destination_unit);
  }
  if (!is_object($destination_unit)) {

    // We couldn't find 'units_unit' entity for destination units. As fallback
    // we return untouched $items array.
    return $items;
  }

  // For scaling we load all origin units at once.
  $origin_units = array();
  foreach ($items as $item) {
    $origin_units[$item[mvf_subfield_to_column('unit')]] = $item[mvf_subfield_to_column('unit')];
  }
  $origin_units = array_keys($origin_units);
  $origin_units = units_unit_load_multiple($origin_units);
  foreach ($items as $delta => $item) {
    $items[$delta][mvf_subfield_to_column('value')] = units_convert($item[mvf_subfield_to_column('value')], $origin_units[$item[mvf_subfield_to_column('unit')]]->machine_name, $destination_unit->machine_name);
    $items[$delta][mvf_subfield_to_column('unit')] = $destination_unit->umid;
  }
  return $items;
}

/**
 * Default theme implementation of hook 'mvf_column_order'.
 *
 * Render form element into a table with JS draggable weight fields.
 *
 * @param array $vars
 *   Arguments for theming
 *
 * @return string
 *   Themed HTML string of the supplied arguments
 */
function theme_mvf_column_order($vars) {
  $element = $vars['element'];
  $header = array(
    'Column',
    'Weight',
    'Extra Settings',
  );
  $rows = array();
  $table_id = 'mvf-column-order-table';
  $group = 'mvf-order';
  foreach (element_children($element) as $key) {
    if (!isset($element[$key]['weight']['#attributes']['class'])) {
      $element[$key]['weight']['#attributes']['class'] = array();
    }
    $element[$key]['weight']['#attributes']['class'][] = $group;
    $rows[] = array(
      'data' => array(
        drupal_render($element[$key]['column']),
        drupal_render($element[$key]['weight']),
        drupal_render($element[$key]),
      ),
      'class' => array(
        'draggable',
      ),
    );
  }
  $output = theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'attributes' => array(
      'id' => $table_id,
    ),
    'caption' => t('Order of Fields'),
  ));
  $output .= drupal_render_children($element);
  drupal_add_tabledrag($table_id, 'order', 'sibling', $group);
  return $output;
}

/**
 * Default theme implementation of hook 'mvf_unit_suggesters_settings'.
 *
 * We sort form elements into a table, adding draggable weights for better UI
 * experience.
 *
 * @param array $vars
 *   Arguments for theming
 *
 * @return string
 *   Themed HTML string of the supplied arguments
 */
function theme_mvf_unit_suggesters_settings($vars) {
  $table_id = 'mvf-unit-suggesters-settings-table';
  $group = 'mvf-order';
  $table = array();
  $table['header'] = array(
    t('Enable'),
    t('Weight'),
    t('Settings'),
  );
  $table['caption'] = $vars['element']['#title'];
  $table['attributes'] = array(
    'id' => $table_id,
  );
  $table['rows'] = array();
  foreach (element_children($vars['element']) as $child) {
    $vars['element'][$child]['weight']['#attributes']['class'][] = $group;
    $table['rows'][] = array(
      'data' => array(
        drupal_render($vars['element'][$child]['enable']),
        drupal_render($vars['element'][$child]['weight']),
        // Rendering whatever is left, whatever left probably will be custom
        // form elements, provided by that Unit Suggester in "getSettingsForm"
        // method.
        drupal_render($vars['element'][$child]),
      ),
      'class' => array(
        'draggable',
      ),
    );
  }
  drupal_add_tabledrag($table_id, 'order', 'sibling', $group);
  return theme('table', $table) . drupal_render_children($vars['element']);
}

/**
 * Collect information about available unit suggesters and their settings.
 *
 * @param array $field
 *   Field API field definition array of MVF field
 * @param array $instance
 *   Field API instance definition array of MVF field
 * @param string $view_mode
 *   Optional name of the view mode. If you want to retrieve unit suggesters for
 *   a specific view mode of a MVF instance, then provide this view mode here.
 *   If it is omitted, a list of generally configured unit suggesters for this
 *   MVF instance will be returned
 *
 * @return array
 *   Array of information about available unit suggesters and their settings for
 *   provided $field and $instance. Array will be sorted by weight of unit
 *   suggesters, i.e. elements will go in the order, in which they are supposed
 *   to suggest output units for provided $field and $instance. Values of this
 *   array will have the following structure:
 *   - plugin: (array) CTools plugin definition array of this unit suggester
 *   - settings: (array) Array of settings for provided $field and $instance.
 *     This array will have the following structure:
 *     - enable: (bool) Whether this unit suggester is enabled for provided
 *       $field and $instance
 *     - weight: (int) Weight (priority) at which this unit suggester should be
 *       asked to suggest output unit for provided $field and $instance
 *     - ... Different unit suggesters might keep any additional settings here,
 *       which varies on per unit suggester basis
 */
function mvf_unit_suggesters_info($field, $instance, $view_mode = NULL) {
  $unit_suggester_info = array();
  $plugins = mvf_get_unit_suggester();
  $measure = mvf_measure_extract($field);
  $unit_suggester_settings = $instance['settings']['mvf']['unit_suggesters_settings'];
  if ($view_mode && isset($instance['display'][$view_mode]['settings']['mvf']['override']) && $instance['display'][$view_mode]['settings']['mvf']['override']) {
    $unit_suggester_settings = $instance['display'][$view_mode]['settings']['mvf']['unit_suggesters_settings'];
  }
  foreach ($plugins as $plugin) {
    if ($plugin['applicable callback'] !== TRUE) {
      $function = ctools_plugin_get_function($plugin, 'applicable callback');
      $is_applicable = $function && $function($measure, $field, $instance, $plugin);
    }
    else {
      $is_applicable = TRUE;
    }
    if ($is_applicable) {
      $settings = isset($unit_suggester_settings[mvf_unit_suggesters_settings_name($plugin)]) ? $unit_suggester_settings[mvf_unit_suggesters_settings_name($plugin)] : array(
        'enable' => FALSE,
        'weight' => 0,
      );
      $unit_suggester_info[] = array(
        'plugin' => $plugin,
        'settings' => $settings,
      );
    }
  }

  // Sorting by weight all available unit suggesters.
  usort($unit_suggester_info, '_mvf_unit_suggesters_info_sort');
  return $unit_suggester_info;
}

/**
 * Collect information about a single unit suggester and its settings.
 *
 * @param array $plugin
 *   cTools plugin definition of the unit suggester that should be retrieved
 * @param array $field
 *   Field definition array of the MVF field from which to retrieve the
 *   information
 * @param array $instance
 *   Field instance definition array of the MVF instance from which to retrieve
 *   the information
 * @param string $view_mode
 *   Particular view mode for which to retrieve the information. You may omit
 *   this argument and the unit suggester info will be retrieved from general
 *   MVF instance settings
 *
 * @return array
 *   Array of information about the settings of the requested unit suggester. It
 *   will have the following structure:
 *   - enable: (bool) whether it is enabled
 *   - weight: (int) weight of the unit suggester compared to other unit
 *     suggesters
 *   - ... Different unit suggesters might keep any additional settings here,
 *     which varies on per unit suggester basis
 */
function mvf_unit_suggester_info($plugin, $field, $instance, $view_mode = NULL) {
  $plugin_name = mvf_unit_suggesters_settings_name($plugin);
  $unit_suggester_settings = $instance['settings']['mvf']['unit_suggesters_settings'];
  if ($view_mode && isset($instance['display'][$view_mode]['settings']['mvf']['override']) && $instance['display'][$view_mode]['settings']['mvf']['override']) {
    $unit_suggester_settings = $instance['display'][$view_mode]['settings']['mvf']['unit_suggesters_settings'];
  }
  return isset($unit_suggester_settings[$plugin_name]) ? $unit_suggester_settings[$plugin_name] : array(
    'enable' => FALSE,
    'weight' => 0,
  );
}

/**
 * Retrieve information about a cTools plugin of MVF unit suggester type.
 *
 * @param string $suggester
 *   Name of a particular unit suggester plugin to return. If skipped, an array
 *   of all available unit suggester plugins will be returned
 *
 * @return array
 *   Depending on whether $suggester is provided it will be either a single
 *   array of information about provided $suggester unit suggester plugin or
 *   array of information on all available unit suggester plugins
 */
function mvf_get_unit_suggester($suggester = NULL) {
  ctools_include('plugins');
  return ctools_get_plugins('mvf', 'unit_suggesters', $suggester);
}

/**
 * Derive name, under which settings of a unit suggester will be stored.
 *
 * @param array $unit_suggester
 *   CTools unit_suggester plugin, for which to generate name
 *
 * @return string
 *   Name under which settings of this unit suggester plugin will be stored
 */
function mvf_unit_suggesters_settings_name($unit_suggester) {
  return $unit_suggester['name'];
}

/**
 * Supportive function for sorting unit suggesters by their weight.
 */
function _mvf_unit_suggesters_info_sort($a, $b) {
  $a_weight = isset($a['settings']['weight']) ? $a['settings']['weight'] : 0;
  $b_weight = isset($b['settings']['weight']) ? $b['settings']['weight'] : 0;
  if ($a_weight == $b_weight) {
    return 0;
  }
  return $a_weight < $b_weight ? -1 : 1;
}

/**
 * Extract 'units_measure' entity from a MVF field.
 *
 * @param array $field
 *   Field definition array of a MVF field
 *
 * @return object
 *   Fully loaded 'units_measure' entity that is used in the provided MVF field
 */
function mvf_measure_extract($field) {
  $mocked_field = mvf_field_mockup($field, 'unit');
  $options = entityreference_get_selection_handler($mocked_field)
    ->getReferencableEntities(NULL, 'CONTAINS', 1);
  if (!empty($options)) {
    $bundles = array_keys($options);
    return units_measure_machine_name_load(reset($bundles));
  }
  return NULL;
}

/**
 * Element validate function.
 *
 * Clean up after the mess left by options module element validate function for
 * unit subfield of MVF.
 */
function mvf_field_widget_unit_options_validate($element, &$form_state) {
  form_set_value($element, $element['#value'], $form_state);
}

/**
 * Menu page callback.
 *
 * Menu #ajax callback path for any settings form of MVF field.
 *
 * @param string $type
 *   Current setting on which it's being fired. Normally it holds values like:
 *     'widget_settings'
 *     'formatter_settings'
 *      ...etc
 * @param string $entity_type
 *   Instanence of what entity type is being edited
 * @param string $bundle
 *   Bundle of the entity type
 * @param string $field_name
 *   Instance of what field name is being edited. It's easy to retrieve instance
 *   definition array and field definition array using all these parameters.
 *   This way we should be able to retrieve enough info about context in which
 *   the function is called to process any required task
 * @param string $subfield
 *   Which subfield is being edited. After retrieving field and instance
 *   definition arrays, one might want to pass them on to mvf_field_mockup() and
 *   mvf_instance_mockup() functions
 *
 * @return array
 *   Array definition of form elements that will be passed to this page callback
 *   delivery callback, which is normally ajax_deliver()
 */
function mvf_ajax($type, $entity_type, $bundle, $field_name, $subfield) {

  // TODO: is it possible to implement it without custom AJAX menu path?
  list($form, $form_state) = ajax_get_form();
  drupal_process_form($form['#form_id'], $form, $form_state);
  switch ($type) {
    case 'formatter':
      return $form['fields'][$field_name]['format']['settings_edit_form']['settings'][$subfield];
      break;
  }
}

/**
 * FAPI element validate function.
 *
 * In fact function does not validate anything, however, overrides $form_state
 * with mocked up field and instance. Supposedly this overriden $form_state
 * will be used in Number module FAPI element validate function. After running
 * all Number module FAPI element validate functions, the effect done by this
 * function should be reverted by calling FAPI element validate function
 * mvf_field_widget_validate_value_form_state_mockup_revert().
 */
function mvf_field_widget_validate_value_form_state_mockup_override($element, &$form_state) {
  $subfield = 'value';

  // Extracting field state from $form_state.
  $field_state = field_form_get_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state);
  if (is_null($field_state)) {

    // This validation callback is called probably from views filter. So we have
    // to mimic $field_state on the fly.
    $field_state = array(
      'field' => field_info_field($element['#field_name']),
    );
    $field_state['instance'] = mvf_field_instance($field_state['field']);
  }
  $mocked_field_state = $field_state;

  // Mocking up field and instance definition arrays. Along the way saving in
  // mocked $field_state the original MVF field and instance definition arrays.
  $mocked_field_state['field'] = mvf_field_mockup($field_state['field'], $subfield);
  $mocked_field_state['field']['mvf'] = $field_state['field'];
  $mocked_field_state['instance'] = mvf_instance_mockup($field_state['field'], $field_state['instance'], $subfield);
  $mocked_field_state['instance']['mvf'] = $field_state['instance'];

  // Writing mocked up $field_state into $form_state.
  field_form_set_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state, $mocked_field_state);
}

/**
 * FAPI element validate function.
 *
 * Function does not validate anything, however, it reverts effect caused by
 * mvf_field_widget_validate_value_form_state_mockup_override() and puts info
 * about real MVF field and instance into $form_state instead of one mocked up
 * for use for a module that defines a sub field of MVF.
 */
function mvf_field_widget_validate_value_form_state_mockup_revert($element, &$form_state) {

  // Extracting mocked $field_state from $form_state.
  $mocked_field_state = field_form_get_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state);
  $field_state = $mocked_field_state;

  // Retrieving original MVF field and instance definition arrays and putting
  // them into $field_state.
  $field_state['field'] = $mocked_field_state['field']['mvf'];
  $field_state['instance'] = $mocked_field_state['instance']['mvf'];

  // Writing original $field_state into $form_state.
  field_form_set_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state, $field_state);
}

/**
 * FAPI element process function.
 *
 * Override form elements defined in entityreference native process function.
 * Hide 'target_type' setting of entityreference subfield.
 */
function mvf_entityreference_field_settings_process($form, &$form_state) {
  $form['target_type']['#access'] = FALSE;
  $form['target_type']['#value'] = 'units_unit';
  return $form;
}

/**
 * Element validation function.
 *
 * Make MVF unit subfield configuration element to follow expected values format
 * in entity reference. We change bundle filter in entity reference from
 * checkboxes to radio buttons, this validation function converts values
 * returned by radios to look like they were returned by checkboxes.
 */
function mvf_entityreference_selection_target_bundles_validate(&$element, &$form_state, $form) {
  $value = array(
    $element['#value'] => $element['#value'],
  );
  $element['#value'] = $value;
  form_set_value($element, $element['#value'], $form_state);
}

/**
 * Options callback for Views handler views_handler_filter_in_operator.
 *
 * Allow to choose referenced units from a given list of options, rather than
 * entering entity_id in the filter - which is the default implementation for
 * it.
 *
 * @param string $field_name
 *   Field name for which options should be generated
 */
function mvf_views_handler_options_list($field_name) {
  $field = field_info_field($field_name);
  $mocked_field = mvf_field_mockup($field, 'unit');
  return module_invoke($mocked_field['module'], 'options_list', $mocked_field);
}

/**
 * Supportive function.
 *
 * Sometimes it's necessary for MVF to pull up any instance of a specific field.
 * This function loads and returns first found instance of the supplied field
 * definition array
 *
 * @param array $field
 *   Field definition array
 *
 * @return array
 *   Instance definition array of the first found instance of the supplied field
 */
function mvf_field_instance($field) {
  $entity_type = array_keys($field['bundles']);
  $entity_type = array_pop($entity_type);
  $instance = field_info_instance($entity_type, $field['field_name'], $field['bundles'][$entity_type][0]);
  return $instance;
}

/**
 * Implements hook_views_api().
 */
function mvf_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'mvf') . '/views',
  );
}

/**
 * Return an array of field types that are provided by MVF.
 *
 * @return array
 *   Array of field types that are provided by MVF.
 */
function mvf_field_types() {
  return array_keys(mvf_field_info());
}

/**
 * Property info alter callback for MVF field structure.
 *
 * We will examine what MVF it is, what units are available for that MVF and for
 * each of those units will generate additional property of converted value.
 */
function mvf_property_info_alter(EntityMetadataWrapper $wrapper, array $propertyInfo) {
  $mvf_info = $wrapper
    ->info();
  $mvf_field = field_info_field($mvf_info['name']);
  $measure = mvf_measure_extract($mvf_field);
  foreach (units_unit_by_measure_load_multiple($measure) as $unit) {
    $key = 'converted_' . entity_id($unit
      ->entityType(), $unit);
    $propertyInfo['properties'][$key] = $propertyInfo['properties']['value'];
    unset($propertyInfo['properties'][$key]['schema field']);
    unset($propertyInfo['properties'][$key]['setter callback']);
    $propertyInfo['properties'][$key]['getter callback'] = 'mvf_entity_property_converted_get';
    $propertyInfo['properties'][$key]['computed'] = TRUE;
    $propertyInfo['properties'][$key]['label'] = t('Value in @label', array(
      '@label' => entity_label($unit
        ->entityType(), $unit),
    ));
    $ids = entity_extract_ids($unit
      ->entityType(), $unit);

    // We store in the property info the conversion target unit, so the getter
    // callback can easily access it.
    $propertyInfo['properties'][$key]['mvf target unit'] = $ids[0];
  }
  return $propertyInfo;
}

/**
 * Property getter callback for MVF converted values.
 *
 * Return MVF value converted into requested unit.
 */
function mvf_entity_property_converted_get($data, array $options, $name, $type, $info) {
  $units = array(
    $data[mvf_subfield_to_column('unit')],
    $info['mvf target unit'],
  );
  $units = units_unit_load_multiple($units);
  $from = $units[$data[mvf_subfield_to_column('unit')]];
  $from = entity_id($from
    ->entityType(), $from);
  $to = $units[$info['mvf target unit']];
  $to = entity_id($to
    ->entityType(), $to);
  return units_convert($data[mvf_subfield_to_column('value')], $from, $to);
}

/**
 * Request a suggested unit on a particular items of MVF field.
 *
 * @param array $items
 *   Array of items for which to get a suggested unit
 * @param array $field
 *   Field definition array of the field for which to get a suggested unit. This
 *   field must be of MVF types, of course
 * @param array $instance
 *   Field instance definition array that corresponds to $field
 * @param object $entity
 *   Fully loaded entity for which to get a suggested unit
 * @param string $view_mode
 *   If you want to retrieve unit suggestion on a specific view mode of the
 *   provided MVF field, then provide it here. You may skip this argument and
 *   then suggested unit will be based on general MVF instance settings
 * @param array $skip_suggestions
 *   In case you want to explicitly ignore some suggestions, provide them here
 *   and the function will not consider them. You can supply here specific IDs
 *   of 'units_unit' entities. However, the most useful elements of this array
 *   would be the constants MVF_UNIT_*
 *
 * @return int
 *   Suggested unit for the provided input arguments. It will be either ID of
 *   'units_unit' entity or any constant of MVF_UNIT_*
 */
function mvf_unit_suggest($items, $field, $instance, $entity, $view_mode = NULL, $skip_suggestions = array()) {
  $skip_suggestions[] = MVF_UNIT_UNKNOWN;
  $unit_suggesters = mvf_unit_suggesters_info($field, $instance, $view_mode);
  $output_unit = MVF_UNIT_UNKNOWN;
  foreach ($unit_suggesters as $v) {
    if ($v['settings']['enable'] && ($function = ctools_plugin_get_function($v['plugin'], 'suggest unit callback'))) {
      $output_unit = $function($items, $field, $instance, $entity, $instance['entity_type'], $v['settings'], $v['plugin']);
      if (!in_array($output_unit, $skip_suggestions)) {
        break;
      }
    }
  }
  return $output_unit;
}

/**
 * Form API process function for 'mvf_unit_suggester' form element.
 */
function _mvf_unit_suggester_process($element) {
  $measure = mvf_measure_extract($element['#field']);
  foreach (mvf_unit_suggesters_info($element['#field'], $element['#instance'], $element['#view_mode']) as $info) {
    $unit_suggester_settings_name = mvf_unit_suggesters_settings_name($info['plugin']);

    // Letting unit suggester provide some configuration form elements of its
    // own.
    $function = ctools_plugin_get_function($info['plugin'], 'settings form callback');
    if ($function) {
      $element[$unit_suggester_settings_name] = $function($measure, $element['#field'], $element['#instance'], $info['settings'], $info['plugin']);
    }
    $element[$unit_suggester_settings_name]['weight'] = array(
      '#type' => 'weight',
      '#title' => t('Weight'),
      '#default_value' => $info['settings']['weight'],
    );
    $element[$unit_suggester_settings_name]['enable'] = array(
      '#type' => 'checkbox',
      '#title' => $info['plugin']['title'],
      '#default_value' => $info['settings']['enable'],
    );
  }
  return $element;
}

Functions

Namesort descending Description
mvf_admin_paths Implements hook_admin_paths().
mvf_ajax Menu page callback.
mvf_ctools_plugin_directory Implements hook_ctools_plugin_directory().
mvf_ctools_plugin_type Implements hook_ctools_plugin_type().
mvf_data_property_info Defines info for the properties of the MVF field data structure.
mvf_element_info Implements hook_element_info().
mvf_entityreference_field_instance_settings_pre_render Form element process function.
mvf_entityreference_field_instance_settings_validate Supportive validation function.
mvf_entityreference_field_settings_process FAPI element process function.
mvf_entityreference_field_settings_validate Supportive validation function.
mvf_entityreference_selection_target_bundles_validate Element validation function.
mvf_entity_property_converted_get Property getter callback for MVF converted values.
mvf_field_delete Implements hook_field_delete().
mvf_field_delete_instance Implements hook_field_delete_instance().
mvf_field_formatter_info Implements hook_field_formatter_info().
mvf_field_formatter_prepare_view Implements hook_field_formatter_prepare_view().
mvf_field_formatter_settings_form Implements hook_field_formatter_settings_form().
mvf_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
mvf_field_formatter_view Implements hook_field_formatter_view().
mvf_field_info Implements hook_field_info().
mvf_field_insert Implements hook_field_insert().
mvf_field_instance Supportive function.
mvf_field_instance_settings_form Implements hook_field_instance_settings_form().
mvf_field_is_empty Implements hook_field_is_empty().
mvf_field_load Implements hook_field_load().
mvf_field_mockup Supportive function.
mvf_field_presave Implements hook_field_presave().
mvf_field_settings_form Implements hook_field_settings_form().
mvf_field_types Return an array of field types that are provided by MVF.
mvf_field_update Implements hook_field_update().
mvf_field_update_instance Implements hook_field_update_instance().
mvf_field_validate Implements hook_field_validate().
mvf_field_widget_form Implements hook_field_widget_form().
mvf_field_widget_info Implements hook_field_widget_info().
mvf_field_widget_settings_form Implements hook_field_widget_settings_form().
mvf_field_widget_unit_options_validate Element validate function.
mvf_field_widget_validate_value_form_state_mockup_override FAPI element validate function.
mvf_field_widget_validate_value_form_state_mockup_revert FAPI element validate function.
mvf_get_unit_suggester Retrieve information about a cTools plugin of MVF unit suggester type.
mvf_instance_mockup Supportive function.
mvf_items_convert Supportive function, normally should be used in formatters of MVF field.
mvf_measure_extract Extract 'units_measure' entity from a MVF field.
mvf_menu Implements hook_menu().
mvf_property_info_alter Property info alter callback for MVF field structure.
mvf_property_info_callback Callback to alter the property info of mvf field.
mvf_subfield_to_column Supportive function.
mvf_theme Implements hook_theme().
mvf_unit_suggest Request a suggested unit on a particular items of MVF field.
mvf_unit_suggesters_info Collect information about available unit suggesters and their settings.
mvf_unit_suggesters_settings_name Derive name, under which settings of a unit suggester will be stored.
mvf_unit_suggester_info Collect information about a single unit suggester and its settings.
mvf_views_api Implements hook_views_api().
mvf_views_handler_options_list Options callback for Views handler views_handler_filter_in_operator.
theme_mvf_column_order Default theme implementation of hook 'mvf_column_order'.
theme_mvf_unit_suggesters_settings Default theme implementation of hook 'mvf_unit_suggesters_settings'.
_mvf_unit_suggesters_info_sort Supportive function for sorting unit suggesters by their weight.
_mvf_unit_suggester_process Form API process function for 'mvf_unit_suggester' form element.
_mvf_widget_process Process function for form element type 'mvf_widget'.

Constants

Namesort descending Description
MVF_MODULE_UNIT Module that defines field type used for sub field 'unit'.
MVF_MODULE_VALUE Module that defines field type used for sub field 'value'.
MVF_UNIT_ORIGINAL Constant denotes outputting the entered value in the originally entered unit measure.
MVF_UNIT_UNKNOWN Constant should be used in Unit Suggesters, when desired output unit cannot be determined.