fraction.field.inc in Fraction 7
Fraction Field API functions
File
fraction.field.incView source
<?php
/**
* @file
* Fraction Field API functions
*/
/**
* Implements of hook_field_info().
*/
function fraction_field_info() {
return array(
'fraction' => array(
'label' => t('Fraction'),
'description' => t('This field stores a decimal in fraction form (with a numerator and denominator) for maximum precision.'),
'settings' => array(),
'instance_settings' => array(
'min' => '',
'max' => '',
'prefix' => '',
'suffix' => '',
),
'default_widget' => 'fraction_default',
'default_formatter' => 'fraction_default',
// Entity API integration.
'property_type' => 'field_item_fraction',
'property_callbacks' => array(
'fraction_property_info_callback',
),
),
);
}
/**
* Callback to alter the property info of fraction fields.
*
* @see fraction_field_info().
*/
function fraction_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
$name = $field['field_name'];
$property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
$property['type'] = $field['cardinality'] != 1 ? 'list<field_item_fraction>' : 'field_item_fraction';
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
$property['auto creation'] = 'fraction_default_values';
$property['property info'] = fraction_data_property_info();
unset($property['query callback']);
}
function fraction_default_values() {
return array(
'numerator' => 0,
'denominator' => 100,
);
}
/**
* Defines info for the properties of the fraction field data structure.
*/
function fraction_data_property_info($name = NULL) {
// Build an array of basic property information for the fraction field.
$properties = array(
'numerator' => array(
'label' => t('Numerator'),
),
'denominator' => array(
'label' => t('Denominator'),
),
);
foreach ($properties as &$property) {
$property += array(
'type' => 'integer',
'description' => !empty($name) ? t('!label of field %name', array(
'!label' => $property['label'],
'%name' => $name,
)) : '',
'getter callback' => 'entity_property_verbatim_get',
'setter callback' => 'entity_property_verbatim_set',
);
}
return $properties;
}
/**
* Implements hook_field_instance_settings_form().
*/
function fraction_field_instance_settings_form($field, $instance) {
$settings = $instance['settings'];
$form['min'] = array(
'#type' => 'textfield',
'#title' => t('Minimum'),
'#default_value' => $settings['min'],
'#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.'),
'#element_validate' => array(
'element_validate_number',
),
);
$form['max'] = array(
'#type' => 'textfield',
'#title' => t('Maximum'),
'#default_value' => $settings['max'],
'#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.'),
'#element_validate' => array(
'element_validate_number',
),
);
$form['prefix'] = array(
'#type' => 'textfield',
'#title' => t('Prefix'),
'#default_value' => $settings['prefix'],
'#size' => 60,
'#description' => t("Define a string that should be prefixed to the value, like '\$ ' or '€ '. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."),
);
$form['suffix'] = array(
'#type' => 'textfield',
'#title' => t('Suffix'),
'#default_value' => $settings['suffix'],
'#size' => 60,
'#description' => t("Define a string that should be suffixed to the value, like ' m', ' kb/s'. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."),
);
return $form;
}
/**
* Implements hook_field_validate().
*
* Possible error codes:
* - 'fraction_min': The value is less than the allowed minimum value.
* - 'fraction_max': The value is greater than the allowed maximum value.
*/
function fraction_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
foreach ($items as $delta => $item) {
$value = '';
if (!empty($item['decimal']) && is_numeric($item['decimal'])) {
$value = $item['decimal'];
}
if (!empty($item['numerator']) && is_numeric($item['numerator']) && !empty($item['denominator']) && is_numeric($item['denominator'])) {
$fraction = fraction($item['numerator'], $item['denominator']);
$value = $fraction
->toDecimal(0, TRUE);
}
if ($value != '') {
if (is_numeric($instance['settings']['min']) && $value < $instance['settings']['min']) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'fraction_min',
'message' => t('%name: the value may be no less than %min.', array(
'%name' => $instance['label'],
'%min' => $instance['settings']['min'],
)),
);
}
if (is_numeric($instance['settings']['max']) && $value > $instance['settings']['max']) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'fraction_max',
'message' => t('%name: the value may be no greater than %max.', array(
'%name' => $instance['label'],
'%max' => $instance['settings']['max'],
)),
);
}
}
}
}
/**
* Implements hook_field_is_empty().
*/
function fraction_field_is_empty($item, $field) {
// This field is empty if:
// a) numerator is not set, or
// b) denominator is not set, or
// c) denominator is empty (eg: zero), or
// d) numerator is an empty string
return !isset($item['numerator']) || !isset($item['denominator']) || empty($item['denominator']) || (string) $item['numerator'] === '';
}
/**
* Implements of hook_field_info().
*/
function fraction_field_widget_info() {
return array(
'fraction_default' => array(
'label' => t('Fraction'),
'field types' => array(
'fraction',
),
'settings' => array(),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
'fraction_decimal' => array(
'label' => t('Decimal'),
'field types' => array(
'fraction',
),
'settings' => array(
'precision' => FRACTION_PRECISION_DEFAULT,
'auto_precision' => TRUE,
),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
);
}
/**
* Implements hook_field_widget_settings_form().
*/
function fraction_field_widget_settings_form($field, $instance) {
$form = array();
$widget = $instance['widget'];
$defaults = field_info_widget_settings($widget['type']);
$settings = array_merge($defaults, $widget['settings']);
if ($widget['type'] == 'fraction_decimal') {
$form['precision'] = array(
'#type' => 'textfield',
'#title' => t('Precision'),
'#description' => t('Specify the number of digits after the decimal place to display when converting the fraction to a decimal. When "Auto precision" is enabled, this value essentially becomes a minimum fallback precision.'),
'#default_value' => $settings['precision'],
);
$form['auto_precision'] = array(
'#type' => 'checkbox',
'#title' => t('Auto precision'),
'#description' => t('Automatically determine the maximum precision if the fraction has a base-10 denominator. For example, 1/100 would have a precision of 2, 1/1000 would have a precision of 3, etc.'),
'#default_value' => $settings['auto_precision'],
);
}
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function fraction_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$numerator = isset($items[$delta]['numerator']) ? $items[$delta]['numerator'] : '';
$denominator = isset($items[$delta]['denominator']) ? $items[$delta]['denominator'] : '';
$settings = $instance['widget']['settings'];
switch ($instance['widget']['type']) {
// Default fraction widget: two textfields (numerator and denominator).
case 'fraction_default':
$element['#type'] = 'fieldset';
$element['numerator'] = array(
'#type' => 'textfield',
'#title' => t('Numerator'),
'#default_value' => $numerator,
'#element_validate' => array(
'element_validate_integer',
),
'#size' => 15,
);
$element['denominator'] = array(
'#type' => 'textfield',
'#title' => t('Denominator'),
'#default_value' => $denominator,
'#element_validate' => array(
'element_validate_integer',
),
'#size' => 15,
);
break;
// Decimal widget: a single textfield that accepts a decimal number.
// The default value is converted to a decimal with the specified precision.
case 'fraction_decimal':
$default_value = '';
if (!empty($items[$delta]) && !fraction_field_is_empty($items[$delta], $field)) {
$auto_precision = !empty($settings['auto_precision']) ? TRUE : FALSE;
$default_value = fraction($numerator, $denominator)
->toDecimal($settings['precision'], $auto_precision);
}
$element['#element_validate'] = array(
'_fraction_decimal_widget_validate',
);
$element['decimal'] = array(
'#type' => 'textfield',
'#title' => $element['#title'],
'#description' => $element['#description'],
'#default_value' => $default_value,
'#element_validate' => array(
'element_validate_number',
),
'#size' => 15,
);
// Add prefix and suffix.
if (!empty($instance['settings']['prefix'])) {
$prefixes = explode('|', $instance['settings']['prefix']);
$element['#field_prefix'] = field_filter_xss(array_pop($prefixes));
}
if (!empty($instance['settings']['suffix'])) {
$suffixes = explode('|', $instance['settings']['suffix']);
$element['#field_suffix'] = field_filter_xss(array_pop($suffixes));
}
break;
}
return $element;
}
/**
* Validation function for the fraction_decimal widget to convert the decimal to a fraction.
*/
function _fraction_decimal_widget_validate($element, &$form_state) {
// Search through the form values to find the current field value.
$parents = $element['#parents'];
$values = drupal_array_get_nested_value($form_state['values'], $parents);
// If the decimal is not an empty string, convert the value to a fraction.
if (trim($values['decimal']) !== "") {
$fraction = fraction_from_decimal($values['decimal']);
$values['numerator'] = $fraction
->getNumerator();
$values['denominator'] = $fraction
->getDenominator();
}
// Set the numerator and denominator values for the form.
form_set_value($element, $values, $form_state);
}
/**
* Implements hook_field_widget_error().
*/
function fraction_field_widget_error($element, $error, $form, &$form_state) {
switch ($error['error']) {
case 'fraction':
form_error($element, $error['message']);
break;
}
}
/**
* Implements of hook_field_info().
*/
function fraction_field_formatter_info() {
return array(
'fraction_default' => array(
'label' => t('Fraction'),
'field types' => array(
'fraction',
),
'settings' => array(
'separator' => '/',
'prefix_suffix' => TRUE,
),
),
'fraction_decimal' => array(
'label' => t('Decimal'),
'field types' => array(
'fraction',
),
'settings' => array(
'precision' => FRACTION_PRECISION_DEFAULT,
'auto_precision' => TRUE,
'prefix_suffix' => TRUE,
),
),
'fraction_percentage' => array(
'label' => t('Percentage'),
'field types' => array(
'fraction',
),
'settings' => array(
'precision' => FRACTION_PRECISION_DEFAULT,
'auto_precision' => TRUE,
'prefix_suffix' => TRUE,
),
),
);
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function fraction_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = array();
if ($display['type'] == 'fraction_default') {
$summary[] = t('Separator: @separator', array(
'@separator' => $settings['separator'],
));
}
elseif (in_array($display['type'], array(
'fraction_decimal',
'fraction_percentage',
))) {
$auto_precision = !empty($settings['auto_precision']) ? 'On' : 'Off';
$summary[] = t('Precision: @precision', array(
'@precision' => $settings['precision'],
));
$summary[] = t('Auto-precision: @auto_precision', array(
'@auto_precision' => $auto_precision,
));
}
if ($settings['prefix_suffix']) {
$summary[] = t('Display with prefix and suffix.');
}
return implode('<br />', $summary);
}
/**
* Implements hook_field_formatter_settings_form().
*/
function fraction_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'fraction_default') {
// Numerator and denominator separator.
$element['separator'] = array(
'#type' => 'textfield',
'#title' => t('Separator'),
'#description' => t('Specify the separator to display between the numerator and denominator.'),
'#default_value' => $settings['separator'],
'#required' => TRUE,
);
}
elseif (in_array($display['type'], array(
'fraction_decimal',
'fraction_percentage',
))) {
// Decimal precision.
$element['precision'] = array(
'#type' => 'textfield',
'#title' => t('Decimal precision'),
'#description' => t('Specify the number of digits after the decimal place to display. When "Auto precision" is enabled, this value essentially becomes a minimum fallback precision.'),
'#default_value' => $settings['precision'],
'#required' => TRUE,
'#element_validate' => array(
'element_validate_integer',
),
);
// Auto precision.
$element['auto_precision'] = array(
'#type' => 'checkbox',
'#title' => t('Auto precision'),
'#description' => t('Automatically determine the maximum precision if the fraction has a base-10 denominator. For example, 1/100 would have a precision of 2, 1/1000 would have a precision of 3, etc.'),
'#default_value' => $settings['auto_precision'],
);
}
$element['prefix_suffix'] = array(
'#type' => 'checkbox',
'#title' => t('Display prefix and suffix.'),
'#default_value' => $settings['prefix_suffix'],
);
return $element;
}
/**
* Implements hook_field_formatter_view().
*/
function fraction_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$settings = $display['settings'];
$element = array();
// Iterate through the items.
foreach ($items as $delta => $item) {
// Start with empty output.
$output = '';
// Merge the item with default values, in case it's empty.
$defaults = array(
'numerator' => 0,
'denominator' => 1,
);
$item = array_merge($defaults, $item);
// Build output based on display type.
switch ($display['type']) {
// This formatter simply outputs the field as a fraction ([numerator]/[denominator]).
case 'fraction_default':
$output = fraction($item['numerator'], $item['denominator'])
->toString(field_filter_xss($settings['separator']));
break;
// This formatter outputs the field as a decimal value with a fixed or automatic precision.
case 'fraction_decimal':
$auto_precision = !empty($settings['auto_precision']) ? TRUE : FALSE;
$output = fraction($item['numerator'], $item['denominator'])
->toDecimal($settings['precision'], $auto_precision);
break;
case 'fraction_percentage':
$output = fraction($item['numerator'], $item['denominator'])
->multiply(fraction_from_decimal('100'))
->toDecimal($settings['precision'], !empty($settings['auto_precision'])) . '%';
break;
}
// Add prefix and suffix, if desired.
if ($settings['prefix_suffix']) {
$prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array(
'',
);
$suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array(
'',
);
$prefix = count($prefixes) > 1 ? format_plural($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0];
$suffix = count($suffixes) > 1 ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0];
$output = $prefix . $output . $suffix;
}
// Create the markup element.
$element[$delta] = array(
'#type' => 'markup',
'#markup' => $output,
);
}
return $element;
}
/**
* Implements hook_field_load().
*/
function fraction_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
// Iterate through entities and their items.
foreach ($entities as $id => $entity) {
foreach ($items[$id] as $delta => $item) {
// Load the numerator and denominator values.
$numerator = $items[$id][$delta]['numerator'];
$denominator = $items[$id][$delta]['denominator'];
// Create a new Fraction object and add it to the field.
$fraction = fraction($numerator, $denominator);
$items[$id][$delta]['fraction'] = fraction($numerator, $denominator);
// Calculate the decimal value of the fraction.
$items[$id][$delta]['decimal'] = $fraction
->toDecimal(0, TRUE);
}
}
}
/**
* Implements hook_field_presave().
*/
function fraction_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
// Convert decimal values to numerator and denominator.
foreach ($items as &$item) {
if (!empty($item['decimal'])) {
// Only proceed if this is a base-10 number. This prevents accidentally
// overwriting non-base-10 numerator/denominator fractions.
// A base-10 number will have a denominator of 1 or no remainder when
// divided by 10.
// See https://www.drupal.org/project/fraction/issues/3109864
if (!empty($item['denominator']) && !($item['denominator'] == 1 || $item['denominator'] % 10 == 0)) {
continue;
}
// Recalculate the numerator and denominator from the decimal value.
$fraction = fraction_from_decimal($item['decimal']);
$item['numerator'] = $fraction
->getNumerator();
$item['denominator'] = $fraction
->getDenominator();
}
}
}