number.module

You are here

Defines numeric field types.

Functions

File

modules/field/modules/number/number.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Defines numeric field types.
  5. */
  6. /**
  7. * Implements hook_help().
  8. */
  9. function number_help($path, $arg) {
  10. switch ($path) {
  11. case 'admin/help#number':
  12. $output = '';
  13. $output .= '<h3>' . t('About') . '</h3>';
  14. $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>';
  15. return $output;
  16. }
  17. }
  18. /**
  19. * Implements hook_field_info().
  20. */
  21. function number_field_info() {
  22. return array(
  23. 'number_integer' => array(
  24. 'label' => t('Integer'),
  25. 'description' => t('This field stores a number in the database as an integer.'),
  26. 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
  27. 'default_widget' => 'number',
  28. 'default_formatter' => 'number_integer',
  29. ),
  30. 'number_decimal' => array(
  31. 'label' => t('Decimal'),
  32. 'description' => t('This field stores a number in the database in a fixed decimal format.'),
  33. 'settings' => array('precision' => 10, 'scale' => 2, 'decimal_separator' => '.'),
  34. 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
  35. 'default_widget' => 'number',
  36. 'default_formatter' => 'number_decimal',
  37. ),
  38. 'number_float' => array(
  39. 'label' => t('Float'),
  40. 'description' => t('This field stores a number in the database in a floating point format.'),
  41. 'settings' => array('decimal_separator' => '.'),
  42. 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
  43. 'default_widget' => 'number',
  44. 'default_formatter' => 'number_decimal',
  45. ),
  46. );
  47. }
  48. /**
  49. * Implements hook_field_settings_form().
  50. */
  51. function number_field_settings_form($field, $instance, $has_data) {
  52. $settings = $field['settings'];
  53. $form = array();
  54. if ($field['type'] == 'number_decimal') {
  55. $form['precision'] = array(
  56. '#type' => 'select',
  57. '#title' => t('Precision'),
  58. '#options' => drupal_map_assoc(range(10, 32)),
  59. '#default_value' => $settings['precision'],
  60. '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'),
  61. '#disabled' => $has_data,
  62. );
  63. $form['scale'] = array(
  64. '#type' => 'select',
  65. '#title' => t('Scale'),
  66. '#options' => drupal_map_assoc(range(0, 10)),
  67. '#default_value' => $settings['scale'],
  68. '#description' => t('The number of digits to the right of the decimal.'),
  69. '#disabled' => $has_data,
  70. );
  71. }
  72. if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') {
  73. $form['decimal_separator'] = array(
  74. '#type' => 'select',
  75. '#title' => t('Decimal marker'),
  76. '#options' => array('.' => t('Decimal point'), ',' => t('Comma')),
  77. '#default_value' => $settings['decimal_separator'],
  78. '#description' => t('The character users will input to mark the decimal point in forms.'),
  79. );
  80. }
  81. return $form;
  82. }
  83. /**
  84. * Implements hook_field_instance_settings_form().
  85. */
  86. function number_field_instance_settings_form($field, $instance) {
  87. $settings = $instance['settings'];
  88. $form['min'] = array(
  89. '#type' => 'textfield',
  90. '#title' => t('Minimum'),
  91. '#default_value' => $settings['min'],
  92. '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.'),
  93. '#element_validate' => array('element_validate_number'),
  94. );
  95. $form['max'] = array(
  96. '#type' => 'textfield',
  97. '#title' => t('Maximum'),
  98. '#default_value' => $settings['max'],
  99. '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.'),
  100. '#element_validate' => array('element_validate_number'),
  101. );
  102. $form['prefix'] = array(
  103. '#type' => 'textfield',
  104. '#title' => t('Prefix'),
  105. '#default_value' => $settings['prefix'],
  106. '#size' => 60,
  107. '#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')."),
  108. );
  109. $form['suffix'] = array(
  110. '#type' => 'textfield',
  111. '#title' => t('Suffix'),
  112. '#default_value' => $settings['suffix'],
  113. '#size' => 60,
  114. '#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')."),
  115. );
  116. return $form;
  117. }
  118. /**
  119. * Implements hook_field_validate().
  120. *
  121. * Possible error codes:
  122. * - 'number_min': The value is less than the allowed minimum value.
  123. * - 'number_max': The value is greater than the allowed maximum value.
  124. */
  125. function number_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  126. foreach ($items as $delta => $item) {
  127. if ($item['value'] != '') {
  128. if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
  129. $errors[$field['field_name']][$langcode][$delta][] = array(
  130. 'error' => 'number_min',
  131. 'message' => t('%name: the value may be no less than %min.', array('%name' => $instance['label'], '%min' => $instance['settings']['min'])),
  132. );
  133. }
  134. if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
  135. $errors[$field['field_name']][$langcode][$delta][] = array(
  136. 'error' => 'number_max',
  137. 'message' => t('%name: the value may be no greater than %max.', array('%name' => $instance['label'], '%max' => $instance['settings']['max'])),
  138. );
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Implements hook_field_presave().
  145. */
  146. function number_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  147. if ($field['type'] == 'number_decimal') {
  148. // Let PHP round the value to ensure consistent behavior across storage
  149. // backends.
  150. foreach ($items as $delta => $item) {
  151. if (isset($item['value'])) {
  152. $items[$delta]['value'] = round($item['value'], $field['settings']['scale']);
  153. }
  154. }
  155. }
  156. }
  157. /**
  158. * Implements hook_field_is_empty().
  159. */
  160. function number_field_is_empty($item, $field) {
  161. if (empty($item['value']) && (string) $item['value'] !== '0') {
  162. return TRUE;
  163. }
  164. return FALSE;
  165. }
  166. /**
  167. * Implements hook_field_formatter_info().
  168. */
  169. function number_field_formatter_info() {
  170. return array(
  171. // The 'Default' formatter is different for integer fields on the one hand,
  172. // and for decimal and float fields on the other hand, in order to be able
  173. // to use different default values for the settings.
  174. 'number_integer' => array(
  175. 'label' => t('Default'),
  176. 'field types' => array('number_integer'),
  177. 'settings' => array(
  178. 'thousand_separator' => ' ',
  179. // The 'decimal_separator' and 'scale' settings are not configurable
  180. // through the UI, and will therefore keep their default values. They
  181. // are only present so that the 'number_integer' and 'number_decimal'
  182. // formatters can use the same code.
  183. 'decimal_separator' => '.',
  184. 'scale' => 0,
  185. 'prefix_suffix' => TRUE,
  186. ),
  187. ),
  188. 'number_decimal' => array(
  189. 'label' => t('Default'),
  190. 'field types' => array('number_decimal', 'number_float'),
  191. 'settings' => array(
  192. 'thousand_separator' => ' ',
  193. 'decimal_separator' => '.',
  194. 'scale' => 2,
  195. 'prefix_suffix' => TRUE,
  196. ),
  197. ),
  198. 'number_unformatted' => array(
  199. 'label' => t('Unformatted'),
  200. 'field types' => array('number_integer', 'number_decimal', 'number_float'),
  201. ),
  202. );
  203. }
  204. /**
  205. * Implements hook_field_formatter_settings_form().
  206. */
  207. function number_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  208. $display = $instance['display'][$view_mode];
  209. $settings = $display['settings'];
  210. if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
  211. $options = array(
  212. '' => t('<none>'),
  213. '.' => t('Decimal point'),
  214. ',' => t('Comma'),
  215. ' ' => t('Space'),
  216. );
  217. $element['thousand_separator'] = array(
  218. '#type' => 'select',
  219. '#title' => t('Thousand marker'),
  220. '#options' => $options,
  221. '#default_value' => $settings['thousand_separator'],
  222. );
  223. if ($display['type'] == 'number_decimal') {
  224. $element['decimal_separator'] = array(
  225. '#type' => 'select',
  226. '#title' => t('Decimal marker'),
  227. '#options' => array('.' => t('Decimal point'), ',' => t('Comma')),
  228. '#default_value' => $settings['decimal_separator'],
  229. );
  230. $element['scale'] = array(
  231. '#type' => 'select',
  232. '#title' => t('Scale'),
  233. '#options' => drupal_map_assoc(range(0, 10)),
  234. '#default_value' => $settings['scale'],
  235. '#description' => t('The number of digits to the right of the decimal.'),
  236. );
  237. }
  238. $element['prefix_suffix'] = array(
  239. '#type' => 'checkbox',
  240. '#title' => t('Display prefix and suffix.'),
  241. '#default_value' => $settings['prefix_suffix'],
  242. );
  243. }
  244. return $element;
  245. }
  246. /**
  247. * Implements hook_field_formatter_settings_summary().
  248. */
  249. function number_field_formatter_settings_summary($field, $instance, $view_mode) {
  250. $display = $instance['display'][$view_mode];
  251. $settings = $display['settings'];
  252. $summary = array();
  253. if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
  254. $summary[] = number_format(1234.1234567890, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
  255. if ($settings['prefix_suffix']) {
  256. $summary[] = t('Display with prefix and suffix.');
  257. }
  258. }
  259. return implode('<br />', $summary);
  260. }
  261. /**
  262. * Implements hook_field_formatter_view().
  263. */
  264. function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  265. $element = array();
  266. $settings = $display['settings'];
  267. switch ($display['type']) {
  268. case 'number_integer':
  269. case 'number_decimal':
  270. foreach ($items as $delta => $item) {
  271. $output = number_format($item['value'], $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
  272. if ($settings['prefix_suffix']) {
  273. $prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array('');
  274. $suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array('');
  275. $prefix = (count($prefixes) > 1) ? format_plural($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0];
  276. $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0];
  277. $output = $prefix . $output . $suffix;
  278. }
  279. $element[$delta] = array('#markup' => $output);
  280. }
  281. break;
  282. case 'number_unformatted':
  283. foreach ($items as $delta => $item) {
  284. $element[$delta] = array('#markup' => $item['value']);
  285. }
  286. break;
  287. }
  288. return $element;
  289. }
  290. /**
  291. * Implements hook_field_widget_info().
  292. */
  293. function number_field_widget_info() {
  294. return array(
  295. 'number' => array(
  296. 'label' => t('Text field'),
  297. 'field types' => array('number_integer', 'number_decimal', 'number_float'),
  298. ),
  299. );
  300. }
  301. /**
  302. * Implements hook_field_widget_form().
  303. */
  304. function number_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  305. $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : '';
  306. // Substitute the decimal separator.
  307. if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') {
  308. $value = strtr($value, '.', $field['settings']['decimal_separator']);
  309. }
  310. $element += array(
  311. '#type' => 'textfield',
  312. '#default_value' => $value,
  313. // Allow a slightly larger size that the field length to allow for some
  314. // configurations where all characters won't fit in input field.
  315. '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 4 : 12,
  316. // Allow two extra characters for signed values and decimal separator.
  317. '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 10,
  318. // Extract the number type from the field type name for easier validation.
  319. '#number_type' => str_replace('number_', '', $field['type']),
  320. );
  321. // Add prefix and suffix.
  322. if (!empty($instance['settings']['prefix'])) {
  323. $prefixes = explode('|', $instance['settings']['prefix']);
  324. $element['#field_prefix'] = field_filter_xss(array_pop($prefixes));
  325. }
  326. if (!empty($instance['settings']['suffix'])) {
  327. $suffixes = explode('|', $instance['settings']['suffix']);
  328. $element['#field_suffix'] = field_filter_xss(array_pop($suffixes));
  329. }
  330. $element['#element_validate'][] = 'number_field_widget_validate';
  331. return array('value' => $element);
  332. }
  333. /**
  334. * FAPI validation of an individual number element.
  335. */
  336. function number_field_widget_validate($element, &$form_state) {
  337. $field = field_widget_field($element, $form_state);
  338. $instance = field_widget_instance($element, $form_state);
  339. $type = $element['#number_type'];
  340. $value = $element['#value'];
  341. // Reject invalid characters.
  342. if (!empty($value)) {
  343. switch ($type) {
  344. case 'float':
  345. case 'decimal':
  346. $regexp = '@([^-0-9\\' . $field['settings']['decimal_separator'] . '])|(.-)@';
  347. $message = t('Only numbers and the decimal separator (@separator) allowed in %field.', array('%field' => $instance['label'], '@separator' => $field['settings']['decimal_separator']));
  348. break;
  349. case 'integer':
  350. $regexp = '@([^-0-9])|(.-)@';
  351. $message = t('Only numbers are allowed in %field.', array('%field' => $instance['label']));
  352. break;
  353. }
  354. if ($value != preg_replace($regexp, '', $value)) {
  355. form_error($element, $message);
  356. }
  357. else {
  358. if ($type == 'decimal' || $type == 'float') {
  359. // Verify that only one decimal separator exists in the field.
  360. if (substr_count($value, $field['settings']['decimal_separator']) > 1) {
  361. $message = t('%field: There should only be one decimal separator (@separator).',
  362. array(
  363. '%field' => t($instance['label']),
  364. '@separator' => $field['settings']['decimal_separator'],
  365. )
  366. );
  367. form_error($element, $message);
  368. }
  369. else {
  370. // Substitute the decimal separator; things should be fine.
  371. $value = strtr($value, $field['settings']['decimal_separator'], '.');
  372. }
  373. }
  374. form_set_value($element, $value, $form_state);
  375. }
  376. }
  377. }
  378. /**
  379. * Implements hook_field_widget_error().
  380. */
  381. function number_field_widget_error($element, $error, $form, &$form_state) {
  382. form_error($element['value'], $error['message']);
  383. }