1 | <?php |
---|
2 | // $Id: content.admin.inc,v 1.181.2.76 2009/11/02 21:21:24 markuspetrux Exp $ |
---|
3 | |
---|
4 | /** |
---|
5 | * @file |
---|
6 | * Administrative interface for content type creation. |
---|
7 | */ |
---|
8 | |
---|
9 | |
---|
10 | /** |
---|
11 | * Menu callback; replacement for node_overview_types(). |
---|
12 | */ |
---|
13 | function content_types_overview() { |
---|
14 | $types = node_get_types(); |
---|
15 | $names = node_get_types('names'); |
---|
16 | $header = array(t('Name'), t('Type'), t('Description'), array('data' => t('Operations'), 'colspan' => '4'),); |
---|
17 | $rows = array(); |
---|
18 | |
---|
19 | foreach ($names as $key => $name) { |
---|
20 | $type = $types[$key]; |
---|
21 | if (node_hook($type, 'form')) { |
---|
22 | $type_url_str = str_replace('_', '-', $type->type); |
---|
23 | $row = array( |
---|
24 | check_plain($name), |
---|
25 | check_plain($type->type), |
---|
26 | ); |
---|
27 | // Make the description smaller |
---|
28 | $row[] = array('data' => filter_xss_admin($type->description), 'class' => 'description'); |
---|
29 | // Set the edit column. |
---|
30 | $row[] = array('data' => l(t('edit'), 'admin/content/node-type/'. $type_url_str)); |
---|
31 | // Set links for managing fields. |
---|
32 | // TODO: a hook to allow other content modules to add more stuff? |
---|
33 | $row[] = array('data' => l(t('manage fields'), 'admin/content/node-type/'. $type_url_str .'/fields')); |
---|
34 | // Set the delete column. |
---|
35 | if ($type->custom) { |
---|
36 | $row[] = array('data' => l(t('delete'), 'admin/content/node-type/'. $type_url_str .'/delete')); |
---|
37 | } |
---|
38 | else { |
---|
39 | $row[] = array('data' => ''); |
---|
40 | } |
---|
41 | |
---|
42 | $rows[] = $row; |
---|
43 | } |
---|
44 | } |
---|
45 | |
---|
46 | // Allow external modules alter the table headers and rows. |
---|
47 | foreach (module_implements('content_types_overview_alter') as $module) { |
---|
48 | $function = $module .'_content_types_overview_alter'; |
---|
49 | $function($header, $rows); |
---|
50 | } |
---|
51 | |
---|
52 | if (empty($rows)) { |
---|
53 | $rows[] = array(array('data' => t('No content types available.'), 'colspan' => '7', 'class' => 'message')); |
---|
54 | } |
---|
55 | |
---|
56 | return theme('table', $header, $rows) .theme('content_overview_links'); |
---|
57 | } |
---|
58 | |
---|
59 | function theme_content_overview_links() { |
---|
60 | return '<div class="content-overview-links">'. l(t('» Add a new content type'), 'admin/content/types/add') .'</div>'; |
---|
61 | } |
---|
62 | |
---|
63 | /** |
---|
64 | * Menu callback; lists all defined fields for quick reference. |
---|
65 | */ |
---|
66 | function content_fields_list() { |
---|
67 | $fields = content_fields(); |
---|
68 | $field_types = _content_field_types(); |
---|
69 | |
---|
70 | // Sort fields by field name. |
---|
71 | ksort($fields); |
---|
72 | |
---|
73 | $header = array(t('Field name'), t('Field type'), t('Used in')); |
---|
74 | $rows = array(); |
---|
75 | foreach ($fields as $field) { |
---|
76 | $row = array(); |
---|
77 | $row[] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field['field_name'])) : $field['field_name']; |
---|
78 | $row[] = t($field_types[$field['type']]['label']); |
---|
79 | |
---|
80 | $types = array(); |
---|
81 | $result = db_query("SELECT nt.name, nt.type FROM {". content_instance_tablename() ."} nfi ". |
---|
82 | "LEFT JOIN {node_type} nt ON nt.type = nfi.type_name ". |
---|
83 | "WHERE nfi.field_name = '%s' ". |
---|
84 | // Keep disabled modules out of table. |
---|
85 | "AND nfi.widget_active = 1 ". |
---|
86 | "ORDER BY nt.name ASC", $field['field_name']); |
---|
87 | while ($type = db_fetch_array($result)) { |
---|
88 | $content_type = content_types($type['type']); |
---|
89 | $types[] = l($type['name'], 'admin/content/node-type/'. $content_type['url_str'] .'/fields'); |
---|
90 | } |
---|
91 | $row[] = implode(', ', $types); |
---|
92 | |
---|
93 | $rows[] = array('data' => $row, 'class' => $field['locked'] ? 'menu-disabled' : ''); |
---|
94 | } |
---|
95 | if (empty($rows)) { |
---|
96 | $output = t('No fields have been defined for any content type yet.'); |
---|
97 | } |
---|
98 | else { |
---|
99 | $output = theme('table', $header, $rows); |
---|
100 | } |
---|
101 | return $output; |
---|
102 | } |
---|
103 | |
---|
104 | /** |
---|
105 | * Helper function to display a message about inactive fields. |
---|
106 | */ |
---|
107 | function content_inactive_message($type_name) { |
---|
108 | $inactive_fields = content_inactive_fields($type_name); |
---|
109 | if (!empty($inactive_fields)) { |
---|
110 | $field_types = _content_field_types(); |
---|
111 | $widget_types = _content_widget_types($type_name); |
---|
112 | drupal_set_message(t('This content type has inactive fields. Inactive fields are not included in lists of available fields until their modules are enabled.'), 'error'); |
---|
113 | foreach ($inactive_fields as $field_name => $field) { |
---|
114 | drupal_set_message(t('!field (!field_name) is an inactive !field_type field that uses a !widget_type widget.', array( |
---|
115 | '!field' => $field['widget']['label'], |
---|
116 | '!field_name' => $field['field_name'], |
---|
117 | '!field_type' => array_key_exists($field['type'], $field_types) ? $field_types[$field['type']]['label'] : $field['type'], |
---|
118 | '!widget_type' => array_key_exists($field['widget']['type'], $widget_types) ? $widget_types[$field['widget']['type']]['label'] : $field['widget']['type'], |
---|
119 | ))); |
---|
120 | } |
---|
121 | } |
---|
122 | } |
---|
123 | |
---|
124 | /** |
---|
125 | * Menu callback; listing of fields for a content type. |
---|
126 | * |
---|
127 | * Allows fields to be reordered and nested in fieldgroups using |
---|
128 | * JS drag-n-drop. Non-CCK form elements can also be moved around. |
---|
129 | */ |
---|
130 | function content_field_overview_form(&$form_state, $type_name) { |
---|
131 | |
---|
132 | content_inactive_message($type_name); |
---|
133 | |
---|
134 | // When displaying the form, make sure the list of fields |
---|
135 | // is up-to-date. |
---|
136 | if (empty($form_state['post'])) { |
---|
137 | content_clear_type_cache(); |
---|
138 | } |
---|
139 | |
---|
140 | // Gather type information. |
---|
141 | $type = content_types($type_name); |
---|
142 | $fields = $type['fields']; |
---|
143 | $field_types = _content_field_types(); |
---|
144 | |
---|
145 | $extra = $type['extra']; |
---|
146 | $groups = $group_options = $group_types = array(); |
---|
147 | if (module_exists('fieldgroup')) { |
---|
148 | $groups = fieldgroup_groups($type['type']); |
---|
149 | $group_types = fieldgroup_types(); |
---|
150 | $group_options = _fieldgroup_groups_label($type['type']); |
---|
151 | // Add the ability to group under the newly created row. |
---|
152 | $group_options['_add_new_group'] = '_add_new_group'; |
---|
153 | } |
---|
154 | |
---|
155 | // Store the default weights as we meet them, to be able to put the |
---|
156 | //'add new' rows after them. |
---|
157 | $weights = array(); |
---|
158 | |
---|
159 | $form = array( |
---|
160 | '#tree' => TRUE, |
---|
161 | '#type_name' => $type['type'], |
---|
162 | '#fields' => array_keys($fields), |
---|
163 | '#groups' => array_keys($groups), |
---|
164 | '#extra' => array_keys($extra), |
---|
165 | '#field_rows' => array(), |
---|
166 | '#group_rows' => array(), |
---|
167 | ); |
---|
168 | |
---|
169 | // Fields. |
---|
170 | foreach ($fields as $name => $field) { |
---|
171 | $weight = $field['widget']['weight']; |
---|
172 | $form[$name] = array( |
---|
173 | 'label' => array('#value' => check_plain($field['widget']['label'])), |
---|
174 | 'field_name' => array('#value' => $field['field_name']), |
---|
175 | 'type' => array('#value' => t($field_types[$field['type']]['label'])), |
---|
176 | 'configure' => array('#value' => l(t('Configure'), 'admin/content/node-type/'. $type['url_str'] .'/fields/'. $field['field_name'])), |
---|
177 | 'remove' => array('#value' => l(t('Remove'), 'admin/content/node-type/'. $type['url_str'] .'/fields/'. $field['field_name'] .'/remove')), |
---|
178 | 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), |
---|
179 | 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), |
---|
180 | 'prev_parent' => array('#type' => 'hidden', '#value' => ''), |
---|
181 | 'hidden_name' => array('#type' => 'hidden', '#default_value' => $field['field_name']), |
---|
182 | '#leaf' => TRUE, |
---|
183 | '#row_type' => 'field', |
---|
184 | 'field' => array('#type' => 'value', '#value' => $field), |
---|
185 | ); |
---|
186 | if ($field['locked']) { |
---|
187 | $form[$name]['configure'] = array('#value' => t('Locked')); |
---|
188 | $form[$name]['remove'] = array(); |
---|
189 | $form[$name]['#disabled_row'] = TRUE; |
---|
190 | } |
---|
191 | $form['#field_rows'][] = $name; |
---|
192 | $weights[] = $weight; |
---|
193 | } |
---|
194 | |
---|
195 | // Groups. |
---|
196 | foreach ($groups as $name => $group) { |
---|
197 | $weight = $group['weight']; |
---|
198 | $form[$name] = array( |
---|
199 | 'label' => array('#value' => check_plain($group['label'])), |
---|
200 | 'group_name' => array('#value' => $group['group_name']), |
---|
201 | 'group_type' => array('#value' => t($group_types[$group['group_type']])), |
---|
202 | 'configure' => array('#value' => l(t('Configure'), 'admin/content/node-type/'. $type['url_str'] .'/groups/'. $group['group_name'])), |
---|
203 | 'remove' => array('#value' => l(t('Remove'), 'admin/content/node-type/'. $type['url_str'] .'/groups/'. $group['group_name'] .'/remove')), |
---|
204 | 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), |
---|
205 | 'parent' => array('#type' => 'hidden', '#default_value' => ''), |
---|
206 | 'hidden_name' => array('#type' => 'hidden', '#default_value' => $group['group_name']), |
---|
207 | '#root' => TRUE, |
---|
208 | '#row_type' => 'group', |
---|
209 | 'group' => array('#type' => 'value', '#value' => $group), |
---|
210 | ); |
---|
211 | // Adjust child fields rows. |
---|
212 | foreach ($group['fields'] as $field_name => $field) { |
---|
213 | $form[$field_name]['parent']['#default_value'] = $name; |
---|
214 | $form[$field_name]['prev_parent']['#value'] = $name; |
---|
215 | } |
---|
216 | $form['#group_rows'][] = $name; |
---|
217 | $weights[] = $weight; |
---|
218 | } |
---|
219 | |
---|
220 | // Non-CCK 'fields'. |
---|
221 | foreach ($extra as $name => $label) { |
---|
222 | $weight = $extra[$name]['weight']; |
---|
223 | $form[$name] = array( |
---|
224 | 'label' => array('#value' => check_plain(t($extra[$name]['label']))), |
---|
225 | 'description' => array('#value' => isset($extra[$name]['description']) ? $extra[$name]['description'] : ''), |
---|
226 | 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), |
---|
227 | 'parent' => array('#type' => 'hidden', '#default_value' => ''), |
---|
228 | 'configure' => array('#value' => isset($extra[$name]['configure']) ? $extra[$name]['configure'] : ''), |
---|
229 | 'remove' => array('#value' => isset($extra[$name]['remove']) ? $extra[$name]['remove'] : ''), |
---|
230 | 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), |
---|
231 | '#leaf' => TRUE, |
---|
232 | '#root' => TRUE, |
---|
233 | '#disabled_row' => TRUE, |
---|
234 | '#row_type' => 'extra', |
---|
235 | ); |
---|
236 | $form['#field_rows'][] = $name; |
---|
237 | $weights[] = $weight; |
---|
238 | } |
---|
239 | |
---|
240 | // Additional row : add new field. |
---|
241 | $weight = max($weights) + 1; |
---|
242 | $field_type_options = content_field_type_options(); |
---|
243 | $widget_type_options = content_widget_type_options(NULL, TRUE); |
---|
244 | if ($field_type_options && $widget_type_options) { |
---|
245 | array_unshift($field_type_options, t('- Select a field type -')); |
---|
246 | array_unshift($widget_type_options, t('- Select a widget -')); |
---|
247 | $name = '_add_new_field'; |
---|
248 | $form[$name] = array( |
---|
249 | 'label' => array( |
---|
250 | '#type' => 'textfield', |
---|
251 | '#size' => 15, |
---|
252 | '#description' => t('Label'), |
---|
253 | ), |
---|
254 | 'field_name' => array( |
---|
255 | '#type' => 'textfield', |
---|
256 | // This field should stay LTR even for RTL languages. |
---|
257 | '#field_prefix' => '<span dir="ltr">field_', |
---|
258 | '#field_suffix' => '</span>‎', |
---|
259 | '#attributes' => array('dir'=>'ltr'), |
---|
260 | '#size' => 15, |
---|
261 | // Field names are limited to 32 characters including the 'field_' |
---|
262 | // prefix which is 6 characters long. |
---|
263 | '#maxlength' => 26, |
---|
264 | '#description' => t('Field name (a-z, 0-9, _)'), |
---|
265 | ), |
---|
266 | 'type' => array( |
---|
267 | '#type' => 'select', |
---|
268 | '#options' => $field_type_options, |
---|
269 | '#description' => theme('advanced_help_topic', 'content', 'fields') . t('Type of data to store.'), |
---|
270 | ), |
---|
271 | 'widget_type' => array( |
---|
272 | '#type' => 'select', |
---|
273 | '#options' => $widget_type_options, |
---|
274 | '#description' => t('Form element to edit the data.'), |
---|
275 | ), |
---|
276 | 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), |
---|
277 | 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), |
---|
278 | 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), |
---|
279 | '#leaf' => TRUE, |
---|
280 | '#add_new' => TRUE, |
---|
281 | '#row_type' => 'add_new_field', |
---|
282 | ); |
---|
283 | $form['#field_rows'][] = $name; |
---|
284 | } |
---|
285 | |
---|
286 | // Additional row : add existing field. |
---|
287 | $existing_field_options = content_existing_field_options($type_name); |
---|
288 | if ($existing_field_options && $widget_type_options) { |
---|
289 | $weight++; |
---|
290 | array_unshift($existing_field_options, t('- Select an existing field -')); |
---|
291 | $name = '_add_existing_field'; |
---|
292 | $form[$name] = array( |
---|
293 | 'label' => array( |
---|
294 | '#type' => 'textfield', |
---|
295 | '#size' => 15, |
---|
296 | '#description' => t('Label'), |
---|
297 | ), |
---|
298 | 'field_name' => array( |
---|
299 | '#type' => 'select', |
---|
300 | '#options' => $existing_field_options, |
---|
301 | '#description' => t('Field to share'), |
---|
302 | ), |
---|
303 | 'widget_type' => array( |
---|
304 | '#type' => 'select', |
---|
305 | '#options' => $widget_type_options, |
---|
306 | '#description' => t('Form element to edit the data.'), |
---|
307 | ), |
---|
308 | 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), |
---|
309 | 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), |
---|
310 | 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), |
---|
311 | '#leaf' => TRUE, |
---|
312 | '#add_new' => TRUE, |
---|
313 | '#row_type' => 'add_existing_field', |
---|
314 | ); |
---|
315 | $form['#field_rows'][] = $name; |
---|
316 | } |
---|
317 | |
---|
318 | // Additional row : add new group. |
---|
319 | if (!empty($group_types)) { |
---|
320 | $weight++; |
---|
321 | $name = '_add_new_group'; |
---|
322 | $form[$name] = array( |
---|
323 | 'label' => array( |
---|
324 | '#type' => 'textfield', |
---|
325 | '#size' => 15, |
---|
326 | '#description' => t('Label'), |
---|
327 | ), |
---|
328 | 'group_name' => array( |
---|
329 | '#type' => 'textfield', |
---|
330 | // This field should stay LTR even for RTL languages. |
---|
331 | '#field_prefix' => '<span dir="ltr">group_', |
---|
332 | '#field_suffix' => '</span>‎', |
---|
333 | '#attributes' => array('dir'=>'ltr'), |
---|
334 | '#size' => 15, |
---|
335 | // Group names are limited to 32 characters including the 'group_' |
---|
336 | // prefix which is 6 characters long. |
---|
337 | '#maxlength' => 26, |
---|
338 | '#description' => t('Group name (a-z, 0-9, _)'), |
---|
339 | ), |
---|
340 | 'group_option' => array( |
---|
341 | '#type' => 'hidden', |
---|
342 | '#value' => '', |
---|
343 | ), |
---|
344 | 'group_type' => array( |
---|
345 | '#type' => 'hidden', |
---|
346 | '#value' => 'standard', |
---|
347 | ), |
---|
348 | 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), |
---|
349 | 'parent' => array('#type' => 'hidden', '#default_value' => ''), |
---|
350 | 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), |
---|
351 | '#root' => TRUE, |
---|
352 | '#add_new' => TRUE, |
---|
353 | '#row_type' => 'add_new_group', |
---|
354 | ); |
---|
355 | if (count($group_types) > 1) { |
---|
356 | $form[$name]['group_type'] = array( |
---|
357 | '#type' => 'select', |
---|
358 | '#description' => t('Type of group.'), |
---|
359 | '#options' => $group_types, |
---|
360 | '#default_value' => 'standard', |
---|
361 | ); |
---|
362 | } |
---|
363 | $form['#group_rows'][] = $name; |
---|
364 | } |
---|
365 | |
---|
366 | $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); |
---|
367 | return $form; |
---|
368 | } |
---|
369 | |
---|
370 | function content_field_overview_form_validate($form, &$form_state) { |
---|
371 | _content_field_overview_form_validate_add_new($form, $form_state); |
---|
372 | _content_field_overview_form_validate_add_existing($form, $form_state); |
---|
373 | } |
---|
374 | |
---|
375 | /** |
---|
376 | * Helper function for content_field_overview_form_validate. |
---|
377 | * |
---|
378 | * Validate the 'add new field' row. |
---|
379 | */ |
---|
380 | function _content_field_overview_form_validate_add_new($form, &$form_state) { |
---|
381 | $field = $form_state['values']['_add_new_field']; |
---|
382 | |
---|
383 | // Validate if any information was provided in the 'add new field' row. |
---|
384 | if (array_filter(array($field['label'], $field['field_name'], $field['type'], $field['widget_type']))) { |
---|
385 | // No label. |
---|
386 | if (!$field['label']) { |
---|
387 | form_set_error('_add_new_field][label', t('Add new field: you need to provide a label.')); |
---|
388 | } |
---|
389 | |
---|
390 | // No field name. |
---|
391 | if (!$field['field_name']) { |
---|
392 | form_set_error('_add_new_field][field_name', t('Add new field: you need to provide a field name.')); |
---|
393 | } |
---|
394 | // Field name validation. |
---|
395 | else { |
---|
396 | $field_name = $field['field_name']; |
---|
397 | |
---|
398 | // Add the 'field_' prefix. |
---|
399 | if (substr($field_name, 0, 6) != 'field_') { |
---|
400 | $field_name = 'field_'. $field_name; |
---|
401 | form_set_value($form['_add_new_field']['field_name'], $field_name, $form_state); |
---|
402 | } |
---|
403 | |
---|
404 | // Invalid field name. |
---|
405 | if (!preg_match('!^field_[a-z0-9_]+$!', $field_name)) { |
---|
406 | form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%field_name' => $field_name))); |
---|
407 | } |
---|
408 | if (strlen($field_name) > 32) { |
---|
409 | form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is too long. The name is limited to 32 characters, including the \'field_\' prefix.', array('%field_name' => $field_name))); |
---|
410 | } |
---|
411 | // A field named 'field_instance' would cause a tablename clash with {content_field_instance} |
---|
412 | if ($field_name == 'field_instance') { |
---|
413 | form_set_error('_add_new_field][field_name', t("Add new field: the name 'field_instance' is a reserved name.")); |
---|
414 | } |
---|
415 | |
---|
416 | // Field name already exists. |
---|
417 | // We need to check inactive fields as well, so we can't use content_fields(). |
---|
418 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
419 | $fields = content_field_instance_read(array(), TRUE); |
---|
420 | $used = FALSE; |
---|
421 | foreach ($fields as $existing_field) { |
---|
422 | $used |= ($existing_field['field_name'] == $field_name); |
---|
423 | } |
---|
424 | if ($used) { |
---|
425 | form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name already exists.', array('%field_name' => $field_name))); |
---|
426 | } |
---|
427 | } |
---|
428 | |
---|
429 | // No field type. |
---|
430 | if (!$field['type']) { |
---|
431 | form_set_error('_add_new_field][type', t('Add new field: you need to select a field type.')); |
---|
432 | } |
---|
433 | |
---|
434 | // No widget type. |
---|
435 | if (!$field['widget_type']) { |
---|
436 | form_set_error('_add_new_field][widget_type', t('Add new field: you need to select a widget.')); |
---|
437 | } |
---|
438 | // Wrong widget type. |
---|
439 | elseif ($field['type']) { |
---|
440 | $widget_types = content_widget_type_options($field['type']); |
---|
441 | if (!isset($widget_types[$field['widget_type']])) { |
---|
442 | form_set_error('_add_new_field][widget_type', t('Add new field: invalid widget.')); |
---|
443 | } |
---|
444 | } |
---|
445 | } |
---|
446 | } |
---|
447 | |
---|
448 | /** |
---|
449 | * Helper function for content_field_overview_form_validate. |
---|
450 | * |
---|
451 | * Validate the 'add existing field' row. |
---|
452 | */ |
---|
453 | function _content_field_overview_form_validate_add_existing($form, &$form_state) { |
---|
454 | // The form element might be absent if no existing fields can be added to |
---|
455 | // this content type |
---|
456 | if (isset($form_state['values']['_add_existing_field'])) { |
---|
457 | $field = $form_state['values']['_add_existing_field']; |
---|
458 | |
---|
459 | // Validate if any information was provided in the 'add existing field' row. |
---|
460 | if (array_filter(array($field['label'], $field['field_name'], $field['widget_type']))) { |
---|
461 | // No label. |
---|
462 | if (!$field['label']) { |
---|
463 | form_set_error('_add_existing_field][label', t('Add existing field: you need to provide a label.')); |
---|
464 | } |
---|
465 | |
---|
466 | // No existing field. |
---|
467 | if (!$field['field_name']) { |
---|
468 | form_set_error('_add_existing_field][field_name', t('Add existing field: you need to select a field.')); |
---|
469 | } |
---|
470 | |
---|
471 | // No widget type. |
---|
472 | if (!$field['widget_type']) { |
---|
473 | form_set_error('_add_existing_field][widget_type', t('Add existing field: you need to select a widget.')); |
---|
474 | } |
---|
475 | // Wrong widget type. |
---|
476 | elseif ($field['field_name'] && ($existing_field = content_fields($field['field_name']))) { |
---|
477 | $widget_types = content_widget_type_options($existing_field['type']); |
---|
478 | if (!isset($widget_types[$field['widget_type']])) { |
---|
479 | form_set_error('_add_existing_field][widget_type', t('Add existing field: invalid widget.')); |
---|
480 | } |
---|
481 | } |
---|
482 | } |
---|
483 | } |
---|
484 | } |
---|
485 | |
---|
486 | function content_field_overview_form_submit($form, &$form_state) { |
---|
487 | $form_values = $form_state['values']; |
---|
488 | |
---|
489 | $type_name = $form['#type_name']; |
---|
490 | $type = content_types($type_name); |
---|
491 | |
---|
492 | // Update field weights. |
---|
493 | $extra = array(); |
---|
494 | foreach ($form_values as $key => $values) { |
---|
495 | // Groups are handled in fieldgroup_content_overview_form_submit(). |
---|
496 | if (in_array($key, $form['#fields'])) { |
---|
497 | db_query("UPDATE {". content_instance_tablename() ."} SET weight = %d WHERE type_name = '%s' AND field_name = '%s'", |
---|
498 | $values['weight'], $type_name, $key); |
---|
499 | } |
---|
500 | elseif (in_array($key, $form['#extra'])) { |
---|
501 | $extra[$key] = $values['weight']; |
---|
502 | } |
---|
503 | } |
---|
504 | |
---|
505 | if ($extra) { |
---|
506 | variable_set('content_extra_weights_'. $type_name, $extra); |
---|
507 | } |
---|
508 | else { |
---|
509 | variable_del('content_extra_weights_'. $type_name); |
---|
510 | } |
---|
511 | |
---|
512 | content_clear_type_cache(); |
---|
513 | |
---|
514 | $destinations = array(); |
---|
515 | |
---|
516 | // Create new field. |
---|
517 | if (!empty($form_values['_add_new_field']['field_name'])) { |
---|
518 | $field = $form_values['_add_new_field']; |
---|
519 | $field['type_name'] = $type_name; |
---|
520 | |
---|
521 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
522 | if (content_field_instance_create($field)) { |
---|
523 | // Store new field information for fieldgroup submit handler. |
---|
524 | $form_state['fields_added']['_add_new_field'] = $field['field_name']; |
---|
525 | $destinations[] = 'admin/content/node-type/'. $type['url_str'] .'/fields/'. $field['field_name']; |
---|
526 | } |
---|
527 | else { |
---|
528 | drupal_set_message(t('There was a problem creating field %label.', array( |
---|
529 | '%label' => $field['label']))); |
---|
530 | } |
---|
531 | } |
---|
532 | |
---|
533 | // Add existing field. |
---|
534 | if (!empty($form_values['_add_existing_field']['field_name'])) { |
---|
535 | $field = $form_values['_add_existing_field']; |
---|
536 | $field['type_name'] = $type_name; |
---|
537 | $existing_field = content_fields($field['field_name']); |
---|
538 | |
---|
539 | if ($existing_field['locked']) { |
---|
540 | drupal_set_message(t('The field %label cannot be added to a content type because it is locked.', array('%label' => $field['field_name']))); |
---|
541 | } |
---|
542 | else { |
---|
543 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
544 | if (content_field_instance_create($field)) { |
---|
545 | // Store new field information for fieldgroup submit handler. |
---|
546 | $form_state['fields_added']['_add_existing_field'] = $field['field_name']; |
---|
547 | $destinations[] = 'admin/content/node-type/'. $type['url_str'] .'/fields/'. $field['field_name']; |
---|
548 | } |
---|
549 | else { |
---|
550 | drupal_set_message(t('There was a problem adding field %label.', array('%label' => $field['field_name']))); |
---|
551 | } |
---|
552 | } |
---|
553 | } |
---|
554 | |
---|
555 | if ($destinations) { |
---|
556 | $destinations[] = urldecode(substr(drupal_get_destination(), 12)); |
---|
557 | unset($_REQUEST['destination']); |
---|
558 | $form_state['redirect'] = content_get_destinations($destinations); |
---|
559 | } |
---|
560 | |
---|
561 | } |
---|
562 | |
---|
563 | /** |
---|
564 | * Menu callback; presents a listing of fields display settings for a content type. |
---|
565 | * |
---|
566 | * Form includes form widgets to select which fields appear for teaser, full node |
---|
567 | * and how the field labels should be rendered. |
---|
568 | */ |
---|
569 | function content_display_overview_form(&$form_state, $type_name, $contexts_selector = 'basic') { |
---|
570 | content_inactive_message($type_name); |
---|
571 | |
---|
572 | // Gather type information. |
---|
573 | $type = content_types($type_name); |
---|
574 | $field_types = _content_field_types(); |
---|
575 | $fields = $type['fields']; |
---|
576 | |
---|
577 | $groups = array(); |
---|
578 | if (module_exists('fieldgroup')) { |
---|
579 | $groups = fieldgroup_groups($type['type']); |
---|
580 | } |
---|
581 | $contexts = content_build_modes($contexts_selector); |
---|
582 | |
---|
583 | $form = array( |
---|
584 | '#tree' => TRUE, |
---|
585 | '#type_name' => $type['type'], |
---|
586 | '#fields' => array_keys($fields), |
---|
587 | '#groups' => array_keys($groups), |
---|
588 | '#contexts' => $contexts_selector, |
---|
589 | ); |
---|
590 | |
---|
591 | if (empty($fields)) { |
---|
592 | drupal_set_message(t('There are no fields configured for this content type. You can add new fields on the <a href="@link">Manage fields</a> page.', array( |
---|
593 | '@link' => url('admin/content/node-type/'. $type['url_str'] .'/fields'))), 'warning'); |
---|
594 | return $form; |
---|
595 | } |
---|
596 | |
---|
597 | // Fields. |
---|
598 | $label_options = array( |
---|
599 | 'above' => t('Above'), |
---|
600 | 'inline' => t('Inline'), |
---|
601 | 'hidden' => t('<Hidden>'), |
---|
602 | ); |
---|
603 | foreach ($fields as $name => $field) { |
---|
604 | $field_type = $field_types[$field['type']]; |
---|
605 | $defaults = $field['display_settings']; |
---|
606 | $weight = $field['widget']['weight']; |
---|
607 | |
---|
608 | $form[$name] = array( |
---|
609 | 'human_name' => array('#value' => check_plain($field['widget']['label'])), |
---|
610 | 'weight' => array('#type' => 'value', '#value' => $weight), |
---|
611 | 'parent' => array('#type' => 'value', '#value' => ''), |
---|
612 | ); |
---|
613 | |
---|
614 | // Label |
---|
615 | if ($contexts_selector == 'basic') { |
---|
616 | $form[$name]['label']['format'] = array( |
---|
617 | '#type' => 'select', |
---|
618 | '#options' => $label_options, |
---|
619 | '#default_value' => isset($defaults['label']['format']) ? $defaults['label']['format'] : 'above', |
---|
620 | ); |
---|
621 | } |
---|
622 | |
---|
623 | // Formatters. |
---|
624 | $options = array(); |
---|
625 | foreach ($field_type['formatters'] as $formatter_name => $formatter_info) { |
---|
626 | $options[$formatter_name] = $formatter_info['label']; |
---|
627 | } |
---|
628 | $options['hidden'] = t('<Hidden>'); |
---|
629 | |
---|
630 | foreach ($contexts as $key => $value) { |
---|
631 | $form[$name][$key]['format'] = array( |
---|
632 | '#type' => 'select', |
---|
633 | '#options' => $options, |
---|
634 | '#default_value' => isset($defaults[$key]['format']) ? $defaults[$key]['format'] : 'default', |
---|
635 | ); |
---|
636 | // exclude from $content |
---|
637 | $form[$name][$key]['exclude'] = array( |
---|
638 | '#type' => 'checkbox', |
---|
639 | '#options' => array(0 => t('Include'), 1 => t('Exclude')), |
---|
640 | '#default_value' => isset($defaults[$key]['exclude']) ? $defaults[$key]['exclude'] : 0, |
---|
641 | ); |
---|
642 | } |
---|
643 | } |
---|
644 | |
---|
645 | // Groups. |
---|
646 | $label_options = array( |
---|
647 | 'above' => t('Above'), |
---|
648 | 'hidden' => t('<Hidden>'), |
---|
649 | ); |
---|
650 | $options = array( |
---|
651 | 'no_style' => t('no styling'), |
---|
652 | 'simple' => t('simple'), |
---|
653 | 'fieldset' => t('fieldset'), |
---|
654 | 'fieldset_collapsible' => t('fieldset - collapsible'), |
---|
655 | 'fieldset_collapsed' => t('fieldset - collapsed'), |
---|
656 | 'hidden' => t('<Hidden>'), |
---|
657 | ); |
---|
658 | foreach ($groups as $name => $group) { |
---|
659 | $defaults = $group['settings']['display']; |
---|
660 | $weight = $group['weight']; |
---|
661 | |
---|
662 | $form[$name] = array( |
---|
663 | 'human_name' => array('#value' => check_plain($group['label'])), |
---|
664 | 'weight' => array('#type' => 'value', '#value' => $weight), |
---|
665 | ); |
---|
666 | if ($contexts_selector == 'basic') { |
---|
667 | $form[$name]['label'] = array( |
---|
668 | '#type' => 'select', |
---|
669 | '#options' => $label_options, |
---|
670 | '#default_value' => isset($defaults['label']) ? $defaults['label'] : 'above', |
---|
671 | ); |
---|
672 | } |
---|
673 | foreach ($contexts as $key => $title) { |
---|
674 | $form[$name][$key]['format'] = array( |
---|
675 | '#type' => 'select', |
---|
676 | '#options' => $options, |
---|
677 | '#default_value' => isset($defaults[$key]['format']) ? $defaults[$key]['format'] : 'fieldset', |
---|
678 | ); |
---|
679 | // exclude in $content |
---|
680 | $form[$name][$key]['exclude'] = array( |
---|
681 | '#type' => 'checkbox', |
---|
682 | '#options' => array(0 => t('Include'), 1 => t('Exclude')), |
---|
683 | '#default_value' => isset($defaults[$key]['exclude']) ? $defaults[$key]['exclude'] : 0, |
---|
684 | ); |
---|
685 | } |
---|
686 | foreach ($group['fields'] as $field_name => $field) { |
---|
687 | $form[$field_name]['parent']['#value'] = $name; |
---|
688 | } |
---|
689 | } |
---|
690 | |
---|
691 | $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); |
---|
692 | return $form; |
---|
693 | } |
---|
694 | |
---|
695 | /** |
---|
696 | * Submit handler for the display overview form. |
---|
697 | */ |
---|
698 | function content_display_overview_form_submit($form, &$form_state) { |
---|
699 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
700 | $form_values = $form_state['values']; |
---|
701 | foreach ($form_values as $key => $values) { |
---|
702 | // Groups are handled in fieldgroup_display_overview_form_submit(). |
---|
703 | if (in_array($key, $form['#fields'])) { |
---|
704 | $field = content_fields($key, $form['#type_name']); |
---|
705 | // We have some numeric keys here, so we can't use array_merge. |
---|
706 | $field['display_settings'] = $values + $field['display_settings']; |
---|
707 | content_field_instance_update($field, FALSE); |
---|
708 | } |
---|
709 | } |
---|
710 | |
---|
711 | // Clear caches and rebuild menu. |
---|
712 | content_clear_type_cache(TRUE); |
---|
713 | menu_rebuild(); |
---|
714 | |
---|
715 | drupal_set_message(t('Your settings have been saved.')); |
---|
716 | } |
---|
717 | |
---|
718 | /** |
---|
719 | * Return an array of field_type options. |
---|
720 | */ |
---|
721 | function content_field_type_options() { |
---|
722 | static $options; |
---|
723 | |
---|
724 | if (!isset($options)) { |
---|
725 | $options = array(); |
---|
726 | $field_types = _content_field_types(); |
---|
727 | $field_type_options = array(); |
---|
728 | foreach ($field_types as $field_type_name => $field_type) { |
---|
729 | // skip field types which have no widget types. |
---|
730 | if (content_widget_type_options($field_type_name)) { |
---|
731 | $options[$field_type_name] = t($field_type['label']); |
---|
732 | } |
---|
733 | } |
---|
734 | asort($options); |
---|
735 | } |
---|
736 | return $options; |
---|
737 | } |
---|
738 | |
---|
739 | /** |
---|
740 | * Return an array of widget type options for a field type. |
---|
741 | * |
---|
742 | * If no field type is provided, returns a nested array of |
---|
743 | * all widget types, keyed by field type human name |
---|
744 | */ |
---|
745 | function content_widget_type_options($field_type = NULL, $by_label = FALSE) { |
---|
746 | static $options; |
---|
747 | |
---|
748 | if (!isset($options)) { |
---|
749 | $options = array(); |
---|
750 | foreach (_content_widget_types() as $widget_type_name => $widget_type) { |
---|
751 | foreach ($widget_type['field types'] as $widget_field_type) { |
---|
752 | $options[$widget_field_type][$widget_type_name] = t($widget_type['label']); |
---|
753 | } |
---|
754 | } |
---|
755 | } |
---|
756 | |
---|
757 | if ($field_type) { |
---|
758 | return !empty($options[$field_type]) ? $options[$field_type] : array(); |
---|
759 | } |
---|
760 | elseif ($by_label) { |
---|
761 | $field_types = _content_field_types(); |
---|
762 | $options_by_label = array(); |
---|
763 | foreach ($options as $field_type => $widgets) { |
---|
764 | $options_by_label[t($field_types[$field_type]['label'])] = $widgets; |
---|
765 | } |
---|
766 | return $options_by_label; |
---|
767 | } |
---|
768 | else { |
---|
769 | return $options; |
---|
770 | } |
---|
771 | } |
---|
772 | |
---|
773 | /** |
---|
774 | * Return an array of existing field to be added to a node type. |
---|
775 | */ |
---|
776 | function content_existing_field_options($type_name) { |
---|
777 | $type = content_types($type_name); |
---|
778 | $fields = content_fields(); |
---|
779 | $field_types = _content_field_types(); |
---|
780 | |
---|
781 | $options = array(); |
---|
782 | foreach ($fields as $field) { |
---|
783 | if (!isset($type['fields'][$field['field_name']]) && !$field['locked']) { |
---|
784 | $field_type = $field_types[$field['type']]; |
---|
785 | $text = t('@type: @field (@label)', array('@type' => t($field_type['label']), '@label' => t($field['widget']['label']), '@field' => $field['field_name'])); |
---|
786 | $options[$field['field_name']] = (drupal_strlen($text) > 80) ? truncate_utf8($text, 77) . '...' : $text; |
---|
787 | } |
---|
788 | } |
---|
789 | // Sort the list by type, then by field name, then by label. |
---|
790 | asort($options); |
---|
791 | |
---|
792 | return $options; |
---|
793 | } |
---|
794 | |
---|
795 | /** |
---|
796 | * A form element for selecting field, widget, and label. |
---|
797 | */ |
---|
798 | function content_field_basic_form(&$form_state, $form_values) { |
---|
799 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
800 | |
---|
801 | $type_name = $form_values['type_name']; |
---|
802 | $type = content_types($form_values['type_name']); |
---|
803 | $field_name = $form_values['field_name']; |
---|
804 | $field_type = $form_values['type']; |
---|
805 | $label = $form_values['label']; |
---|
806 | |
---|
807 | $form = array(); |
---|
808 | |
---|
809 | $form['basic'] = array( |
---|
810 | '#type' => 'fieldset', |
---|
811 | '#title' => t('Edit basic information'), |
---|
812 | ); |
---|
813 | $form['basic']['field_name'] = array( |
---|
814 | '#title' => t('Field name'), |
---|
815 | '#type' => 'textfield', |
---|
816 | '#value' => $field_name, |
---|
817 | '#description' => t("The machine-readable name of the field. This name cannot be changed."), |
---|
818 | '#disabled' => TRUE, |
---|
819 | ); |
---|
820 | $form['basic']['label'] = array( |
---|
821 | '#type' => 'textfield', |
---|
822 | '#title' => t('Label'), |
---|
823 | '#default_value' => $label, |
---|
824 | '#required' => TRUE, |
---|
825 | '#description' => t('A human-readable name to be used as the label for this field in the %type content type.', array('%type' => $type['name'])), |
---|
826 | ); |
---|
827 | $form['basic']['type'] = array( |
---|
828 | '#type' => 'select', |
---|
829 | '#title' => t('Field type'), |
---|
830 | '#options' => content_field_type_options(), |
---|
831 | '#default_value' => $field_type, |
---|
832 | '#description' => t('The type of data you would like to store in the database with this field. This option cannot be changed.'), |
---|
833 | '#disabled' => TRUE, |
---|
834 | ); |
---|
835 | $form['basic']['widget_type'] = array( |
---|
836 | '#type' => 'select', |
---|
837 | '#title' => t('Widget type'), |
---|
838 | '#required' => TRUE, |
---|
839 | '#options' => content_widget_type_options($field_type), |
---|
840 | '#default_value' => $form_values['widget_type'], |
---|
841 | '#description' => t('The type of form element you would like to present to the user when creating this field in the %type content type.', array('%type' => $type['name'])), |
---|
842 | ); |
---|
843 | |
---|
844 | $form['type_name'] = array( |
---|
845 | '#type' => 'value', |
---|
846 | '#value' => $type_name, |
---|
847 | ); |
---|
848 | |
---|
849 | $form['submit'] = array( |
---|
850 | '#type' => 'submit', |
---|
851 | '#value' => t('Continue'), |
---|
852 | ); |
---|
853 | |
---|
854 | $form['#validate'] = array(); |
---|
855 | $form['#submit'] = array('content_field_basic_form_submit'); |
---|
856 | |
---|
857 | return $form; |
---|
858 | } |
---|
859 | |
---|
860 | /** |
---|
861 | * Create a new field for a content type. |
---|
862 | */ |
---|
863 | function content_field_basic_form_submit($form, &$form_state) { |
---|
864 | $form_values = $form_state['values']; |
---|
865 | |
---|
866 | $label = $form_values['label']; |
---|
867 | |
---|
868 | // Set the right module information |
---|
869 | $field_types = _content_field_types(); |
---|
870 | $widget_types = _content_widget_types(); |
---|
871 | $form_values['module'] = $field_types[$form_values['type']]['module']; |
---|
872 | $form_values['widget_module'] = $widget_types[$form_values['widget_type']]['module']; |
---|
873 | |
---|
874 | // Make sure we retain previous values and only over-write changed values. |
---|
875 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
876 | $instances = content_field_instance_read(array('field_name' => $form_values['field_name'], 'type_name' => $form_values['type_name'])); |
---|
877 | $field = array_merge(content_field_instance_collapse($instances[0]), $form_values); |
---|
878 | if (content_field_instance_update($field)) { |
---|
879 | drupal_set_message(t('Updated basic settings for field %label.', array( |
---|
880 | '%label' => $label))); |
---|
881 | } |
---|
882 | else { |
---|
883 | drupal_set_message(t('There was a problem updating the basic settings for field %label.', array( |
---|
884 | '%label' => $label))); |
---|
885 | } |
---|
886 | |
---|
887 | $type = content_types($form_values['type_name']); |
---|
888 | $form_state['redirect'] = 'admin/content/node-type/'. $type['url_str'] .'/fields/'. $form_values['field_name']; |
---|
889 | $form_state['rebuild'] = FALSE; |
---|
890 | } |
---|
891 | |
---|
892 | /** |
---|
893 | * Menu callback; present a form for removing a field from a content type. |
---|
894 | */ |
---|
895 | function content_field_remove_form(&$form_state, $type_name, $field_name) { |
---|
896 | $type = content_types($type_name); |
---|
897 | $field = $type['fields'][$field_name]; |
---|
898 | |
---|
899 | $form = array(); |
---|
900 | $form['type_name'] = array( |
---|
901 | '#type' => 'value', |
---|
902 | '#value' => $type_name, |
---|
903 | ); |
---|
904 | $form['field_name'] = array( |
---|
905 | '#type' => 'value', |
---|
906 | '#value' => $field_name, |
---|
907 | ); |
---|
908 | |
---|
909 | $output = confirm_form($form, |
---|
910 | t('Are you sure you want to remove the field %field?', array('%field' => $field['widget']['label'])), |
---|
911 | 'admin/content/node-type/'. $type['url_str'] .'/fields', |
---|
912 | t('If you have any content left in this field, it will be lost. This action cannot be undone.'), |
---|
913 | t('Remove'), t('Cancel'), |
---|
914 | 'confirm' |
---|
915 | ); |
---|
916 | |
---|
917 | if ($field['locked']) { |
---|
918 | unset($output['actions']['submit']); |
---|
919 | $output['description']['#value'] = t('This field is <strong>locked</strong> and cannot be removed.'); |
---|
920 | } |
---|
921 | |
---|
922 | return $output; |
---|
923 | } |
---|
924 | |
---|
925 | /** |
---|
926 | * Remove a field from a content type. |
---|
927 | */ |
---|
928 | function content_field_remove_form_submit($form, &$form_state) { |
---|
929 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
930 | $form_values = $form_state['values']; |
---|
931 | |
---|
932 | $type = content_types($form_values['type_name']); |
---|
933 | $field = $type['fields'][$form_values['field_name']]; |
---|
934 | if ($field['locked']) { |
---|
935 | return; |
---|
936 | } |
---|
937 | |
---|
938 | if ($type && $field && $form_values['confirm']) { |
---|
939 | if (content_field_instance_delete($form_values['field_name'], $form_values['type_name'])) { |
---|
940 | drupal_set_message(t('Removed field %field from %type.', array( |
---|
941 | '%field' => $field['widget']['label'], |
---|
942 | '%type' => $type['name']))); |
---|
943 | } |
---|
944 | else { |
---|
945 | drupal_set_message(t('There was a problem deleting %field from %type.', array( |
---|
946 | '%field' => $field['widget']['label'], |
---|
947 | '%type' => $type['name']))); |
---|
948 | } |
---|
949 | $form_state['redirect'] = 'admin/content/node-type/'. $type['url_str'] .'/fields'; |
---|
950 | } |
---|
951 | } |
---|
952 | |
---|
953 | /** |
---|
954 | * Menu callback; presents the field editing page. |
---|
955 | */ |
---|
956 | function content_field_edit_form(&$form_state, $type_name, $field_name) { |
---|
957 | $output = ''; |
---|
958 | $type = content_types($type_name); |
---|
959 | $field = $type['fields'][$field_name]; |
---|
960 | |
---|
961 | if ($field['locked']) { |
---|
962 | $output = array(); |
---|
963 | $output['locked'] = array( |
---|
964 | '#value' => t('The field %field is locked and cannot be edited.', array('%field' => $field['widget']['label'])), |
---|
965 | ); |
---|
966 | return $output; |
---|
967 | } |
---|
968 | |
---|
969 | $field_types = _content_field_types(); |
---|
970 | $field_type = $field_types[$field['type']]; |
---|
971 | $widget_types = _content_widget_types(); |
---|
972 | $widget_type = $widget_types[$field['widget']['type']]; |
---|
973 | |
---|
974 | $title = isset($field['widget']['label']) ? $field['widget']['label'] : $field['field_name']; |
---|
975 | drupal_set_title(check_plain($title)); |
---|
976 | |
---|
977 | // See if we need to change the widget type or label. |
---|
978 | if (isset($form_state['change_basic'])) { |
---|
979 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
980 | $field_values = content_field_instance_collapse($field); |
---|
981 | return content_field_basic_form($form_state, $field_values); |
---|
982 | } |
---|
983 | |
---|
984 | $add_new_sequence = isset($_REQUEST['destinations']); |
---|
985 | |
---|
986 | // Remove menu tabs when we are in an 'add new' sequence. |
---|
987 | if ($add_new_sequence) { |
---|
988 | menu_set_item(NULL, menu_get_item('node')); |
---|
989 | } |
---|
990 | |
---|
991 | $form = array(); |
---|
992 | $form['#field'] = $field; |
---|
993 | $form['#type'] = $type; |
---|
994 | |
---|
995 | // Basic iformation : hide when we are in an 'add new' sequence. |
---|
996 | $form['basic'] = array( |
---|
997 | '#type' => 'fieldset', |
---|
998 | '#title' => t('%type basic information', array('%type' => $type['name'])), |
---|
999 | '#access' => !$add_new_sequence, |
---|
1000 | ); |
---|
1001 | $form['basic']['label'] = array( |
---|
1002 | '#type' => 'textfield', |
---|
1003 | '#title' => t('Label'), |
---|
1004 | '#value' => $field['widget']['label'], |
---|
1005 | '#disabled' => TRUE, |
---|
1006 | ); |
---|
1007 | $form['basic']['field_name'] = array( |
---|
1008 | '#type' => 'hidden', |
---|
1009 | '#title' => t('Field name'), |
---|
1010 | '#value' => $field['field_name'], |
---|
1011 | '#disabled' => TRUE, |
---|
1012 | ); |
---|
1013 | $form['basic']['type'] = array( |
---|
1014 | '#type' => 'hidden', |
---|
1015 | '#title' => t('Field type'), |
---|
1016 | '#value' => $field['type'], |
---|
1017 | '#disabled' => TRUE, |
---|
1018 | ); |
---|
1019 | $widget_options = content_widget_type_options($field['type']); |
---|
1020 | $form['basic']['widget_type'] = array( |
---|
1021 | '#type' => 'select', |
---|
1022 | '#title' => t('Widget type'), |
---|
1023 | '#options' => $widget_options, |
---|
1024 | '#default_value' => $field['widget']['type'] ? $field['widget']['type'] : key($widget_options), |
---|
1025 | '#disabled' => TRUE, |
---|
1026 | ); |
---|
1027 | $form['basic']['change'] = array( |
---|
1028 | '#type' => 'submit', |
---|
1029 | '#value' => t('Change basic information'), |
---|
1030 | '#submit' => array('content_field_edit_form_submit_update_basic'), |
---|
1031 | ); |
---|
1032 | |
---|
1033 | $form['widget'] = array( |
---|
1034 | '#type' => 'fieldset', |
---|
1035 | '#title' => t('%type settings', array('%type' => $type['name'])), |
---|
1036 | '#description' => t('These settings apply only to the %field field as it appears in the %type content type.', array( |
---|
1037 | '%field' => $field['widget']['label'], |
---|
1038 | '%type' => $type['name'])), |
---|
1039 | ); |
---|
1040 | $form['widget']['weight'] = array( |
---|
1041 | '#type' => 'hidden', |
---|
1042 | '#default_value' => $field['widget']['weight'], |
---|
1043 | ); |
---|
1044 | |
---|
1045 | $additions = (array) module_invoke($widget_type['module'], 'widget_settings', 'form', $field['widget']); |
---|
1046 | drupal_alter('widget_settings', $additions, 'form', $field['widget']); |
---|
1047 | $form['widget'] = array_merge($form['widget'], $additions); |
---|
1048 | |
---|
1049 | $form['widget']['description'] = array( |
---|
1050 | '#type' => 'textarea', |
---|
1051 | '#title' => t('Help text'), |
---|
1052 | '#default_value' => $field['widget']['description'], |
---|
1053 | '#rows' => 5, |
---|
1054 | '#description' => t('Instructions to present to the user below this field on the editing form.<br />Allowed HTML tags: @tags', array('@tags' => _content_filter_xss_display_allowed_tags())), |
---|
1055 | '#required' => FALSE, |
---|
1056 | ); |
---|
1057 | |
---|
1058 | // Add handling for default value if not provided by field. |
---|
1059 | if (content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) { |
---|
1060 | |
---|
1061 | // Store the original default value for use in programmed forms. |
---|
1062 | // Set '#default_value' instead of '#value' so programmed values |
---|
1063 | // can override whatever we set here. |
---|
1064 | $default_value = isset($field['widget']['default_value']) ? $field['widget']['default_value'] : array(); |
---|
1065 | $default_value_php = isset($field['widget']['default_value_php']) ? $field['widget']['default_value_php'] : ''; |
---|
1066 | $form['widget']['default_value'] = array( |
---|
1067 | '#type' => 'value', |
---|
1068 | '#default_value' => $default_value, |
---|
1069 | ); |
---|
1070 | $form['widget']['default_value_php'] = array( |
---|
1071 | '#type' => 'value', |
---|
1072 | '#default_value' => $default_value_php, |
---|
1073 | ); |
---|
1074 | |
---|
1075 | // We can't tell at the time we build the form if this is a programmed |
---|
1076 | // form or not, so we always end up adding the default value widget |
---|
1077 | // even if we won't use it. |
---|
1078 | $form['widget']['default_value_fieldset'] = array( |
---|
1079 | '#type' => 'fieldset', |
---|
1080 | '#title' => t('Default value'), |
---|
1081 | '#collapsible' => TRUE, |
---|
1082 | '#collapsed' => TRUE, |
---|
1083 | ); |
---|
1084 | |
---|
1085 | // Default value widget. |
---|
1086 | $widget_form = array('#node' => (object) array('type' => $type_name)); |
---|
1087 | $widget_form_state = array('values' => array($field['field_name'] => $default_value)); |
---|
1088 | // Make sure the default value is not a required field. |
---|
1089 | $widget_field = $field; |
---|
1090 | $widget_field['required'] = FALSE; |
---|
1091 | module_load_include('inc', 'content', 'includes/content.node_form'); |
---|
1092 | $form_element = content_field_form($widget_form, $widget_form_state, $widget_field, 0); |
---|
1093 | $form['widget']['default_value_fieldset']['default_value_widget'] = $form_element; |
---|
1094 | $form['widget']['default_value_fieldset']['default_value_widget']['#tree'] = TRUE; |
---|
1095 | // Set up form info that the default value widget will need to find in the form. |
---|
1096 | $form['#field_info'] = array($widget_field['field_name'] => $widget_field); |
---|
1097 | |
---|
1098 | // Advanced: PHP code. |
---|
1099 | $form['widget']['default_value_fieldset']['advanced_options'] = array( |
---|
1100 | '#type' => 'fieldset', |
---|
1101 | '#title' => t('PHP code'), |
---|
1102 | '#collapsible' => TRUE, |
---|
1103 | '#collapsed' => empty($field['widget']['default_value_php']), |
---|
1104 | ); |
---|
1105 | |
---|
1106 | if (user_access('Use PHP input for field settings (dangerous - grant with care)')) { |
---|
1107 | $db_info = content_database_info($field); |
---|
1108 | $columns = array_keys($db_info['columns']); |
---|
1109 | foreach ($columns as $key => $column) { |
---|
1110 | $columns[$key] = t("'@column' => value for @column", array('@column' => $column)); |
---|
1111 | } |
---|
1112 | $sample = t("return array(\n 0 => array(@columns),\n // You'll usually want to stop here. Provide more values\n // if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); |
---|
1113 | |
---|
1114 | $form['widget']['default_value_fieldset']['advanced_options']['default_value_php'] = array( |
---|
1115 | '#type' => 'textarea', |
---|
1116 | '#title' => t('Code'), |
---|
1117 | '#default_value' => isset($field['widget']['default_value_php']) ? $field['widget']['default_value_php'] : '', |
---|
1118 | '#rows' => 6, |
---|
1119 | '#tree' => TRUE, |
---|
1120 | '#description' => t('Advanced usage only: PHP code that returns a default value. Should not include <?php ?> delimiters. If this field is filled out, the value returned by this code will override any value specified above. Expected format: <pre>!sample</pre>To figure out the expected format, you can use the <em>devel load</em> tab provided by <a href="@link_devel">devel module</a> on a %type content page.', array( |
---|
1121 | '!sample' => $sample, |
---|
1122 | '@link_devel' => 'http://www.drupal.org/project/devel', |
---|
1123 | '%type' => $type_name)), |
---|
1124 | ); |
---|
1125 | } |
---|
1126 | else { |
---|
1127 | $form['widget']['default_value_fieldset']['advanced_options']['markup_default_value_php'] = array( |
---|
1128 | '#type' => 'item', |
---|
1129 | '#title' => t('Code'), |
---|
1130 | '#value' => !empty($field['widget']['default_value_php']) ? '<code>'. check_plain($field['widget']['default_value_php']) .'</code>' : t('<none>'), |
---|
1131 | '#description' => empty($field['widget']['default_value_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override any value specified above.'), |
---|
1132 | ); |
---|
1133 | } |
---|
1134 | } |
---|
1135 | |
---|
1136 | $form['field'] = array( |
---|
1137 | '#type' => 'fieldset', |
---|
1138 | '#title' => t('Global settings'), |
---|
1139 | '#description' => t('These settings apply to the %field field in every content type in which it appears.', array('%field' => $field['widget']['label'])), |
---|
1140 | ); |
---|
1141 | $form['field']['required'] = array( |
---|
1142 | '#type' => 'checkbox', |
---|
1143 | '#title' => t('Required'), |
---|
1144 | '#default_value' => $field['required'], |
---|
1145 | ); |
---|
1146 | $description = t('Maximum number of values users can enter for this field.'); |
---|
1147 | if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { |
---|
1148 | $description .= '<br/>'. t("'Unlimited' will provide an 'Add more' button so the users can add as many values as they like."); |
---|
1149 | } |
---|
1150 | $description .= '<br/><strong>'. t('Warning! Changing this setting after data has been created could result in the loss of data!') .'</strong>'; |
---|
1151 | $form['field']['multiple'] = array( |
---|
1152 | '#type' => 'select', |
---|
1153 | '#title' => t('Number of values'), |
---|
1154 | '#options' => array(1 => t('Unlimited'), 0 => 1) + drupal_map_assoc(range(2, 10)), |
---|
1155 | '#default_value' => $field['multiple'], |
---|
1156 | '#description' => $description, |
---|
1157 | ); |
---|
1158 | |
---|
1159 | $form['field']['previous_field'] = array( |
---|
1160 | '#type' => 'hidden', |
---|
1161 | '#value' => serialize($field), |
---|
1162 | ); |
---|
1163 | |
---|
1164 | $additions = (array) module_invoke($field_type['module'], 'field_settings', 'form', $field); |
---|
1165 | drupal_alter('field_settings', $additions, 'form', $field); |
---|
1166 | $form['field'] = array_merge($form['field'], $additions); |
---|
1167 | |
---|
1168 | $form['submit'] = array( |
---|
1169 | '#type' => 'submit', |
---|
1170 | '#value' => t('Save field settings'), |
---|
1171 | ); |
---|
1172 | $form['type_name'] = array( |
---|
1173 | '#type' => 'value', |
---|
1174 | '#value' => $type_name, |
---|
1175 | ); |
---|
1176 | $form['field_name'] = array( |
---|
1177 | '#type' => 'value', |
---|
1178 | '#value' => $field_name, |
---|
1179 | ); |
---|
1180 | $form['type'] = array( |
---|
1181 | '#type' => 'value', |
---|
1182 | '#value' => $field['type'], |
---|
1183 | ); |
---|
1184 | $form['module'] = array( |
---|
1185 | '#type' => 'value', |
---|
1186 | '#value' => $field['module'], |
---|
1187 | ); |
---|
1188 | $form['widget']['label'] = array( |
---|
1189 | '#type' => 'value', |
---|
1190 | '#value' => $field['widget']['label'], |
---|
1191 | ); |
---|
1192 | $form['widget_module'] = array( |
---|
1193 | '#type' => 'value', |
---|
1194 | '#value' => $field['widget']['module'], |
---|
1195 | ); |
---|
1196 | $form['columns'] = array( |
---|
1197 | '#type' => 'value', |
---|
1198 | '#value' => $field['columns'], |
---|
1199 | ); |
---|
1200 | return $form; |
---|
1201 | } |
---|
1202 | |
---|
1203 | /** |
---|
1204 | * Validate a field's settings. |
---|
1205 | */ |
---|
1206 | function content_field_edit_form_validate($form, &$form_state) { |
---|
1207 | $form_values = $form_state['values']; |
---|
1208 | if (isset($form_state['change_basic']) || $form_values['op'] == t('Change basic information')) { |
---|
1209 | return; |
---|
1210 | } |
---|
1211 | |
---|
1212 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
1213 | $previous_field = unserialize($form_values['previous_field']); |
---|
1214 | $field = content_field_instance_expand($form_values); |
---|
1215 | $field['db_storage'] = content_storage_type($field); |
---|
1216 | |
---|
1217 | $field_types = _content_field_types(); |
---|
1218 | $field_type = $field_types[$field['type']]; |
---|
1219 | $widget_types = _content_widget_types(); |
---|
1220 | $widget_type = $widget_types[$field['widget']['type']]; |
---|
1221 | |
---|
1222 | if ($dropped_data = content_alter_db_analyze($previous_field, $field)) { |
---|
1223 | // @TODO |
---|
1224 | // This is a change that might result in loss of data. |
---|
1225 | // Add a confirmation form here. |
---|
1226 | // dsm($dropped_data); |
---|
1227 | } |
---|
1228 | |
---|
1229 | module_invoke($widget_type['module'], 'widget_settings', 'validate', array_merge($field, $form_values)); |
---|
1230 | module_invoke($field_type['module'], 'field_settings', 'validate', array_merge($field, $form_values)); |
---|
1231 | |
---|
1232 | // If content.module is handling the default value, |
---|
1233 | // validate the result using the field validation. |
---|
1234 | if (content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_DEFAULT) { |
---|
1235 | |
---|
1236 | // If this is a programmed form, get rid of the default value widget, |
---|
1237 | // we have the default values already. |
---|
1238 | if ($form['#programmed']) { |
---|
1239 | form_set_value(array('#parents' => array('default_value_widget')), NULL, $form_state); |
---|
1240 | return; |
---|
1241 | } |
---|
1242 | |
---|
1243 | if (isset($form_values['default_value_php']) && |
---|
1244 | ($php = trim($form_values['default_value_php']))) { |
---|
1245 | $error = FALSE; |
---|
1246 | ob_start(); |
---|
1247 | $return = eval($php); |
---|
1248 | ob_end_clean(); |
---|
1249 | if (!is_array($return)) { |
---|
1250 | $error = TRUE; |
---|
1251 | } |
---|
1252 | else { |
---|
1253 | foreach ($return as $item) { |
---|
1254 | if (!is_array($item)) { |
---|
1255 | $error = TRUE; |
---|
1256 | break; |
---|
1257 | } |
---|
1258 | } |
---|
1259 | } |
---|
1260 | if ($error) { |
---|
1261 | $db_info = content_database_info($field); |
---|
1262 | $columns = array_keys($db_info['columns']); |
---|
1263 | foreach ($columns as $key => $column) { |
---|
1264 | $columns[$key] = t("'@column' => value for @column", array('@column' => $column)); |
---|
1265 | } |
---|
1266 | $sample = t("return array(\n 0 => array(@columns),\n // You'll usually want to stop here. Provide more values\n // if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); |
---|
1267 | |
---|
1268 | form_set_error('default_value_php', t('The default value PHP code returned an incorrect value.<br/>Expected format: <pre>!sample</pre> Returned value: @value', array( |
---|
1269 | '!sample' => $sample, |
---|
1270 | '@value' => print_r($return, TRUE)))); |
---|
1271 | return; |
---|
1272 | } |
---|
1273 | else { |
---|
1274 | $default_value = $return; |
---|
1275 | $is_code = TRUE; |
---|
1276 | form_set_value(array('#parents' => array('default_value_php')), $php, $form_state); |
---|
1277 | form_set_value(array('#parents' => array('default_value')), array(), $form_state); |
---|
1278 | } |
---|
1279 | } |
---|
1280 | elseif (!empty($form_values['default_value_widget'])) { |
---|
1281 | // Fields that handle their own multiple values may use an expected |
---|
1282 | // value as the top-level key, so just pop off the top element. |
---|
1283 | $key = array_shift(array_keys($form_values['default_value_widget'])); |
---|
1284 | $default_value = $form_values['default_value_widget'][$key]; |
---|
1285 | $is_code = FALSE; |
---|
1286 | form_set_value(array('#parents' => array('default_value_php')), '', $form_state); |
---|
1287 | form_set_value(array('#parents' => array('default_value')), $default_value, $form_state); |
---|
1288 | } |
---|
1289 | if (isset($default_value)) { |
---|
1290 | $node = array(); |
---|
1291 | $node[$form_values['field_name']] = $default_value; |
---|
1292 | $field['required'] = FALSE; |
---|
1293 | $field_function = $field_type['module'] .'_field'; |
---|
1294 | |
---|
1295 | $errors_before = form_get_errors(); |
---|
1296 | |
---|
1297 | // Widget now does its own validation, should be no need |
---|
1298 | // to add anything for widget validation here. |
---|
1299 | if (function_exists($field_function)) { |
---|
1300 | $field_function('validate', $node, $field, $default_value, $form, NULL); |
---|
1301 | } |
---|
1302 | // The field validation routine won't set an error on the right field, |
---|
1303 | // so set it here. |
---|
1304 | $errors_after = form_get_errors(); |
---|
1305 | if (count($errors_after) > count($errors_before)) { |
---|
1306 | if (trim($form_values['default_value_php'])) { |
---|
1307 | form_set_error('default_value_php', t("The PHP code for 'default value' returned @value, which is invalid.", array( |
---|
1308 | '@value' => print_r($default_value, TRUE)))); |
---|
1309 | } |
---|
1310 | else { |
---|
1311 | form_set_error('default_value', t('The default value is invalid.')); |
---|
1312 | } |
---|
1313 | } |
---|
1314 | } |
---|
1315 | } |
---|
1316 | } |
---|
1317 | |
---|
1318 | /** |
---|
1319 | * Button submit handler. |
---|
1320 | */ |
---|
1321 | function content_field_edit_form_submit_update_basic($form, &$form_state) { |
---|
1322 | $form_state['change_basic'] = TRUE; |
---|
1323 | $form_state['rebuild'] = TRUE; |
---|
1324 | } |
---|
1325 | |
---|
1326 | /** |
---|
1327 | * Save a field's settings after editing. |
---|
1328 | */ |
---|
1329 | function content_field_edit_form_submit($form, &$form_state) { |
---|
1330 | module_load_include('inc', 'content', 'includes/content.crud'); |
---|
1331 | $form_values = $form_state['values']; |
---|
1332 | content_field_instance_update($form_values); |
---|
1333 | |
---|
1334 | if (isset($_REQUEST['destinations'])) { |
---|
1335 | drupal_set_message(t('Added field %label.', array('%label' => $form_values['label']))); |
---|
1336 | $form_state['redirect'] = content_get_destinations($_REQUEST['destinations']); |
---|
1337 | } |
---|
1338 | else { |
---|
1339 | drupal_set_message(t('Saved field %label.', array('%label' => $form_values['label']))); |
---|
1340 | $type = content_types($form_values['type_name']); |
---|
1341 | $form_state['redirect'] = 'admin/content/node-type/'. $type['url_str'] .'/fields'; |
---|
1342 | } |
---|
1343 | } |
---|
1344 | |
---|
1345 | /** |
---|
1346 | * Helper function to handle multipage redirects. |
---|
1347 | */ |
---|
1348 | function content_get_destinations($destinations) { |
---|
1349 | $query = array(); |
---|
1350 | $path = array_shift($destinations); |
---|
1351 | if ($destinations) { |
---|
1352 | $query['destinations'] = $destinations; |
---|
1353 | } |
---|
1354 | return array($path, $query); |
---|
1355 | } |
---|
1356 | |
---|
1357 | /** |
---|
1358 | * Content Schema Alter |
---|
1359 | * |
---|
1360 | * Alter the database schema. |
---|
1361 | * |
---|
1362 | * TODO figure out an API-safe way to use batching to update the nodes that |
---|
1363 | * will be affected by this change so the node_save() hooks will fire. |
---|
1364 | * |
---|
1365 | */ |
---|
1366 | function content_alter_schema($previous_field, $new_field) { |
---|
1367 | content_alter_db($previous_field, $new_field); |
---|
1368 | } |
---|
1369 | |
---|
1370 | /** |
---|
1371 | * Schema Alter Analyze |
---|
1372 | * |
---|
1373 | * Analyze if changes will remove columns or delta values, thus losing data. |
---|
1374 | * Do this so we can delete the data and fire the necessary hooks, before |
---|
1375 | * we actually alter the schema. |
---|
1376 | */ |
---|
1377 | function content_alter_db_analyze($previous_field, $new_field) { |
---|
1378 | $dropped = array(); |
---|
1379 | // There is no loss of data if there was no previous data. |
---|
1380 | if (empty($previous_field)) { |
---|
1381 | return $dropped; |
---|
1382 | } |
---|
1383 | |
---|
1384 | // Analyze possible data loss from changes in storage type. |
---|
1385 | if (!empty($previous_field) && !empty($new_field)) { |
---|
1386 | // Changing from multiple to not multiple data, will cause loss of all |
---|
1387 | // values greater than zero. |
---|
1388 | if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && |
---|
1389 | $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE) { |
---|
1390 | $dropped['delta'] = 0; |
---|
1391 | } |
---|
1392 | // Changing from one multiple value to another will cause loss of all |
---|
1393 | // values for deltas greater than or equal to the new multiple value. |
---|
1394 | elseif (isset($previous_field['multiple']) && isset($new_field['multiple'])) { |
---|
1395 | if ($previous_field['multiple'] > $new_field['multiple'] && |
---|
1396 | $new_field['multiple'] > 1) { |
---|
1397 | $dropped['delta'] = $new_field['multiple']; |
---|
1398 | } |
---|
1399 | } |
---|
1400 | } |
---|
1401 | |
---|
1402 | // Analyze possible data loss from changes in field columns. |
---|
1403 | $previous_schema = !empty($previous_field) ? content_table_schema($previous_field) : array('fields' => array()); |
---|
1404 | $new_schema = !empty($new_field) ? content_table_schema($new_field) : array('fields' => array()); |
---|
1405 | $dropped_columns = array_diff(array_keys($previous_schema['fields']), array_keys($new_schema['fields'])); |
---|
1406 | if ($dropped_columns) { |
---|
1407 | $dropped['columns'] = $dropped_columns; |
---|
1408 | } |
---|
1409 | // if (empty($new_schema['fields'])) { |
---|
1410 | // // No new columns, will lose all columns for a field. |
---|
1411 | // foreach ($previous_schema['fields'] as $column => $attributes) { |
---|
1412 | // $dropped['columns'][] = $column; |
---|
1413 | // } |
---|
1414 | // } |
---|
1415 | // else { |
---|
1416 | // // Check both old and new columns to see if we are deleting some columns for a field. |
---|
1417 | // foreach ($previous_schema['fields'] as $column => $attributes) { |
---|
1418 | // if (!isset($new_schema['fields'][$column])) { |
---|
1419 | // $dropped['columns'][] = $column; |
---|
1420 | // } |
---|
1421 | // } |
---|
1422 | // } |
---|
1423 | |
---|
1424 | return $dropped; |
---|
1425 | } |
---|
1426 | |
---|
1427 | /** |
---|
1428 | * Perform adds, alters, and drops as needed to synchronize the database with |
---|
1429 | * new field definitions. |
---|
1430 | */ |
---|
1431 | function content_alter_db($previous_field, $new_field) { |
---|
1432 | $ret = array(); |
---|
1433 | |
---|
1434 | // One or the other of these must be valid. |
---|
1435 | if (empty($previous_field) && empty($new_field)) { |
---|
1436 | return $ret; |
---|
1437 | } |
---|
1438 | |
---|
1439 | // Gather relevant information : schema, table name... |
---|
1440 | $previous_schema = !empty($previous_field) ? content_table_schema($previous_field) : array(); |
---|
1441 | $new_schema = !empty($new_field) ? content_table_schema($new_field) : array(); |
---|
1442 | if (!empty($previous_field)) { |
---|
1443 | $previous_db_info = content_database_info($previous_field); |
---|
1444 | $previous_table = $previous_db_info['table']; |
---|
1445 | } |
---|
1446 | if (!empty($new_field)) { |
---|
1447 | $new_db_info = content_database_info($new_field); |
---|
1448 | $new_table = $new_db_info['table']; |
---|
1449 | } |
---|
1450 | |
---|
1451 | // Deletion of a field instance: drop relevant columns and tables and return. |
---|
1452 | if (empty($new_field)) { |
---|
1453 | if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { |
---|
1454 | db_drop_table($ret, $previous_table); |
---|
1455 | } |
---|
1456 | else { |
---|
1457 | foreach ($previous_schema['fields'] as $column => $attributes) { |
---|
1458 | if (!in_array($column, array('nid', 'vid', 'delta'))) { |
---|
1459 | db_drop_field($ret, $previous_table, $column); |
---|
1460 | } |
---|
1461 | } |
---|
1462 | } |
---|
1463 | content_alter_db_cleanup(); |
---|
1464 | return $ret; |
---|
1465 | } |
---|
1466 | |
---|
1467 | // Check that content types that have fields do have a per-type table. |
---|
1468 | if (!empty($new_field)) { |
---|
1469 | $base_tablename = _content_tablename($new_field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); |
---|
1470 | if (!db_table_exists($base_tablename)) { |
---|
1471 | db_create_table($ret, $base_tablename, content_table_schema()); |
---|
1472 | } |
---|
1473 | } |
---|
1474 | |
---|
1475 | // Create new table and columns, if not already created. |
---|
1476 | if (!db_table_exists($new_table)) { |
---|
1477 | db_create_table($ret, $new_table, $new_schema); |
---|
1478 | } |
---|
1479 | else { |
---|
1480 | // Or add fields and/or indexes to an existing table. |
---|
1481 | foreach ($new_schema['fields'] as $column => $attributes) { |
---|
1482 | if (!in_array($column, array('nid', 'vid', 'delta'))) { |
---|
1483 | // Create the column if it does not exist. |
---|
1484 | if (!db_column_exists($new_table, $column)) { |
---|
1485 | db_add_field($ret, $new_table, $column, $attributes); |
---|
1486 | } |
---|
1487 | // Create the index if requested to, and it does not exist. |
---|
1488 | if (isset($new_schema['indexes'][$column]) && !content_db_index_exists($new_table, $column)) { |
---|
1489 | db_add_index($ret, $new_table, $column, $new_schema['indexes'][$column]); |
---|
1490 | } |
---|
1491 | } |
---|
1492 | } |
---|
1493 | } |
---|
1494 | |
---|
1495 | // If this is a new field, we're done. |
---|
1496 | if (empty($previous_field)) { |
---|
1497 | content_alter_db_cleanup(); |
---|
1498 | return $ret; |
---|
1499 | } |
---|
1500 | |
---|
1501 | // If the previous table doesn't exist, we're done. |
---|
1502 | // Could happen if someone tries to run a schema update from an |
---|
1503 | // content.install update function more than once. |
---|
1504 | if (!db_table_exists($previous_table)) { |
---|
1505 | content_alter_db_cleanup(); |
---|
1506 | return $ret; |
---|
1507 | } |
---|
1508 | |
---|
1509 | // If changing data from one schema to another, see if changes require that |
---|
1510 | // we drop multiple values or migrate data from one storage type to another. |
---|
1511 | $migrate_columns = array_intersect_assoc($new_schema['fields'], $previous_schema['fields']); |
---|
1512 | unset($migrate_columns['nid'], $migrate_columns['vid'], $migrate_columns['delta']); |
---|
1513 | |
---|
1514 | // If we're going from one multiple value a smaller one or to single, |
---|
1515 | // drop all delta values higher than the new maximum delta value. |
---|
1516 | // Not needed if the new multiple is unlimited or if the new table is the content table. |
---|
1517 | if ($new_table != $base_tablename && $new_field['multiple'] < $previous_field['multiple'] && $new_field['multiple'] != 1) { |
---|
1518 | db_query("DELETE FROM {". $new_table ."} WHERE delta >= ". max(1, $new_field['multiple'])); |
---|
1519 | } |
---|
1520 | |
---|
1521 | // If going from multiple to non-multiple, make sure the field tables have |
---|
1522 | // the right database structure to accept migrated data. |
---|
1523 | if ($new_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { |
---|
1524 | if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && count($previous_schema['fields'])) { |
---|
1525 | // Already using per-field storage; change multiplicity if needed. |
---|
1526 | if ($previous_field['multiple'] > 0 && $new_field['multiple'] == 0) { |
---|
1527 | db_drop_field($ret, $new_table, 'delta'); |
---|
1528 | db_drop_primary_key($ret, $new_table); |
---|
1529 | db_add_primary_key($ret, $new_table, array('vid')); |
---|
1530 | } |
---|
1531 | else if ($previous_field['multiple'] == 0 && $new_field['multiple'] > 0) { |
---|
1532 | db_add_field($ret, $new_table, 'delta', array( |
---|
1533 | 'type' => 'int', |
---|
1534 | 'unsigned' => TRUE, |
---|
1535 | 'not null' => TRUE, |
---|
1536 | 'default' => 0)); |
---|
1537 | db_drop_primary_key($ret, $new_table); |
---|
1538 | db_add_primary_key($ret, $new_table, array('vid', 'delta')); |
---|
1539 | } |
---|
1540 | } |
---|
1541 | } |
---|
1542 | |
---|
1543 | // Migrate data from per-content-type storage. |
---|
1544 | if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && |
---|
1545 | $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { |
---|
1546 | $columns = array_keys($migrate_columns); |
---|
1547 | if ($new_field['multiple']) { |
---|
1548 | db_query('INSERT INTO {'. $new_table .'} (vid, nid, delta, '. implode(', ', $columns) .') '. |
---|
1549 | ' SELECT vid, nid, 0, '. implode(', ', $columns) .' FROM {'. $previous_table .'}'); |
---|
1550 | } |
---|
1551 | else { |
---|
1552 | db_query('INSERT INTO {'. $new_table .'} (vid, nid, '. implode(', ', $columns) .') '. |
---|
1553 | ' SELECT vid, nid, '. implode(', ', $columns) .' FROM {'. $previous_table .'}'); |
---|
1554 | } |
---|
1555 | foreach ($columns as $column_name) { |
---|
1556 | db_drop_field($ret, $previous_table, $column_name); |
---|
1557 | } |
---|
1558 | } |
---|
1559 | |
---|
1560 | // Migrate data from per-field storage, and drop per-field table. |
---|
1561 | if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && |
---|
1562 | $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE) { |
---|
1563 | // In order to be able to use drupal_write_record, we need to |
---|
1564 | // rebuild the schema now. |
---|
1565 | content_alter_db_cleanup(); |
---|
1566 | if ($previous_field['multiple']) { |
---|
1567 | $result = db_query("SELECT * FROM {". $previous_table ."} c JOIN {node} n ON c.nid = n.nid WHERE delta = 0 AND n.type = '%s'", $new_field['type_name']); |
---|
1568 | } |
---|
1569 | else { |
---|
1570 | $result = db_query("SELECT * FROM {". $previous_table ."} c JOIN {node} n ON c.nid = n.nid WHERE n.type = '%s'", $new_field['type_name']); |
---|
1571 | } |
---|
1572 | $record = array(); |
---|
1573 | while ($data = db_fetch_array($result)) { |
---|
1574 | $record['nid'] = $data['nid']; |
---|
1575 | $record['vid'] = $data['vid']; |
---|
1576 | if ($previous_field['multiple']) { |
---|
1577 | $record['delta'] = $data['delta']; |
---|
1578 | } |
---|
1579 | foreach ($migrate_columns as $column => $attributes) { |
---|
1580 | if (is_null($data[$column])) { |
---|
1581 | $record[$column] = NULL; |
---|
1582 | } |
---|
1583 | else { |
---|
1584 | $record[$column] = $data[$column]; |
---|
1585 | // Prevent double serializtion in drupal_write_record. |
---|
1586 | if (isset($attributes['serialize']) && $attributes['serialize']) { |
---|
1587 | $record[$column] = unserialize($record[$column]); |
---|
1588 | } |
---|
1589 | } |
---|
1590 | } |
---|
1591 | if (db_result(db_query('SELECT COUNT(*) FROM {'. $new_table . |
---|
1592 | '} WHERE vid = %d AND nid = %d', $data['vid'], $data['nid']))) { |
---|
1593 | $keys = $new_field['multiple'] ? array('vid', 'delta') : array('vid'); |
---|
1594 | drupal_write_record($new_table, $record, $keys); |
---|
1595 | } |
---|
1596 | else { |
---|
1597 | drupal_write_record($new_table, $record); |
---|
1598 | } |
---|
1599 | } |
---|
1600 | db_drop_table($ret, $previous_table); |
---|
1601 | } |
---|
1602 | |
---|
1603 | // Change modified columns that don't involve storage changes. |
---|
1604 | foreach ($new_schema['fields'] as $column => $attributes) { |
---|
1605 | if (isset($previous_schema['fields'][$column]) && |
---|
1606 | $previous_field['db_storage'] == $new_field['db_storage']) { |
---|
1607 | if ($attributes != $previous_schema['fields'][$column]) { |
---|
1608 | if (!in_array($column, array('nid', 'vid', 'delta'))) { |
---|
1609 | db_change_field($ret, $new_table, $column, $column, $attributes); |
---|
1610 | } |
---|
1611 | } |
---|
1612 | } |
---|
1613 | } |
---|
1614 | |
---|
1615 | // Remove obsolete columns. |
---|
1616 | foreach ($previous_schema['fields'] as $column => $attributes) { |
---|
1617 | if (!isset($new_schema['fields'][$column])) { |
---|
1618 | if (!in_array($column, array('nid', 'vid', 'delta'))) { |
---|
1619 | db_drop_field($ret, $previous_table, $column); |
---|
1620 | } |
---|
1621 | } |
---|
1622 | } |
---|
1623 | |
---|
1624 | // TODO: debugging stuff - should be removed |
---|
1625 | if (module_exists('devel')) { |
---|
1626 | //dsm($ret); |
---|
1627 | } |
---|
1628 | return $ret; |
---|
1629 | } |
---|
1630 | |
---|
1631 | /** |
---|
1632 | * Helper function for handling cleanup operations when schema changes are made. |
---|
1633 | */ |
---|
1634 | function content_alter_db_cleanup() { |
---|
1635 | // Rebuild the whole database schema. |
---|
1636 | // TODO: this could be optimized. We don't need to rebuild in *every case*... |
---|
1637 | // Or do we? This affects the schema and menu and may have unfortunate |
---|
1638 | // delayed effects if we don't clear everything out at this point. |
---|
1639 | content_clear_type_cache(TRUE); |
---|
1640 | } |
---|
1641 | |
---|
1642 | /** |
---|
1643 | * Helper function to order fields and groups when theming (preprocessing) |
---|
1644 | * overview forms. |
---|
1645 | * |
---|
1646 | * The $form is passed by reference because we assign depths as parenting |
---|
1647 | * relationships are sorted out. |
---|
1648 | */ |
---|
1649 | function _content_overview_order(&$form, $field_rows, $group_rows) { |
---|
1650 | // Put weight and parenting values into a $dummy render structure |
---|
1651 | // and let drupal_render figure out the corresponding row order. |
---|
1652 | $dummy = array(); |
---|
1653 | // Group rows: account for weight. |
---|
1654 | if (module_exists('fieldgroup')) { |
---|
1655 | foreach ($group_rows as $name) { |
---|
1656 | $dummy[$name] = array('#weight' => $form[$name]['weight']['#value'], '#value' => $name .' '); |
---|
1657 | } |
---|
1658 | } |
---|
1659 | // Field rows : account for weight and parenting. |
---|
1660 | foreach ($field_rows as $name) { |
---|
1661 | $dummy[$name] = array('#weight' => $form[$name]['weight']['#value'], '#value' => $name .' '); |
---|
1662 | if (module_exists('fieldgroup')) { |
---|
1663 | if ($parent = $form[$name]['parent']['#value']) { |
---|
1664 | $form[$name]['#depth'] = 1; |
---|
1665 | $dummy[$parent][$name] = $dummy[$name]; |
---|
1666 | unset($dummy[$name]); |
---|
1667 | } |
---|
1668 | } |
---|
1669 | } |
---|
1670 | return $dummy ? explode(' ', trim(drupal_render($dummy))) : array(); |
---|
1671 | } |
---|
1672 | |
---|
1673 | /** |
---|
1674 | * Batching process for changing the field schema, |
---|
1675 | * running each affected node through node_save() first, to |
---|
1676 | * fire all hooks. |
---|
1677 | * |
---|
1678 | * TODO This is just a placeholder for now because batching can't be safely |
---|
1679 | * used with API hooks. Need to come back and figure out how to incorporate |
---|
1680 | * this and get it working properly when the fields are altered via the API. |
---|
1681 | */ |
---|
1682 | function content_alter_fields($previous_field, $new_field) { |
---|
1683 | // See what values need to be updated in the field data. |
---|
1684 | $mask = content_alter_db_mask($previous_field, $new_field); |
---|
1685 | |
---|
1686 | // We use batch processing to prevent timeout when updating a large number |
---|
1687 | // of nodes. If there is no previous data to adjust, we can just go straight |
---|
1688 | // to altering the schema, otherwise use batch processing to update |
---|
1689 | // the database one node at a time, then update the schema. |
---|
1690 | if (empty($mask)) { |
---|
1691 | return content_alter_db($previous_field, $new_field); |
---|
1692 | } |
---|
1693 | $updates = array( |
---|
1694 | 'mask' => $mask['mask'], |
---|
1695 | 'alt_mask' => $mask['alt_mask'], |
---|
1696 | 'delta' => $mask['delta'], |
---|
1697 | ); |
---|
1698 | $batch = array( |
---|
1699 | 'operations' => array( |
---|
1700 | array('content_field_batch_update', array($previous_field['field_name'] => $updates)), |
---|
1701 | array('content_alter_db', array($previous_field, $new_field)) |
---|
1702 | ), |
---|
1703 | 'finished' => '_content_alter_fields_finished', |
---|
1704 | 'title' => t('Processing'), |
---|
1705 | 'error_message' => t('The update has encountered an error.'), |
---|
1706 | 'file' => './'. drupal_get_path('module', 'content') .'/includes/content.admin.inc', |
---|
1707 | ); |
---|
1708 | batch_set($batch); |
---|
1709 | if (!empty($url)) { |
---|
1710 | batch_process($url, $url); |
---|
1711 | } |
---|
1712 | } |
---|
1713 | |
---|
1714 | /** |
---|
1715 | * Content Replace Fields 'finished' callback. |
---|
1716 | */ |
---|
1717 | function _content_alter_fields_finished($success, $results, $operations) { |
---|
1718 | if ($success) { |
---|
1719 | drupal_set_message(t('The database has been altered and data has been migrated or deleted.')); |
---|
1720 | } |
---|
1721 | else { |
---|
1722 | drupal_set_message(t('An error occurred and database alteration did not complete.'), 'error'); |
---|
1723 | $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:'); |
---|
1724 | $message .= theme('item_list', $results); |
---|
1725 | drupal_set_message($message); |
---|
1726 | } |
---|
1727 | } |
---|
1728 | |
---|
1729 | /** |
---|
1730 | * Create a mask for the column data that should be deleted in each field. |
---|
1731 | * |
---|
1732 | * This is a bit tricky. We could theoretically have some columns |
---|
1733 | * that should be set to empty and others with valid info that should |
---|
1734 | * not be emptied out. But if delta values > X are to be wiped out, they |
---|
1735 | * need to wipe out even columns that still have values. And the NULL |
---|
1736 | * values in these columns after the alteration may be enough to make |
---|
1737 | * the item 'empty', as defined by hook_content_is_empty(), even if |
---|
1738 | * some columns still have values, so all these things need to be tested. |
---|
1739 | */ |
---|
1740 | function content_alter_db_mask($previous_field, $new_field) { |
---|
1741 | // Get an array of column values that will be dropped by this |
---|
1742 | // schema change and create a mask to feed to content_batch_update. |
---|
1743 | |
---|
1744 | $dropped = content_alter_db_analyze($previous_field, $new_field); |
---|
1745 | if (empty($dropped)) { |
---|
1746 | return array(); |
---|
1747 | } |
---|
1748 | $mask = array('mask' => array()); |
---|
1749 | foreach (array_keys($previous_field['columns']) as $column_name) { |
---|
1750 | // The basic mask will empty the dropped columns. |
---|
1751 | if (isset($dropped['columns']) && in_array($column_name, $dropped['columns'])) { |
---|
1752 | $mask['mask'][$column_name] = NULL; |
---|
1753 | } |
---|
1754 | // Over the delta we'll empty all columns. |
---|
1755 | if (isset($dropped['delta'])) { |
---|
1756 | $mask['alt_mask'][$column_name] = NULL; |
---|
1757 | } |
---|
1758 | } |
---|
1759 | if (isset($dropped['delta'])) { |
---|
1760 | $mask['delta'] = $dropped['delta']; |
---|
1761 | } |
---|
1762 | return $mask; |
---|
1763 | } |
---|
1764 | |
---|
1765 | /** |
---|
1766 | * Content Field Batch Update Operation |
---|
1767 | * |
---|
1768 | * Find all nodes that contain a field and update their values. |
---|
1769 | * |
---|
1770 | * @param $updates |
---|
1771 | * an array like: |
---|
1772 | * 'field_name' => array( |
---|
1773 | * 'mask' => array() |
---|
1774 | * // Keyed array of column names and replacement values for use |
---|
1775 | * // below delta, or for all values if no delta is supplied. |
---|
1776 | * 'alt_mask' => array() |
---|
1777 | * // Optional, keyed array of column names and replacement values for use |
---|
1778 | * // at or above delta, if a delta is supplied. |
---|
1779 | * 'delta' => # |
---|
1780 | * // Optional, the number to use as the delta value where you switch from |
---|
1781 | * // one mask to the other. |
---|
1782 | * ), |
---|
1783 | */ |
---|
1784 | function content_field_batch_update($updates, &$context) { |
---|
1785 | if (empty($field)) { |
---|
1786 | $context['finished'] = 1; |
---|
1787 | return; |
---|
1788 | } |
---|
1789 | $field_name = $updates['field_name']; |
---|
1790 | $field = content_fields($field_name); |
---|
1791 | |
---|
1792 | if (!isset($context['sandbox']['progress'])) { |
---|
1793 | $db_info = content_database_info($field); |
---|
1794 | |
---|
1795 | // Might run into non-existent tables when cleaning up a corrupted |
---|
1796 | // database, like some of the old content storage changes in the |
---|
1797 | // .install files. |
---|
1798 | if (!db_table_exists($db_info['table'])) { |
---|
1799 | return $context['finished'] = 1; |
---|
1800 | } |
---|
1801 | $nodes = array(); |
---|
1802 | $result = db_query("SELECT nid FROM {". $db_info['table'] ."}"); |
---|
1803 | while ($node = db_fetch_array($result)) { |
---|
1804 | $nodes[] = $node['nid']; |
---|
1805 | } |
---|
1806 | $context['sandbox']['progress'] = 0; |
---|
1807 | $context['sandbox']['max'] = count($nodes); |
---|
1808 | $context['sandbox']['nodes'] = $nodes; |
---|
1809 | } |
---|
1810 | |
---|
1811 | // Process nodes by groups of 5. |
---|
1812 | $count = min(5, count($context['sandbox']['nodes'])); |
---|
1813 | |
---|
1814 | for ($i = 1; $i <= $count; $i++) { |
---|
1815 | // For each nid, load the node, empty the column values |
---|
1816 | // or the whole field, and re-save it. |
---|
1817 | $nid = array_shift($context['sandbox']['nodes']); |
---|
1818 | $node = content_field_replace($nid, array($updates)); |
---|
1819 | |
---|
1820 | // Store result for post-processing in the finished callback. |
---|
1821 | $context['results'][] = l($node->title, 'node/'. $node->nid); |
---|
1822 | |
---|
1823 | // Update our progress information. |
---|
1824 | $context['sandbox']['progress']++; |
---|
1825 | $context['message'] = t('Processing %title', array('%title' => $node->title)); |
---|
1826 | } |
---|
1827 | |
---|
1828 | // Inform the batch engine that we are not finished, |
---|
1829 | // and provide an estimation of the completion level we reached. |
---|
1830 | if ($context['sandbox']['progress'] != $context['sandbox']['max']) { |
---|
1831 | $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; |
---|
1832 | } |
---|
1833 | } |
---|
1834 | |
---|
1835 | /** |
---|
1836 | * Content Field Replace |
---|
1837 | * |
---|
1838 | * Replace field values in a node from an array of update values. |
---|
1839 | * |
---|
1840 | * Supply an array of one or more fields and masks of field column values |
---|
1841 | * to be replaced into field values, one mask for basic values and an optional |
---|
1842 | * different mask for values in field items equal to or higher than a |
---|
1843 | * specified delta. |
---|
1844 | * |
---|
1845 | * The masks should contain only the column values to be substituted in. |
---|
1846 | * The supplied values will be merged into the existing values to replace |
---|
1847 | * only the values in the mask, leaving all other values unchanged. |
---|
1848 | * |
---|
1849 | * The ability to set different masks starting at a delta allows the |
---|
1850 | * possibility of setting values above a certain delta to NULL prior |
---|
1851 | * to altering the database schema. |
---|
1852 | * |
---|
1853 | * @param $nid |
---|
1854 | * @param $updates |
---|
1855 | * an array like: |
---|
1856 | * 'field_name' => array( |
---|
1857 | * 'mask' => array() |
---|
1858 | * // Keyed array of column names and replacement values for use |
---|
1859 | * // below delta, or for all values if no delta is supplied. |
---|
1860 | * 'alt_mask' => array() |
---|
1861 | * // Optional, keyed array of column names and replacement values for use |
---|
1862 | * // at or above delta, if a delta is supplied. |
---|
1863 | * 'delta' => # |
---|
1864 | * // Optional, the number to use as the delta value where you switch from |
---|
1865 | * // one mask to the other. |
---|
1866 | * ), |
---|
1867 | */ |
---|
1868 | function content_field_replace($nid, $updates) { |
---|
1869 | $node = node_load($nid, NULL, TRUE); |
---|
1870 | foreach ($updates as $field_name => $update) { |
---|
1871 | $items = isset($node->$field_name) ? $node->$field_name : array(); |
---|
1872 | foreach ($items as $delta => $value) { |
---|
1873 | $field_mask = (isset($update['delta']) && isset($update['alt_mask']) && $delta >= $update['delta']) ? $update['alt_mask'] : $mask['mask']; |
---|
1874 | // Merge the mask into the field values to do the replacements. |
---|
1875 | $items[$delta] = array_merge($items[$delta], $field_mask); |
---|
1876 | } |
---|
1877 | // Test if the new values will make items qualify as empty. |
---|
1878 | $items = content_set_empty($field, $items); |
---|
1879 | $node->$field_name = $items; |
---|
1880 | } |
---|
1881 | node_save($node); |
---|
1882 | return $node; |
---|
1883 | } |
---|
1884 | |
---|
1885 | /** |
---|
1886 | * Helper form element validator : integer. |
---|
1887 | */ |
---|
1888 | function _element_validate_integer($element, &$form_state) { |
---|
1889 | $value = $element['#value']; |
---|
1890 | if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { |
---|
1891 | form_error($element, t('%name must be an integer.', array('%name' => $element['#title']))); |
---|
1892 | } |
---|
1893 | } |
---|
1894 | |
---|
1895 | /** |
---|
1896 | * Helper form element validator : integer > 0. |
---|
1897 | */ |
---|
1898 | function _element_validate_integer_positive($element, &$form_state) { |
---|
1899 | $value = $element['#value']; |
---|
1900 | if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) { |
---|
1901 | form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title']))); |
---|
1902 | } |
---|
1903 | } |
---|
1904 | |
---|
1905 | /** |
---|
1906 | * Helper form element validator : number. |
---|
1907 | */ |
---|
1908 | function _element_validate_number($element, &$form_state) { |
---|
1909 | $value = $element['#value']; |
---|
1910 | if ($value != '' && !is_numeric($value)) { |
---|
1911 | form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); |
---|
1912 | } |
---|
1913 | } |
---|