number.module 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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. if ($field['type'] == 'number_float') {
  157. // Remove the decimal point from float values with decimal
  158. // point but no decimal numbers.
  159. foreach ($items as $delta => $item) {
  160. if (isset($item['value'])) {
  161. $items[$delta]['value'] = floatval($item['value']);
  162. }
  163. }
  164. }
  165. }
  166. /**
  167. * Implements hook_field_is_empty().
  168. */
  169. function number_field_is_empty($item, $field) {
  170. if (empty($item['value']) && (string) $item['value'] !== '0') {
  171. return TRUE;
  172. }
  173. return FALSE;
  174. }
  175. /**
  176. * Implements hook_field_formatter_info().
  177. */
  178. function number_field_formatter_info() {
  179. return array(
  180. // The 'Default' formatter is different for integer fields on the one hand,
  181. // and for decimal and float fields on the other hand, in order to be able
  182. // to use different default values for the settings.
  183. 'number_integer' => array(
  184. 'label' => t('Default'),
  185. 'field types' => array('number_integer'),
  186. 'settings' => array(
  187. 'thousand_separator' => '',
  188. // The 'decimal_separator' and 'scale' settings are not configurable
  189. // through the UI, and will therefore keep their default values. They
  190. // are only present so that the 'number_integer' and 'number_decimal'
  191. // formatters can use the same code.
  192. 'decimal_separator' => '.',
  193. 'scale' => 0,
  194. 'prefix_suffix' => TRUE,
  195. ),
  196. ),
  197. 'number_decimal' => array(
  198. 'label' => t('Default'),
  199. 'field types' => array('number_decimal', 'number_float'),
  200. 'settings' => array(
  201. 'thousand_separator' => '',
  202. 'decimal_separator' => '.',
  203. 'scale' => 2,
  204. 'prefix_suffix' => TRUE,
  205. ),
  206. ),
  207. 'number_unformatted' => array(
  208. 'label' => t('Unformatted'),
  209. 'field types' => array('number_integer', 'number_decimal', 'number_float'),
  210. ),
  211. );
  212. }
  213. /**
  214. * Implements hook_field_formatter_settings_form().
  215. */
  216. function number_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  217. $display = $instance['display'][$view_mode];
  218. $settings = $display['settings'];
  219. $element = array();
  220. if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
  221. $options = array(
  222. '' => t('<none>'),
  223. '.' => t('Decimal point'),
  224. ',' => t('Comma'),
  225. ' ' => t('Space'),
  226. );
  227. $element['thousand_separator'] = array(
  228. '#type' => 'select',
  229. '#title' => t('Thousand marker'),
  230. '#options' => $options,
  231. '#default_value' => $settings['thousand_separator'],
  232. );
  233. if ($display['type'] == 'number_decimal') {
  234. $element['decimal_separator'] = array(
  235. '#type' => 'select',
  236. '#title' => t('Decimal marker'),
  237. '#options' => array('.' => t('Decimal point'), ',' => t('Comma')),
  238. '#default_value' => $settings['decimal_separator'],
  239. );
  240. $element['scale'] = array(
  241. '#type' => 'select',
  242. '#title' => t('Scale'),
  243. '#options' => drupal_map_assoc(range(0, 10)),
  244. '#default_value' => $settings['scale'],
  245. '#description' => t('The number of digits to the right of the decimal.'),
  246. );
  247. }
  248. $element['prefix_suffix'] = array(
  249. '#type' => 'checkbox',
  250. '#title' => t('Display prefix and suffix.'),
  251. '#default_value' => $settings['prefix_suffix'],
  252. );
  253. }
  254. return $element;
  255. }
  256. /**
  257. * Implements hook_field_formatter_settings_summary().
  258. */
  259. function number_field_formatter_settings_summary($field, $instance, $view_mode) {
  260. $display = $instance['display'][$view_mode];
  261. $settings = $display['settings'];
  262. $summary = array();
  263. if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
  264. $summary[] = number_format(1234.1234567890, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
  265. if ($settings['prefix_suffix']) {
  266. $summary[] = t('Display with prefix and suffix.');
  267. }
  268. }
  269. return implode('<br />', $summary);
  270. }
  271. /**
  272. * Implements hook_field_formatter_view().
  273. */
  274. function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  275. $element = array();
  276. $settings = $display['settings'];
  277. switch ($display['type']) {
  278. case 'number_integer':
  279. case 'number_decimal':
  280. foreach ($items as $delta => $item) {
  281. $output = number_format($item['value'], $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
  282. if ($settings['prefix_suffix']) {
  283. $prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array('');
  284. $suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array('');
  285. $prefix = (count($prefixes) > 1) ? format_plural($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0];
  286. $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0];
  287. $output = $prefix . $output . $suffix;
  288. }
  289. $element[$delta] = array('#markup' => $output);
  290. }
  291. break;
  292. case 'number_unformatted':
  293. foreach ($items as $delta => $item) {
  294. $element[$delta] = array('#markup' => $item['value']);
  295. }
  296. break;
  297. }
  298. return $element;
  299. }
  300. /**
  301. * Implements hook_field_widget_info().
  302. */
  303. function number_field_widget_info() {
  304. return array(
  305. 'number' => array(
  306. 'label' => t('Text field'),
  307. 'field types' => array('number_integer', 'number_decimal', 'number_float'),
  308. ),
  309. );
  310. }
  311. /**
  312. * Implements hook_field_widget_form().
  313. */
  314. function number_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  315. $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : '';
  316. // Substitute the decimal separator.
  317. if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') {
  318. $value = strtr($value, '.', $field['settings']['decimal_separator']);
  319. }
  320. $element += array(
  321. '#type' => 'textfield',
  322. '#default_value' => $value,
  323. // Allow a slightly larger size that the field length to allow for some
  324. // configurations where all characters won't fit in input field.
  325. '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 4 : 12,
  326. // Allow two extra characters for signed values and decimal separator.
  327. '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 10,
  328. // Extract the number type from the field type name for easier validation.
  329. '#number_type' => str_replace('number_', '', $field['type']),
  330. );
  331. // Add prefix and suffix.
  332. if (!empty($instance['settings']['prefix'])) {
  333. $prefixes = explode('|', $instance['settings']['prefix']);
  334. $element['#field_prefix'] = field_filter_xss(array_pop($prefixes));
  335. }
  336. if (!empty($instance['settings']['suffix'])) {
  337. $suffixes = explode('|', $instance['settings']['suffix']);
  338. $element['#field_suffix'] = field_filter_xss(array_pop($suffixes));
  339. }
  340. $element['#element_validate'][] = 'number_field_widget_validate';
  341. return array('value' => $element);
  342. }
  343. /**
  344. * FAPI validation of an individual number element.
  345. */
  346. function number_field_widget_validate($element, &$form_state) {
  347. $field = field_widget_field($element, $form_state);
  348. $instance = field_widget_instance($element, $form_state);
  349. $type = $element['#number_type'];
  350. $value = $element['#value'];
  351. // Reject invalid characters.
  352. if (!empty($value)) {
  353. switch ($type) {
  354. case 'float':
  355. case 'decimal':
  356. $regexp = '@([^-0-9\\' . $field['settings']['decimal_separator'] . '])|(.-)@';
  357. $message = t('Only numbers and the decimal separator (@separator) allowed in %field.', array('%field' => $instance['label'], '@separator' => $field['settings']['decimal_separator']));
  358. break;
  359. case 'integer':
  360. $regexp = '@([^-0-9])|(.-)@';
  361. $message = t('Only numbers are allowed in %field.', array('%field' => $instance['label']));
  362. break;
  363. }
  364. if ($value != preg_replace($regexp, '', $value)) {
  365. form_error($element, $message);
  366. }
  367. else {
  368. if ($type == 'decimal' || $type == 'float') {
  369. // Verify that only one decimal separator exists in the field.
  370. if (substr_count($value, $field['settings']['decimal_separator']) > 1) {
  371. $message = t('%field: There should only be one decimal separator (@separator).',
  372. array(
  373. '%field' => t($instance['label']),
  374. '@separator' => $field['settings']['decimal_separator'],
  375. )
  376. );
  377. form_error($element, $message);
  378. }
  379. else {
  380. // Substitute the decimal separator; things should be fine.
  381. $value = strtr($value, $field['settings']['decimal_separator'], '.');
  382. }
  383. }
  384. form_set_value($element, $value, $form_state);
  385. }
  386. }
  387. }
  388. /**
  389. * Implements hook_field_widget_error().
  390. */
  391. function number_field_widget_error($element, $error, $form, &$form_state) {
  392. form_error($element['value'], $error['message']);
  393. }