#894880 by yched: fix notices in check_plain() when rendering empty 'plain text'...
[project/cck.git] / modules / text / text.module
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Defines simple text field types.
7 */
8
9 /**
10 * Implementation of hook_theme().
11 */
12 function text_theme() {
13 return array(
14 'text_textarea' => array(
15 'arguments' => array('element' => NULL),
16 ),
17 'text_textfield' => array(
18 'arguments' => array('element' => NULL),
19 ),
20 'text_formatter_default' => array(
21 'arguments' => array('element' => NULL),
22 ),
23 'text_formatter_plain' => array(
24 'arguments' => array('element' => NULL),
25 ),
26 'text_formatter_trimmed' => array(
27 'arguments' => array('element' => NULL),
28 ),
29 'text_formatter_foo' => array(
30 'arguments' => array('element' => NULL),
31 ),
32 );
33 }
34
35 /**
36 * Implementation of hook_field_info().
37 */
38 function text_field_info() {
39 return array(
40 'text' => array(
41 'label' => t('Text'),
42 'description' => t('Store text in the database.'),
43 // 'content_icon' => 'icon_content_text.png',
44 ),
45 );
46 }
47
48 /**
49 * Implementation of hook_field_settings().
50 */
51 function text_field_settings($op, $field) {
52 switch ($op) {
53 case 'form':
54 $form = array();
55 $options = array(0 => t('Plain text'), 1 => t('Filtered text (user selects input format)'));
56 $form['text_processing'] = array(
57 '#type' => 'radios',
58 '#title' => t('Text processing'),
59 '#default_value' => is_numeric($field['text_processing']) ? $field['text_processing'] : 0,
60 '#options' => $options,
61 );
62 $form['max_length'] = array(
63 '#type' => 'textfield',
64 '#title' => t('Maximum length'),
65 '#default_value' => is_numeric($field['max_length']) ? $field['max_length'] : '',
66 '#required' => FALSE,
67 '#element_validate' => array('_element_validate_integer_positive'),
68 '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
69 );
70 $form['allowed_values_fieldset'] = array(
71 '#type' => 'fieldset',
72 '#title' => t('Allowed values'),
73 '#collapsible' => TRUE,
74 '#collapsed' => TRUE,
75 );
76 $form['allowed_values_fieldset']['allowed_values'] = array(
77 '#type' => 'textarea',
78 '#title' => t('Allowed values list'),
79 '#default_value' => !empty($field['allowed_values']) ? $field['allowed_values'] : '',
80 '#required' => FALSE,
81 '#rows' => 10,
82 '#description' => 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 it must match the field storage type (%type). The label is optional, and the key will be used as the label if no label is specified.<br />Allowed HTML tags: @tags', array('%type' => $field['type'], '@tags' => _content_filter_xss_display_allowed_tags())),
83 );
84 $form['allowed_values_fieldset']['advanced_options'] = array(
85 '#type' => 'fieldset',
86 '#title' => t('PHP code'),
87 '#collapsible' => TRUE,
88 '#collapsed' => empty($field['allowed_values_php']),
89 );
90 if (user_access('Use PHP input for field settings (dangerous - grant with care)')) {
91 $form['allowed_values_fieldset']['advanced_options']['allowed_values_php'] = array(
92 '#type' => 'textarea',
93 '#title' => t('Code'),
94 '#default_value' => !empty($field['allowed_values_php']) ? $field['allowed_values_php'] : '',
95 '#rows' => 6,
96 '#description' => t('Advanced usage only: PHP code that returns a keyed array of allowed values. Should not include &lt;?php ?&gt; delimiters. If this field is filled out, the array returned by this code will override the allowed values list above.'),
97 );
98 }
99 else {
100 $form['allowed_values_fieldset']['advanced_options']['markup_allowed_values_php'] = array(
101 '#type' => 'item',
102 '#title' => t('Code'),
103 '#value' => !empty($field['allowed_values_php']) ? '<code>'. check_plain($field['allowed_values_php']) .'</code>' : t('&lt;none&gt;'),
104 '#description' => empty($field['allowed_values_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override the allowed values list above.'),
105 );
106 }
107 return $form;
108
109 case 'save':
110 return array('text_processing', 'max_length', 'allowed_values', 'allowed_values_php');
111
112 case 'database columns':
113 if (empty($field['max_length']) || $field['max_length'] > 255) {
114 $columns['value'] = array('type' => 'text', 'size' => 'big', 'not null' => FALSE, 'sortable' => TRUE, 'views' => TRUE);
115 }
116 else {
117 $columns['value'] = array('type' => 'varchar', 'length' => $field['max_length'], 'not null' => FALSE, 'sortable' => TRUE, 'views' => TRUE);
118 }
119 if (!empty($field['text_processing'])) {
120 $columns['format'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'views' => FALSE);
121 }
122 return $columns;
123
124 case 'views data':
125 $allowed_values = content_allowed_values($field);
126 if (count($allowed_values)) {
127 $data = content_views_field_views_data($field);
128 $db_info = content_database_info($field);
129 $table_alias = content_views_tablename($field);
130
131 // Filter: Add a 'many to one' filter.
132 $copy = $data[$table_alias][$field['field_name'] .'_value'];
133 $copy['title'] = t('@label (!name) - Allowed values', array('@label' => t($field['widget']['label']), '!name' => $field['field_name']));
134 $copy['filter']['handler'] = 'content_handler_filter_many_to_one';
135 unset($copy['field'], $copy['argument'], $copy['sort']);
136 $data[$table_alias][$field['field_name'] .'_value_many_to_one'] = $copy;
137 // Argument : swap the handler to the 'many to one' operator.
138 $data[$table_alias][$field['field_name'] .'_value']['argument']['handler'] = 'content_handler_argument_many_to_one';
139 return $data;
140 }
141 }
142 }
143
144 /**
145 * Implementation of hook_field().
146 */
147 function text_field($op, &$node, $field, &$items, $teaser, $page) {
148 switch ($op) {
149 case 'validate':
150 $allowed_values = content_allowed_values($field);
151 if (is_array($items)) {
152 foreach ($items as $delta => $item) {
153 $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
154 if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
155 if (!empty($item['value'])) {
156 if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
157 form_set_error($error_element, t('%name: illegal value.', array('%name' => t($field['widget']['label']))));
158 }
159 if (!empty($field['max_length']) && drupal_strlen($item['value']) > $field['max_length']) {
160 form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $field['widget']['label'], '%max' => $field['max_length'])));
161 }
162 }
163 }
164 }
165 return $items;
166
167 case 'sanitize':
168 foreach ($items as $delta => $item) {
169 if (!empty($field['text_processing'])) {
170 $check = is_null($node) || (isset($node->build_mode) && $node->build_mode == NODE_BUILD_PREVIEW);
171 $text = isset($item['value']) ? check_markup($item['value'], $item['format'], $check) : '';
172 }
173 else {
174 $text = isset($item['value']) ? check_plain($item['value']) : '';
175 }
176 $items[$delta]['safe'] = $text;
177 }
178 }
179 }
180
181 /**
182 * Implementation of hook_content_is_empty().
183 */
184 function text_content_is_empty($item, $field) {
185 if (empty($item['value']) && (string)$item['value'] !== '0') {
186 return TRUE;
187 }
188 return FALSE;
189 }
190
191 /**
192 * Implementation of hook_field_formatter_info().
193 */
194 function text_field_formatter_info() {
195 return array(
196 'default' => array(
197 'label' => t('Default'),
198 'field types' => array('text'),
199 'multiple values' => CONTENT_HANDLE_CORE,
200 ),
201 'plain' => array(
202 'label' => t('Plain text'),
203 'field types' => array('text'),
204 'multiple values' => CONTENT_HANDLE_CORE,
205 ),
206 'trimmed' => array(
207 'label' => t('Trimmed'),
208 'field types' => array('text'),
209 'multiple values' => CONTENT_HANDLE_CORE,
210 ),
211 );
212 }
213
214 /**
215 * Theme function for 'default' text field formatter.
216 */
217 function theme_text_formatter_default($element) {
218 return ($allowed =_text_allowed_values($element)) ? $allowed : $element['#item']['safe'];
219 }
220
221 /**
222 * Theme function for 'plain' text field formatter.
223 */
224 function theme_text_formatter_plain($element) {
225 return ($allowed =_text_allowed_values($element)) ? $allowed : strip_tags($element['#item']['safe']);
226 }
227
228 /**
229 * Theme function for 'trimmed' text field formatter.
230 */
231 function theme_text_formatter_trimmed($element) {
232 $field = content_fields($element['#field_name'], $element['#type_name']);
233 return ($allowed =_text_allowed_values($element)) ? $allowed : node_teaser($element['#item']['safe'], $field['text_processing'] ? $element['#item']['format'] : NULL);
234 }
235
236 function _text_allowed_values($element) {
237 $field = content_fields($element['#field_name'], $element['#type_name']);
238 if (($allowed_values = content_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) {
239 return $allowed_values[$element['#item']['value']];
240 }
241 }
242
243 /**
244 * Implementation of hook_widget_info().
245 *
246 * Here we indicate that the content module will handle
247 * the default value and multiple values for these widgets.
248 *
249 * Callbacks can be omitted if default handing is used.
250 * They're included here just so this module can be used
251 * as an example for custom modules that might do things
252 * differently.
253 */
254 function text_widget_info() {
255 return array(
256 'text_textfield' => array(
257 'label' => t('Text field'),
258 'field types' => array('text'),
259 'multiple values' => CONTENT_HANDLE_CORE,
260 'callbacks' => array(
261 'default value' => CONTENT_CALLBACK_DEFAULT,
262 ),
263 ),
264 'text_textarea' => array(
265 'label' => t('Text area (multiple rows)'),
266 'field types' => array('text'),
267 'multiple values' => CONTENT_HANDLE_CORE,
268 'callbacks' => array(
269 'default value' => CONTENT_CALLBACK_DEFAULT,
270 ),
271 ),
272 );
273 }
274
275 /**
276 * Implementation of FAPI hook_elements().
277 *
278 * Any FAPI callbacks needed for individual widgets can be declared here,
279 * and the element will be passed to those callbacks for processing.
280 *
281 * Drupal will automatically theme the element using a theme with
282 * the same name as the hook_elements key.
283 *
284 * Autocomplete_path is not used by text_widget but other widgets can use it
285 * (see nodereference and userreference).
286 */
287 function text_elements() {
288 return array(
289 'text_textfield' => array(
290 '#input' => TRUE,
291 '#columns' => array('value'), '#delta' => 0,
292 '#process' => array('text_textfield_process'),
293 '#autocomplete_path' => FALSE,
294 ),
295 'text_textarea' => array(
296 '#input' => TRUE,
297 '#columns' => array('value', 'format'), '#delta' => 0,
298 '#process' => array('text_textarea_process'),
299 '#filter_value' => FILTER_FORMAT_DEFAULT,
300 ),
301 );
302 }
303
304 /**
305 * Implementation of hook_widget_settings().
306 */
307 function text_widget_settings($op, $widget) {
308 switch ($op) {
309 case 'form':
310 $form = array();
311 $rows = (isset($widget['rows']) && is_numeric($widget['rows'])) ? $widget['rows'] : 5;
312 $size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60;
313 if ($widget['type'] == 'text_textfield') {
314 $form['rows'] = array('#type' => 'hidden', '#value' => $rows);
315 $form['size'] = array(
316 '#type' => 'textfield',
317 '#title' => t('Size of textfield'),
318 '#default_value' => $size,
319 '#element_validate' => array('_element_validate_integer_positive'),
320 '#required' => TRUE,
321 );
322 }
323 else {
324 $form['rows'] = array(
325 '#type' => 'textfield',
326 '#title' => t('Rows'),
327 '#default_value' => $rows,
328 '#element_validate' => array('_element_validate_integer_positive'),
329 '#required' => TRUE,
330 );
331 $form['size'] = array('#type' => 'hidden', '#value' => $size);
332 }
333 return $form;
334
335 case 'save':
336 return array('rows', 'size');
337 }
338 }
339
340 /**
341 * Implementation of hook_widget().
342 *
343 * Attach a single form element to the form. It will be built out and
344 * validated in the callback(s) listed in hook_elements. We build it
345 * out in the callbacks rather than here in hook_widget so it can be
346 * plugged into any module that can provide it with valid
347 * $field information.
348 *
349 * Content module will set the weight, field name and delta values
350 * for each form element. This is a change from earlier CCK versions
351 * where the widget managed its own multiple values.
352 *
353 * If there are multiple values for this field, the content module will
354 * call this function as many times as needed.
355 *
356 * @param $form
357 * the entire form array, $form['#node'] holds node information
358 * @param $form_state
359 * the form_state, $form_state['values'][$field['field_name']]
360 * holds the field's form values.
361 * @param $field
362 * the field array
363 * @param $items
364 * array of default values for this field
365 * @param $delta
366 * the order of this item in the array of subelements (0, 1, 2, etc)
367 *
368 * @return
369 * the form item for a single element for this field
370 */
371 function text_widget(&$form, &$form_state, $field, $items, $delta = 0) {
372 $element = array(
373 '#type' => $field['widget']['type'],
374 '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
375 );
376 return $element;
377 }
378
379 /**
380 * Process an individual element.
381 *
382 * Build the form element. When creating a form using FAPI #process,
383 * note that $element['#value'] is already set.
384 *
385 * The $fields array is in $form['#field_info'][$element['#field_name']].
386 */
387 function text_textfield_process($element, $edit, $form_state, $form) {
388 $field = $form['#field_info'][$element['#field_name']];
389 $field_key = $element['#columns'][0];
390 $delta = $element['#delta'];
391 $element[$field_key] = array(
392 '#type' => 'textfield',
393 '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
394 '#autocomplete_path' => $element['#autocomplete_path'],
395 '#size' => !empty($field['widget']['size']) ? $field['widget']['size'] : 60,
396 '#attributes' => array('class' => 'text'),
397 // The following values were set by the content module and need
398 // to be passed down to the nested element.
399 '#title' => $element['#title'],
400 '#description' => $element['#description'],
401 '#required' => $element['#required'],
402 '#field_name' => $element['#field_name'],
403 '#type_name' => $element['#type_name'],
404 '#delta' => $element['#delta'],
405 '#columns' => $element['#columns'],
406 );
407
408 $element[$field_key]['#maxlength'] = !empty($field['max_length']) ? $field['max_length'] : NULL;
409
410 if (!empty($field['text_processing'])) {
411 $filter_key = $element['#columns'][1];
412 $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
413 $parents = array_merge($element['#parents'] , array($filter_key));
414 $element[$filter_key] = filter_form($format, 1, $parents);
415 }
416
417 // Used so that hook_field('validate') knows where to flag an error.
418 $element['_error_element'] = array(
419 '#type' => 'value',
420 '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
421 );
422
423 return $element;
424 }
425
426 /**
427 * Process an individual element.
428 *
429 * Build the form element. When creating a form using FAPI #process,
430 * note that $element['#value'] is already set.
431 *
432 * The $fields array is in $form['#field_info'][$element['#field_name']].
433 */
434 function text_textarea_process($element, $edit, $form_state, $form) {
435 $field = $form['#field_info'][$element['#field_name']];
436 $field_key = $element['#columns'][0];
437 $element[$field_key] = array(
438 '#type' => 'textarea',
439 '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
440 '#rows' => !empty($field['widget']['rows']) ? $field['widget']['rows'] : 10,
441 '#weight' => 0,
442 // The following values were set by the content module and need
443 // to be passed down to the nested element.
444 '#title' => $element['#title'],
445 '#description' => $element['#description'],
446 '#required' => $element['#required'],
447 '#field_name' => $element['#field_name'],
448 '#type_name' => $element['#type_name'],
449 '#delta' => $element['#delta'],
450 '#columns' => $element['#columns'],
451 );
452
453 if (!empty($field['text_processing'])) {
454 $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
455 $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
456 $parents = array_merge($element['#parents'] , array($filter_key));
457 $element[$filter_key] = filter_form($format, 1, $parents);
458 }
459
460 // Used so that hook_field('validate') knows where to flag an error.
461 $element['_error_element'] = array(
462 '#type' => 'value',
463 '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
464 );
465
466 return $element;
467 }
468
469 /**
470 * FAPI theme for an individual text elements.
471 *
472 * The textfield or textarea is already rendered by the
473 * textfield or textarea themes and the html output
474 * lives in $element['#children']. Override this theme to
475 * make custom changes to the output.
476 *
477 * $element['#field_name'] contains the field name
478 * $element['#delta] is the position of this element in the group
479 */
480 function theme_text_textfield($element) {
481 return $element['#children'];
482 }
483
484 function theme_text_textarea($element) {
485 return $element['#children'];
486 }