number.module 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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. $element = array();
  211. if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
  212. $options = array(
  213. '' => t('<none>'),
  214. '.' => t('Decimal point'),
  215. ',' => t('Comma'),
  216. ' ' => t('Space'),
  217. );
  218. $element['thousand_separator'] = array(
  219. '#type' => 'select',
  220. '#title' => t('Thousand marker'),
  221. '#options' => $options,
  222. '#default_value' => $settings['thousand_separator'],
  223. );
  224. if ($display['type'] == 'number_decimal') {
  225. $element['decimal_separator'] = array(
  226. '#type' => 'select',
  227. '#title' => t('Decimal marker'),
  228. '#options' => array('.' => t('Decimal point'), ',' => t('Comma')),
  229. '#default_value' => $settings['decimal_separator'],
  230. );
  231. $element['scale'] = array(
  232. '#type' => 'select',
  233. '#title' => t('Scale'),
  234. '#options' => drupal_map_assoc(range(0, 10)),
  235. '#default_value' => $settings['scale'],
  236. '#description' => t('The number of digits to the right of the decimal.'),
  237. );
  238. }
  239. $element['prefix_suffix'] = array(
  240. '#type' => 'checkbox',
  241. '#title' => t('Display prefix and suffix.'),
  242. '#default_value' => $settings['prefix_suffix'],
  243. );
  244. }
  245. return $element;
  246. }
  247. /**
  248. * Implements hook_field_formatter_settings_summary().
  249. */
  250. function number_field_formatter_settings_summary($field, $instance, $view_mode) {
  251. $display = $instance['display'][$view_mode];
  252. $settings = $display['settings'];
  253. $summary = array();
  254. if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
  255. $summary[] = number_format(1234.1234567890, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
  256. if ($settings['prefix_suffix']) {
  257. $summary[] = t('Display with prefix and suffix.');
  258. }
  259. }
  260. return implode('<br />', $summary);
  261. }
  262. /**
  263. * Implements hook_field_formatter_view().
  264. */
  265. function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  266. $element = array();
  267. $settings = $display['settings'];
  268. switch ($display['type']) {
  269. case 'number_integer':
  270. case 'number_decimal':
  271. foreach ($items as $delta => $item) {
  272. $output = number_format($item['value'], $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
  273. if ($settings['prefix_suffix']) {
  274. $prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array('');
  275. $suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array('');
  276. $prefix = (count($prefixes) > 1) ? format_plural($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0];
  277. $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0];
  278. $output = $prefix . $output . $suffix;
  279. }
  280. $element[$delta] = array('#markup' => $output);
  281. }
  282. break;
  283. case 'number_unformatted':
  284. foreach ($items as $delta => $item) {
  285. $element[$delta] = array('#markup' => $item['value']);
  286. }
  287. break;
  288. }
  289. return $element;
  290. }
  291. /**
  292. * Implements hook_field_widget_info().
  293. */
  294. function number_field_widget_info() {
  295. return array(
  296. 'number' => array(
  297. 'label' => t('Text field'),
  298. 'field types' => array('number_integer', 'number_decimal', 'number_float'),
  299. ),
  300. );
  301. }
  302. /**
  303. * Implements hook_field_widget_form().
  304. */
  305. function number_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  306. $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : '';
  307. // Substitute the decimal separator.
  308. if ($field['type'] == 'number_decimal' || $field['type'] == 'number_float') {
  309. $value = strtr($value, '.', $field['settings']['decimal_separator']);
  310. }
  311. $element += array(
  312. '#type' => 'textfield',
  313. '#default_value' => $value,
  314. // Allow a slightly larger size that the field length to allow for some
  315. // configurations where all characters won't fit in input field.
  316. '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 4 : 12,
  317. // Allow two extra characters for signed values and decimal separator.
  318. '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 10,
  319. // Extract the number type from the field type name for easier validation.
  320. '#number_type' => str_replace('number_', '', $field['type']),
  321. );
  322. // Add prefix and suffix.
  323. if (!empty($instance['settings']['prefix'])) {
  324. $prefixes = explode('|', $instance['settings']['prefix']);
  325. $element['#field_prefix'] = field_filter_xss(array_pop($prefixes));
  326. }
  327. if (!empty($instance['settings']['suffix'])) {
  328. $suffixes = explode('|', $instance['settings']['suffix']);
  329. $element['#field_suffix'] = field_filter_xss(array_pop($suffixes));
  330. }
  331. $element['#element_validate'][] = 'number_field_widget_validate';
  332. return array('value' => $element);
  333. }
  334. /**
  335. * FAPI validation of an individual number element.
  336. */
  337. function number_field_widget_validate($element, &$form_state) {
  338. $field = field_widget_field($element, $form_state);
  339. $instance = field_widget_instance($element, $form_state);
  340. $type = $element['#number_type'];
  341. $value = $element['#value'];
  342. // Reject invalid characters.
  343. if (!empty($value)) {
  344. switch ($type) {
  345. case 'float':
  346. case 'decimal':
  347. $regexp = '@([^-0-9\\' . $field['settings']['decimal_separator'] . '])|(.-)@';
  348. $message = t('Only numbers and the decimal separator (@separator) allowed in %field.', array('%field' => $instance['label'], '@separator' => $field['settings']['decimal_separator']));
  349. break;
  350. case 'integer':
  351. $regexp = '@([^-0-9])|(.-)@';
  352. $message = t('Only numbers are allowed in %field.', array('%field' => $instance['label']));
  353. break;
  354. }
  355. if ($value != preg_replace($regexp, '', $value)) {
  356. form_error($element, $message);
  357. }
  358. else {
  359. if ($type == 'decimal' || $type == 'float') {
  360. // Verify that only one decimal separator exists in the field.
  361. if (substr_count($value, $field['settings']['decimal_separator']) > 1) {
  362. $message = t('%field: There should only be one decimal separator (@separator).',
  363. array(
  364. '%field' => t($instance['label']),
  365. '@separator' => $field['settings']['decimal_separator'],
  366. )
  367. );
  368. form_error($element, $message);
  369. }
  370. else {
  371. // Substitute the decimal separator; things should be fine.
  372. $value = strtr($value, $field['settings']['decimal_separator'], '.');
  373. }
  374. }
  375. form_set_value($element, $value, $form_state);
  376. }
  377. }
  378. }
  379. /**
  380. * Implements hook_field_widget_error().
  381. */
  382. function number_field_widget_error($element, $error, $form, &$form_state) {
  383. form_error($element['value'], $error['message']);
  384. }