/[drupal]/contributions/modules/conditional_fields/conditional_fields.module
ViewVC logotype

Contents of /contributions/modules/conditional_fields/conditional_fields.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.7 - (show annotations) (download) (as text)
Mon May 11 13:27:53 2009 UTC (6 months, 2 weeks ago) by peterpoe
Branch: MAIN
CVS Tags: HEAD
Changes since 1.6: +512 -311 lines
File MIME type: text/x-php
Synching HEAD.
1 <?php
2 // $Id$
3
4 // Fields settings
5 define ('C_FIELDS_JS_NO', 0);
6 define ('C_FIELDS_JS_HIDE', 1);
7 define ('C_FIELDS_JS_DISABLE', 2);
8
9 define ('C_FIELDS_ANIMATION_NO', 0);
10 define ('C_FIELDS_ANIMATION_FADE', 1);
11 define ('C_FIELDS_ANIMATION_SLIDE', 2);
12
13 define ('C_FIELDS_ORPHANED_HIDE', 0);
14 define ('C_FIELDS_ORPHANED_SHOW_TRIGGERED', 1);
15 define ('C_FIELDS_ORPHANED_SHOW_ALL', 2);
16
17 function conditional_fields_help($path, $arg) {
18 switch ($path) {
19 case 'admin/content/types/%/conditional':
20 return t('These settings only apply to the conditional fields of this content type.');
21 break;
22 }
23 }
24
25 /**
26 * Implementation of hook_menu().
27 */
28 function conditional_fields_menu() {
29 $items = array();
30
31 foreach (node_get_types() as $type) {
32 $content_type = content_types($type->type);
33
34 $items['admin/content/node-type/' . $content_type['url_str'] . '/conditional'] = array(
35 'title' => 'Conditional fields',
36 'page callback' => 'drupal_get_form',
37 'page arguments' => array('_conditional_fields_admin', $content_type['type']),
38 'access arguments' => array('administer conditional fields'),
39 'type' => MENU_LOCAL_TASK,
40 'weight' => 5
41 );
42 }
43
44 return $items;
45 }
46
47 /**
48 * Implementation of hook_perm()
49 */
50 function conditional_fields_perm() {
51 return array('administer conditional fields');
52 }
53
54 /**
55 * Administration form for conditional fields
56 */
57 function _conditional_fields_admin($form, $type) {
58 $form = array();
59
60 $form['js_set'] = array(
61 '#type' => 'fieldset',
62 '#title' => t('User Interface options'),
63 '#collapsible' => TRUE
64 );
65 $form['js_set']['js'] = array(
66 '#type' => 'radios',
67 '#options' => array(
68 C_FIELDS_JS_NO => t("Don't use javascript. Fields are only hidden on node view."),
69 C_FIELDS_JS_HIDE => t('Hide untriggered fields.'),
70 C_FIELDS_JS_DISABLE => t('Disable untriggered fields.')
71 ),
72 '#title' => 'Javascript',
73 '#description' => t('Choose the desired javascript behaviour in node editing forms.'),
74 '#default_value' => variable_get('c_fields_js_' . $type, C_FIELDS_JS_HIDE)
75 );
76 $form['js_set']['anim'] = array(
77 '#type' => 'fieldset',
78 '#title' => t('Animation'),
79 '#description' => t("These settings have effect only if you select the 'Hide untriggered fields' option above."),
80 );
81 $form['js_set']['anim']['animation'] = array(
82 '#type' => 'radios',
83 '#title' => t('Type'),
84 '#default_value' => variable_get('c_fields_animation_' . $type, C_FIELDS_ANIMATION_NO),
85 '#options' => array(
86 C_FIELDS_ANIMATION_NO => t('No animation'),
87 C_FIELDS_ANIMATION_FADE => t('Slide down'),
88 C_FIELDS_ANIMATION_SLIDE => t('Fade'),
89 ),
90 );
91 $form['js_set']['anim']['anim_speed'] = array(
92 '#type' => 'radios',
93 '#title' => t('Speed'),
94 '#description' => t('The speed at which the animation is performed. Slow = 600ms; Normal = 400ms; Fast = 200ms.'),
95 '#default_value' => variable_get('c_fields_anim_speed_' . $type, 'normal'),
96 '#options' => array(
97 'slow' => t('Slow'),
98 'normal' => t('Normal'),
99 'fast' => t('Fast'),
100 ),
101 );
102 $form['orphaned'] = array(
103 '#type' => 'fieldset',
104 '#title' => t('Orphaned controlled fields settings'),
105 '#description' => t('Configure the visibility/editability of controlled fields whose controlling fields are not visible/editable.'),
106 '#collapsible' => TRUE,
107 '#collapsed' => TRUE
108 );
109 $options = array(C_FIELDS_ORPHANED_HIDE => t('Hide'), C_FIELDS_ORPHANED_SHOW_TRIGGERED => t('Show only triggered'), C_FIELDS_ORPHANED_SHOW_ALL => t('Show all'));
110 $form['orphaned']['orphaned_view'] = array(
111 '#type' => 'radios',
112 '#title' => t('On node view'),
113 '#options' => $options,
114 '#default_value' => variable_get('c_fields_view_' . $type, C_FIELDS_ORPHANED_SHOW_TRIGGERED)
115 );
116 $form['orphaned']['orphaned_edit'] = array(
117 '#type' => 'radios',
118 '#title' => t('On node edit'),
119 '#options' => $options,
120 '#default_value' => variable_get('c_fields_edit_' . $type, C_FIELDS_ORPHANED_SHOW_TRIGGERED)
121 );
122 $form['show_all'] = array(
123 '#type' => 'checkbox',
124 '#title' => t('Administrators see all fields'),
125 '#description' => t('Select this box to let users with the <a href="@access-control-page">administer conditional fields</a> permission to view all controlled fields of a node.', array('@access-control-page' => url('admin/user/permissions', array('fragment' => 'module-conditional_fields')))),
126 '#default_value' => variable_get('c_fields_show_all_' . $type, 0)
127 );
128 $form['reset'] = array(
129 '#type' => 'checkbox',
130 '#title' => t('Reset'),
131 '#description' => t('Delete all conditional fields configured for this content type. This will delete the conditional fields settings, not the fields themselves.'),
132 '#default_value' => 0
133 );
134 $form['submit'] = array(
135 '#type' => 'submit',
136 '#value' => t('Save'),
137 );
138
139 return $form;
140 }
141
142 function _conditional_fields_admin_submit($form, &$form_state) {
143 $type = $form['#parameters'][2];
144
145 if ($form_state['values']['reset'] == 1) {
146 conditional_fields_node_type_delete($type);
147 $message = t(' All configured conditional fields have been deleted.');
148 }
149
150 variable_set('c_fields_js_' . $type, $form_state['values']['js']);
151 variable_set('c_fields_animation_' . $type, $form_state['values']['animation']);
152 variable_set('c_fields_anim_speed_' . $type, $form_state['values']['anim_speed']);
153 variable_set('c_fields_view_' . $type, $form_state['values']['orphaned_view']);
154 variable_set('c_fields_edit_' . $type, $form_state['values']['orphaned_edit']);
155 variable_set('c_fields_show_all_' . $type, $form_state['values']['show_all']);
156
157 drupal_set_message(t('Conditional fields options for this content type saved.') . $message);
158 }
159
160 /**
161 * Implementation of hook_nodeapi()
162 */
163 function conditional_fields_nodeapi(&$node, $op, $teaser, $page) {
164 if ($op == 'view') {
165 // First we check if there any conditional fields in this node type
166 $type = content_types($node->type);
167 if (!$data = conditional_fields_load_data($type['type'])) {
168 return;
169 }
170
171 // Then we check if user is an administrator and this content type
172 // and has the show hidden fields pref enabled
173 if (user_access('administer conditional fields') && variable_get('c_fields_show_all_' . $type['type'], 0)) {
174 return;
175 }
176
177 foreach ($data as $field) {
178
179 // We might have to look for the field in a group
180 $controlled_group = conditional_fields_get_group($node->type, $field['field_name']);
181 $controlling_group = conditional_fields_get_group($node->type, $field['control_field_name']);
182
183 // The controlled field is not in a group and is not viewed for other reasons. Skip.
184 if (!$controlled_group && !$node->content[$field['field_name']]) {
185 continue;
186 }
187
188 // The controlling field is not in a group and is not viewed for other reasons. Skip.
189 if (!$controlling_group && !$node->content[$field['control_field_name']]) {
190 continue;
191 }
192
193 // The controlled field is in a group and is not viewed for other reasons. Skip.
194 if ($controlled_group && !$node->content[$controlled_group]['group'][$field['field_name']]) {
195 continue;
196 }
197
198 // The controlling field is in a group and is not viewed for other reasons. Skip.
199 if ($controlling_group && !$node->content[$controlling_group]['group'][$field['control_field_name']]) {
200 continue;
201 }
202
203 // Create an array with the selected controlling field's values
204 // Check if the controlling field is viewed as well
205 if (!$controlling_group && $node->content[$field['control_field_name']] ||
206 $controlling_group && $node->content[$controlling_group]['group'][$field['control_field_name']]) {
207 foreach ($node->$field['control_field_name'] as $value) {
208 $current_values[$field['control_field_name']] = $value['value'];
209 if (!empty($value['value'])) {
210 $viewed = TRUE;
211 }
212 }
213 }
214
215 if ($viewed) {
216 // Hide the controlled field if it is not triggered
217 if (!conditional_fields_is_triggered($current_values[$field['control_field_name']], $field['trigger_values'])) {
218 if ($controlled_group) {
219 unset($node->content[$controlled_group]['group'][$field['field_name']]);
220 }
221 else {
222 unset($node->content[$field['field_name']]);
223 }
224 }
225 }
226 else if ($node->content[$field['field_name']]) {
227 // Apply orphaned fields settings
228 $orphaned_settings = variable_get('c_fields_view_' . $node->type, C_FIELDS_ORPHANED_SHOW_TRIGGERED);
229 switch ($orphaned_settings) {
230 case C_FIELDS_ORPHANED_SHOW_TRIGGERED:
231 // If the field was triggered, don't hide it
232 if (conditional_fields_is_triggered($current_values[$field['control_field_name']], $field['trigger_values'])) {
233 break;
234 }
235 case C_FIELDS_ORPHANED_HIDE:
236 // We hide the field
237 if ($controlled_group) {
238 unset($node->content[$controlled_group]['group'][$field['field_name']]);
239 }
240 else {
241 unset($node->content[$field['field_name']]);
242 }
243 case C_FIELDS_ORPHANED_SHOW_ALL:
244 // Nothing to do...
245 break;
246 }
247 }
248 }
249 }
250 }
251
252 /**
253 * Implementation of hook_form_alter()
254 */
255 function conditional_fields_form_alter(&$form, $form_state, $form_id) {
256 switch ($form_id) {
257 case 'content_field_edit_form':
258 if ($form['widget'] && $form_state['post']['op'] != t('Change basic information')) {
259 conditional_fields_content_admin_field($form);
260 }
261 break;
262 case 'fieldgroup_group_edit_form':
263 conditional_fields_fieldgroup_group_edit_form($form);
264 break;
265 case 'content_field_overview_form':
266 // Find conditional fields, mark them, and disable group select for them
267 $conditional_fields = conditional_fields_field_overview_form($form);
268 break;
269 case '_content_admin_field_remove':
270 $form['#submit'] = $form['#submit'] + array('_conditional_fields_content_admin_field_remove_submit' => array());
271 break;
272 case 'fieldgroup_remove_group':
273 $form['#submit'] = $form['#submit'] + array('_conditional_fields_fieldgroup_remove_group_submit' => array('group_name' => arg(5)));
274 break;
275 case $form['type']['#value'] . '_node_form':
276 conditional_fields_node_editing_form($form, $form_state);
277 break;
278 case 'content_add_more_js':
279 // Handle ahah multiple fields
280 $key = array_keys($form);
281 if (db_result(db_query("SELECT COUNT(*) FROM {conditional_fields} WHERE field_name = '%s'", $key[0]))) {
282 $form[$key[0]]['#prefix'] .= '<div id="conditional-' . conditional_fields_form_clean_id($key[0]) . '" class="conditional-field controlled-field">';
283 $form[$key[0]]['#suffix'] = $form[$key[0]]['#suffix'] . '</div>';
284 foreach (element_children($form[$key[0]]) as $element) {
285 conditional_fields_unset_required_field($form[$key[0]][$element]);
286 }
287 }
288 break;
289 case 'content_copy_import_form':
290 $form['#submit'][] = 'conditional_fields_import';
291 break;
292 }
293 }
294
295 /**
296 * Alteration of the field editing form
297 */
298 function conditional_fields_content_admin_field(&$form) {
299
300 $type = array();
301 $type = content_types($form['type_name']['#value']);
302
303 // Load conditional fields data.
304 $data = conditional_fields_load_data($type['type']);
305
306 // Get all fields controlled by this one.
307 $controlled_fields = array();
308 foreach ($data as $row) {
309 if ($row['control_field_name'] == $form['field_name']['#value']) {
310 $controlled_fields[$row['field_name']] = $row['trigger_values'];
311 }
312 }
313
314 // Add extra validation funcion
315 $form['#validate'] = array_merge(array('conditional_fields_content_admin_field_validate'), $form['#validate']);
316
317 $form['#controlled_fields'] = $controlled_fields;
318
319 // Check if field is in a group
320 if (module_exists('fieldgroup')) {
321 $controlled_field_in_group = fieldgroup_get_group($form['type_name']['#value'], $form['field_name']['#value']);
322 }
323
324 // Get available fields, which are:
325 foreach ($type['fields'] as $field) {
326 // - Not this one :)
327 if ($field['field_name'] == $form['field_name']['#value']) {
328 continue;
329 }
330 // - AND not controlled by this one
331 if ($controlled_fields[$field['field_name']]) {
332 continue;
333 }
334 // - AND fields not in a group (if this field isn't in a group), or fields in the same group.
335 if (isset($controlled_field_in_group)) {
336 if ($controlled_field_in_group != fieldgroup_get_group($field['type_name'], $field['field_name'])) {
337 continue;
338 }
339 }
340 // - AND with Allowed values
341 if (!$allowed_values[$field['field_name']] = content_allowed_values($field)) {
342 continue;
343 }
344 $available_fields[$field['field_name']] = $field;
345 }
346
347 if (isset($available_fields)) {
348 // Add controlled fields notice
349 if (!empty($controlled_fields)) {
350 foreach ($controlled_fields as $field => $trigger_values) {
351 if (substr($field, 0, 6) == 'group_') { // It's a group
352 $rows[] = array($field, implode($trigger_values, ', '), t('group'), '<a href="' . url('admin/content/node-type/' . $type['url_str'] . '/groups/' . $field, array('fragment' => 'conditional-fields-settings')) . '">' . t('edit') . '</a>');
353 }
354 else { // It's a field
355 $rows[] = array($field, implode($trigger_values, ', '), t('field'), '<a href="' . url('admin/content/node-type/' . $type['url_str'] . '/fields/' . $field, array('fragment' => 'conditional-fields-settings')) . '">' . t('edit') . '</a>');
356 }
357 }
358 }
359
360 if ($rows) {
361 $description = t('<p>Below is a list of all fields and groups controlled by this field. If you want to make this field controllable, you have to clear the settings for each controlled field.</p>') . theme('table', array(t('Name'), t('Trigger values'), t('Type'), t('Options')), $rows);
362 }
363 else {
364 // Add extra submission funcion
365 $form['#submit'] = array_merge(array('conditional_fields_forms_submit'), $form['#submit']);
366 }
367
368 $form['widget'] = _conditional_fields_build_form($type, $form['widget'], $form['field_name']['#value'], $controlled_fields, $available_fields, $allowed_values, 'field', $description);
369 }
370 return;
371 }
372
373 /**
374 * Alteration of the fieldgroup editing form
375 */
376 function conditional_fields_fieldgroup_group_edit_form(&$form) {
377 if (!user_access('administer conditional fields')) {
378 return;
379 }
380
381 // Find fields with allowed values which are not inside a group
382 foreach ($form['#content_type']['fields'] as $field) {
383 $in_group = fieldgroup_get_group($form['#content_type']['type'], $field['field_name']);
384 if (!$in_group) {
385 if ($allowed_values[$field['field_name']] = content_allowed_values($field)) {
386 $available_fields[$field['field_name']] = $field;
387 }
388 }
389 }
390
391 if (isset($available_fields)) {
392 $form = _conditional_fields_build_form($form['#content_type'], $form, $form['group_name']['#default_value'], array(), $available_fields, $allowed_values, 'group');
393 // Add extra validation funcion
394 $form['#validate'][] = 'conditional_fields_content_admin_field_validate';
395 // Add extra submission funcion
396 $form['#submit'] = array_merge(array('conditional_fields_forms_submit'), $form['#submit']);
397 }
398 return;
399 }
400
401 /**
402 * This adds conditional fields settings in the field and fieldgroup editing forms.
403 * Valid choices for $op are 'field' and 'group'
404 */
405 function _conditional_fields_build_form($type, $form, $control_field, $controlled_fields, $available_fields, $allowed_values, $op, $description = NULL) {
406 if (!$description) {
407 $description = t('Choose which allowed values of available controlling fields will trigger this @context, making it visible both in node editing and view. If no value is set, the @context will be always visible. Only fields and groups within the same group as this one, and with <em>Allowed values</em> set, are available for control.', array('@context' => t($op))) . $description;
408 }
409
410 $form['conditional_fields'] = array(
411 '#type' => 'fieldset',
412 '#title' => t('Conditional fields settings'),
413 '#tree' => TRUE,
414 '#collapsible' => TRUE,
415 '#collapsed' => TRUE,
416 '#description' => $description,
417 '#weight' => 8,
418 '#attributes' => array('id' => 'conditional-fields-settings'),
419 );
420
421 if (empty($controlled_fields)) { // Disallow nested conditional fields
422
423 $default_values = conditional_fields_default_values($control_field, $available_fields);
424
425 // Create selection lists
426 foreach ($available_fields as $field) {
427 $allowed_values[$field['field_name']] = array('conditional_field_no_value' => t('- Not controlling -')) + $allowed_values[$field['field_name']];
428
429 if (isset($default_values[$field['field_name']]) && $default_values[$field['field_name']] != FALSE) {
430 $default_value = $default_values[$field['field_name']];
431 $set = TRUE;
432 }
433 else {
434 $default_value = 'conditional_field_no_value';
435 }
436
437 $form['conditional_fields'][$field['field_name']] = array(
438 '#type' => 'select',
439 '#multiple' => TRUE,
440 '#title' => t($field['widget']['label']),
441 // To do: set right url for groups
442 '#description' => t('<a href="@edit-field">Edit the allowed values</a> of the %field-name field.', array('@edit-field' => url('admin/content/node-type/' . $type['url_str'] . '/fields/' . $field['field_name'], array('query' => 'destination=admin/content/node-type/' . arg(3) . '/' . arg(4) . '/' . arg(5), 'fragment' => 'edit-allowed-values')), '%field-name' => t($field['widget']['label'])) ),
443 '#options' => $allowed_values[$field['field_name']],
444 '#default_value' => $default_value,
445 );
446
447 }
448
449 // Don't collapse the settings if we already have a configuration
450 if ($set) {
451 $form['conditional_fields']['#collapsed'] = FALSE;
452 }
453 }
454
455 return $form;
456 }
457
458 /**
459 * Check selection of values
460 */
461 function conditional_fields_content_admin_field_validate($form, &$form_state) {
462 if ($form_state['values']['conditional_fields']) {
463 foreach ($form_state['values']['conditional_fields'] as $available_field => $trigger_values) {
464 // Disallow selecting Not set and values at the same time
465 if ($trigger_values['conditional_field_no_value'] && count($trigger_values) > 1) {
466 form_set_error('conditional_fields][' . $available_field, t("You cannot select 'Not controlling' and other values at the same time."));
467 }
468 }
469 }
470 else {
471 // Warn user on allowed values change
472 if (!$GLOBALS['content_copy'] && !empty($form['#controlled_fields']) && $form_state['values']['allowed_values'] != $form_state['#field_info'][$form['values']['field_name']]['allowed_values']) {
473 drupal_set_message(t('If you removed one or more allowed values from the field, you might have to edit its controlled Conditional fields and/or fieldgroups settings.'), 'error');
474 }
475 }
476 }
477
478
479 /**
480 * Handle saving of conditional field settings.
481 * The controlled field can be either a field or a group
482 */
483 function conditional_fields_forms_submit($form, &$form_state) {
484
485 isset($form_state['values']['field_name']) ? $controlled_field = $form_state['values']['field_name'] : $controlled_field = $form_state['values']['group_name'];
486 isset($form['#field']['type_name']) ? $type = $form['#field']['type_name'] : $type = $form['#content_type']['type'];
487
488 conditional_fields_save_field($type, $controlled_field, $form_state['values']['conditional_fields']);
489 }
490
491 /**
492 * Alteration of the node editing form
493 */
494 function conditional_fields_node_editing_form(&$form, $form_state) {
495 $type_name = $form['type']['#value'];
496
497 // Do nothing if there are no conditional fields
498 if (!$data = conditional_fields_load_data($type_name)) {
499 return;
500 }
501
502 // Remove from data fields that user can't edit
503 // and apply orphaned fields settings
504 $orphaned_settings = variable_get('c_fields_edit_' . $type_name, C_FIELDS_ORPHANED_SHOW_TRIGGERED);
505 foreach ($data as $key => $field) {
506 // Store group name or FALSE if no group
507 $field['in_group'] = conditional_fields_get_group($type_name, $field['field_name']);
508
509 // First check access
510 if ((!$field['in_group'] && $form[$field['field_name']]['#access'] === FALSE) ||
511 ($field['in_group'] && $form[$field['in_group']][$field['field_name']]['#access'] == FALSE)) {
512 unset($data[$key]);
513 continue;
514 }
515
516 // Check if controlling field is present. If not, apply orphaned fields settings
517 $show_triggered = FALSE;
518 switch ($orphaned_settings) {
519 case C_FIELDS_ORPHANED_SHOW_TRIGGERED:
520 // We will only hide untriggered fields
521 // E.g.: fields whose controlling fields have a triggering default value
522 // or a triggering value set by other users with permissions
523 $show_triggered = TRUE;
524 case C_FIELDS_ORPHANED_HIDE:
525 // Check if the controlling field is in a group
526 $field['control_field_in_group'] = conditional_fields_get_group($type_name, $field['control_field_name']);
527 // Check if the controlling field is in form
528 // If not, unset controlled field
529 if ($field['control_field_in_group']) {
530 if (!$form[$field['control_field_in_group']][$field['control_field_name']] ||
531 $form[$field['control_field_in_group']][$field['control_field_name']]['#type'] == 'markup' ||
532 $form[$field['control_field_in_group']][$field['control_field_name']]['#access'] == FALSE) {
533 if (!$show_triggered || !in_array($form_state['values'][$field['control_field_name']][0]['value'], $field['trigger_values'])) {
534 unset($form[$field['control_field_in_group']][$field['field_name']]);
535 unset($data[$key]);
536 }
537 }
538 }
539 else {
540 if (!$form[$field['control_field_name']] ||
541 $form[$field['control_field_name']]['#type'] == 'markup' ||
542 $form[$field['control_field_name']]['#access'] == FALSE) {
543 if (!$show_triggered || !in_array($form_state['values'][$field['control_field_name']][0]['value'], $field['trigger_values'])) {
544 unset($form[$field['field_name']]);
545 unset($data[$key]);
546 }
547 }
548 }
549 break;
550 case C_FIELDS_ORPHANED_SHOW_ALL:
551 // Do nothing: the default behavior is ok
552 break;
553 }
554 }
555
556 // Data could be empty by now
557 if (empty($data)) {
558 return;
559 }
560
561 // We build a javascript variable:
562 // - 'controlling_fields' -> An object contaninig all ids of controlling fields, with their controlled fields and groups
563 // To do: look if we should make this themeable
564 foreach ($data as $row) {
565 // Add javascript settings for this field
566 $settings['controlling_fields']['#conditional-' . conditional_fields_form_clean_id($row['control_field_name'])]['#conditional-' . conditional_fields_form_clean_id($row['field_name'])] = array('field_id' => '#conditional-' . conditional_fields_form_clean_id($row['field_name']), 'trigger_values' => $row['trigger_values']);
567 // To do: feature, add an array of controlled fields to js to allow for multiple controlling fields for a field.
568
569 // Build helper arrays
570 $controlling_fields[$row['control_field_name']] = $row['control_field_name'];
571 $controlled_fields[$row['field_name']] = $row['field_name'];
572 }
573
574 // Controlled fields and fields inside controlled groups should only be required when user triggers them.
575 // Since required input check is hardcoded in _form_validate, we need to unset it here.
576 // We will check triggered fields in a custom validation form.
577 // Here we also add enclosing divs for easier javascript handling to controlling fields and to controlled fields and groups
578 $required_fields = array();
579
580 foreach (element_children($form) as $element) {
581 // Fields
582 if (substr($element, 0, 6) == 'field_') {
583 if ($form[$element]['#theme'] == 'content_multiple_values') {
584 $form[$element]['#conditional_fields_multiple'] = TRUE;
585 }
586 if ($controlling_fields[$element]) {
587 $form[$element]['#controlling_field'] = $element;
588 $form[$element]['#theme'] = array('conditional_fields_form_item');
589 }
590 else if ($controlled_fields[$element]) {
591 if ($form[$element]['#required']) {
592 conditional_fields_unset_required_field($form[$element]);
593 $required_fields[$element] = array('field' => $element);
594 }
595 $form[$element]['#controlled_field'] = $element;
596 $form[$element]['#theme'] = array('conditional_fields_form_item');
597 }
598 }
599 else if (substr($element, 0, 6) == 'group_') {
600 // Groups
601 if ($controlled_fields[$element]) {
602 // Group markup is still hardcoded.
603 $form[$element]['#prefix'] = '<div id="conditional-' . conditional_fields_form_clean_id($element) . '" class="conditional-field controlled-field">';
604 $form[$element]['#suffix'] = '</div>';
605 }
606 // Fields in groups
607 foreach (element_children($form[$element]) as $group_element) {
608 // All required fields inside a conditional group must be handled by conditional fields
609 if ($controlled_fields[$group_element] && $form[$element][$group_element]['#required']) {
610 conditional_fields_unset_required_field($form[$element][$group_element]);
611 $required_fields[$group_element] = array('field' => $group_element, 'in_group' => $element);
612 $form[$element][$group_element]['#theme'] = array('conditional_fields_form_item');
613 }
614 // Manage also conditional fields inside normal groups
615 if ($controlling_fields[$group_element]) {
616 $form[$element][$group_element]['#controlling_field'] = $group_element;
617 $form[$element][$group_element]['#theme'] = array('conditional_fields_form_item');
618 }
619 else if ($controlled_fields[$group_element]) {
620 // Manage multiple ahah fields
621 if ($form[$element][$group_element]['#theme'] == 'content_multiple_values') {
622 $form[$element][$group_element]['#conditional_fields_multiple'] = TRUE;
623 }
624 $form[$element][$group_element]['#controlled_field'] = $group_element;
625 $form[$element][$group_element]['#theme'] = array('conditional_fields_form_item');
626 }
627 }
628 }
629 }
630
631 // Apply user interface settings
632 $ui_settings = variable_get('c_fields_js_' . $type_name, C_FIELDS_JS_HIDE);
633 switch ($ui_settings) {
634 case C_FIELDS_JS_DISABLE:
635 $settings['ui_settings'] = 'disable';
636 break;
637 case C_FIELDS_JS_HIDE:
638 $settings['ui_settings']['animation'] = (int)variable_get('c_fields_animation_' . $type_name, C_FIELDS_ANIMATION_NO);
639 $settings['ui_settings']['anim_speed'] = variable_get('c_fields_anim_speed_' . $type_name, "normal");
640 break;
641 }
642
643 if ($ui_settings != C_FIELDS_JS_NO) {
644 conditional_fields_add_js($settings);
645 }
646
647 // Pass variables for validation
648 $form['#conditional_fields']['data'] = $data;
649 $form['#conditional_fields']['required_fields'] = $required_fields;
650 $form['#conditional_fields']['settings'] = $settings;
651
652 // Add extra validation function
653 $form['#validate'] = array_merge(array('conditional_fields_node_editing_form_validate'), (array)$form['#validate']);
654 }
655
656 /**
657 * Get the fieldgroup of a field
658 */
659 function conditional_fields_get_group($type_name, $field_name) {
660 if (!module_exists('fieldgroup')) {
661 return FALSE;
662 }
663 return fieldgroup_get_group($type_name, $field_name);
664 }
665
666 /**
667 * Validation for node editing form.
668 */
669 function conditional_fields_node_editing_form_validate($form, &$form_state) {
670 // When form fails validation, hook_form_alter is not called, so we add js here too
671 if ($form['#conditional_fields']['settings']['ui_settings']) {
672 conditional_fields_add_js($form['#conditional_fields']['settings']);
673 }
674
675 foreach ($form['#conditional_fields']['data'] as $row) {
676 // Check if the controlling field was triggered
677 $triggered = conditional_fields_is_triggered($form_state['values'][$row['control_field_name']], $row['trigger_values']);
678
679 $required_fields = $form['#conditional_fields']['required_fields'];
680
681 // Controlled field
682 if (substr($row['field_name'], 0, 6) == 'field_') {
683
684 if ($triggered) {
685
686 // Check required
687 if (!empty($required_fields) && $required_fields[$row['field_name']]) {
688
689 // Check if the controlled field is empty
690 if (conditional_fields_check_empty($form_state['values'][$row['field_name']])) {
691
692 // Check whether the controlled field is in a group or not and set error accordingly
693 if (!isset($required_fields[$row['field_name']]['in_group'])) {
694 form_error($form[$row['field_name']],
695 t('!name field is required.',
696 array('!name' => $form[$row['field_name']]['#title'])));
697 }
698 else {
699 form_error($form[$required_fields[$row['field_name']]['in_group']][$row['field_name']],
700 t('!name field is required.',
701 array('!name' => $form[$required_fields[$row['field_name']]['in_group']][$row['field_name']]['#title'])));
702 }
703 }
704 }
705 }
706 else {
707 // Do not submit values of controlled fields which were not triggered (except on preview)
708 if (!in_array('node_form_build_preview', $form_state['submit_handlers'])) {
709 foreach ($form['#field_info'][$row['field_name']]['widget']['default_value'] as $delta => $value) {
710 $form_state['values'][$row['field_name']][$delta]['value'] = $value['value'];
711 }
712 }
713 }
714 }
715
716 // Controlled group
717 else if (substr($row['field_name'], 0, 6) == 'group_') {
718
719 foreach (element_children($form[$row['field_name']]) as $field_in_group) {
720
721 // Check if the controlling field was triggered
722 if ($triggered) {
723
724 // Check required
725 if (!empty($required_fields)
726 && $required_fields[$field_in_group]
727 && !$form[$row['field_name']][$field_in_group]['#controlled_field']
728 && conditional_fields_check_empty($form_state['values'][$field_in_group])) {
729
730 form_error($form[$row['field_name']][$field_in_group],
731 t('!name field is required.',
732 array('!name' => $form[$row['field_name']][$field_in_group]['#title'])));
733 }
734 }
735 else {
736 // Do not submit values of controlled fields which were not triggered (except on preview)
737 if (!in_array('node_form_build_preview', $form_state['submit_handlers'])) {
738 foreach ($form['#field_info'][$field_in_group]['widget']['default_value'] as $delta => $value) {
739 $form_state['values'][$field_in_group][$delta]['value'] = $value['value'];
740 }
741 }
742 }
743 }
744 }
745 }
746 }
747
748 /**
749 * Checks if a submitted field value is empty
750 */
751 function conditional_fields_check_empty($field) {
752 // Normal fields
753 if (isset($field[0]['value'])) {
754 $value = $field[0]['value'];
755 }
756 // Node reference
757 else if (isset($field[0]['nid'])) {
758 $value = $field[0]['nid'];
759 }
760
761 if (!count($value) || (is_string($value) && strlen(trim($value)) == 0)) {
762 return TRUE;
763 }
764
765 return FALSE;
766 }
767
768 /**
769 * Returns true if the field was triggered
770 * $selected_values The values of the controlling field selected by the user when creating the node
771 * $trigger_values An array containing the information we need to select the trigger values
772 */
773 function conditional_fields_is_triggered($selected_values, $trigger_values) {
774 foreach ((array)$selected_values as $values) {
775 foreach ((array)$values as $value) {
776 if (isset($value) && in_array($value, $trigger_values)) {
777 return TRUE;
778 }
779 }
780 }
781 return FALSE;
782 }
783
784 /**
785 * Returns an array of conditional fields settings for a given node type.
786 * $structure can be either 'flat' or 'row' . 'row' data is data per row,
787 * while 'flat' data is a list of both controlling and controlled fields.
788 */
789 function conditional_fields_load_data($type, $structure = 'row', $reset = FALSE) {
790 static $data;
791 if ($reset) {
792 unset($data);
793 }
794 if (!$data[$structure][$type]) {
795 $data[$structure][$type] = array();
796 if ($structure == 'row') {
797 $query = db_query("SELECT control_field_name, field_name, trigger_values FROM {conditional_fields} WHERE type = '%s'", $type);
798 while ($result = db_fetch_array($query)) {
799 $result['trigger_values'] = unserialize($result['trigger_values']);
800 $data['row'][$type][] = $result;
801 }
802 }
803 else if ($structure == 'flat') {
804 $query = db_query("SELECT control_field_name, field_name FROM {conditional_fields} WHERE type = '%s'", $type);
805 while ($result = db_fetch_array($query)) {
806 $data['flat'][$type][$result['control_field_name']] = $result['control_field_name'];
807 $data['flat'][$type][$result['field_name']] = $result['field_name'];
808 }
809 $data['flat'][$type] = array_unique($data['flat'][$type]);
810 }
811
812 }
813 return $data[$structure][$type];
814 }
815
816 /**
817 * Find conditional fields and mark them.
818 */
819 function conditional_fields_field_overview_form(&$form) {
820 // Check if we have conditional data
821 if (!$data = conditional_fields_load_data($form['#type_name'])) {
822 return;
823 }
824
825 foreach ($data as $field) {
826 if (in_array($field['control_field_name'], $form['#fields'])) {
827 $form[$field['control_field_name']]['field_name']['#value'] .= theme('conditional_fields_manage_marker', NULL, $field['field_name']);
828 }
829 if (in_array($field['field_name'], $form['#fields'])) {
830 $form[$field['field_name']]['field_name']['#value'] .= theme('conditional_fields_manage_marker', $field['control_field_name'], NULL);
831 }
832 // Mark groups
833 if (in_array($field['field_name'], $form['#groups'])) {
834 $form[$field['field_name']]['group_name']['#value'] .= theme('conditional_fields_manage_marker', $field['control_field_name'], NULL);
835 }
836 }
837
838 // Add validation funcion
839 $form['#validate'] = array_merge(array('conditional_fields_field_overview_form_validate'), $form['#validate']);
840 }
841
842 /**
843 * Conditional fields can't change group.
844 */
845 function conditional_fields_field_overview_form_validate($form, $form_state) {
846 $data = conditional_fields_load_data($form['#type_name'], 'flat');
847
848 foreach ($form['#fields'] as $field_name) {
849 if ($data[$field_name] && $form_state['values'][$field_name]['parent'] != $form_state['values'][$field_name]['prev_parent']) {
850 form_set_error('', t("You can't change the parent group of a conditional field."));
851 }
852 }
853 }
854
855 /**
856 * Returns an array of fields and fieldgroups controlled by the field $fieldname.
857 * $type is the content type name of the field
858 */
859 function conditional_fields_get_control_fields($field_name, $type) {
860 static $controlled_fields;
861 if (!$controlled_fields[$type][$field_name]) {
862 if ($type) {
863 $query = db_query("SELECT field_name FROM {conditional_fields} WHERE control_field_name = '%s' AND type = '%s'", $field_name, $type);
864 }
865 else {
866 $query = db_query("SELECT field_name FROM {conditional_fields} WHERE control_field_name = '%s'", $field_name);
867 }
868 if (module_exists('fieldgroup')) {
869 $type_groups = array_keys(fieldgroup_groups($type));
870 }
871 $controlled_fields[$type][$field_name] = array('field' => array(), 'group' => array());
872 while ($controlled_field = db_fetch_object($query)) {
873 if ($type_groups) {
874 in_array($controlled_field->field_name, $type_groups) ? $field_or_group = 'group' : $field_or_group = 'field';
875 }
876 else {
877 $field_or_group = 'field';
878 }
879 $controlled_fields[$type][$field_name][$field_or_group][] = $controlled_field->field_name;
880 }
881 }
882 return $controlled_fields[$type][$field_name];
883 }
884
885 /**
886 * Load default values from conditional_fields table.
887 */
888 function conditional_fields_default_values($control_field, $conditional_fields) {
889 foreach ($conditional_fields as $field) {
890 $query = db_query("SELECT trigger_values FROM {conditional_fields} WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", $field['field_name'], $control_field, $field['type_name']);
891 $default_values[$field['field_name']] = unserialize(db_result($query));
892 }
893 return $default_values;
894 }
895
896 /**
897 * Adds javascript to the node editing form
898 */
899 function conditional_fields_add_js($settings) {
900 // Avoid adding js twice when the node form has failed validation
901 static $js;
902 if (!$js) {
903 drupal_add_js(array('ConditionalFields' => $settings), 'setting');
904 drupal_add_js(drupal_get_path('module', 'conditional_fields') . '/conditional_fields.js');
905 $js = TRUE;
906 }
907 }
908
909 /*
910 * Clean conditional fields settings pertaining to this removed field
911 */
912 function _conditional_fields_content_admin_field_remove_submit($form, $form_state) {
913 conditional_fields_remove_field_settings($form_state['field_name']);
914 }
915
916 /*
917 * Clean conditional fields settings pertaining to this removed group
918 */
919 function _conditional_fields_fieldgroup_remove_group_submit($form, $form_state, $group_name) {
920 conditional_fields_remove_field_settings($group_name);
921 }
922
923 /*
924 * Remove all settings for a field.
925 * Since our field names are really the field instance name, it should be safe to remove without checking.
926 */
927 function conditional_fields_remove_field_settings($field_name) {
928 db_query("DELETE FROM {conditional_fields} WHERE control_field_name = '%s' OR field_name = '%s'", $field_name, $field_name);
929 }
930
931 /**
932 * Implementation of hook_node_type()
933 */
934 function conditional_fields_node_type($op, $info) {
935 switch ($op) {
936 case 'update':
937 conditional_fields_node_type_update($info);
938 break;
939 case 'delete':
940 conditional_fields_node_type_delete($info->type);
941 break;
942 }
943 }
944
945 /**
946 * Update conditional fields to a new type name
947 */
948 function conditional_fields_node_type_update($info) {
949 if (isset($info->old_type) && $info->type != $info->old_type) {
950
951 db_query("UPDATE {conditional_fields} SET type = '%s' WHERE type ='%s'", $info->type, $info->old_type);
952
953 // Update variables
954 db_query("UPDATE {variable} SET name = 'c_fields_js_%s' WHERE name ='c_fields_js_%s'", $info->type, $info->old_type);
955 db_query("UPDATE {variable} SET name = 'c_fields_animation_%s' WHERE name ='c_fields_animation_%s'", $info->type, $info->old_type);
956 db_query("UPDATE {variable} SET name = 'c_fields_anim_speed_%s' WHERE name ='c_fields_anim_speed_%s'", $info->type, $info->old_type);
957 db_query("UPDATE {variable} SET name = 'c_fields_show_all_%s' WHERE name ='c_fields_show_all_%s'", $info->type, $info->old_type);
958 db_query("UPDATE {variable} SET name = 'c_fields_view_%s' WHERE name ='c_fields_view_%s'", $info->type, $info->old_type);
959 db_query("UPDATE {variable} SET name = 'c_fields_edit_%s' WHERE name ='c_fields_edit_%s'", $info->type, $info->old_type);
960 cache_clear_all('variables', 'cache');
961 }
962 }
963
964 /**
965 * Remove conditional fields of a node type
966 */
967 function conditional_fields_node_type_delete($type) {
968 db_query("DELETE FROM {conditional_fields} WHERE type = '%s'", $type);
969
970 // Delete variables
971 variable_del('c_fields_js_' . $type);
972 variable_del('c_fields_animation_' . $type);
973 variable_del('c_fields_anim_speed_' . $type);
974 variable_del('c_fields_show_all_' . $type);
975 variable_del('c_fields_view_' . $type);
976 variable_del('c_fields_edit_' . $type);
977 }
978
979 /**
980 * Implementation of hook_content_fieldapi
981 */
982 function conditional_fields_content_fieldapi($ops, $field) {
983 // Handle deletion of fields
984 if ($ops == 'delete instance') {
985 db_query("DELETE FROM {conditional_fields} WHERE type = '%s' AND (control_field_name = '%s' OR field_name = '%s')", $field['type_name'], $field['field_name'], $field['field_name']);
986 }
987 // Import conditional fields definitions with content_copy module
988 else if ($ops == 'create instance') {
989 if ($field['conditional_fields']) {
990 conditional_fields_save_field($field['type_name'], $field['field_name'], $field['conditional_fields']);
991 }
992 }
993 }
994
995 /**
996 * Handle saving of individual controlled field settings
997 * $controlling_fields is a keyed array of controlling fields names and trigger values
998 *
999 * Use this function with caution, as it doesn't check if the content type and the fields actually exist.
1000 */
1001 function conditional_fields_save_field($type_name, $controlled_field, $controlling_fields) {
1002 foreach ($controlling_fields as $controlling_field => $trigger_values) {
1003 // If the row already exists
1004 if (db_result(db_query("SELECT COUNT(*) FROM {conditional_fields} WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", $controlling_field, $controlled_field, $type_name))) {
1005 // If no value is set, delete the entry, else update it
1006 if (empty($trigger_values) || $trigger_values['conditional_field_no_value']) {
1007 conditional_fields_delete_field($type_name, $controlled_field, $controlling_field);
1008 }
1009 else {
1010 conditional_fields_update_field($type_name, $controlled_field, $controlling_field, $trigger_values);
1011 }
1012 }
1013 else {
1014 // If values are set, create new entry
1015 if (!empty($trigger_values) && !$trigger_values['conditional_field_no_value']) {
1016 conditional_fields_insert_field($type_name, $controlled_field, $controlling_field, $trigger_values);
1017 }
1018 }
1019 }
1020 }
1021
1022 /**
1023 * Insert into database the data of a new conditional field
1024 */
1025 function conditional_fields_insert_field($type_name, $controlled_field, $controlling_field, $trigger_values) {
1026 db_query("INSERT INTO {conditional_fields} (control_field_name, field_name, type, trigger_values) VALUES ('%s', '%s', '%s', '%s')", $controlling_field, $controlled_field, $type_name, serialize($trigger_values));
1027 }
1028
1029 /**
1030 * Update an existing conditonal field's database data
1031 */
1032 function conditional_fields_update_field($type_name, $controlled_field, $controlling_field, $trigger_values) {
1033 db_query("UPDATE {conditional_fields} SET trigger_values = '%s' WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", serialize($trigger_values), $controlling_field, $controlled_field, $type_name);
1034 }
1035
1036 /**
1037 * Delete a conditonal field's database data
1038 */
1039 function conditional_fields_delete_field($type_name, $controlled_field, $controlling_field) {
1040 db_query("DELETE FROM {conditional_fields} WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", $controlling_field, $controlled_field, $type_name);
1041 }
1042
1043 /**
1044 * Wrapper for conditional_fields_set_required_field_recurse
1045 */
1046 function conditional_fields_set_required_field($item) {
1047 conditional_fields_set_required_field_recurse($item);
1048 return $item;
1049 };
1050
1051 /**
1052 * Recursive function to set required for all conditionally required fields.
1053 * This causes Drupal to render conditionally required fields in a way that
1054 * indicates they are required when visible.
1055 */
1056 function conditional_fields_set_required_field_recurse(&$item) {
1057 $item['#required'] = TRUE;
1058 foreach (element_children($item) as $child) {
1059 conditional_fields_set_required_field_recurse($item[$child]);
1060 }
1061 }
1062
1063 /**
1064 * Unset the #required property and set a #required_field property for internal use.
1065 */
1066 function conditional_fields_unset_required_field(&$field) {
1067 if ($field['#required']) {
1068 unset($field['#required']);
1069 $field['#required_field'] = TRUE;
1070 }
1071 foreach (element_children($field) as $child) {
1072 conditional_fields_unset_required_field($field[$child]);
1073 }
1074 }
1075
1076
1077 /**