mvf.module in Measured Value Field 7
Same filename and directory in other branches
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.moduleView 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
Constants
Name | 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. |