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

/**
 * @file
 * Defines list field types that can be used with the Options module.
 */

9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Implements hook_help().
 */
function list_help($path, $arg) {
  switch ($path) {
    case 'admin/help#list':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The List module defines various fields for storing a list of items, for use with the Field module. Usually these items are entered through a select list, checkboxes, or radio buttons. 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
 */
function list_field_info() {
  return array(
    'list' => array(
      'label' => t('List'),
      'description' => t('This field stores numeric keys from key/value lists of allowed values where the key is a simple alias for the position of the value, i.e. 0|First option, 1|Second option, 2|Third option.'),
30
      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
31
32
33
34
35
36
      'default_widget' => 'options_select',
      'default_formatter' => 'list_default',
    ),
    'list_boolean' => array(
      'label' => t('Boolean'),
      'description' => t('This field stores simple on/off or yes/no options.'),
37
      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
38
39
40
41
42
43
      'default_widget' => 'options_select',
      'default_formatter' => 'list_default',
    ),
    'list_number' => array(
      'label' => t('List (numeric)'),
      'description' => t('This field stores keys from key/value lists of allowed numbers where the stored numeric key has significance and must be preserved, i.e. \'Lifetime in days\': 1|1 day, 7|1 week, 31|1 month.'),
44
      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
45
46
47
48
49
50
      'default_widget' => 'options_select',
      'default_formatter' => 'list_default',
    ),
    'list_text' => array(
      'label' => t('List (text)'),
      'description' => t('This field stores keys from key/value lists of allowed values where the stored key has significance and must be a varchar, i.e. \'US States\': IL|Illinois, IA|Iowa, IN|Indiana'),
51
      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
52
53
54
55
56
57
58
      'default_widget' => 'options_select',
      'default_formatter' => 'list_default',
    ),
  );
}

/**
59
 * Implements hook_field_schema().
60
 */
61
function list_field_schema($field) {
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
  switch ($field['type']) {
    case 'list_text':
      $columns = array(
        'value' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => FALSE,
        ),
      );
      break;
    case 'list_number':
      $columns = array(
        'value' => array(
          'type' => 'float',
          'unsigned' => TRUE,
          'not null' => FALSE,
        ),
      );
      break;
    default:
      $columns = array(
        'value' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => FALSE,
        ),
      );
      break;
  }
91
92
93
94
95
96
  return array(
    'columns' => $columns,
    'indexes' => array(
      'value' => array('value'),
    ),
  );
97
98
}

99
/**
100
 * Implements hook_field_settings_form().
101
102
103
104
105
106
 *
 * @todo: If $has_data, add a form validate function to verify that the
 * new allowed values do not exclude any keys for which data already
 * exists in the databae (use field_attach_query()) to find out.
 * Implement the validate function via hook_field_update_forbid() so
 * list.module does not depend on form submission.
107
 */
108
function list_field_settings_form($field, $instance, $has_data) {
109
110
111
112
113
114
115
116
117
  $settings = $field['settings'];

  $form['allowed_values'] = array(
    '#type' => 'textarea',
    '#title' => t('Allowed values list'),
    '#default_value' => $settings['allowed_values'],
    '#required' => FALSE,
    '#rows' => 10,
    '#description' => '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label. The key is the value that will be stored in the database, and must be a %type value. The label is optional, and the key will be used as the label if no label is specified.', array('%type' => $field['type'] == 'list_text' ? t('text') : t('numeric'))) . '</p>',
118
    '#element_validate' => array('list_allowed_values_setting_validate'),
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    '#list_field_type' => $field['type'],
    '#access' => empty($settings['allowed_values_function']),
  );

  // Alter the description for allowed values depending on the widget type.
  if ($instance['widget']['type'] == 'options_onoff') {
    $form['allowed_values']['#description'] .= '<p>' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") . '</p>';
  }
  elseif ($instance['widget']['type'] == 'options_buttons') {
    $form['allowed_values']['#description'] .= '<p>' . t("The 'checkboxes/radio buttons' widget will display checkboxes if the <em>Number of values</em> option is greater than 1 for this field, otherwise radios will be displayed.") . '</p>';
  }
  $form['allowed_values']['#description'] .= t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags()));

  $form['allowed_values_function'] = array(
    '#type' => 'value',
    '#value' => $settings['allowed_values_function'],
  );
  $form['allowed_values_function_display'] = array(
    '#type' => 'item',
    '#title' => t('Allowed values list'),
    '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $settings['allowed_values_function'])),
    '#access' => !empty($settings['allowed_values_function']),
  );

  return $form;
}

146
/**
147
 * Element validate callback; check that the entered values are valid.
148
 */
149
150
151
152
153
154
155
156
157
158
function list_allowed_values_setting_validate($element, &$form_state) {
  $values = list_extract_allowed_values($element['#value'], $element['#list_field_type'] == 'list');
  $field_type = $element['#list_field_type'];

  // Check that keys are valid for the field type.
  foreach ($values as $key => $value) {
    if ($field_type == 'list_number' && !is_numeric($key)) {
      form_error($element, t('Allowed values list: each key must be a valid integer or decimal.'));
      break;
    }
159
160
    elseif ($field_type == 'list_text' && drupal_strlen($key) > 255) {
      form_error($element, t('Allowed values list: each key must be a string at most 255 characters long.'));
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
      break;
    }
    elseif ($field_type == 'list' && !preg_match('/^-?\d+$/', $key)) {
      form_error($element, t('Allowed values list: keys must be integers.'));
      break;
    }
    elseif ($field_type == 'list_boolean' && !in_array($key, array('0', '1'))) {
      form_error($element, t('Allowed values list: keys must be either 0 or 1.'));
      break;
    }
  }

  // Check that boolean fields get two values.
  if ($field_type == 'list_boolean' && count($values) != 2) {
    form_error($element, t('Allowed values list: two values are required.'));
176
177
178
179
  }
}

/**
180
 * Implements hook_field_update_field().
181
182
 */
function list_field_update_field($field, $prior_field, $has_data) {
183
  drupal_static_reset('list_allowed_values');
184
185
}

186
/**
187
188
189
190
191
192
193
194
195
196
197
 * Returns the set of allowed values for a list field.
 *
 * The strings are not safe for output. Keys and values of the array should be
 * sanitized through field_filter_xss() before being displayed.
 *
 * @param $field
 *   The field definition.
 *
 * @return
 *   The array of allowed values. Keys of the array are the raw stored values
 *   (integer or text), values of the array are the display aliases.
198
199
 */
function list_allowed_values($field) {
200
  $allowed_values = &drupal_static(__FUNCTION__, array());
201

202
203
  if (!isset($allowed_values[$field['id']])) {
    $values = array();
204

205
206
207
208
209
210
211
212
    $function = $field['settings']['allowed_values_function'];
    if (!empty($function) && function_exists($function)) {
      $values = $function($field);
    }
    elseif (!empty($field['settings']['allowed_values'])) {
      $position_keys = $field['type'] == 'list';
      $values = list_extract_allowed_values($field['settings']['allowed_values'], $position_keys);
    }
213

214
    $allowed_values[$field['id']] = $values;
215
216
  }

217
  return $allowed_values[$field['id']];
218
219
220
}

/**
221
 * Generates an array of values from a string.
222
223
224
225
226
 *
 * Explode a string with keys and labels separated with '|' and with each new
 * value on its own line.
 *
 * @param $string_values
227
228
229
230
 *   The list of choices as a string, in the format expected by the
 *   'allowed_values' setting:
 *    - Values are separated by a carriage return.
 *    - Each value is in the format "value|label" or "value".
231
232
233
234
235
236
 * @param $position_keys
 *   Boolean value indicating whether to generate keys based on the position of
 *   the value if a key is not manually specified, effectively generating
 *   integer-based keys. This should only be TRUE for fields that have a type of
 *   "list". Otherwise the value will be used as the key if not specified.
 */
237
238
function list_extract_allowed_values($string_values, $position_keys = FALSE) {
  $values = array();
239
240
241
242
243
244
245
246
247
248
249
250
251
252

  $list = explode("\n", $string_values);
  $list = array_map('trim', $list);
  $list = array_filter($list, 'strlen');
  foreach ($list as $key => $value) {
    // Check for a manually specified key.
    if (strpos($value, '|') !== FALSE) {
      list($key, $value) = explode('|', $value);
    }
    // Otherwise see if we need to use the value as the key. The "list" type
    // will automatically convert non-keyed lines to integers.
    elseif (!$position_keys) {
      $key = $value;
    }
253
    $values[$key] = (isset($value) && $value !== '') ? $value : $key;
254
255
  }

256
  return $values;
257
258
}

259
/**
260
 * Implements hook_field_validate().
261
262
263
 *
 * Possible error codes:
 * - 'list_illegal_value': The value is not part of the list of allowed values.
264
 */
265
function list_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
266
  $allowed_values = list_allowed_values($field);
267
268
269
  foreach ($items as $delta => $item) {
    if (!empty($item['value'])) {
      if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
270
        $errors[$field['field_name']][$langcode][$delta][] = array(
271
272
273
          'error' => 'list_illegal_value',
          'message' => t('%name: illegal value.', array('%name' => t($instance['label']))),
        );
274
275
276
277
278
279
      }
    }
  }
}

/**
280
 * Implements hook_field_is_empty().
281
282
283
284
285
286
287
288
 */
function list_field_is_empty($item, $field) {
  if (empty($item['value']) && (string)$item['value'] !== '0') {
    return TRUE;
  }
  return FALSE;
}

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/**
 * Implements hook_field_widget_info_alter().
 *
 * The List module does not implement widgets of its own, but reuses the
 * widgets defined in options.module.
 *
 * @see list_options_list().
 */
function list_field_widget_info_alter(&$info) {
  $widgets = array(
    'options_select' => array('list', 'list_text', 'list_number', 'list_boolean'),
    'options_buttons' => array('list', 'list_text', 'list_number', 'list_boolean'),
    'options_onoff' => array('list_boolean'),
  );

  foreach ($widgets as $widget => $field_types) {
    $info[$widget]['field types'] = array_merge($info[$widget]['field types'], $field_types);
  }
}

/**
 * Implements hook_options_list().
 */
function list_options_list($field) {
  return list_allowed_values($field);
}

316
/**
317
 * Implements hook_field_formatter_info().
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
 */
function list_field_formatter_info() {
  return array(
    'list_default' => array(
      'label' => t('Default'),
      'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
    ),
    'list_key' => array(
      'label' => t('Key'),
      'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
    ),
  );
}

/**
333
 * Implements hook_field_formatter_view().
334
 */
335
function list_field_formatter_view($object_type, $object, $field, $instance, $langcode, $items, $display) {
336
  $element = array();
337
338
339
340

  switch ($display['type']) {
    case 'list_default':
      $allowed_values = list_allowed_values($field);
341
342
      foreach ($items as $delta => $item) {
        if (isset($allowed_values[$item['value']])) {
343
          $output = field_filter_xss($allowed_values[$item['value']]);
344
345
346
        }
        else {
          // If no match was found in allowed values, fall back to the key.
347
          $output = field_filter_xss($value);
348
349
        }
        $element[$delta] = array('#markup' => $output);
350
351
352
353
      }
      break;

    case 'list_key':
354
      foreach ($items as $delta => $item) {
355
        $element[$delta] = array('#markup' => field_filter_xss($item['value']));
356
      }
357
      break;
358
359
  }

360
  return $element;
361
}