number.module 12.7 KB
Newer Older
1 2 3 4 5 6 7 8
<?php
// $Id$

/**
 * @file
 * Defines numeric field types.
 */

9 10 11 12 13 14 15 16 17 18 19 20 21
/**
 * Implements hook_help().
 */
function number_help($path, $arg) {
  switch ($path) {
    case 'admin/help#number':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Number module defines various numeric field types for the Field module. Numbers can be in integer, decimal, or floating-point form, and they can be formatted when displayed. Number fields can be limited to a specific set of input values or to a range of values. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>';
      return $output;
  }
}

22
/**
23
 * Implements hook_field_info().
24 25 26 27 28 29 30 31
 */
function number_field_info() {
  return array(
    'number_integer' => array(
      'label' => t('Integer'),
      'description' => t('This field stores a number in the database as an integer.'),
      'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
      'default_widget' => 'number',
32
      'default_formatter' => 'number_integer',
33 34 35 36
    ),
    'number_decimal' => array(
      'label' => t('Decimal'),
      'description' => t('This field stores a number in the database in a fixed decimal format.'),
37
      'settings' => array('precision' => 10, 'scale' => 2, 'decimal_separator' => '.'),
38 39
      'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
      'default_widget' => 'number',
40
      'default_formatter' => 'number_decimal',
41 42 43 44
    ),
    'number_float' => array(
      'label' => t('Float'),
      'description' => t('This field stores a number in the database in a floating point format.'),
45
      'settings' => array('decimal_separator' => '.'),
46 47
      'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
      'default_widget' => 'number',
48
      'default_formatter' => 'number_decimal',
49 50 51 52
    ),
  );
}

53
/**
54
 * Implements hook_field_schema().
55 56
 */
function number_field_schema($field) {
57 58
  switch ($field['type']) {
    case 'number_integer' :
59
      $columns = array(
60 61 62 63 64 65 66 67
        'value' => array(
          'type' => 'int',
          'not null' => FALSE
        ),
      );
      break;

    case 'number_float' :
68
      $columns = array(
69 70 71 72 73 74 75 76
        'value' => array(
          'type' => 'float',
          'not null' => FALSE
        ),
      );
      break;

    case 'number_decimal' :
77
      $columns = array(
78 79 80 81 82 83 84 85 86
        'value' => array(
          'type' => 'numeric',
          'precision' => $field['settings']['precision'],
          'scale' => $field['settings']['scale'],
          'not null' => FALSE
        ),
      );
      break;
  }
87 88 89
  return array(
    'columns' => $columns,
  );
90 91
}

92
/**
93
 * Implements hook_field_settings_form().
94
 */
95
function number_field_settings_form($field, $instance, $has_data) {
96 97 98 99 100 101 102 103 104 105
  $settings = $field['settings'];
  $form = array();

  if ($field['type'] == 'number_decimal') {
    $form['precision'] = array(
      '#type' => 'select',
      '#title' => t('Precision'),
      '#options' => drupal_map_assoc(range(10, 32)),
      '#default_value' => $settings['precision'],
      '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'),
106
      '#disabled' => $has_data,
107 108 109 110 111 112 113
    );
    $form['scale'] = array(
      '#type' => 'select',
      '#title' => t('Scale'),
      '#options' => drupal_map_assoc(range(0, 10)),
      '#default_value' => $settings['scale'],
      '#description' => t('The number of digits to the right of the decimal.'),
114
      '#disabled' => $has_data,
115
    );
116 117 118
  }
  if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') {
    $form['decimal_separator'] = array(
119 120 121 122 123 124 125
      '#type' => 'select',
      '#title' => t('Decimal marker'),
      '#options' => array(
        '.' => 'decimal point',
        ',' => 'comma',
        ' ' => 'space',
      ),
126
      '#default_value' => $settings['decimal_separator'],
127 128 129 130 131 132 133 134
      '#description' => t('The character users will input to mark the decimal point in forms.'),
    );
  }

  return $form;
}

/**
135
 * Implements hook_field_instance_settings_form().
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
 */
function number_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 '&euro; '. 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 suffixed to the value, like ' m', ' kb/s'. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."),
  );

  return $form;
}

172
/**
173
 * Implements hook_field_validate().
174 175 176 177
 *
 * Possible error codes:
 * - 'number_min': The value is smaller than the allowed minimum value.
 * - 'number_max': The value is larger than the allowed maximum value.
178
 */
179
function number_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
180 181 182
  foreach ($items as $delta => $item) {
    if ($item['value'] != '') {
      if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
183
        $errors[$field['field_name']][$langcode][$delta][] = array(
184 185 186 187 188
          'error' => 'number_min',
          'message' => t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])),
        );
      }
      if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
189
        $errors[$field['field_name']][$langcode][$delta][] = array(
190 191 192
          'error' => 'number_max',
          'message' => t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])),
        );
193 194 195 196 197
      }
    }
  }
}

198 199 200
/**
 * Implements hook_field_presave().
 */
201
function number_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
202 203 204 205 206 207 208 209 210 211 212
  if ($field['type'] == 'number_decimal') {
    // Let PHP round the value to ensure consistent behavior across storage
    // backends.
    foreach ($items as $delta => $item) {
      if (isset($item['value'])) {
        $items[$delta]['value'] = round($item['value'], $field['settings']['scale']);
      }
    }
  }
}

213
/**
214
 * Implements hook_field_is_empty().
215 216
 */
function number_field_is_empty($item, $field) {
217
  if (empty($item['value']) && (string) $item['value'] !== '0') {
218 219 220 221 222 223
    return TRUE;
  }
  return FALSE;
}

/**
224
 * Implements hook_field_formatter_info().
225 226 227
 */
function number_field_formatter_info() {
  return array(
228
    'number_integer' => array(
229
      'label' => t('Default'),
230 231 232 233 234 235 236 237 238
      'field types' => array('number_integer'),
      'settings' =>  array(
        'thousand_separator' => ' ',
        'decimal_separator' => '.',
        'scale' => 0,
        'prefix_suffix' => TRUE,
      ),
    ),
    'number_decimal' => array(
239
      'label' => t('Default'),
240 241 242 243 244 245 246 247 248
      'field types' => array('number_decimal', 'number_float'),
      'settings' =>  array(
        'thousand_separator' => ' ',
        'decimal_separator' => '.',
        'scale' => 2,
        'prefix_suffix' => TRUE,
      ),
    ),
    'number_unformatted' => array(
249
      'label' => t('Unformatted'),
250 251 252 253 254 255
      'field types' => array('number_integer', 'number_decimal', 'number_float'),
    ),
  );
}

/**
256
 * Implements hook_field_formatter_view().
257
 */
258
function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
259 260
  $element = array();
  $settings = $display['settings'];
261

262 263 264
  switch ($display['type']) {
    case 'number_integer':
    case 'number_decimal':
265 266 267 268 269 270 271 272 273 274
      foreach ($items as $delta => $item) {
        $output = number_format($item['value'], $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
        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;
        }
        $element[$delta] = array('#markup' => $output);
275 276
      }
      break;
277

278
    case 'number_unformatted':
279 280 281
      foreach ($items as $delta => $item) {
        $element[$delta] = array('#markup' => $item['value']);
      }
282
      break;
283 284
  }

285
  return $element;
286 287 288
}

/**
289
 * Implements hook_field_widget_info().
290 291 292 293 294 295 296 297 298 299 300
 */
function number_field_widget_info() {
  return array(
    'number' => array(
      'label' => t('Text field'),
      'field types' => array('number_integer', 'number_decimal', 'number_float'),
    ),
  );
}

/**
301
 * Implements hook_field_widget_form().
302
 */
303
function number_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
304 305 306 307
  $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : '';
  // Substitute the decimal separator.
  if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') {
    $value = strtr($value, '.', $field['settings']['decimal_separator']);
308 309
  }

310
  $element += array(
311 312
    '#type' => 'textfield',
    '#default_value' => $value,
313 314
    // Allow a slightly larger size that the field length to allow for some
    // configurations where all characters won't fit in input field.
315 316
    '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 12,
    '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] : 10,
317 318
    // Extract the number type from the field type name for easier validation.
    '#number_type' => str_replace('number_', '', $field['type']),
319 320
  );

321
  // Add prefix and suffix.
322 323
  if (!empty($instance['settings']['prefix'])) {
    $prefixes = explode('|', $instance['settings']['prefix']);
324
    $element['#field_prefix'] = field_filter_xss(array_pop($prefixes));
325 326 327
  }
  if (!empty($instance['settings']['suffix'])) {
    $suffixes = explode('|', $instance['settings']['suffix']);
328
    $element['#field_suffix'] = field_filter_xss(array_pop($suffixes));
329 330
  }

331
  $element['#element_validate'][] = 'number_field_widget_validate';
332

333
  return array('value' => $element);
334 335 336
}

/**
337
 * FAPI validation of an individual number element.
338
 */
339
function number_field_widget_validate($element, &$form_state) {
340 341
  $field = $form_state['field'][$element['#field_name']][$element['#language']]['field'];
  $instance = $form_state['field'][$element['#field_name']][$element['#language']]['instance'];
342 343 344 345 346 347 348 349 350 351 352 353 354

  $type = $element['#number_type'];
  $value = $element['#value'];

  // Reject invalid characters.
  if (!empty($value)) {
    switch ($type) {
      case 'float':
      case 'decimal':
        $regexp = '@[^-0-9\\' . $field['settings']['decimal_separator'] . ']@';
        $message = t('Only numbers and the decimal separator (@separator) allowed in %field.', array('%field' => t($instance['label']), '@separator' => $field['settings']['decimal_separator']));
        break;

355
      case 'integer':
356 357 358
        $regexp = '@[^-0-9]@';
        $message = t('Only numbers are allowed in %field.', array('%field' => t($instance['label'])));
        break;
359
    }
360 361
    if ($value != preg_replace($regexp, '', $value)) {
      form_error($element, $message);
362 363
    }
    else {
364 365 366 367 368
      // Substitute the decimal separator,
      if ($type == 'decimal' || $type == 'float') {
        $value = strtr($value, $field['settings']['decimal_separator'], '.');
      }
      form_set_value($element, $value, $form_state);
369 370 371 372 373
    }
  }
}

/**
374
 * Implements hook_field_widget_error().
375
 */
376
function number_field_widget_error($element, $error, $form, &$form_state) {
377
  form_error($element['value'], $error['message']);
378
}