2
// $Id: content_multigroup.module,v 1.1.2.4 2008/10/22 11:02:41 yched Exp $
6
* Create complex, repeating groups of CCK fields that work in unison.
12
function content_multigroup_help($path, $arg) {
14
case ('admin/help#content_multigroup'):
15
return t('The fields in a Standard group are independent of each other and each can have either single or multiple values. The fields in a Multigroup are treated as a repeating collection of single value fields.');
20
* Implementation of hook_init().
22
function content_multigroup_init() {
23
drupal_add_css(drupal_get_path('module', 'content_multigroup') .'/content_multigroup.css');
27
* Implementation of hook_theme().
29
function content_multigroup_theme() {
31
'content_multigroup_node_form' => array(
32
'arguments' => array('element' => NULL),
34
'content_multigroup_node_label' => array(
35
'arguments' => array('text' => NULL),
37
'content_multigroup_display_simple' => array(
38
'arguments' => array('element' => NULL),
40
'content_multigroup_display_hr' => array(
41
'arguments' => array('element' => NULL),
43
'content_multigroup_display_table' => array(
44
'arguments' => array('element' => NULL),
50
* Implementation of hook_fieldgroup_types().
52
function content_multigroup_fieldgroup_types() {
53
return array('multigroup' => t('Multigroup'));
57
* Implementation of hook_fieldgroup_default_settings().
59
function content_multigroup_fieldgroup_default_settings($group_type) {
60
if ($group_type == 'multigroup') {
61
module_load_include('inc', 'content', 'includes/content.admin');
62
$settings = array('multigroup' => array('multiple' => 1));
63
foreach (array_keys(content_build_modes()) as $key) {
64
$settings['multigroup']['display_settings'][$key]['format'] = 'fieldset';
70
function content_multigroup_multiple_values() {
74
0 => 1) + drupal_map_assoc(range(2, 10));
78
* Implementation of hook_menu().
80
function content_multigroup_menu() {
82
// Callback for AHAH add more buttons.
83
$items['content_multigroup/js_add_more'] = array(
84
'page callback' => 'content_multigroup_add_more_js',
85
'access arguments' => array('access content'),
86
'type' => MENU_CALLBACK,
92
* Implementation of hook_form_alter().
94
function content_multigroup_form_alter(&$form, $form_state, $form_id) {
95
// If this is a field edit form and the field is in a Multigroup,
96
// override the multiple value settings.
97
if ($form_id == 'content_field_edit_form' && isset($form['widget'])) {
98
$content_type = content_types($form['type_name']['#value']);
99
$groups = fieldgroup_groups($content_type['type']);
100
$group_name = _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']);
101
$group = isset($groups[$group_name]) ? $groups[$group_name] : array();
102
if (!empty($group) && $group['group_type'] == 'multigroup') {
103
$form['field']['multiple']['#value'] = $group['settings']['multigroup']['multiple'];
104
$form['field']['multiple']['#access'] = FALSE;
107
elseif ($form_id == 'content_field_overview_form') {
108
content_multigroup_field_overview_form($form, $form_state);
109
$form['#validate'][] = 'content_multigroup_field_overview_form_validate';
111
elseif ($form_id == 'content_display_overview_form') {
112
content_multigroup_display_overview_form($form, $form_state, $form_id);
113
$form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']);
115
elseif ($form_id == 'fieldgroup_group_edit_form') {
116
return content_multigroup_group_edit_form($form, $form_state, $form_id);
120
function content_multigroup_field_overview_form(&$form, &$form_state) {
121
$options = fieldgroup_types();
122
$options['standard'] = t('Standard');
123
$options['multigroup'] = t('Multigroup');
124
$form['_add_new_group']['group_type'] = array(
126
'#description' => t('Type of group.'),
127
'#options' => $options,
128
'#default_value' => 'standard',
133
* Validation for creating/moving fields and groups on the
134
* Manage Fields screen.
136
function content_multigroup_field_overview_form_validate($form, &$form_state) {
137
$form_values = $form_state['values'];
139
$type_name = $form['#type_name'];
143
$group = $form_values['_add_new_group'];
144
if (array_filter(array($group['label'], $group['group_name']))) {
146
$group['settings'] = field_group_default_settings($form_values['_add_new_group']['group_type']);
147
$group = $form_values['_add_new_group'];
148
$validation = fieldgroup_validate_name($group, $form['#type_name']);
150
// If there's something wrong with the new group,
151
// don't bother doing any more validation, further
152
// processing will be stopped by the fieldgroup module.
153
if (!empty($validation['errors'])) {
156
$group['group_name'] = $validation['group_name'];
157
$new_group_name = $group['group_name'];
158
$groups['_add_new_group'] = $group;
161
// See if we have fields moving into or out of a Multigroup.
162
// Set any fields to use the new name here so they will get processed
163
// correctly by the fieldgroup module when saved.
164
foreach ($form_values as $key => $values) {
165
if ($values['parent'] == '_add_new_group') {
166
$values['parent'] = $new_group_name;
167
$form_values[$key] = $values;
170
if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') {
171
// Gather up info about all groups.
172
$group_name = $form_values[$key]['group']['group_name'];
173
$groups[$group_name] = $form_values[$key]['group'];
175
if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'field') {
176
if ($values['prev_parent'] != $values['parent']) {
177
// Gather up fields that have moved in or out of a group.
178
$fields[$key] = $form_values[$key]['field'];
183
if (!empty($fields)) {
184
foreach ($fields as $field_name => $field) {
185
$new_group = $form_values[$field_name]['parent'];
186
$old_group = $form_values[$field_name]['prev_parent'];
187
if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') {
188
$allowed_in = content_multigroup_allowed_in($field, $groups[$new_group]);
189
if (!$allowed_in['allowed']) {
190
form_set_error($field_name, $allowed_in['message']);
193
if (!empty($allowed_in['message'])) {
194
drupal_set_message($allowed_in['message']);
196
module_load_include('inc', 'content', 'includes/content.crud');
197
$content_type = content_types($type_name);
198
$multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
199
$multiple_values = content_multigroup_multiple_values();
200
$field = $content_type['fields'][$field_name];
201
$field['multiple'] = $multiple;
202
$field = content_field_instance_collapse($field);
203
content_field_instance_update($field);
204
drupal_set_message(t('The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group.', array(
205
'%field' => $field['label'], '%multiple' => $multiple_values[$multiple], '%group' => $groups[$new_group]['label'])));
208
elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
209
$allowed_out = content_multigroup_allowed_out($field, $groups[$old_group]);
210
if (!$allowed_out['allowed']) {
211
form_set_error($field_name, $allowed_out['message']);
213
elseif (!empty($allowed_out['message'])) {
214
drupal_set_message($allowed_out['message']);
222
* Helper function for deciding if a field is
223
* allowed into a Multigroup.
225
function content_multigroup_allowed_in($field, $group) {
226
if ($group['group_type'] != 'multigroup') {
227
return array('allowed' => TRUE, 'message' => '');
230
// We can't allow fields with more multiple values than the group has
231
// to be moved into it.
232
$max_existing = content_max_delta($field['field_name']);
233
$group_max = $group['settings']['multigroup']['multiple'];
234
$multiple_values = content_multigroup_multiple_values();
235
if ($group_max != 1 && $max_existing > $group_max) {
238
'message' => t('This change is not allowed. The field %field already has %multiple values in the database but the group %group only allows %group_max. Making this change would result in the loss of data.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing, '%group' => $group['label'], '%group_max' => $multiple_values[$group_max]))
242
// Fields that handle their own multiple values may not have the same values
243
// in Multigroup fields and normal fields. We don't know if they will work or not.
245
// Adding a hook here where widgets that handle their own multiple values
246
// that will work correctly in Multigroups can allow their fields in.
248
if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) {
249
$allowed_widgets = array(
250
'optionwidgets_select',
251
'optionwidgets_buttons',
252
'optionwidgets_onoff',
253
'nodereference_buttons',
254
'nodereference_select',
255
'userreference_buttons',
256
'userreference_select',
258
$allowed_widgets = array_merge($allowed_widgets, module_invoke_all('content_multigroup_allowed_widgets'));
259
if (!in_array($field['widget']['type'], $allowed_widgets)) {
262
'message' => t('This change is not allowed. The field %field handles multiple values differently than the Content module. Making this change could result in the loss of data.', array('%field' => $field['widget']['label']))
267
// Allow other modules to intervene.
268
// Any failure will prevent this action.
269
foreach (module_implements('content_multigroup_allowed_in') as $module) {
270
$function = $module .'_content_multigroup_allowed_in';
271
$result = $function($field, $group);
272
if ($result['allowed'] === FALSE) {
273
return array('allowed' => FALSE, 'message' => $result['message']);
277
$message = t('You are moving the field %field into a Multigroup.', array('%field' => $field['widget']['label']));
278
return array('allowed' => TRUE, 'message' => $message);
282
* Helper function for deciding if a field is
283
* allowed out of a Multigroup.
285
function content_multigroup_allowed_out($field, $group) {
286
if ($group['group_type'] != 'multigroup') {
287
return array('allowed' => TRUE, 'message' => '');
289
// Optionwidgets do not behave the same in a Multigroup field as out of it.
290
// In a Multigroup the same option can be selected multiple times,
291
// but that is not possible in a normal group.
293
// Adding a hook here where widgets that handle their own multiple values
294
// can indicate their fields should not be removed from Multigroups.
296
$max_existing = content_max_delta($field['field_name']);
297
$no_remove_widgets = array(
298
'optionwidgets_select',
299
'optionwidgets_buttons',
300
'optionwidgets_onoff',
301
'nodereference_buttons',
302
'nodereference_select',
303
'userreference_buttons',
304
'userreference_select',
306
$no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('content_multigroup_no_remove_widgets'));
307
if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) {
310
'message' => t('This change is not allowed. The field %field already has data created and uses a widget that stores data differently in a Standard group than in a Multigroup. Making this change could result in the loss of data.', array('%field' => $field['widget']['label']))
314
// Allow other modules to intervene.
315
// Any failure will prevent this action.
316
foreach (module_implements('content_multigroup_allowed_out') as $module) {
317
$function = $module .'_content_multigroup_allowed_out';
318
$result = $function($field, $group);
319
if ($result['allowed'] === FALSE) {
320
return array('allowed' => FALSE, 'message' => $result['message']);
324
$message = t('You are moving the field %field out of a Multigroup.', array('%field' => $field['widget']['label']));
325
return array('allowed' => TRUE, 'message' => $message);
329
* Menu callback; presents a listing of fields display settings for a content type.
331
* Add an additional selector for setting multigroup field display format.
333
function content_multigroup_display_overview_form(&$form, &$form_state) {
335
$type_name = $form['#type_name'];
336
$contexts_selector = $form['#contexts'];
338
// Gather type information.
339
$type = content_types($type_name);
340
$field_types = _content_field_types();
341
$fields = $type['fields'];
343
$groups = $group_options = array();
344
if (module_exists('fieldgroup')) {
345
$groups = fieldgroup_groups($type['type']);
346
$group_options = _fieldgroup_groups_label($type['type']);
348
$contexts = content_build_modes($contexts_selector);
350
// Multigroups, extra values.
351
$label_options = array(
352
'above' => t('Above'),
353
'hidden' => t('<Hidden>'),
357
'fieldset' => t('Fieldset'),
358
'hr' => t('Horizontal line'),
359
//'table' => t('Table'), // TODO add this later
360
'hidden' => t('<Hidden>'),
362
foreach ($groups as $name => $group) {
363
if ($group['group_type'] != 'multigroup') {
366
$defaults = $group['settings']['multigroup']['display_settings'];
368
$form_name = $name .'_subgroup';
369
$form['#fields'] = array_merge(array($form_name), $form['#fields']);
370
$form[$form_name] = array(
371
'human_name' => array('#value' => t('[Subgroup format]')),
372
'weight' => array('#type' => 'value', '#value' => -20),
373
'parent' => array('#type' => 'value', '#value' => $name),
375
if ($contexts_selector == 'basic') {
376
$form[$form_name]['label'] = array(
378
'#options' => $label_options,
379
'#default_value' => isset($defaults['label']) ? $defaults['label'] : 'above',
382
foreach ($contexts as $key => $title) {
383
$form[$form_name][$key]['format'] = array(
385
'#options' => $options,
386
'#default_value' => isset($defaults[$key]) ? $defaults[$key] : 'fieldset',
394
* Submit handler for the display overview form.
396
* Do this in pre_save so we catch it before the content module
397
* tries to use our 'field'.
399
function content_multigroup_display_overview_form_submit($form, &$form_state) {
400
$form_values = $form_state['values'];
402
// Find any groups we inserted into the display fields form,
403
// save our settings, and remove them from $form_state.
404
foreach ($form_values as $key => $values) {
405
if (in_array($key, $form['#fields']) && substr($key, -9) == '_subgroup') {
406
$group_name = str_replace('_subgroup', '', $key);
407
$groups = fieldgroup_groups($form['#type_name']);
408
$group = $groups[$group_name];
410
// We have some numeric keys here, so we can't use array_merge.
411
foreach ($values as $k => $v) {
412
$group['settings']['multigroup']['display_settings'][$k] = $v;
414
fieldgroup_save_group($form['#type_name'], $group);
416
// Make sure group information is immediately updated.
417
cache_clear_all('fieldgroup_data', content_cache_tablename());
418
fieldgroup_groups('', FALSE, TRUE);
419
unset($form_state['values'][$key]);
425
* Alter the Fieldgroup edit form
426
* to add Multigroup settings.
428
function content_multigroup_group_edit_form(&$form, &$form_state) {
429
$type_name = $form['#content_type']['type'];
430
$group_name = $form['group_name']['#default_value'];
432
$content_type = content_types($type_name);
433
$groups = fieldgroup_groups($content_type['type']);
434
$group = $groups[$group_name];
436
if ($group['group_type'] != 'multigroup') {
440
module_load_include('inc', 'content', 'includes/content.admin');
441
module_load_include('inc', 'content', 'includes/content.crud');
442
$form['group_type'] = array(
444
'#value' => $group['group_type'],
446
$form['settings']['multigroup'] = array(
447
'#type' => 'fieldset',
448
'#title' => t('Other settings'),
449
'#collapsed' => FALSE,
450
'#collapsible' => TRUE,
453
$description = t('Number of times to repeat the collection of Multigroup fields.') . ' ';
454
$description .= t("'Unlimited' will provide an 'Add more' button so the users can add repeat it as many times as they like.") . ' ';
455
$description .= t('All fields in this group will automatically be set to allow this number of values.');
457
$multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
458
$form['settings']['multigroup']['multiple'] = array(
461
'#title' => t('Number of repeats'),
462
'#options' => content_multigroup_multiple_values(),
463
'#default_value' => $multiple,
464
'#description' => $description,
467
$form['settings']['multigroup']['labels'] = array(
468
'#type' => 'fieldset',
469
'#title' => t('Labels'),
470
'#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."),
475
for ($i = 0; $i < 10; $i++) {
476
$form['settings']['multigroup']['labels'][$i] = array(
477
'#type' => 'textfield',
478
'#title' => t('Subgroup %number label', array('%number' => $i + 1)),
479
'#default_value' => isset($group['settings']['multigroup']['labels'][$i]) ? $group['settings']['multigroup']['labels'][$i] : '',
483
$form['#validate'][] = 'content_multigroup_group_edit_form_validate';
484
$form['#submit'][] = 'content_multigroup_group_edit_form_submit';
489
* Validate the Fieldgroup edit form.
491
function content_multigroup_group_edit_form_validate($form, &$form_state) {
492
$form_values = $form_state['values'];
493
$group_type = $form_values['group_type'];
494
if ($group_type != 'multigroup') {
497
$content_type = $form['#content_type'];
498
$groups = fieldgroup_groups($content_type['type']);
499
$group = $groups[$form_values['group_name']];
500
foreach ($group['fields'] as $field_name => $data) {
501
// Make sure we don't set the multiple values to a number that
502
// would result in lost data.
503
$max_existing = content_max_delta($field_name);
504
if ($form_values['settings']['multigroup']['multiple'] != 1
505
&& $max_existing > $form_values['settings']['multigroup']['multiple']) {
506
form_set_error('settings][multigroup][multiple', t('The field %field in this group already has %multiple values in the database. To prevent the loss of data you cannot set the number of Multigroup values to less than this.', array('%field' => $data['label'], '%multiple' => $max_existing)));
512
* Submit the Fieldgroup edit form.
514
* Update multiple values of fields contained in Multigroups.
516
function content_multigroup_group_edit_form_submit($form, &$form_state) {
517
$form_values = $form_state['values'];
518
$group_type = $form_values['group_type'];
519
if ($group_type != 'multigroup') {
522
module_load_include('inc', 'content', 'includes/content.crud');
523
$content_type = $form['#content_type'];
524
$groups = fieldgroup_groups($content_type['type']);
525
$group = $groups[$form_values['group_name']];
526
$multiple = $form_values['settings']['multigroup']['multiple'];
527
foreach ($group['fields'] as $field_name => $data) {
528
$field = $content_type['fields'][$field_name];
529
$field['multiple'] = $multiple;
530
$field = content_field_instance_collapse($field);
531
content_field_instance_update($field);
536
* Implementation of hook_fieldgroup_form().
538
* Align the delta values of each field in the Multigroup.
540
* Swap the field name and delta for each Multigroup so we can
541
* d-n-d each collection of fields as a single delta item.
543
function content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) {
544
if ($group['group_type'] != 'multigroup' ||
545
!empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) {
549
$node = $form['#node'];
550
$fields = $group['fields'];
551
$content_fields = content_fields();
552
$group_name = $group['group_name'];
554
// Use the first field in the group to get the item counts.
555
$first_field_name = array_shift(array_keys($group['fields']));
556
$first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array();
557
$first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array();
559
$group['multiple'] = $group['settings']['multigroup']['multiple'];
560
switch ($group['multiple']) {
565
// Is this a new node?
566
if (empty($first_field_items)) {
570
$filled_items = content_set_empty($first_field, $first_field_items);
571
$current_item_count = isset($form_state['item_count'][$group_name])
572
? $form_state['item_count'][$group_name]
573
: count($first_field_items);
574
// We always want at least one empty icon for the user to fill in.
575
$max = ($current_item_count > count($filled_items))
576
? $current_item_count - 1
577
: count($filled_items);
581
$max = $group['multiple'] - 1;
585
$form[$group_name]['#theme'] = 'content_multigroup_node_form';
586
$form[$group_name]['#multiple'] = !empty($max);
587
$form[$group_name]['#type_name'] = $group['type_name'];
588
$form[$group_name]['#group_name'] = $group_name;
589
$form[$group_name]['#group_label'] = $group['label'];
590
$form[$group_name]['#element_validate'] = array('content_multigroup_node_form_validate');
591
$form[$group_name]['#tree'] = TRUE;
593
for ($delta = 0; $delta <= $max; $delta++) {
594
content_multigroup_group_form($form, $form_state, $group, $delta);
597
// Unset the original group field values now that we've moved them.
598
foreach ($fields as $field_name => $field) {
599
unset($form[$group_name][$field_name]);
602
if ($add_more = content_multigroup_add_more($form, $form_state, $group)) {
603
$form[$group_name] += $add_more;
608
* Create a new delta value for the group.
610
* Called in form_alter and by AHAH add more.
612
function content_multigroup_group_form(&$form, &$form_state, $group, $delta) {
613
if ($group['group_type'] != 'multigroup' ||
614
!empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) {
617
module_load_include('inc', 'content', 'includes/content.node_form');
619
$node = $form['#node'];
620
$fields = $group['fields'];
621
$content_fields = content_fields();
622
$group_name = $group['group_name'];
623
$group['multiple'] = $group['settings']['multigroup']['multiple'];
624
$form[$group_name]['#fields'] = array_keys($group['fields']);
626
foreach ($fields as $field_name => $group_field) {
627
if (empty($form[$group_name][$delta])) {
628
$form[$group_name] += array($delta => array($field_name => array()));
631
$form[$group_name][$delta][$field_name] = array();
634
$form[$group_name][$delta]['_weight'] = array(
636
'#delta' => $delta, // this 'delta' is the 'weight' element's property
637
'#default_value' => $delta,
640
$form[$group_name][$delta]['_delta'] = array(
646
$field = $content_fields[$field_name];
648
// Make each field into a pseudo single value field
649
// with the right delta value.
650
$field['multiple'] = FALSE;
652
// Make sure new fields after the first have an 'empty' option.
653
$field['required'] = $delta > 0 ? FALSE : $field['required'];
655
$form['#field_info'][$field_name] = $field;
656
$node_copy = drupal_clone($node);
658
// Set the form '#node' to the delta value we want so the Content
659
// module will feed the right $items to the field module in
660
// content_field_form().
662
// There may be missing delta values for fields that were
663
// never created, so check first.
664
if (!empty($node->$field_name) && count($node->$field_name) >= $delta + 1) {
665
$node_copy->$field_name = array($delta => $node->{$field_name}[$delta]);
668
$node_copy->$field_name = array($delta => NULL);
670
$form['#node'] = $node_copy;
671
$field_form = content_field_form($form, $form_state, $field, $delta);
673
// Place the new $field_form into the $delta position in the group form.
674
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
675
$value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0;
676
$form[$group_name][$delta][$field_name] = $field_form[$field_name][$value];
679
$form[$group_name][$delta][$field_name] = $field_form[$field_name];
681
$form[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight'];
683
// Add in our validation step, and make sure it preceeds other
684
// processing so we can massage the element back to the normal position.
685
if (empty($form[$group_name][$delta][$field_name]['#element_validate'])) {
686
$form[$group_name][$delta][$field_name]['#element_validate'] = array();
688
array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'content_multigroup_node_item_validate');
691
// Reset the form '#node' back to its original value.
692
$form['#node'] = $node;
698
* Swap transposed field/delta values back
699
* to their normal positions in the node.
701
function content_multigroup_node_item_validate($element, &$form_state) {
702
static $weights = array();
704
//dsm($form_state['values']);
705
$form_values = $form_state['values'];
706
$field_name = array_pop($element['#parents']);
707
$delta = array_pop($element['#parents']);
708
$group_name = array_pop($element['#parents']);
710
// Identify the new delta value for each field.
712
// Find the original delta values for this group, save as static value
713
// because the group will acquire and lose values while we process it.
714
if (!array_key_exists($group_name, $weights)) {
715
$items = $form_state['values'][$group_name];
716
$weights[$group_name] = array();
717
foreach ($items as $count => $value) {
718
// Allow for the possibility of matching _weights and missing deltas.
719
$weight = floatval($value['_weight']);
720
$old_delta = intval($value['_delta']);
721
if (empty($weights[$group_name][$weight]) || !in_array($old_delta, $weights[$group_name][$weight])) {
722
$weights[$group_name][$weight][] = $old_delta;
725
ksort($weights[$group_name]);
728
foreach ($weights[$group_name] as $weight => $values) {
729
foreach ($values as $old_delta) {
730
if ($old_delta === $delta) {
732
//dsm('moving delta values: '.$group_name.'>'.$field_name.'>'.'from '. $old_delta .' to '. $delta);
738
// We figured out what the new order for the fields is,
739
// so set the value for the new delta.
741
// We move these new values back up to the top level of the
742
// node and out of the group so the Content module will find and
743
// save the new values and so they don't get mixed into the
744
// remaining, unaltered, values in the group.
745
array_push($element['#parents'], $field_name);
746
array_push($element['#parents'], $delta);
748
// It's very important to use $form_values instead of $element['#value']
749
// here, because $element['#value'] is sometimes missing changes
750
// made in #element_validate processing done by other modules.
751
$value = isset($form_values[$group_name][$delta][$field_name]) ? $form_values[$group_name][$delta][$field_name] : NULL;
753
// Fields that use optionwidgets have an extra array level in the value
754
// because of the optionwidgets transposition that forces a delta value
755
// into the result array. This works fine when a delta value is between
756
// the field name and the field value, as in normal nodes, but not when
757
// we reverse the field and the delta, so in this case we need to
758
// promote the nested delta value back up to the field level.
759
if (is_array($value) && content_multigroup_uses_optionwidgets($field_name, $element['#type_name'])) {
760
$value = array_shift($value);
763
//dsm('setting value of '. $field_name.'>'.$delta);
765
form_set_value($element, $value, $form_state);
769
* Helper function for identifying fields that use
770
* optionwidgets transpositions.
772
function content_multigroup_uses_optionwidgets($field_name, $type_name) {
773
static $optionwidgets;
774
if (empty($optionwidgets)) {
775
$optionwidgets = array(
776
'optionwidgets_select',
777
'optionwidgets_buttons',
778
'optionwidgets_onoff',
779
'nodereference_buttons',
780
'nodereference_select',
781
'userreference_buttons',
782
'userreference_select',
784
// Add hook where other widgets that use optionwidgets can announce it.
785
$optionwidgets = array_merge($optionwidgets, module_invoke_all('content_multigroup_uses_optionwidgets'));
788
$types = content_types($type_name);
789
$fields = $types['fields'];
790
$field = $fields[$field_name];
791
if (in_array($field['widget']['type'], $optionwidgets)) {
798
* Validation for the whole node group.
800
function content_multigroup_node_form_validate($element, $form_state) {
801
// We moved all the new field values out of the field group
802
// and up to the top level of the node, now get rid of the
803
// original group values.
804
form_set_value($element, NULL, $form_state);
809
* Implementation of hook_fieldgroup_view().
811
function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
812
if ($group['group_type'] != 'multigroup') {
816
$group_name = $group['group_name'];
817
$node_copy = drupal_clone($node);
818
$max = $group['settings']['multigroup']['multiple'];
821
foreach ($group['fields'] as $field_name => $field) {
822
$count = max($count, count($node->$field_name));
825
$group['multiple'] = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
826
$labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
827
$format = isset($group['settings']['multigroup']['display_settings'][$context]['format']) ? $group['settings']['multigroup']['display_settings'][$context]['format'] : 'fieldset';
828
$show_label = isset($group['settings']['multigroup']['display_settings']['label']) ? $group['settings']['multigroup']['display_settings']['label'] : 'above';
830
switch ($group['multiple']) {
838
$max = $group['multiple'];
842
for ($delta = 0; $delta < $max; $delta++) {
843
$element[$delta] = array('#weight' => $delta);
845
$label = !empty($labels[$delta]) && $show_label == 'above' ? $labels[$delta] : '';
847
foreach ($group['fields'] as $field_name => $field) {
849
// Create a pseudo node that only has the value we want
850
// in this group and pass it to the formatter.
851
if (isset($node->content[$field_name])) {
852
$node_copy->content[$field_name]['field']['items'] = array(
853
$delta => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL,
855
$element[$delta][$field_name] = $node_copy->content[$field_name];
856
$element[$delta][$field_name]['#delta'] = $delta;
861
$element[$delta]['#theme'] = 'content_multigroup_display_table';
862
$element[$delta]['#title'] = $label;
865
$element[$delta]['#type'] = 'fieldset';
866
$element[$delta]['#title'] = $label;
869
$element[$delta]['#theme'] = 'content_multigroup_display_hr';
870
$element[$delta]['#title'] = $label;
873
$element[$delta]['#theme'] = 'content_multigroup_display_simple';
874
$element[$delta]['#title'] = $label;
880
foreach ($group['fields'] as $field_name => $field) {
881
if (isset($element[$field_name])) {
882
unset($element[$field_name]);
888
* Theme an individual form element.
890
* Combine multiple values into a table with drag-n-drop reordering.
892
function theme_content_multigroup_node_form($element) {
894
if ($element['#multiple'] >= 1) {
895
$table_id = $element['#group_name'] .'_values';
896
$order_class = $element['#group_name'] .'-delta-order';
906
$groups = fieldgroup_groups($element['#type_name']);
907
$group = $groups[$element['#group_name']];
908
$labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
909
$multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
912
foreach (element_children($element) as $delta => $key) {
913
if ($key !== $element['#group_name'] .'_add_more') {
914
$label = !empty($labels[$i]) ? theme('content_multigroup_node_label', $labels[$i]) : '';
915
$element[$key]['_weight']['#attributes']['class'] = $order_class;
916
$delta_element = drupal_render($element[$key]['_weight']);
918
array('data' => '', 'class' => 'content-multiple-drag'),
919
$label . drupal_render($element[$key]),
920
array('data' => $delta_element, 'class' => 'delta-order'),
924
// TODO Tablesort drag n drop is not working with complex
925
// field validation. The fields appear to work correctly,
926
// but element validation seems to get missed or confused
927
// causing validation errors. Need to investigate why.
928
'class' => 'draggable',
934
$output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
935
$output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
936
$output .= drupal_render($element[$element['#group_name'] .'_add_more']);
938
drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
941
foreach (element_children($element) as $key) {
942
$output .= drupal_render($element[$key]);
949
function content_multigroup_add_more(&$form, &$form_state, $group) {
950
// Add AHAH add more button, if not working with a programmed form.
951
$multiple = $group['settings']['multigroup']['multiple'];
952
$form_element = array();
953
if ($multiple != 1 || !empty($form['#programmed'])) {
954
return $form_element;
957
// Make sure the form is cached so ahah can work.
958
$form['#cache'] = TRUE;
959
$content_type = content_types($group['type_name']);
960
$group_name = $group['group_name'];
961
$group_name_css = str_replace('_', '-', $group_name);
963
$form_element[$group_name .'_add_more'] = array(
965
'#name' => $group_name .'_add_more',
966
'#value' => t('Add more values'),
967
'#weight' => $multiple + 1,
968
// Submit callback for disabled JavaScript. drupal_get_form() might get
969
// the form from the cache, so we can't rely on content_form_alter()
970
// including this file. Therefore, call a proxy function to do this.
971
'#submit' => array('content_multigroup_add_more_submit_proxy'),
973
'path' => 'content_multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name,
974
'wrapper' => $group_name_css .'-items',
975
'method' => 'replace',
978
// When JS is disabled, the content_add_more_submit handler will find
979
// the relevant field using these entries.
980
'#group_name' => $group_name,
981
'#type_name' => $group['type_name'],
984
// Add wrappers for the group and 'more' button.
985
// TODO: could be simplified ?
986
$form_element['#prefix'] = '<div class="clear-block" id="'. $group_name_css .'-add-more-wrapper"><div id="'. $group_name_css .'-items">';
987
$form_element[$group_name .'_add_more']['#prefix'] = '<div class="content-add-more">';
988
$form_element[$group_name .'_add_more']['#suffix'] = '</div></div></div>';
990
return $form_element;
994
* Submit handler to add more choices to a content form. This handler is used when
995
* JavaScript is not available. It makes changes to the form state and the
996
* entire form is rebuilt during the page reload.
998
function content_multigroup_add_more_submit($form, &$form_state) {
999
// Set the form to rebuild and run submit handlers.
1000
node_form_submit_build_node($form, $form_state);
1001
$group_name = $form_state['clicked_button']['#group_name'];
1002
$type_name = $form_state['clicked_button']['#type_name'];
1004
// Make the changes we want to the form state.
1005
if ($form_state['values'][$group_name][$group_name .'_add_more']) {
1006
$form_state['item_count'][$group_name] = count($form_state['values'][$group_name]);
1011
* Menu callback for AHAH addition of new empty widgets.
1013
* Adapted from content_add_more_js to work with groups instead of fields.
1015
function content_multigroup_add_more_js($type_name_url, $group_name) {
1016
$type = content_types($type_name_url);
1017
$groups = fieldgroup_groups($type['type']);
1018
$group = $groups[$group_name];
1019
$group['multiple'] = $group['settings']['multigroup']['multiple'];
1021
if (($group['multiple'] != 1) || empty($_POST['form_build_id'])) {
1023
drupal_json(array('data' => ''));
1027
// Retrieve the cached form.
1028
$form_state = array('submitted' => FALSE);
1029
$form_build_id = $_POST['form_build_id'];
1030
$form = form_get_cache($form_build_id, $form_state);
1032
// Invalid form_build_id.
1033
drupal_json(array('data' => ''));
1037
// We don't simply return a new empty widget to append to existing ones, because
1038
// - ahah.js won't simply let us add a new row to a table
1039
// - attaching the 'draggable' behavior won't be easy
1040
// So we resort to rebuilding the whole table of widgets including the existing ones,
1041
// which makes us jump through a few hoops.
1043
// The form that we get from the cache is unbuilt. We need to build it so that
1044
// _value callbacks can be executed and $form_state['values'] populated.
1045
// We only want to affect $form_state['values'], not the $form itself
1046
// (built forms aren't supposed to enter the cache) nor the rest of $form_data,
1047
// so we use copies of $form and $form_data.
1049
$form_state_copy = $form_state;
1050
$form_copy['#post'] = array();
1051
form_builder($_POST['form_id'], $form_copy, $form_state_copy);
1052
// Just grab the data we need.
1053
$form_state['values'] = $form_state_copy['values'];
1054
// Reset cached ids, so that they don't affect the actual form we output.
1055
form_clean_id(NULL, TRUE);
1057
// Sort the $form_state['values'] we just built *and* the incoming $_POST data
1058
// according to d-n-d reordering.
1059
unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']);
1060
foreach ($_POST[$group_name] as $delta => $item) {
1061
$form_state['values'][$group_name][$delta]['_weight'] = $item['_weight'];
1062
$form_state['values'][$group_name][$delta]['_delta'] = $item['_delta'];
1064
$form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]);
1065
$_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]);
1067
// Build our new form element for the whole group, asking for one more element.
1069
$form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1);
1070
$delta = max(array_keys($_POST[$group_name])) + 1;
1071
content_multigroup_group_form($form, $form_state, $group, $delta);
1073
// Save the new definition of the form.
1074
$form_state['values'] = array();
1075
form_set_cache($form_build_id, $form, $form_state);
1077
// Build the new form against the incoming $_POST values so that we can
1078
// render the new element.
1079
$_POST[$group_name][$delta]['_weight'] = $delta;
1080
$form_state = array('submitted' => FALSE);
1083
'#programmed' => FALSE,
1085
$form = form_builder($_POST['form_id'], $form, $form_state);
1087
// Render the new output.
1088
$group_form = $form[$group_name];
1090
// We add a div around the new content to receive the ahah effect.
1091
$group_form[$delta]['#prefix'] = '<div class="ahah-new-content">'. (isset($group_form[$delta]['#prefix']) ? $group_form[$delta]['#prefix'] : '');
1092
$group_form[$delta]['#suffix'] = (isset($group_form[$delta]['#suffix']) ? $group_form[$delta]['#suffix'] : '') .'</div>';
1094
// If a newly inserted widget contains AHAH behaviors, they normally won't
1095
// work because AHAH doesn't know about those - it just attaches to the exact
1096
// form elements that were initially specified in the Drupal.settings object.
1097
// The new ones didn't exist then, so we need to update Drupal.settings
1098
// by ourselves in order to let AHAH know about those new form elements.
1099
$javascript = drupal_add_js(NULL, NULL);
1100
$output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>' : '';
1102
$output = theme('status_messages') . drupal_render($group_form) . $output_js;
1103
drupal_json(array('status' => TRUE, 'data' => $output));
1108
* Theme the sub group label in the node form.
1110
function theme_content_multigroup_node_label($text) {
1111
if (!empty($text)) {
1112
return '<h3>'. check_plain($text) .'</h3>';
1116
function theme_content_multigroup_display_simple($element) {
1118
if (!empty($element['#title'])) {
1119
$label .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
1122
foreach (element_children($element) as $key) {
1123
$output .= drupal_render($element[$key]);
1128
function theme_content_multigroup_display_hr($element) {
1130
if (!empty($element['#title'])) {
1131
$label .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
1133
$output = '<hr class="content-multigroup" />'. $label;
1134
foreach (element_children($element) as $key) {
1135
$output .= drupal_render($element[$key]);
1140
function theme_content_multigroup_display_table($element) {
1142
if (!empty($element['#title'])) {
1143
$label .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
1146
foreach (element_children($element) as $key) {
1147
$output .= drupal_render($element[$key]);
b'\\ No newline at end of file'