Index: content.node_form.js
===================================================================
--- content.node_form.js (revision 0)
+++ content.node_form.js (revision 0)
@@ -0,0 +1,255 @@
+// $Id: content.node_form.js,v 1.1.2.1 2009/06/06 23:44:56 markuspetrux Exp $
+
+/**
+ * Private namespace for local methods.
+ */
+Drupal.contentRemoveButtons = Drupal.contentRemoveButtons || {};
+
+/**
+ * Manipulation of content remove buttons.
+ *
+ * TableDrag objects for multiple value fields (and multigroups) are scanned
+ * to find 'remove' checkboxes. These checkboxes are hidden when javascript is
+ * enabled (using the Global CSS Killswitch, html.js, defined in drupal.js).
+ * A new 'remove' button is created here in place of these checkboxes aimed to
+ * provide a more user-friendly method to remove items.
+ */
+Drupal.behaviors.contentRemoveButtons = function(context) {
+ var self = Drupal.contentRemoveButtons;
+
+ $('table.content-multiple-table', context).not('.content-remove-buttons-processed').addClass('content-remove-buttons-processed').each(function() {
+ var table = this, tableDrag = Drupal.tableDrag[$(table).attr('id')];
+
+ // Replace remove checkboxes with buttons.
+ $('input.content-multiple-remove-checkbox', table).each(function() {
+ var $checkbox = $(this), $row = $checkbox.parents('tr:first');
+ var isRemoved = $checkbox.attr('checked');
+ var $button = $(Drupal.theme('contentRemoveButton', tableDrag.getRemoveButtonTitle(isRemoved)));
+
+ // Bind the onClick event to the remove button.
+ $button.bind('click', function(event) {
+ self.onClick($button, $checkbox, $row, tableDrag);
+ return false;
+ });
+
+ // Attach the new button to the DOM tree.
+ $checkbox.parent().append($button);
+
+ // If the row is removed, then hide the contents of the cells and show
+ // the removed warning on the cell next to the drag'n'drop cell.
+ if (isRemoved) {
+ self.getCellWrappers($row).hide();
+ self.showRemovedWarning($row, tableDrag);
+
+ // FAPI not rendering the form on errors - case #1:
+ // If the form has been submitted and any error was found, FAPI will
+ // send back the same exact form that was submitted to show the error
+ // messages, but it will not invoke the rendering engine which is where
+ // we actually assign the removed class to the row, so we need to check
+ // this situation here and add the class if it is not present.
+ if (!$row.hasClass('content-multiple-removed-row')) {
+ $row.addClass('content-multiple-removed-row');
+ }
+ }
+ else {
+ // FAPI not rendering the form on errors - case #2:
+ // Similar issue than #1, but this time caused when user removes an
+ // item, previews, FAPI renders the new form with the removed class,
+ // then user changes anything in the form that causes an error, and
+ // also restores the previously removed item. This time, FAPI will
+ // send the form validation error with the item not flagged for removal
+ // but having the removed class that was present when the form was
+ // rendered in the previous step. So we need to remove this class here,
+ // if present, because the item is not really flagged for removal.
+ if ($row.hasClass('content-multiple-removed-row')) {
+ $row.removeClass('content-multiple-removed-row');
+ }
+ }
+ });
+ });
+};
+
+/**
+ * onClick handler for remove buttons.
+ *
+ * @param $button
+ * The jQuery object of the remove button.
+ * @param $checkbox
+ * The jQuery object of the remove checkbox.
+ * @param $row
+ * The jQuery object of the table row.
+ * @param tableDrag
+ * The tableDrag object where the row is.
+ */
+Drupal.contentRemoveButtons.onClick = function($button, $checkbox, $row, tableDrag) {
+ var self = Drupal.contentRemoveButtons;
+
+ // Prevent the user from firing this event while another one is still being
+ // processed. This flag is (should be) restored at end of animations.
+ // Note that this technique is required because the browser may experience
+ // delays while performing the animation, for whatever reason, and if this
+ // process it fired more than once at the same time for the same row, then
+ // it may cause unexpected behavior because the state of the elements being
+ // manipulated would be unknown.
+ if ($row.animating) {
+ return;
+ }
+ $row.animating = true;
+
+ // Toggle the state of the checkbox.
+ var isRemoved = !$checkbox.attr('checked');
+ $checkbox.attr('checked', isRemoved);
+
+ // Toggle the row class.
+ if (isRemoved) {
+ $row.addClass('content-multiple-removed-row');
+ }
+ else {
+ $row.removeClass('content-multiple-removed-row');
+ }
+
+ // Toggle the button title.
+ $button.attr('title', tableDrag.getRemoveButtonTitle(isRemoved));
+
+ // Get the list of cell wrappers in this row.
+ var $cellWrappers = self.getCellWrappers($row);
+
+ // If for whatever reason this row doesn't have cells with elements,
+ // then we are done, but we still need to reset the global busy flag
+ // and display the tableDrag changed warning.
+ if (!$cellWrappers.size()) {
+ tableDrag.displayChangedWarning();
+ $row.animating = false;
+ return;
+ }
+
+ // Toggle the visible state of the row cells.
+ $cellWrappers.each(function() {
+ var $cellWrapper = $(this);
+
+ // Drop the removed warning during restore operation.
+ if (!isRemoved) {
+ self.hideRemovedWarning($row);
+ }
+
+ // Toggle the visibility state of the contents of cells.
+ $cellWrapper.animate({opacity: (isRemoved ? 'hide' : 'show')}, 'fast', function() {
+ var $cell = $cellWrapper.parent();
+
+ // Show the removed warning during remove operation.
+ if (isRemoved && $cell.prev(':first').hasClass('content-multiple-drag')) {
+ self.showRemovedWarning($row, tableDrag);
+ }
+
+ // Disable the busy flag when animation of last cell has finished.
+ if ($cell.next(':first').hasClass('delta-order')) {
+ tableDrag.displayChangedWarning();
+ $row.animating = false;
+ }
+ });
+ });
+};
+
+/**
+ * Show the removed warning on the given row.
+ *
+ * @param $row
+ * The jQuery object of the table row.
+ * @param tableDrag
+ * The tableDrag object where the row is.
+ */
+Drupal.contentRemoveButtons.showRemovedWarning = function($row, tableDrag) {
+ $('.content-multiple-drag', $row).next(':first').append(Drupal.theme('contentRemovedWarning', tableDrag.getRemovedWarning()));
+};
+
+/**
+ * Hide the removed warning from the given row.
+ *
+ * @param $row
+ * The jQuery object of the table row.
+ */
+Drupal.contentRemoveButtons.hideRemovedWarning = function($row) {
+ if ($('.content-multiple-removed-warning', $row).size()) {
+ $('.content-multiple-removed-warning', $row).remove();
+ }
+};
+
+/**
+ * Get cell wrappers for the given row.
+ *
+ * @param $row
+ * The jQuery object of the table row.
+ */
+Drupal.contentRemoveButtons.getCellWrappers = function($row) {
+ // Create cell wrappers if this row has not already been processed.
+ if (!$('.content-multiple-cell-content-wrapper', $row).size()) {
+ // Wrap the contents of all cells (except the drag'n'drop, weight and
+ // remove button cells) with a dummy block element. This operation makes
+ // animations faster because we just need to show/hide a single element
+ // per cell, and it also prevents from creating more than one warning
+ // element per row.
+ $row.children('td:not(.content-multiple-drag):not(.delta-order):not(.content-multiple-remove-cell)').each(function() {
+ var $cell = $(this);
+ $cell.wrapInner('
');
+ });
+ }
+ return $('.content-multiple-cell-content-wrapper', $row);
+};
+
+/**
+ * Display table change warning when appropriate.
+ */
+Drupal.tableDrag.prototype.displayChangedWarning = function() {
+ if (this.changed == false) {
+ $(Drupal.theme('tableDragChangedWarning')).insertAfter(this.table).hide().fadeIn('slow');
+ this.changed = true;
+ }
+};
+
+/**
+ * Get the title of the remove button.
+ *
+ * This method is an extension of the tableDrag class. This means a separate
+ * module can override this method for a particular tableDrag instance. For
+ * example, the multigroup module can change the text to read 'Remove this
+ * group of items', another module could change it to 'Remove this image',
+ * and so on...
+ * To override this function:
+ *
+ * @code
+ * var tableId = $(table).attr('id');
+ * Drupal.tableDrag[tableId].getRemoveButtonTitle = function(isRemoved) {
+ * return (isRemoved ? Drupal.t('Restore this foo') : Drupal.t('Remove this foo'));
+ * };
+ * @endcode
+ *
+ * @param isRemoved
+ * A flag that indicates the state of the button.
+ */
+Drupal.tableDrag.prototype.getRemoveButtonTitle = function(isRemoved) {
+ return (isRemoved ? Drupal.t('Restore this item') : Drupal.t('Remove this item'));
+};
+
+/**
+ * Get the item removed warning.
+ *
+ * This method is an extension of the tableDrag class. It can be overridden by
+ * a separate module. See getRemoveButtonTitle() for further information.
+ */
+Drupal.tableDrag.prototype.getRemovedWarning = function() {
+ return Drupal.t('Removed');
+};
+
+/**
+ * Theme the remove button.
+ */
+Drupal.theme.prototype.contentRemoveButton = function(title) {
+ return '';
+};
+
+/**
+ * Theme the item removed warning.
+ */
+Drupal.theme.prototype.contentRemovedWarning = function(warning) {
+ return '
';
@@ -372,3 +367,74 @@
print drupal_to_js(array('status' => TRUE, 'data' => $output));
exit;
}
+
+/**
+ * Store an element into a form.
+ *
+ * @param $name
+ * The field name.
+ * @param $type
+ * The content type where the field instance belongs to.
+ * @param $form
+ * The form to store this field element into.
+ * @param $element
+ * The form element to store.
+ */
+function content_set_form_element($name, $type, &$form, $element, $is_group = FALSE) {
+ if (module_exists('fieldgroup') && ($parents = _fieldgroup_field_get_parents($type, $name, $is_group))) {
+ foreach (module_implements('fieldgroup_parents_alter') as $module) {
+ $parents = call_user_func($module .'_fieldgroup_parents_alter', $form, $parents, $type, $name);
+ }
+ $reference = &$form;
+ if ($is_group) {
+ array_shift($parents);
+ $parents = array_reverse($parents);
+ }
+ else {
+ $parents = array_reverse($parents);
+ }
+
+ foreach (array_values($parents) as $group_name) {
+ $reference = &$reference[$group_name];
+ }
+
+ $reference[$name] = $element;
+
+ }
+ else {
+ $form[$name] = $element;
+ }
+}
+
+/**
+ * Retrieve an element from a form.
+ *
+ * @param $name
+ * The field name.
+ * @param $type
+ * The content type where the field instance belongs to.
+ * @param $form
+ * The form to retrieve this field element from.
+ */
+function content_get_form_element($name, $type, $form, $is_group = FALSE) {
+ if (module_exists('fieldgroup') && ($parents = _fieldgroup_field_get_parents($type, $name, $is_group))) {
+ foreach (module_implements('fieldgroup_parents_alter') as $module) {
+ $parents = call_user_func($module .'_fieldgroup_parents_alter', $form, $parents, $type, $name);
+ }
+ $reference = &$form;
+ if ($is_group) {
+ array_shift($parents);
+ $parents = array_reverse($parents);
+ }
+ else {
+ $parents = array_reverse($parents);
+ }
+
+ foreach (array_values($parents) as $group_name) {
+ $reference = &$reference[$group_name];
+ }
+
+ return $reference[$name];
+ }
+ return $form[$name];
+}
Index: modules/content_multigroup/README.txt
===================================================================
--- modules/content_multigroup/README.txt (revision 1263)
+++ modules/content_multigroup/README.txt (working copy)
@@ -1,4 +1,100 @@
-; $Id: README.txt,v 1.1.2.4 2009/06/04 18:57:59 yched Exp $
+; $Id: README.txt,v 1.1.2.4.2.3 2009/08/10 03:53:24 markuspetrux Exp $
-Ongoing work on the multigroup module has moved to the experimental
-CCK 3.0 branch.
+CONTENTS OF THIS FILE
+=====================
+- USING MULTIGROUPS
+- FIELDS AND WIDGETS THAT WORK IN MULTIGROUPS
+- VIEWS INTEGRATION
+- TROUBLESHOOTING
+
+
+USING MULTIGROUPS
+=================
+
+The Multigroup group treats all included fields like a single field, keeping
+the related delta values of all included fields synchronized.
+
+To use a Multigroup, create a new group, make it the 'Multigroup' type, set
+the number of multiple values for all the fields in the Multigroup, and drag
+into it the fields that should be included.
+
+All fields in the Multigroup will automatically get the group setting for
+multiple values. On the node form, the group is rearranged to keep the delta
+values for each field in a single drag 'n drop group, by transposing the
+normal array(group_name => field_name => delta => value) into
+array(group_name => delta => field_name => value).
+
+During validation and submission, the field values are restored to their
+normal positions.
+
+
+FIELDS AND WIDGETS THAT WORK IN MULTIGROUPS
+===========================================
+
+All fields that allow the Content module to handle their multiple values should
+work here. Fields that handle their own multiple values will not be allowed
+into Multigroups unless they implement hook_content_multigroup_allowed_widgets()
+to add their widgets to the allowed widget list. Example:
+
+ @code
+ function MODULE_content_multigroup_allowed_widgets() {
+ return array('WIDGET_NAME_1', 'WIDGET_NAME_2', ...);
+ }
+ @endcode
+
+All fields that allow the Content module to handle their multiple values
+should work correctly when a field placed on a Multigroup is moved off, to a
+normal field group, or to the top level of the form. Fields that handle their
+own multiple values which may store different results in Multigroup and
+standard groups should implement hook_content_multigroup_no_remove_widgets()
+to add their widgets to the list of widgets that cannot be removed from
+Multigroups. Example:
+
+ @code
+ function MODULE_content_multigroup_no_remove_widgets() {
+ return array('WIDGET_NAME_1', 'WIDGET_NAME_2', ...);
+ }
+ @endcode
+
+The Content Taxonomy module [1] is an example where it implements the previous
+hooks for a few widgets.
+
+[1] http://drupal.org/project/content_taxonomy
+
+If a simple array of widgets is not sufficient to test whether this action
+will work, modules can implement hook_content_multigroup_allowed_in()
+and hook_content_multigroup_allowed_out() to intervene. Both hooks should
+return an array as in the following example:
+
+ @code
+ function MODULE_content_multigroup_allowed_in() {
+ return array(
+ 'allowed' => FALSE,
+ 'message' => t('This change is not allowed. Reason here...'),
+ );
+ }
+ @endcode
+
+Custom code and modules that add fields to groups outside of the UI should
+use content_multigroup_allowed_in() and content_multigroup_allowed_out() to
+test whether fields are allowed in or out of a Multigroup. These functions
+can be located in content_multigroup.admin.inc.
+
+
+VIEWS INTEGRATION
+=================
+
+For each multigroup, there is a new filter under "Content multigroup" category
+in Views that provides a method to synchronize fields by delta.
+
+
+TROUBLESHOOTING
+===============
+
+The most likely cause of problems with field modules not working in multigroup
+is if they wipe out #element_validate with their own validation functions, or
+they hard-code assumptions into submit or validation processes that the form
+is structured in the usual field => delta => value order instead of allowing
+for the possibility of a different structure. See Nodereference for an example
+of a field that handles validation without making assumptions about the form
+structure.
Index: modules/content_multigroup/content_multigroup.admin.inc
===================================================================
--- modules/content_multigroup/content_multigroup.admin.inc (revision 0)
+++ modules/content_multigroup/content_multigroup.admin.inc (revision 0)
@@ -0,0 +1,525 @@
+ t('N/A'),
+ 1 => t('Unlimited'),
+ 0 => 1) + drupal_map_assoc(range(2, 10));
+}
+
+/**
+ * Validation for creating/moving fields and groups on the
+ * Manage Fields screen.
+ */
+function content_multigroup_field_overview_form_validate($form, &$form_state) {
+ $form_values = $form_state['values'];
+
+ $type_name = $form['#type_name'];
+ $fields = array();
+ $groups = array();
+
+ $group = $form_values['_add_new_group'];
+ if (array_filter(array($group['label'], $group['group_name']))) {
+ $group['settings'] = field_group_default_settings($group['group_type']);
+ $validation = fieldgroup_validate_name($group, $form['#type_name']);
+
+ // If there's something wrong with the new group,
+ // don't bother doing any more validation, further
+ // processing will be stopped by the fieldgroup module.
+ if (!empty($validation['errors'])) {
+ return;
+ }
+ $group['group_name'] = $validation['group_name'];
+ $new_group_name = $group['group_name'];
+ $groups['_add_new_group'] = $group;
+ }
+
+ // See if we have fields moving into or out of a Multigroup.
+ // Set any fields to use the new name here so they will get processed
+ // correctly by the fieldgroup module when saved.
+ $group_rows = array();
+ foreach ($form_values as $key => $values) {
+ if ($values['parent'] == '_add_new_group') {
+ $values['parent'] = $new_group_name;
+ $form_values[$key] = $values;
+ }
+
+ if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') {
+ // Gather up info about all groups.
+ $group_name = $form_values[$key]['group']['group_name'];
+ $groups[$group_name] = $form_values[$key]['group'];
+ $group_rows[$group_name] = $group_name;
+ }
+ if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'field') {
+ if ($values['prev_parent'] != $values['parent']) {
+ // Gather up fields that have moved in or out of a group.
+ $fields[$key] = $form_values[$key]['field'];
+ }
+ }
+ }
+
+ $rebuild = FALSE;
+
+ foreach ($groups as $key => $values) {
+ if (in_array($key, $group_rows)) {
+ if (!empty($groups[$key]['parent']) && $groups[$groups[$key]['parent']]['group_type'] == 'multigroup') {
+ $error_message = t('You cannot place any kind of group inside a multigroup. ' . $key . ' was moved back to where it started.');
+ form_set_value($form[$key]['weight'], $form[$key]['weight']['#default_value'], $form_state);
+ form_set_value($form[$key]['parent'], $form[$key]['parent']['#default_value'], $form_state);
+ drupal_set_message($error_message, 'error');
+ }
+ }
+ }
+
+ foreach ($fields as $field_name => $field) {
+ $new_group = $form_values[$field_name]['parent'];
+ $old_group = $form_values[$field_name]['prev_parent'];
+ if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') {
+ $allowed_in = content_multigroup_allowed_in($field, $groups[$new_group]);
+ if (!$allowed_in['allowed']) {
+ form_set_error($field_name, $allowed_in['message']);
+ }
+ else {
+ if (!empty($allowed_in['message'])) {
+ drupal_set_message($allowed_in['message']);
+ }
+ module_load_include('inc', 'content', 'includes/content.crud');
+ $content_type = content_types($type_name);
+ $group_multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
+ $multiple_values = content_multigroup_multiple_values();
+ $field = $content_type['fields'][$field_name];
+ $field['multiple'] = $group_multiple;
+ $field = content_field_instance_collapse($field);
+ content_field_instance_update($field, FALSE);
+ $rebuild = TRUE;
+ 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(
+ '%field' => $field['label'], '%multiple' => $multiple_values[$group_multiple], '%group' => $groups[$new_group]['label'])));
+ }
+ }
+ elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
+ $allowed_out = content_multigroup_allowed_out($field, $groups[$old_group]);
+ if (!$allowed_out['allowed']) {
+ form_set_error($field_name, $allowed_out['message']);
+ }
+ elseif (!empty($allowed_out['message'])) {
+ drupal_set_message($allowed_out['message']);
+ }
+ }
+ }
+
+ // Clear caches and rebuild menu only if any field has been updated.
+ if ($rebuild) {
+ content_clear_type_cache(TRUE);
+ menu_rebuild();
+ }
+}
+
+/**
+ * Helper function for deciding if a field is
+ * allowed into a Multigroup.
+ */
+function content_multigroup_allowed_in($field, $group) {
+ if ($group['group_type'] != 'multigroup') {
+ return array('allowed' => TRUE, 'message' => '');
+ }
+
+ // We can't allow fields with more multiple values than the group has
+ // to be moved into it.
+ $max_existing = content_max_delta($field['field_name']);
+ $group_multiple = $group['settings']['multigroup']['multiple'];
+ $multiple_values = content_multigroup_multiple_values();
+ if ($group_multiple != 1 && $max_existing > $group_multiple) {
+ return array(
+ 'allowed' => FALSE,
+ '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_multiple]))
+ );
+ }
+
+ // Fields that handle their own multiple values may not have the same values
+ // in Multigroup fields and normal fields. We don't know if they will work or not.
+
+ // Adding a hook here where widgets that handle their own multiple values
+ // that will work correctly in Multigroups can allow their fields in.
+
+ if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) {
+ $allowed_widgets = array(
+ 'optionwidgets_select',
+ 'optionwidgets_buttons',
+ 'optionwidgets_onoff',
+ 'nodereference_buttons',
+ 'nodereference_select',
+ 'userreference_buttons',
+ 'userreference_select',
+ );
+ $allowed_widgets = array_merge($allowed_widgets, module_invoke_all('content_multigroup_allowed_widgets'));
+ if (!in_array($field['widget']['type'], $allowed_widgets)) {
+ return array(
+ 'allowed' => FALSE,
+ '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']))
+ );
+ }
+ }
+
+ // Allow other modules to intervene.
+ // Any failure will prevent this action.
+ foreach (module_implements('content_multigroup_allowed_in') as $module) {
+ $function = $module .'_content_multigroup_allowed_in';
+ $result = $function($field, $group);
+ if ($result['allowed'] === FALSE) {
+ return array('allowed' => FALSE, 'message' => $result['message']);
+ }
+ }
+
+ $message = t('You are moving the field %field into a Multigroup.', array('%field' => $field['widget']['label']));
+ return array('allowed' => TRUE, 'message' => $message);
+}
+
+/**
+ * Helper function for deciding if a field is
+ * allowed out of a Multigroup.
+ */
+function content_multigroup_allowed_out($field, $group) {
+ if ($group['group_type'] != 'multigroup') {
+ return array('allowed' => TRUE, 'message' => '');
+ }
+ // Optionwidgets do not behave the same in a Multigroup field as out of it.
+ // In a Multigroup the same option can be selected multiple times,
+ // but that is not possible in a normal group.
+
+ // Adding a hook here where widgets that handle their own multiple values
+ // can indicate their fields should not be removed from Multigroups.
+
+ $max_existing = content_max_delta($field['field_name']);
+ $no_remove_widgets = array(
+ 'optionwidgets_select',
+ 'optionwidgets_buttons',
+ 'optionwidgets_onoff',
+ 'nodereference_buttons',
+ 'nodereference_select',
+ 'userreference_buttons',
+ 'userreference_select',
+ );
+ $no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('content_multigroup_no_remove_widgets'));
+ if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) {
+ return array(
+ 'allowed' => FALSE,
+ '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']))
+ );
+ }
+
+ // Allow other modules to intervene.
+ // Any failure will prevent this action.
+ foreach (module_implements('content_multigroup_allowed_out') as $module) {
+ $function = $module .'_content_multigroup_allowed_out';
+ $result = $function($field, $group);
+ if ($result['allowed'] === FALSE) {
+ return array('allowed' => FALSE, 'message' => $result['message']);
+ }
+ }
+
+ $message = t('You are moving the field %field out of a Multigroup.', array('%field' => $field['widget']['label']));
+ return array('allowed' => TRUE, 'message' => $message);
+}
+
+/**
+ * Alter the basic field settings form.
+ *
+ * It should not be possible to choose a widget type that is not compatible
+ * with multigroups.
+ */
+function content_multigroup_field_basic_form(&$form, &$form_state) {
+ $field_name = $form['basic']['field_name']['#value'];
+ $type_name = $form['type_name']['#value'];
+
+ // Ignore this field if it is not part of a field group.
+ if (!($group_name = fieldgroup_get_group($type_name, $field_name))) {
+ return;
+ }
+
+ // Retrieve information about the group the field is in.
+ $groups = fieldgroup_groups($type_name);
+ $group = $groups[$group_name];
+
+ // Ignore this field if it is not part of a multigroup.
+ if ($group['group_type'] != 'multigroup') {
+ return;
+ }
+
+ // Retrieve information about the field itself.
+ $field = content_fields($field_name, $type_name);
+
+ // Check if the widget can be moved out of the multigroup.
+ $allowed_out = content_multigroup_allowed_out($field, $group);
+ if (!$allowed_out['allowed']) {
+ $form['basic']['widget_type']['#disabled'] = TRUE;
+ $form['basic']['widget_type']['#suffix'] = '
'. t('The widget type cannot be changed because the field %field already has data created and this widget stores data differently in a Standard group than in a Multigroup. Allowing this change could result in the loss of data.', array('%field' => $field['widget']['label'])) .'
';
+ return;
+ }
+
+ // Remove from the list of available widgets those that are not
+ // compatible with multigroups.
+ $widget_types = _content_widget_types();
+ foreach (array_keys($form['basic']['widget_type']['#options']) as $widget_type) {
+ if ($field['widget']['type'] != $widget_type) {
+ $field_copy = $field;
+ $field_copy['widget']['type'] = $widget_type;
+ $field_copy['widget']['module'] = $widget_types[$widget_type]['module'];
+ $allowed_in = content_multigroup_allowed_in($field_copy, $group);
+ if (!$allowed_in['allowed']) {
+ unset($form['basic']['widget_type']['#options'][$widget_type]);
+ }
+ }
+ }
+}
+
+/**
+ * Alter the "Display fields" form.
+ *
+ * Add an additional selector for setting multigroup field display format.
+ */
+function content_multigroup_display_overview_form(&$form, &$form_state) {
+
+ $type_name = $form['#type_name'];
+ $contexts_selector = $form['#contexts'];
+
+ // Gather type information.
+ $content_type = content_types($type_name);
+
+ // The content module stops building the form if the type has no fields.
+ if (empty($content_type['fields'])) {
+ return;
+ }
+
+ $groups = array();
+ if (module_exists('fieldgroup')) {
+ $groups = fieldgroup_groups($type_name);
+ }
+ $contexts = content_build_modes($contexts_selector);
+
+ // Multigroups, extra values.
+ $label_options = array(
+ 'above' => t('Above'),
+ 'hidden' => t(''),
+ );
+ $options = array(
+ 'simple' => t('Simple'),
+ 'fieldset' => t('Fieldset'),
+ 'fieldset_collapsible' => t('Fieldset - collapsible'),
+ 'fieldset_collapsed' => t('Fieldset - collapsed'),
+ 'hr' => t('Horizontal line'),
+ 'table-single' => t('Table - Single column'),
+ 'table-multiple' => t('Table - Multiple columns'),
+ );
+ foreach ($groups as $group_name => $group) {
+ if ($group['group_type'] != 'multigroup') {
+ continue;
+ }
+ $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
+
+ $subgroup_name = $group_name .'_subgroup';
+ $form['#fields'] = array_merge(array($subgroup_name), $form['#fields']);
+ $form[$subgroup_name] = array(
+ 'human_name' => array('#value' => t('[Subgroup format]')),
+ 'weight' => array('#type' => 'value', '#value' => -20),
+ 'parent' => array('#type' => 'value', '#value' => $group_name),
+ 'subgroup' => array('#type' => 'value', '#value' => 1),
+ );
+ if ($contexts_selector == 'basic') {
+ $form[$subgroup_name]['label'] = array(
+ '#type' => 'select',
+ '#options' => $label_options,
+ '#default_value' => isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above',
+ );
+ }
+ foreach ($contexts as $key => $title) {
+ $form[$subgroup_name][$key]['format'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => isset($subgroup_settings[$key]['format']) ? $subgroup_settings[$key]['format'] : 'fieldset',
+ );
+ $form[$subgroup_name][$key]['exclude'] = array('#type' => 'value', '#value' => 0);
+ }
+ }
+
+ $form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']);
+}
+
+/**
+ * Submit handler for the display overview form.
+ *
+ * Do this in pre_save so we catch it before the content module
+ * tries to use our 'field'.
+ */
+function content_multigroup_display_overview_form_submit($form, &$form_state) {
+ $groups = fieldgroup_groups($form['#type_name']);
+ $reset_cache = FALSE;
+
+ // Find any subgroups we inserted into the display fields form,
+ // save our settings, and remove them from $form_state.
+ foreach ($form_state['values'] as $key => $values) {
+ if (in_array($key, $form['#fields']) && !empty($values['parent']) && !empty($values['subgroup'])) {
+ $group_name = $values['parent'];
+ $group = $groups[$group_name];
+ unset($values['subgroup'], $values['parent']);
+
+ // We have some numeric keys here, so we can't use array_merge.
+ foreach ($values as $k => $v) {
+ $group['settings']['multigroup']['subgroup'][$k] = $v;
+ }
+
+ // Update the group information in the database. Note that
+ // 'fieldgroup_data' in cache tables are also cleared here,
+ // but we need to reset static caches of fieldgroup_groups().
+ fieldgroup_save_group($form['#type_name'], $group);
+ $reset_cache = TRUE;
+
+ // Remove the subgroup from $form_state.
+ unset($form_state['values'][$key]);
+ }
+ }
+ if ($reset_cache) {
+ fieldgroup_groups('', FALSE, TRUE);
+ }
+}
+
+/**
+ * Alter the Fieldgroup edit form to add Multigroup settings.
+ */
+function content_multigroup_group_edit_form(&$form, &$form_state) {
+ $type_name = $form['#content_type']['type'];
+ $group_name = $form['group_name']['#default_value'];
+
+ $content_type = content_types($type_name);
+ $groups = fieldgroup_groups($type_name);
+ $group = $groups[$group_name];
+
+ if ($group['group_type'] != 'multigroup') {
+ return;
+ }
+
+ module_load_include('inc', 'content', 'includes/content.admin');
+ module_load_include('inc', 'content', 'includes/content.crud');
+ $form['group_type'] = array(
+ '#type' => 'hidden',
+ '#value' => $group['group_type'],
+ );
+ $form['settings']['multigroup'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Multigroup settings'),
+ '#collapsed' => FALSE,
+ '#collapsible' => TRUE,
+ );
+
+ if (isset($group['settings']['multigroup']['subgroup'])) {
+ // Preserve subgroup display settings.
+ $form['settings']['multigroup']['subgroup'] = array(
+ '#type' => 'value',
+ '#value' => $group['settings']['multigroup']['subgroup'],
+ );
+ }
+
+ $form['settings']['multigroup']['multiple-columns'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Multiple columns'),
+ '#default_value' => isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0,
+ '#description' => t('Enable this option to render each field on a separate column on the node edit form.'),
+ );
+
+ $form['settings']['multigroup']['required'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Required'),
+ '#default_value' => isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 1,
+ '#description' => t('Enable this option to require a minimum of one collection of fields in this Multigroup.'),
+ );
+
+ $description = t('Number of times to repeat the collection of Multigroup fields.') .' ';
+ $description .= t("'Unlimited' will provide an 'Add more' button so the users can add items as many times as they like.") .' ';
+ $description .= t('All fields in this group will automatically be set to allow this number of values.');
+
+ $group_multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
+ $form['settings']['multigroup']['multiple'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of repeats'),
+ '#options' => content_multigroup_multiple_values(),
+ '#default_value' => $group_multiple,
+ '#description' => $description,
+ );
+
+ $form['settings']['multigroup']['labels'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Labels'),
+ '#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."),
+ );
+ if ($group_multiple < 2) {
+ $group_multiple = 0;
+ }
+ for ($i = 0; $i < 10; $i++) {
+ $form['settings']['multigroup']['labels'][$i] = array(
+ '#type' => 'textfield',
+ '#title' => t('Subgroup %number label', array('%number' => $i + 1)),
+ '#default_value' => isset($group['settings']['multigroup']['labels'][$i]) ? $group['settings']['multigroup']['labels'][$i] : '',
+ );
+ }
+
+ $form['#validate'][] = 'content_multigroup_group_edit_form_validate';
+ $form['#submit'][] = 'content_multigroup_group_edit_form_submit';
+}
+
+/**
+ * Validate the Fieldgroup edit form.
+ */
+function content_multigroup_group_edit_form_validate($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $group_type = $form_values['group_type'];
+ if ($group_type != 'multigroup') {
+ return;
+ }
+ $content_type = $form['#content_type'];
+ $groups = fieldgroup_groups($content_type['type']);
+ $group = $groups[$form_values['group_name']];
+ foreach ($group['fields'] as $field_name => $data) {
+ // Make sure we don't set the multiple values to a number that
+ // would result in lost data.
+ $max_existing = content_max_delta($field_name);
+ if ($form_values['settings']['multigroup']['multiple'] != 1
+ && $max_existing > $form_values['settings']['multigroup']['multiple']) {
+ 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)));
+ }
+ }
+}
+
+/**
+ * Submit the Fieldgroup edit form.
+ *
+ * Update multiple values of fields contained in Multigroups.
+ */
+function content_multigroup_group_edit_form_submit($form, &$form_state) {
+ $form_values = $form_state['values'];
+ $group_type = $form_values['group_type'];
+ if ($group_type != 'multigroup') {
+ return;
+ }
+ module_load_include('inc', 'content', 'includes/content.crud');
+ $content_type = $form['#content_type'];
+ $groups = fieldgroup_groups($content_type['type']);
+ $group = $groups[$form_values['group_name']];
+ $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+ if (!empty($group_fields)) {
+ foreach ($group_fields as $field_name => $field) {
+ $field['multiple'] = $form_values['settings']['multigroup']['multiple'];
+ $field = content_field_instance_collapse($field);
+ content_field_instance_update($field, FALSE);
+ }
+ content_clear_type_cache(TRUE);
+ menu_rebuild();
+ }
+}
Index: modules/content_multigroup/content_multigroup.css
===================================================================
--- modules/content_multigroup/content_multigroup.css (revision 1263)
+++ modules/content_multigroup/content_multigroup.css (working copy)
@@ -1,9 +1,42 @@
-/* $Id: content_multigroup.css,v 1.1.2.1 2008/10/14 15:16:50 karens Exp $ */
+/* $Id: content_multigroup.css,v 1.1.4.2 2009/06/07 00:06:21 markuspetrux Exp $ */
label.content-multigroup {
- font-weight:bold;
-}
+ font-weight: bold;
+}
-/* Not styled by default, but available to style */
+/* Not styled by default, but available to style. */
hr.content-multigroup {
-}
\ No newline at end of file
+}
+
+/* Inline field labels visible within the context of multigroups. */
+.content-multigroup-wrapper .field .field-label-inline {
+ visibility: visible;
+}
+
+/**
+ * Hide field labels and description on the node edit form when the multiple
+ * columns option is enabled.
+ */
+.content-multigroup-edit-table-multiple-columns label,
+.content-multigroup-edit-table-multiple-columns .description {
+ display: none;
+}
+
+/* Hide field labels when using 'table-multiple' display mode. */
+.content-multigroup-display-table-multiple-columns .field .field-label,
+.content-multigroup-display-table-multiple-columns .field .field-label-inline,
+.content-multigroup-display-table-multiple-columns .field .field-label-inline-first {
+ display: none;
+}
+
+/* Display table with a row for each subgroup and all fields in a single column. */
+.content-multigroup-display-table-single-column .content-multigroup-wrapper {
+ clear: both;
+}
+.content-multigroup-display-table-single-column .content-multigroup-wrapper label.content-multigroup {
+ display: block;
+}
+.content-multigroup-display-table-single-column .content-multigroup-wrapper .field {
+ float: left;
+ margin-right: 1em;
+}
Index: modules/content_multigroup/content_multigroup.info
===================================================================
--- modules/content_multigroup/content_multigroup.info (revision 0)
+++ modules/content_multigroup/content_multigroup.info (revision 0)
@@ -0,0 +1,13 @@
+; $Id: content_multigroup.info,v 1.1.2.2 2009/06/07 01:16:40 markuspetrux Exp $
+name = Content Multigroup
+description = Combine multiple CCK fields into repeating field collections that work in unison.
+dependencies[] = content
+dependencies[] = fieldgroup
+package = CCK
+core = 6.x
+; Information added by drupal.org packaging script on 2010-01-26
+version = "6.x-3.x-dev"
+core = "6.x"
+project = "cck"
+datestamp = "1264464167"
+
Index: modules/content_multigroup/content_multigroup.install
===================================================================
--- modules/content_multigroup/content_multigroup.install (revision 0)
+++ modules/content_multigroup/content_multigroup.install (revision 0)
@@ -0,0 +1,47 @@
+ 2,
+ 'path' => drupal_get_path('module', 'content_multigroup') . '/views',
+ );
}
/**
+ * Implementation of hook_ctools_plugin_directory().
+ */
+function content_multigroup_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'ctools' && $plugin == 'content_types') {
+ return 'panels/' . $plugin;
+ }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function content_multigroup_menu() {
+ $items = array();
+ // Callback for AHAH add more buttons.
+ $items['content_multigroup/js_add_more'] = array(
+ 'page callback' => 'content_multigroup_add_more_js',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'content_multigroup.node_form.inc',
+ );
+ return $items;
+}
+
+/**
* Implementation of hook_theme().
*/
function content_multigroup_theme() {
return array(
'content_multigroup_node_form' => array(
'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_form.inc',
),
'content_multigroup_node_label' => array(
'arguments' => array('text' => NULL),
+ 'file' => 'content_multigroup.node_form.inc',
),
'content_multigroup_display_simple' => array(
'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
),
+ 'content_multigroup_display_fieldset' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
'content_multigroup_display_hr' => array(
'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
),
- 'content_multigroup_display_table' => array(
+ 'content_multigroup_display_table_single' => array(
'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
),
+ 'content_multigroup_display_table_multiple' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
);
}
/**
+ * Implementation of hook_elements().
+ */
+function content_multigroup_elements() {
+ return array(
+ 'content_multigroup_display_fieldset' => array('#value' => NULL),
+ );
+}
+
+/**
* Implementation of hook_fieldgroup_types().
*/
function content_multigroup_fieldgroup_types() {
@@ -61,40 +107,19 @@
module_load_include('inc', 'content', 'includes/content.admin');
$settings = array('multigroup' => array('multiple' => 1));
foreach (array_keys(content_build_modes()) as $key) {
- $settings['multigroup']['display_settings'][$key]['format'] = 'fieldset';
+ $settings['display'][$key]['format'] = 'fieldset';
}
return $settings;
}
}
-function content_multigroup_multiple_values() {
- return array(
- //'' => t('N/A'),
- 1 => t('Unlimited'),
- 0 => 1) + drupal_map_assoc(range(2, 10));
-}
-
/**
- * Implementation of hook_menu().
- */
-function content_multigroup_menu() {
- $items = array();
- // Callback for AHAH add more buttons.
- $items['content_multigroup/js_add_more'] = array(
- 'page callback' => 'content_multigroup_add_more_js',
- 'access arguments' => array('access content'),
- 'type' => MENU_CALLBACK,
- );
- return $items;
-}
-
-/**
* Implementation of hook_form_alter().
*/
function content_multigroup_form_alter(&$form, $form_state, $form_id) {
- // If this is a field edit form and the field is in a Multigroup,
- // override the multiple value settings.
if ($form_id == 'content_field_edit_form' && isset($form['widget'])) {
+ // If this is a field edit form and the field is in a Multigroup,
+ // override the multiple value settings.
$content_type = content_types($form['type_name']['#value']);
$groups = fieldgroup_groups($content_type['type']);
$group_name = _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']);
@@ -104,1047 +129,60 @@
$form['field']['multiple']['#access'] = FALSE;
}
}
+ elseif ($form_id == 'content_field_edit_form' && isset($form_state['change_basic'])) {
+ // This is the basic field settings form. It should not be possible to
+ // choose a widget type that is not compatible with multigroups.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ content_multigroup_field_basic_form($form, $form_state);
+ }
elseif ($form_id == 'content_field_overview_form') {
- content_multigroup_field_overview_form($form, $form_state);
+ // Validation for creating/moving fields and groups on the
+ // Manage Fields screen.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
$form['#validate'][] = 'content_multigroup_field_overview_form_validate';
}
- elseif ($form_id == 'content_display_overview_form') {
- content_multigroup_display_overview_form($form, $form_state, $form_id);
- $form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']);
+ elseif ($form_id == 'content_display_overview_form' && !empty($form['#groups'])) {
+ // Add an additional selector for setting multigroup field display
+ // format to the Display Fields screen.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ content_multigroup_display_overview_form($form, $form_state);
}
elseif ($form_id == 'fieldgroup_group_edit_form') {
- return content_multigroup_group_edit_form($form, $form_state, $form_id);
+ // Alter the Fieldgroup edit form to add Multigroup settings.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ content_multigroup_group_edit_form($form, $form_state);
}
}
-function content_multigroup_field_overview_form(&$form, &$form_state) {
- $options = fieldgroup_types();
- $options['standard'] = t('Standard');
- $options['multigroup'] = t('Multigroup');
- $form['_add_new_group']['group_type'] = array(
- '#type' => 'select',
- '#description' => t('Type of group.'),
- '#options' => $options,
- '#default_value' => 'standard',
- );
-}
-
/**
- * Validation for creating/moving fields and groups on the
- * Manage Fields screen.
- */
-function content_multigroup_field_overview_form_validate($form, &$form_state) {
- $form_values = $form_state['values'];
-
- $type_name = $form['#type_name'];
- $fields = array();
- $groups = array();
-
- $group = $form_values['_add_new_group'];
- if (array_filter(array($group['label'], $group['group_name']))) {
-
- $group['settings'] = field_group_default_settings($form_values['_add_new_group']['group_type']);
- $group = $form_values['_add_new_group'];
- $validation = fieldgroup_validate_name($group, $form['#type_name']);
-
- // If there's something wrong with the new group,
- // don't bother doing any more validation, further
- // processing will be stopped by the fieldgroup module.
- if (!empty($validation['errors'])) {
- return;
- }
- $group['group_name'] = $validation['group_name'];
- $new_group_name = $group['group_name'];
- $groups['_add_new_group'] = $group;
- }
-
- // See if we have fields moving into or out of a Multigroup.
- // Set any fields to use the new name here so they will get processed
- // correctly by the fieldgroup module when saved.
- foreach ($form_values as $key => $values) {
- if ($values['parent'] == '_add_new_group') {
- $values['parent'] = $new_group_name;
- $form_values[$key] = $values;
- }
-
- if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') {
- // Gather up info about all groups.
- $group_name = $form_values[$key]['group']['group_name'];
- $groups[$group_name] = $form_values[$key]['group'];
- }
- if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'field') {
- if ($values['prev_parent'] != $values['parent']) {
- // Gather up fields that have moved in or out of a group.
- $fields[$key] = $form_values[$key]['field'];
- }
- }
- }
-
- if (!empty($fields)) {
- foreach ($fields as $field_name => $field) {
- $new_group = $form_values[$field_name]['parent'];
- $old_group = $form_values[$field_name]['prev_parent'];
- if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') {
- $allowed_in = content_multigroup_allowed_in($field, $groups[$new_group]);
- if (!$allowed_in['allowed']) {
- form_set_error($field_name, $allowed_in['message']);
- }
- else {
- if (!empty($allowed_in['message'])) {
- drupal_set_message($allowed_in['message']);
- }
- module_load_include('inc', 'content', 'includes/content.crud');
- $content_type = content_types($type_name);
- $multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
- $multiple_values = content_multigroup_multiple_values();
- $field = $content_type['fields'][$field_name];
- $field['multiple'] = $multiple;
- $field = content_field_instance_collapse($field);
- content_field_instance_update($field);
- 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(
- '%field' => $field['label'], '%multiple' => $multiple_values[$multiple], '%group' => $groups[$new_group]['label'])));
- }
- }
- elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
- $allowed_out = content_multigroup_allowed_out($field, $groups[$old_group]);
- if (!$allowed_out['allowed']) {
- form_set_error($field_name, $allowed_out['message']);
- }
- elseif (!empty($allowed_out['message'])) {
- drupal_set_message($allowed_out['message']);
- }
- }
- }
- }
-}
-
-/**
- * Helper function for deciding if a field is
- * allowed into a Multigroup.
- */
-function content_multigroup_allowed_in($field, $group) {
- if ($group['group_type'] != 'multigroup') {
- return array('allowed' => TRUE, 'message' => '');
- }
-
- // We can't allow fields with more multiple values than the group has
- // to be moved into it.
- $max_existing = content_max_delta($field['field_name']);
- $group_max = $group['settings']['multigroup']['multiple'];
- $multiple_values = content_multigroup_multiple_values();
- if ($group_max != 1 && $max_existing > $group_max) {
- return array(
- 'allowed' => FALSE,
- '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]))
- );
- }
-
- // Fields that handle their own multiple values may not have the same values
- // in Multigroup fields and normal fields. We don't know if they will work or not.
-
- // Adding a hook here where widgets that handle their own multiple values
- // that will work correctly in Multigroups can allow their fields in.
-
- if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) {
- $allowed_widgets = array(
- 'optionwidgets_select',
- 'optionwidgets_buttons',
- 'optionwidgets_onoff',
- 'nodereference_buttons',
- 'nodereference_select',
- 'userreference_buttons',
- 'userreference_select',
- );
- $allowed_widgets = array_merge($allowed_widgets, module_invoke_all('content_multigroup_allowed_widgets'));
- if (!in_array($field['widget']['type'], $allowed_widgets)) {
- return array(
- 'allowed' => FALSE,
- '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']))
- );
- }
- }
-
- // Allow other modules to intervene.
- // Any failure will prevent this action.
- foreach (module_implements('content_multigroup_allowed_in') as $module) {
- $function = $module .'_content_multigroup_allowed_in';
- $result = $function($field, $group);
- if ($result['allowed'] === FALSE) {
- return array('allowed' => FALSE, 'message' => $result['message']);
- }
- }
-
- $message = t('You are moving the field %field into a Multigroup.', array('%field' => $field['widget']['label']));
- return array('allowed' => TRUE, 'message' => $message);
-}
-
-/**
- * Helper function for deciding if a field is
- * allowed out of a Multigroup.
- */
-function content_multigroup_allowed_out($field, $group) {
- if ($group['group_type'] != 'multigroup') {
- return array('allowed' => TRUE, 'message' => '');
- }
- // Optionwidgets do not behave the same in a Multigroup field as out of it.
- // In a Multigroup the same option can be selected multiple times,
- // but that is not possible in a normal group.
-
- // Adding a hook here where widgets that handle their own multiple values
- // can indicate their fields should not be removed from Multigroups.
-
- $max_existing = content_max_delta($field['field_name']);
- $no_remove_widgets = array(
- 'optionwidgets_select',
- 'optionwidgets_buttons',
- 'optionwidgets_onoff',
- 'nodereference_buttons',
- 'nodereference_select',
- 'userreference_buttons',
- 'userreference_select',
- );
- $no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('content_multigroup_no_remove_widgets'));
- if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) {
- return array(
- 'allowed' => FALSE,
- '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']))
- );
- }
-
- // Allow other modules to intervene.
- // Any failure will prevent this action.
- foreach (module_implements('content_multigroup_allowed_out') as $module) {
- $function = $module .'_content_multigroup_allowed_out';
- $result = $function($field, $group);
- if ($result['allowed'] === FALSE) {
- return array('allowed' => FALSE, 'message' => $result['message']);
- }
- }
-
- $message = t('You are moving the field %field out of a Multigroup.', array('%field' => $field['widget']['label']));
- return array('allowed' => TRUE, 'message' => $message);
-}
-
-/**
- * Menu callback; presents a listing of fields display settings for a content type.
+ * After build callback for multigroups in node form.
*
- * Add an additional selector for setting multigroup field display format.
+ * This proxy function is necessary to prevent from breaking AHAH handlers.
*/
-function content_multigroup_display_overview_form(&$form, &$form_state) {
-
- $type_name = $form['#type_name'];
- $contexts_selector = $form['#contexts'];
-
- // Gather type information.
- $type = content_types($type_name);
- $field_types = _content_field_types();
- $fields = $type['fields'];
-
- $groups = $group_options = array();
- if (module_exists('fieldgroup')) {
- $groups = fieldgroup_groups($type['type']);
- $group_options = _fieldgroup_groups_label($type['type']);
- }
- $contexts = content_build_modes($contexts_selector);
-
- // Multigroups, extra values.
- $label_options = array(
- 'above' => t('Above'),
- 'hidden' => t(''),
- );
- $options = array(
- 'none' => t('none'),
- 'fieldset' => t('Fieldset'),
- 'hr' => t('Horizontal line'),
- //'table' => t('Table'), // TODO add this later
- 'hidden' => t(''),
- );
- foreach ($groups as $name => $group) {
- if ($group['group_type'] != 'multigroup') {
- continue;
- }
- $defaults = $group['settings']['multigroup']['display_settings'];
-
- $form_name = $name .'_subgroup';
- $form['#fields'] = array_merge(array($form_name), $form['#fields']);
- $form[$form_name] = array(
- 'human_name' => array('#value' => t('[Subgroup format]')),
- 'weight' => array('#type' => 'value', '#value' => -20),
- 'parent' => array('#type' => 'value', '#value' => $name),
- );
- if ($contexts_selector == 'basic') {
- $form[$form_name]['label'] = array(
- '#type' => 'select',
- '#options' => $label_options,
- '#default_value' => isset($defaults['label']) ? $defaults['label'] : 'above',
- );
- }
- foreach ($contexts as $key => $title) {
- $form[$form_name][$key]['format'] = array(
- '#type' => 'select',
- '#options' => $options,
- '#default_value' => isset($defaults[$key]) ? $defaults[$key] : 'fieldset',
- );
- }
- }
- return $form;
+function content_multigroup_node_form_after_build($form, &$form_state) {
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.node_form');
+ return _content_multigroup_node_form_after_build($form, $form_state);
}
/**
- * Submit handler for the display overview form.
- *
- * Do this in pre_save so we catch it before the content module
- * tries to use our 'field'.
- */
-function content_multigroup_display_overview_form_submit($form, &$form_state) {
- $form_values = $form_state['values'];
-
- // Find any groups we inserted into the display fields form,
- // save our settings, and remove them from $form_state.
- foreach ($form_values as $key => $values) {
- if (in_array($key, $form['#fields']) && substr($key, -9) == '_subgroup') {
- $group_name = str_replace('_subgroup', '', $key);
- $groups = fieldgroup_groups($form['#type_name']);
- $group = $groups[$group_name];
-
- // We have some numeric keys here, so we can't use array_merge.
- foreach ($values as $k => $v) {
- $group['settings']['multigroup']['display_settings'][$k] = $v;
- }
- fieldgroup_save_group($form['#type_name'], $group);
-
- // Make sure group information is immediately updated.
- cache_clear_all('fieldgroup_data', content_cache_tablename());
- fieldgroup_groups('', FALSE, TRUE);
- unset($form_state['values'][$key]);
- }
- }
-}
-
-/**
- * Alter the Fieldgroup edit form
- * to add Multigroup settings.
- */
-function content_multigroup_group_edit_form(&$form, &$form_state) {
- $type_name = $form['#content_type']['type'];
- $group_name = $form['group_name']['#default_value'];
-
- $content_type = content_types($type_name);
- $groups = fieldgroup_groups($content_type['type']);
- $group = $groups[$group_name];
-
- if ($group['group_type'] != 'multigroup') {
- return;
- }
-
- module_load_include('inc', 'content', 'includes/content.admin');
- module_load_include('inc', 'content', 'includes/content.crud');
- $form['group_type'] = array(
- '#type' => 'hidden',
- '#value' => $group['group_type'],
- );
- $form['settings']['multigroup'] = array(
- '#type' => 'fieldset',
- '#title' => t('Other settings'),
- '#collapsed' => FALSE,
- '#collapsible' => TRUE,
- );
-
- $description = t('Number of times to repeat the collection of Multigroup fields.') . ' ';
- $description .= t("'Unlimited' will provide an 'Add more' button so the users can add repeat it as many times as they like.") . ' ';
- $description .= t('All fields in this group will automatically be set to allow this number of values.');
-
- $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
- $form['settings']['multigroup']['multiple'] = array(
- '#tree' => TRUE,
- '#type' => 'select',
- '#title' => t('Number of repeats'),
- '#options' => content_multigroup_multiple_values(),
- '#default_value' => $multiple,
- '#description' => $description,
- );
-
- $form['settings']['multigroup']['labels'] = array(
- '#type' => 'fieldset',
- '#title' => t('Labels'),
- '#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."),
- );
- if ($multiple < 2) {
- $multiple = 0;
- }
- for ($i = 0; $i < 10; $i++) {
- $form['settings']['multigroup']['labels'][$i] = array(
- '#type' => 'textfield',
- '#title' => t('Subgroup %number label', array('%number' => $i + 1)),
- '#default_value' => isset($group['settings']['multigroup']['labels'][$i]) ? $group['settings']['multigroup']['labels'][$i] : '',
- );
- }
-
- $form['#validate'][] = 'content_multigroup_group_edit_form_validate';
- $form['#submit'][] = 'content_multigroup_group_edit_form_submit';
- return $form;
-}
-
-/**
- * Validate the Fieldgroup edit form.
- */
-function content_multigroup_group_edit_form_validate($form, &$form_state) {
- $form_values = $form_state['values'];
- $group_type = $form_values['group_type'];
- if ($group_type != 'multigroup') {
- return;
- }
- $content_type = $form['#content_type'];
- $groups = fieldgroup_groups($content_type['type']);
- $group = $groups[$form_values['group_name']];
- foreach ($group['fields'] as $field_name => $data) {
- // Make sure we don't set the multiple values to a number that
- // would result in lost data.
- $max_existing = content_max_delta($field_name);
- if ($form_values['settings']['multigroup']['multiple'] != 1
- && $max_existing > $form_values['settings']['multigroup']['multiple']) {
- 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)));
- }
- }
-}
-
-/**
- * Submit the Fieldgroup edit form.
- *
- * Update multiple values of fields contained in Multigroups.
- */
-function content_multigroup_group_edit_form_submit($form, &$form_state) {
- $form_values = $form_state['values'];
- $group_type = $form_values['group_type'];
- if ($group_type != 'multigroup') {
- return;
- }
- module_load_include('inc', 'content', 'includes/content.crud');
- $content_type = $form['#content_type'];
- $groups = fieldgroup_groups($content_type['type']);
- $group = $groups[$form_values['group_name']];
- $multiple = $form_values['settings']['multigroup']['multiple'];
- foreach ($group['fields'] as $field_name => $data) {
- $field = $content_type['fields'][$field_name];
- $field['multiple'] = $multiple;
- $field = content_field_instance_collapse($field);
- content_field_instance_update($field);
- }
-}
-
-/**
* Implementation of hook_fieldgroup_form().
- *
- * Align the delta values of each field in the Multigroup.
- *
- * Swap the field name and delta for each Multigroup so we can
- * d-n-d each collection of fields as a single delta item.
*/
function content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) {
- if ($group['group_type'] != 'multigroup' ||
- !empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) {
- return;
- }
-
- $node = $form['#node'];
- $fields = $group['fields'];
- $content_fields = content_fields();
$group_name = $group['group_name'];
-
- // Use the first field in the group to get the item counts.
- $first_field_name = array_shift(array_keys($group['fields']));
- $first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array();
- $first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array();
-
- $group['multiple'] = $group['settings']['multigroup']['multiple'];
- switch ($group['multiple']) {
- case 0:
- $max = 0;
- break;
- case 1:
- // Is this a new node?
- if (empty($first_field_items)) {
- $max = 1;
- }
- else {
- $filled_items = content_set_empty($first_field, $first_field_items);
- $current_item_count = isset($form_state['item_count'][$group_name])
- ? $form_state['item_count'][$group_name]
- : count($first_field_items);
- // We always want at least one empty icon for the user to fill in.
- $max = ($current_item_count > count($filled_items))
- ? $current_item_count - 1
- : count($filled_items);
- }
- break;
- default:
- $max = $group['multiple'] - 1;
- break;
- }
-
- $form[$group_name]['#theme'] = 'content_multigroup_node_form';
- $form[$group_name]['#multiple'] = !empty($max);
- $form[$group_name]['#type_name'] = $group['type_name'];
- $form[$group_name]['#group_name'] = $group_name;
- $form[$group_name]['#group_label'] = $group['label'];
- $form[$group_name]['#element_validate'] = array('content_multigroup_node_form_validate');
- $form[$group_name]['#tree'] = TRUE;
-
- for ($delta = 0; $delta <= $max; $delta++) {
- content_multigroup_group_form($form, $form_state, $group, $delta);
- }
-
- // Unset the original group field values now that we've moved them.
- foreach ($fields as $field_name => $field) {
- unset($form[$group_name][$field_name]);
- }
-
- if ($add_more = content_multigroup_add_more($form, $form_state, $group)) {
- $form[$group_name] += $add_more;
- }
-}
-
-/**
- * Create a new delta value for the group.
- *
- * Called in form_alter and by AHAH add more.
- */
-function content_multigroup_group_form(&$form, &$form_state, $group, $delta) {
- if ($group['group_type'] != 'multigroup' ||
- !empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) {
- return;
- }
- module_load_include('inc', 'content', 'includes/content.node_form');
-
- $node = $form['#node'];
- $fields = $group['fields'];
- $content_fields = content_fields();
- $group_name = $group['group_name'];
- $group['multiple'] = $group['settings']['multigroup']['multiple'];
- $form[$group_name]['#fields'] = array_keys($group['fields']);
-
- foreach ($fields as $field_name => $group_field) {
- if (empty($form[$group_name][$delta])) {
- $form[$group_name] += array($delta => array($field_name => array()));
+ if ($group['group_type'] == 'multigroup' && !empty($form[$group_name])) {
+ if (!isset($form[$group_name]['#access']) || $form[$group_name]['#access']) {
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.node_form');
+ _content_multigroup_fieldgroup_form($form, $form_state, $form_id, $group);
}
- else {
- $form[$group_name][$delta][$field_name] = array();
- }
-
- $form[$group_name][$delta]['_weight'] = array(
- '#type' => 'weight',
- '#delta' => $delta, // this 'delta' is the 'weight' element's property
- '#default_value' => $delta,
- '#weight' => 100,
- );
- $form[$group_name][$delta]['_delta'] = array(
- '#type' => 'hidden',
- '#value' => $delta,
- );
-
-
- $field = $content_fields[$field_name];
-
- // Make each field into a pseudo single value field
- // with the right delta value.
- $field['multiple'] = FALSE;
-
- // Make sure new fields after the first have an 'empty' option.
- $field['required'] = $delta > 0 ? FALSE : $field['required'];
-
- $form['#field_info'][$field_name] = $field;
- $node_copy = drupal_clone($node);
-
- // Set the form '#node' to the delta value we want so the Content
- // module will feed the right $items to the field module in
- // content_field_form().
-
- // There may be missing delta values for fields that were
- // never created, so check first.
- if (!empty($node->$field_name) && count($node->$field_name) >= $delta + 1) {
- $node_copy->$field_name = array($delta => $node->{$field_name}[$delta]);
- }
- else {
- $node_copy->$field_name = array($delta => NULL);
- }
- $form['#node'] = $node_copy;
- $field_form = content_field_form($form, $form_state, $field, $delta);
-
- // Place the new $field_form into the $delta position in the group form.
- if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
- $value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0;
- $form[$group_name][$delta][$field_name] = $field_form[$field_name][$value];
- }
- else {
- $form[$group_name][$delta][$field_name] = $field_form[$field_name];
- }
- $form[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight'];
-
- // Add in our validation step, and make sure it preceeds other
- // processing so we can massage the element back to the normal position.
- if (empty($form[$group_name][$delta][$field_name]['#element_validate'])) {
- $form[$group_name][$delta][$field_name]['#element_validate'] = array();
- }
- array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'content_multigroup_node_item_validate');
}
-
- // Reset the form '#node' back to its original value.
- $form['#node'] = $node;
-
- return $form;
}
/**
- * Swap transposed field/delta values back
- * to their normal positions in the node.
- */
-function content_multigroup_node_item_validate($element, &$form_state) {
- static $weights = array();
-
- //dsm($form_state['values']);
- $form_values = $form_state['values'];
- $field_name = array_pop($element['#parents']);
- $delta = array_pop($element['#parents']);
- $group_name = array_pop($element['#parents']);
-
- // Identify the new delta value for each field.
-
- // Find the original delta values for this group, save as static value
- // because the group will acquire and lose values while we process it.
- if (!array_key_exists($group_name, $weights)) {
- $items = $form_state['values'][$group_name];
- $weights[$group_name] = array();
- foreach ($items as $count => $value) {
- // Allow for the possibility of matching _weights and missing deltas.
- $weight = floatval($value['_weight']);
- $old_delta = intval($value['_delta']);
- if (empty($weights[$group_name][$weight]) || !in_array($old_delta, $weights[$group_name][$weight])) {
- $weights[$group_name][$weight][] = $old_delta;
- }
- }
- ksort($weights[$group_name]);
- }
- $count = 0;
- foreach ($weights[$group_name] as $weight => $values) {
- foreach ($values as $old_delta) {
- if ($old_delta === $delta) {
- $delta = $count;
- //dsm('moving delta values: '.$group_name.'>'.$field_name.'>'.'from '. $old_delta .' to '. $delta);
- break 2;
- }
- $count++;
- }
- }
- // We figured out what the new order for the fields is,
- // so set the value for the new delta.
-
- // We move these new values back up to the top level of the
- // node and out of the group so the Content module will find and
- // save the new values and so they don't get mixed into the
- // remaining, unaltered, values in the group.
- array_push($element['#parents'], $field_name);
- array_push($element['#parents'], $delta);
-
- // It's very important to use $form_values instead of $element['#value']
- // here, because $element['#value'] is sometimes missing changes
- // made in #element_validate processing done by other modules.
- $value = isset($form_values[$group_name][$delta][$field_name]) ? $form_values[$group_name][$delta][$field_name] : NULL;
-
- // Fields that use optionwidgets have an extra array level in the value
- // because of the optionwidgets transposition that forces a delta value
- // into the result array. This works fine when a delta value is between
- // the field name and the field value, as in normal nodes, but not when
- // we reverse the field and the delta, so in this case we need to
- // promote the nested delta value back up to the field level.
- if (is_array($value) && content_multigroup_uses_optionwidgets($field_name, $element['#type_name'])) {
- $value = array_shift($value);
- }
-
- //dsm('setting value of '. $field_name.'>'.$delta);
- //dsm($value);
- form_set_value($element, $value, $form_state);
-}
-
-/**
- * Helper function for identifying fields that use
- * optionwidgets transpositions.
- */
-function content_multigroup_uses_optionwidgets($field_name, $type_name) {
- static $optionwidgets;
- if (empty($optionwidgets)) {
- $optionwidgets = array(
- 'optionwidgets_select',
- 'optionwidgets_buttons',
- 'optionwidgets_onoff',
- 'nodereference_buttons',
- 'nodereference_select',
- 'userreference_buttons',
- 'userreference_select',
- );
- // Add hook where other widgets that use optionwidgets can announce it.
- $optionwidgets = array_merge($optionwidgets, module_invoke_all('content_multigroup_uses_optionwidgets'));
- }
-
- $types = content_types($type_name);
- $fields = $types['fields'];
- $field = $fields[$field_name];
- if (in_array($field['widget']['type'], $optionwidgets)) {
- return TRUE;
- }
- return FALSE;
-}
-
-/**
- * Validation for the whole node group.
- */
-function content_multigroup_node_form_validate($element, $form_state) {
- // We moved all the new field values out of the field group
- // and up to the top level of the node, now get rid of the
- // original group values.
- form_set_value($element, NULL, $form_state);
- return;
-}
-
-/**
* Implementation of hook_fieldgroup_view().
*/
function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
- if ($group['group_type'] != 'multigroup') {
- return;
+ if ($group['group_type'] == 'multigroup') {
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.node_view');
+ _content_multigroup_fieldgroup_view($node, $element, $group, $context);
}
-
- $group_name = $group['group_name'];
- $node_copy = drupal_clone($node);
- $max = $group['settings']['multigroup']['multiple'];
-
- $count = 0;
- foreach ($group['fields'] as $field_name => $field) {
- $count = max($count, count($node->$field_name));
- }
-
- $group['multiple'] = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
- $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
- $format = isset($group['settings']['multigroup']['display_settings'][$context]['format']) ? $group['settings']['multigroup']['display_settings'][$context]['format'] : 'fieldset';
- $show_label = isset($group['settings']['multigroup']['display_settings']['label']) ? $group['settings']['multigroup']['display_settings']['label'] : 'above';
-
- switch ($group['multiple']) {
- case 0:
- $max = 0;
- break;
- case 1:
- $max = $count;
- break;
- default:
- $max = $group['multiple'];
- break;
- }
-
- for ($delta = 0; $delta < $max; $delta++) {
- $element[$delta] = array('#weight' => $delta);
-
- $label = !empty($labels[$delta]) && $show_label == 'above' ? $labels[$delta] : '';
-
- foreach ($group['fields'] as $field_name => $field) {
-
- // Create a pseudo node that only has the value we want
- // in this group and pass it to the formatter.
- if (isset($node->content[$field_name])) {
- $node_copy->content[$field_name]['field']['items'] = array(
- $delta => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL,
- );
- $element[$delta][$field_name] = $node_copy->content[$field_name];
- $element[$delta][$field_name]['#delta'] = $delta;
- }
- }
- switch ($format) {
- case 'table':
- $element[$delta]['#theme'] = 'content_multigroup_display_table';
- $element[$delta]['#title'] = $label;
- break;
- case 'fieldset':
- $element[$delta]['#type'] = 'fieldset';
- $element[$delta]['#title'] = $label;
- break;
- case 'hr':
- $element[$delta]['#theme'] = 'content_multigroup_display_hr';
- $element[$delta]['#title'] = $label;
- break;
- default:
- $element[$delta]['#theme'] = 'content_multigroup_display_simple';
- $element[$delta]['#title'] = $label;
- break;
- }
-
- }
-
- foreach ($group['fields'] as $field_name => $field) {
- if (isset($element[$field_name])) {
- unset($element[$field_name]);
- }
- }
}
-
-/**
- * Theme an individual form element.
- *
- * Combine multiple values into a table with drag-n-drop reordering.
- */
-function theme_content_multigroup_node_form($element) {
- $output = '';
- if ($element['#multiple'] >= 1) {
- $table_id = $element['#group_name'] .'_values';
- $order_class = $element['#group_name'] .'-delta-order';
-
- $header = array(
- array(
- 'data' => '',
- 'colspan' => 2
- ),
- t('Order'),
- );
- $rows = array();
- $groups = fieldgroup_groups($element['#type_name']);
- $group = $groups[$element['#group_name']];
- $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
- $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
-
- $i = 0;
- foreach (element_children($element) as $delta => $key) {
- if ($key !== $element['#group_name'] .'_add_more') {
- $label = !empty($labels[$i]) ? theme('content_multigroup_node_label', $labels[$i]) : '';
- $element[$key]['_weight']['#attributes']['class'] = $order_class;
- $delta_element = drupal_render($element[$key]['_weight']);
- $cells = array(
- array('data' => '', 'class' => 'content-multiple-drag'),
- $label . drupal_render($element[$key]),
- array('data' => $delta_element, 'class' => 'delta-order'),
- );
- $rows[] = array(
- 'data' => $cells,
- // TODO Tablesort drag n drop is not working with complex
- // field validation. The fields appear to work correctly,
- // but element validation seems to get missed or confused
- // causing validation errors. Need to investigate why.
- 'class' => 'draggable',
- );
- }
- $i++;
- }
-
- $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
- $output .= $element['#description'] ? '
'. $element['#description'] .'
' : '';
- $output .= drupal_render($element[$element['#group_name'] .'_add_more']);
-
- drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
- }
- else {
- foreach (element_children($element) as $key) {
- $output .= drupal_render($element[$key]);
- }
- }
-
- return $output;
-}
-
-function content_multigroup_add_more(&$form, &$form_state, $group) {
- // Add AHAH add more button, if not working with a programmed form.
- $multiple = $group['settings']['multigroup']['multiple'];
- $form_element = array();
- if ($multiple != 1 || !empty($form['#programmed'])) {
- return $form_element;
- }
- else {
- // Make sure the form is cached so ahah can work.
- $form['#cache'] = TRUE;
- $content_type = content_types($group['type_name']);
- $group_name = $group['group_name'];
- $group_name_css = str_replace('_', '-', $group_name);
-
- $form_element[$group_name .'_add_more'] = array(
- '#type' => 'submit',
- '#name' => $group_name .'_add_more',
- '#value' => t('Add more values'),
- '#weight' => $multiple + 1,
- // Submit callback for disabled JavaScript. drupal_get_form() might get
- // the form from the cache, so we can't rely on content_form_alter()
- // including this file. Therefore, call a proxy function to do this.
- '#submit' => array('content_multigroup_add_more_submit_proxy'),
- '#ahah' => array(
- 'path' => 'content_multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name,
- 'wrapper' => $group_name_css .'-items',
- 'method' => 'replace',
- 'effect' => 'fade',
- ),
- // When JS is disabled, the content_add_more_submit handler will find
- // the relevant field using these entries.
- '#group_name' => $group_name,
- '#type_name' => $group['type_name'],
- );
-
- // Add wrappers for the group and 'more' button.
- // TODO: could be simplified ?
- $form_element['#prefix'] = '
';
- }
- return $form_element;
-}
-
-/**
- * Submit handler to add more choices to a content form. This handler is used when
- * JavaScript is not available. It makes changes to the form state and the
- * entire form is rebuilt during the page reload.
- */
-function content_multigroup_add_more_submit($form, &$form_state) {
- // Set the form to rebuild and run submit handlers.
- node_form_submit_build_node($form, $form_state);
- $group_name = $form_state['clicked_button']['#group_name'];
- $type_name = $form_state['clicked_button']['#type_name'];
-
- // Make the changes we want to the form state.
- if ($form_state['values'][$group_name][$group_name .'_add_more']) {
- $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]);
- }
-}
-
-/**
- * Menu callback for AHAH addition of new empty widgets.
- *
- * Adapted from content_add_more_js to work with groups instead of fields.
- */
-function content_multigroup_add_more_js($type_name_url, $group_name) {
- $type = content_types($type_name_url);
- $groups = fieldgroup_groups($type['type']);
- $group = $groups[$group_name];
- $group['multiple'] = $group['settings']['multigroup']['multiple'];
-
- if (($group['multiple'] != 1) || empty($_POST['form_build_id'])) {
- // Invalid request.
- drupal_json(array('data' => ''));
- exit;
- }
-
- // Retrieve the cached form.
- $form_state = array('submitted' => FALSE);
- $form_build_id = $_POST['form_build_id'];
- $form = form_get_cache($form_build_id, $form_state);
- if (!$form) {
- // Invalid form_build_id.
- drupal_json(array('data' => ''));
- exit;
- }
-
- // We don't simply return a new empty widget to append to existing ones, because
- // - ahah.js won't simply let us add a new row to a table
- // - attaching the 'draggable' behavior won't be easy
- // So we resort to rebuilding the whole table of widgets including the existing ones,
- // which makes us jump through a few hoops.
-
- // The form that we get from the cache is unbuilt. We need to build it so that
- // _value callbacks can be executed and $form_state['values'] populated.
- // We only want to affect $form_state['values'], not the $form itself
- // (built forms aren't supposed to enter the cache) nor the rest of $form_data,
- // so we use copies of $form and $form_data.
- $form_copy = $form;
- $form_state_copy = $form_state;
- $form_copy['#post'] = array();
- form_builder($_POST['form_id'], $form_copy, $form_state_copy);
- // Just grab the data we need.
- $form_state['values'] = $form_state_copy['values'];
- // Reset cached ids, so that they don't affect the actual form we output.
- form_clean_id(NULL, TRUE);
-
- // Sort the $form_state['values'] we just built *and* the incoming $_POST data
- // according to d-n-d reordering.
- unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']);
- foreach ($_POST[$group_name] as $delta => $item) {
- $form_state['values'][$group_name][$delta]['_weight'] = $item['_weight'];
- $form_state['values'][$group_name][$delta]['_delta'] = $item['_delta'];
- }
- $form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]);
- $_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]);
-
- // Build our new form element for the whole group, asking for one more element.
-
- $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1);
- $delta = max(array_keys($_POST[$group_name])) + 1;
- content_multigroup_group_form($form, $form_state, $group, $delta);
-
- // Save the new definition of the form.
- $form_state['values'] = array();
- form_set_cache($form_build_id, $form, $form_state);
-
- // Build the new form against the incoming $_POST values so that we can
- // render the new element.
- $_POST[$group_name][$delta]['_weight'] = $delta;
- $form_state = array('submitted' => FALSE);
- $form += array(
- '#post' => $_POST,
- '#programmed' => FALSE,
- );
- $form = form_builder($_POST['form_id'], $form, $form_state);
-
- // Render the new output.
- $group_form = $form[$group_name];
-
- // We add a div around the new content to receive the ahah effect.
- $group_form[$delta]['#prefix'] = '
';
-
- // If a newly inserted widget contains AHAH behaviors, they normally won't
- // work because AHAH doesn't know about those - it just attaches to the exact
- // form elements that were initially specified in the Drupal.settings object.
- // The new ones didn't exist then, so we need to update Drupal.settings
- // by ourselves in order to let AHAH know about those new form elements.
- $javascript = drupal_add_js(NULL, NULL);
- $output_js = isset($javascript['setting']) ? '' : '';
-
- $output = theme('status_messages') . drupal_render($group_form) . $output_js;
- drupal_json(array('status' => TRUE, 'data' => $output));
- exit;
-}
-
-/**
- * Theme the sub group label in the node form.
- */
-function theme_content_multigroup_node_label($text) {
- if (!empty($text)) {
- return '
'. check_plain($text) .'
';
- }
-}
-
-function theme_content_multigroup_display_simple($element) {
- $label = '';
- if (!empty($element['#title'])) {
- $label .= '';
- }
- $output = $label;
- foreach (element_children($element) as $key) {
- $output .= drupal_render($element[$key]);
- }
- return $output;
-}
-
-function theme_content_multigroup_display_hr($element) {
- $label = '';
- if (!empty($element['#title'])) {
- $label .= '';
- }
- $output = ''. $label;
- foreach (element_children($element) as $key) {
- $output .= drupal_render($element[$key]);
- }
- return $output;
-}
-
-function theme_content_multigroup_display_table($element) {
- $label = '';
- if (!empty($element['#title'])) {
- $label .= '';
- }
- $output = $label;
- foreach (element_children($element) as $key) {
- $output .= drupal_render($element[$key]);
- }
- return $output;
-}
\ No newline at end of file
Index: modules/content_multigroup/content_multigroup.node_form.inc
===================================================================
--- modules/content_multigroup/content_multigroup.node_form.inc (revision 0)
+++ modules/content_multigroup/content_multigroup.node_form.inc (revision 0)
@@ -0,0 +1,971 @@
+ $field) {
+ if (isset($group['fields'][$field_name]) && isset($form[$group_name][$field_name])) {
+ if (!isset($form[$group_name][$field_name]['#access']) || $form[$group_name][$field_name]['#access']) {
+ $group_fields[$field_name] = $field;
+ }
+ }
+ }
+
+ // Quit if there are no field in the form for this group.
+ if (empty($group_fields)) {
+ return;
+ }
+
+ switch ($group_multiple) {
+ case 0:
+ $group_deltas = array(0);
+ $max_delta = 0;
+ break;
+
+ case 1:
+ // Compute unique deltas from all deltas used by fields in this multigroup.
+ $group_deltas = array();
+ $max_delta = -1;
+ foreach (array_keys($group_fields) as $field_name) {
+ if (!empty($node->$field_name) && is_array($node->$field_name)) {
+ foreach (array_keys($node->$field_name) as $delta) {
+ $group_deltas[$delta] = $delta;
+ }
+ sort($group_deltas);
+ $max_delta = max($max_delta, max($group_deltas));
+ }
+ }
+ $current_item_count = isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : max(1, count($group_deltas));
+ while (count($group_deltas) < $current_item_count) {
+ $max_delta++;
+ $group_deltas[] = $max_delta;
+ }
+ break;
+
+ default:
+ $max_delta = $group_multiple - 1;
+ $group_deltas = range(0, $max_delta);
+ break;
+ }
+
+ $form[$group_name]['#theme'] = 'content_multigroup_node_form';
+ $form[$group_name]['#item_count'] = count($group_deltas);
+ $form[$group_name]['#type_name'] = $group['type_name'];
+ $form[$group_name]['#group_name'] = $group_name;
+ $form[$group_name]['#group_label'] = $group['label'];
+ $form[$group_name]['#group_fields'] = $group_fields;
+ $form[$group_name]['#tree'] = TRUE;
+ if (!isset($form['#multigroups'])) {
+ $form['#multigroups'] = array();
+ }
+ $form['#multigroups'][$group_name] = $group_fields;
+
+ // Add a visual indication to the fieldgroup title if the multigroup is required.
+ if (!empty($group['settings']['multigroup']['required'])) {
+ $form[$group_name]['#title'] .= ' *';
+ }
+
+ // Attach our own after build handler to the form, used to fix posting data
+ // and the form structure, moving fields back to their original positions.
+ // That is, move them from group->delta->field back to field->delta.
+ if (!isset($form['#after_build'])) {
+ $form['#after_build'] = array();
+ }
+ if (!in_array('content_multigroup_node_form_after_build', $form['#after_build'])) {
+ array_unshift($form['#after_build'], 'content_multigroup_node_form_after_build');
+ }
+
+ // Attach our own validation handler to the form, used to check for empty fields.
+ if (!isset($form['#validate'])) {
+ $form['#validate'] = array();
+ }
+ if (!in_array('content_multigroup_node_form_validate', $form['#validate'])) {
+ array_unshift($form['#validate'], 'content_multigroup_node_form_validate');
+ }
+
+ // Attach our own pre_render handler to the form, used to fix the required
+ // attribute of all fields in multigroups.
+ if (!isset($form['#pre_render'])) {
+ $form['#pre_render'] = array();
+ }
+ if (!in_array('content_multigroup_node_form_pre_render', $form['#pre_render'])) {
+ array_unshift($form['#pre_render'], 'content_multigroup_node_form_pre_render');
+ }
+ $elements[$group_name] = array();
+ foreach ($group_deltas as $delta) {
+ $element = content_multigroup_group_form($form, $form_state, $group, $delta);
+ $elements[$group_name] = array_merge($elements[$group_name], $element[$group_name]);
+ }
+ $form[$group_name] = $elements[$group_name];
+
+ // Unset the original group field values now that we've moved them.
+ foreach (array_keys($group_fields) as $field_name) {
+ unset($form[$group_name][$field_name]);
+ }
+
+ if (($add_more = content_multigroup_add_more($form, $form_state, $group)) !== FALSE) {
+ $form[$group_name] += $add_more;
+ }
+}
+
+/**
+ * Create a new delta value for the group.
+ *
+ * Called in form_alter and by AHAH add more.
+ */
+function content_multigroup_group_form(&$form, &$form_state, $group, $delta) {
+ module_load_include('inc', 'content', 'includes/content.node_form');
+ $element = array();
+ $type_name = $group['type_name'];
+ $content_type = content_types($type_name);
+ $group_name = $group['group_name'];
+
+ if (!isset($form[$group_name])) {//nested AHAH, not initial build
+ $element[$group_name] = content_get_form_element($group_name, $type_name, $form, TRUE);
+ }
+ else {//initial build (via content_multigroup_fieldgroup_form) or non-nested AHAH
+ $element[$group_name] = $form[$group_name];
+ }
+ if (($group['group_type'] != 'multigroup')
+ || (!(empty($element[$group['group_name']]['#access'])) && $element[$group['group_name']]['#access'] != TRUE)
+ || empty($element[$group['group_name']])) {
+ return;
+ }
+
+ $group_fields = $form['#multigroups'][$group_name];
+ $element[$group_name]['#fields'] = array_keys($group_fields);
+ $node = $form['#node'];
+ $group_multiple = $group['settings']['multigroup']['multiple'];
+
+ foreach ($group_fields as $field_name => $field) {
+ if (empty($element[$group_name][$delta])) {
+ $element[$group_name] += array($delta => array($field_name => array()));
+ }
+ else {
+ $element[$group_name][$delta][$field_name] = array();
+ }
+
+ $item_count = (isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : $element[$group_name]['#item_count']);
+ $element[$group_name][$delta]['_weight'] = array(
+ '#type' => 'weight',
+ '#delta' => $item_count, // this 'delta' is the 'weight' element's property
+ '#default_value' => $delta,
+ '#weight' => 100,
+ );
+
+ // Add a checkbox to allow users remove a single delta subgroup.
+ // See content_set_empty() and theme_content_multigroup_node_form().
+ if ($group_multiple == 1) {
+ $element[$group_name][$delta]['_remove'] = array(
+ '#type' => 'checkbox',
+ '#attributes' => array('class' => 'content-multiple-remove-checkbox'),
+ '#default_value' => isset($form_state['multigroup_removed'][$group_name][$delta]) ? $form_state['multigroup_removed'][$group_name][$delta] : 0,
+ );
+ }
+
+ // Make each field into a pseudo single value field
+ // with the right delta value.
+ $field['multiple'] = 0;
+
+ $form['#field_info'][$field_name] = $field;
+ $node_copy = drupal_clone($node);
+
+ // Set the form '#node' to the delta value we want so the Content
+ // module will feed the right $items to the field module in
+ // content_field_form().
+
+ // There may be missing delta values for fields that were
+ // never created, so check first.
+ if (!empty($node->$field_name) && isset($node->{$field_name}[$delta])) {
+ $node_copy->$field_name = array($delta => $node->{$field_name}[$delta]);
+ }
+ else {
+ $value = NULL;
+ // Try to obtain default values only if the node is being created.
+ if (!isset($node->nid) && content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) {
+ // If a module wants to insert custom default values here,
+ // it should provide a hook_default_value() function to call,
+ // otherwise the content module's content_default_value() function
+ // will be used.
+ $callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value';
+ if (function_exists($callback)) {
+ $items = $callback($form, $form_state, $field, 0);
+ $value = $items[0];
+ }
+ }
+ $node_copy->$field_name = array($delta => $value);
+ }
+ $form['#node'] = $node_copy;
+
+ // Place the new element into the $delta position in the group form.
+ if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
+ $field_form = content_field_form($form, $form_state, $field, $delta);
+ $value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0;
+ $element[$group_name][$delta][$field_name] = $field_form[$field_name][$value];
+ }
+ else {
+ // When the form is submitted, get the element data from the form values.
+ if (isset($form_state['values'][$field_name])) {
+ $form_state_copy = $form_state;
+ if (isset($form_state_copy['values'][$field_name][$delta])) {
+ $form_state_copy['values'][$field_name] = array($delta => $form_state_copy['values'][$field_name][$delta]);
+ }
+ else {
+ $form_state_copy['values'][$field_name] = array($delta => NULL);
+ }
+ $field_form = content_field_form($form, $form_state_copy, $field, $delta);
+ }
+ else {
+ $field_form = content_field_form($form, $form_state, $field, $delta);
+ }
+
+ // Multiple value fields have an additional level in the array form that
+ // needs to get fixed in $form_state['values'].
+ if (!isset($field_form[$field_name]['#element_validate'])) {
+ $field_form[$field_name]['#element_validate'] = array();
+ }
+ $field_form[$field_name]['#element_validate'][] = 'content_multigroup_fix_multivalue_fields';
+
+ $element[$group_name][$delta][$field_name] = $field_form[$field_name];
+ }
+ $element[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight'];
+ }
+
+ // Reset the form '#node' back to its original value.
+ $form['#node'] = $node;
+
+ return $element;
+}
+
+/**
+ * Fix required flag during form rendering stage.
+ *
+ * Required fields should display the required star in the rendered form.
+ */
+function content_multigroup_node_form_pre_render(&$form) {
+ foreach ($form['#multigroups'] as $group_name => $group_fields) {
+ $required_fields = array();
+ foreach ($group_fields as $field_name => $field) {
+ if ($field['required']) {
+ $required_fields[] = $field_name;
+ }
+ }
+ if (!empty($required_fields)) {
+ content_multigroup_node_form_fix_required($form[$group_name], $required_fields, TRUE);
+ }
+ }
+ return $form;
+}
+
+/**
+ * Fix form and posting data when the form is submitted.
+ *
+ * FormAPI uses form_builder() during form processing to map incoming $_POST
+ * data to the proper elements in the form. It builds the '#parents' array,
+ * copies the $_POST array to the '#post' member of all form elements, and it
+ * also builds the $form_state['values'] array. Then the '#after_build' hook is
+ * invoked to allow custom processing of the form structure, and that happens
+ * just before validation and submit handlers are executed.
+ *
+ * During hook_form_alter(), the multigroup module altered the form structure
+ * moving elements from field->delta to multigroup->delta->field position,
+ * which is what has been processed by FormAPI to build the form structures,
+ * but field validation (and submit) handlers expect their data to be located
+ * in their original positions.
+ *
+ * We now need to move the fields back to their original positions in the form,
+ * and we need to do so without altering the form rendering process, which is
+ * now reflecting the structure the multigroup is interested in. We just need
+ * to fix the parts of the form that affect validation and submit processing.
+ */
+function _content_multigroup_node_form_after_build($form, &$form_state) {
+ // Disable required flag during FormAPI validation, except when building the
+ // form for an 'Add more values' request.
+ $required = !empty($form_state['multigroup_add_more']);
+ foreach ($form['#multigroups'] as $group_name => $group_fields) {
+ $required_fields = array();
+ foreach ($group_fields as $field_name => $field) {
+ if ($field['required']) {
+ $required_fields[] = $field_name;
+ }
+ }
+ if (!empty($required_fields)) {
+ content_multigroup_node_form_fix_required($form[$group_name], $required_fields, $required);
+ }
+ }
+
+ if ($form_state['submitted']) {
+ // Fix value positions in $form_state for the fields in multigroups.
+ foreach (array_keys($form['#multigroups']) as $group_name) {
+ content_multigroup_node_form_transpose_elements($form, $form_state, $form['#node']->type, $group_name);
+ }
+
+ // Fix form element parents for all fields in multigroups.
+ content_multigroup_node_form_fix_parents($form, $form['#multigroups']);
+
+ // Update posting data to reflect delta changes in the form structure.
+ if (!empty($_POST)) {
+ content_multigroup_node_form_fix_post($form);
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Fix required flag for required fields.
+ *
+ * We need to let the user enter an empty set of fields for a delta subgroup,
+ * even if it contains required fields, which is equivalent to say a subgroup
+ * should be ignored, not to be stored into the database.
+ * So, we need to check for required fields, but only for non-empty subgroups.
+ *
+ * When the form is processed for rendering, the required flag is enabled for
+ * all required fields, so the user can see what's required and what's not.
+ *
+ * When the form is processed for validation, the required flag is disabled,
+ * so that FormAPI does not report errors for empty fields.
+ *
+ * @see content_multigroup_node_form_validate().
+ */
+function content_multigroup_node_form_fix_required(&$elements, $required_fields, $required) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ if (count($elements[$key]['#array_parents']) >= 3 && in_array($elements[$key]['#array_parents'][2], $required_fields) && isset($elements[$key]['#required'])) {
+ $elements[$key]['#required'] = $required;
+ }
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_required($elements[$key], $required_fields, $required);
+ }
+ }
+}
+
+/**
+ * Node form validation handler.
+ *
+ * Perform validation for empty fields ignoring subgroups flagged for removal.
+ * Note that FormAPI validation for required fields is disabled because we need
+ * to accept empty fields that are flagged for removal.
+ */
+function content_multigroup_node_form_validate($form, &$form_state) {
+ $type_name = $form['#node']->type;
+ $groups = fieldgroup_groups($type_name);
+
+ foreach ($form['#multigroups'] as $group_name => $group_fields) {
+ $group = $groups[$group_name];
+ $group_required = isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 1;
+
+ $non_empty_subgroups = $non_removed_subgroups = $required_field_errors = array();
+ foreach ($group_fields as $field_name => $field) {
+ // Tell the content module that it is not needed to enforce requirement
+ // of fields in this multigroup because we are doing it here.
+ // See content_multiple_value_nodeapi_validate().
+ $form_state['values']['_content_ignore_required_fields'][$field_name] = TRUE;
+
+ // FIXME: why is this happening?
+ // $form_state['values'][$field_name] ends up missing sometimes and $form_state['values'][$group_name][0][$field_name] contains the actual data
+ // This is a hack/temporary-fix such that whenever this situation happens, the data ends up where it should be
+ //
+ if (!array_key_exists($field_name, $form_state['values'])){
+ foreach ($form_state['values'][$group_name] as $delta => $item) {
+ if (is_numeric($delta) && is_array($form_state['values'][$group_name][$delta]) && array_key_exists('_remove', $form_state['values'][$group_name][$delta])){
+ // In the case where '_remove' is set to 1 (TRUE), then the field is to be removed.
+ // Do not bother copying removed fields and they will be auto-deleted from the form and the database!
+ if (!$form_state['values'][$group_name][$delta]['_remove']){
+ $form_state['values'][$field_name][$delta] = $form_state['values'][$group_name][$delta][$field_name];
+ }
+ }
+ }
+ }
+
+ foreach ($form_state['values'][$field_name] as $delta => $item) {
+ // Ignore subgroups flagged for removal.
+ if ($form_state['multigroup_removed'][$group_name][$delta]) {
+ continue;
+ }
+ // Keep track of non-removed subgroups.
+ $non_removed_subgroups[$delta] = TRUE;
+
+ $is_empty_function = $field['module'] .'_content_is_empty';
+ if ($is_empty_function($form_state['values'][$field_name][$delta], $field)) {
+ // Ignore fields that are not required.
+ if (!$field['required']) {
+ continue;
+ }
+
+ // Build an error message for this field in this subgroup, but do
+ // not flag it, yet.
+ if (!empty($item['_error_element'])) {
+ // Here we don't know the number of elements and subelements a
+ // widget could have added to the form, so we need to extract
+ // components from the top, where we have group/delta/field, and
+ // then push back field/delta on top of the list.
+ $error_element = explode('][', $item['_error_element']);
+ array_shift($error_element);
+ array_shift($error_element);
+ array_shift($error_element);
+ array_unshift($error_element, $field_name, $delta);
+ $error_element = implode('][', $error_element);
+ }
+ else {
+ $error_element = '';
+ }
+ $required_field_errors[$delta][$field_name] = array(
+ 'element' => $error_element,
+ 'message' => t('!name field is required in group @group.', array(
+ '!name' => $form[$group_name][$delta][$field_name]['#title'],
+ '@group' => t($group['label']),
+ )),
+ );
+ }
+ else {
+ $non_empty_subgroups[$delta] = TRUE;
+ }
+ }
+ }
+
+ // Required multigroups require at least one non-empty subgroup of fields.
+ if ($group_required && empty($non_empty_subgroups)) {
+ form_set_error('', t('Group @name requires one collection of fields minimum.', array('@name' => t($group['label']))));
+ continue;
+ }
+
+ // Do not enforce field requirements if there is only one non-removed
+ // subgroup of fields, and that subgroup is empty.
+ if (count($non_removed_subgroups) == 1) {
+ $delta = key($non_removed_subgroups);
+ if (isset($required_field_errors[$delta]) && !isset($non_empty_subgroups[$delta])) {
+ unset($required_field_errors[$delta]);
+ }
+ }
+
+ // Ok, now we can flag errors for all required fields that have not been
+ // filled in when they should.
+ foreach ($required_field_errors as $delta => $error_list) {
+ foreach ($error_list as $field_name => $error_info) {
+ form_set_error($error_info['element'], $error_info['message']);
+ }
+ }
+ }
+}
+
+/**
+ * Transpose element positions in $form_state for the fields in a multigroup.
+ */
+function content_multigroup_node_form_transpose_elements(&$form, &$form_state, $type_name, $group_name) {
+ $groups = fieldgroup_groups($type_name);
+ $group = $groups[$group_name];
+ $group_fields = $form['#multigroups'][$group_name];
+
+ // Save the remove state of multigroup items in the $form_state array.
+ if (!isset($form_state['multigroup_removed'])) {
+ $form_state['multigroup_removed'] = array();
+ }
+ if (!isset($form_state['multigroup_removed'][$group_name])) {
+ $form_state['multigroup_removed'][$group_name] = array();
+ }
+
+ // Move group data from group->delta->field to field->delta.
+ $group_data = array();
+ foreach ($form_state['values'][$group_name] as $delta => $items) {
+ // Skip 'add more' button.
+ if (!is_array($items) || !isset($items['_weight'])) {
+ continue;
+ }
+ foreach ($group_fields as $field_name => $field) {
+ if (!isset($group_data[$field_name])) {
+ $group_data[$field_name] = array();
+ }
+ // Get field weight and remove state from the group and keep track of the
+ // current delta for each field item.
+ $item_defaults = array(
+ '_weight' => $items['_weight'],
+ '_remove' => $items['_remove'],
+ '_old_delta' => $delta,
+ );
+ $group_data[$field_name][$delta] = (is_array($items[$field_name]) ? array_merge($items[$field_name], $item_defaults) : $item_defaults);
+ // Store the remove state and the element weight in the form element as
+ // well, so we can restore them later.
+ // See content_multigroup_fix_multivalue_fields().
+ // See content_multigroup_fix_element_values().
+ $form[$group_name][$delta][$field_name]['#_weight'] = $items['_weight'];
+ $form[$group_name][$delta][$field_name]['#removed'] = $items['_remove'];
+
+ // Insert an element valitation callback of our own at the end of the
+ // list to ensure the drag'n'drop weight of the element is not lost by
+ // a form_set_value() operation made by the validation callback of the
+ // widget element.
+ if (!isset($form[$group_name][$delta][$field_name]['#element_validate'])) {
+ $form[$group_name][$delta][$field_name]['#element_validate'] = array();
+ }
+ $form[$group_name][$delta][$field_name]['#element_validate'][] = 'content_multigroup_fix_element_values';
+ }
+ $form_state['multigroup_removed'][$group_name][$delta] = $items['_remove'];
+ }
+
+ $form_group_sorted = FALSE;
+ foreach ($group_data as $field_name => $items) {
+
+ // Sort field items according to drag-n-drop reordering. Deltas are also
+ // rebuilt to start counting from 0 to n. Note that since all fields in the
+ // group share the same weight, their deltas remain in sync.
+ usort($items, '_content_sort_items_helper');
+
+ // Now we need to apply the same ordering to the form elements. Also,
+ // note that deltas have changed during the sort operation, so we need
+ // to reflect this delta conversion in the form.
+ if (!$form_group_sorted) {
+ $form_group_items = array();
+ $form_deltas = array();
+ foreach ($items as $new_delta => $item) {
+ $form_deltas[$item['_old_delta']] = $new_delta;
+ $form_group_items[$new_delta] = $form[$group_name][$item['_old_delta']];
+ unset($form[$group_name][$item['_old_delta']]);
+ }
+ foreach ($form_group_items as $new_delta => $form_group_item) {
+ $form[$group_name][$new_delta] = $form_group_item;
+ }
+ content_multigroup_node_form_fix_deltas($form[$group_name], $form_deltas);
+ $form_group_sorted = TRUE;
+ }
+
+ // Get rid of the old delta value.
+ foreach (array_keys($items) as $delta) {
+ unset($items[$delta]['_old_delta']);
+ }
+
+ // Fix field and delta positions in the $_POST array.
+ if (!empty($_POST)) {
+ $_POST[$field_name] = array();
+ foreach ($items as $new_delta => $item) {
+ $_POST[$field_name][$new_delta] = $item;
+ }
+ if (isset($_POST[$group_name])) {
+ unset($_POST[$group_name]);
+ }
+ }
+
+ // Move field items back to their original positions.
+ $form_state['values'][$field_name] = $items;
+ }
+
+ // Finally, get rid of the group data in form values.
+ unset($form_state['values'][$group_name]);
+}
+
+/**
+ * Fix deltas for all affected form elements.
+ */
+function content_multigroup_node_form_fix_deltas(&$elements, $form_deltas) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ // Fix the second item, the delta value, of the element's '#parents' array.
+ $elements[$key]['#parents'][1] = $form_deltas[$elements[$key]['#parents'][1]];
+
+ // If present, fix delta value in '#delta' attribute of the element.
+ if (isset($elements[$key]['#delta']) && isset($form_deltas[$elements[$key]['#delta']])) {
+ $elements[$key]['#delta'] = $form_deltas[$elements[$key]['#delta']];
+ }
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_deltas($elements[$key], $form_deltas);
+ }
+ }
+}
+
+/**
+ * Fix form element parents for all fields in multigroups.
+ *
+ * The $element['#parents'] array needs to reflect the position of the fields
+ * in the $form_state['values'] array so that form_set_value() can be safely
+ * used by field validation handlers.
+ */
+function content_multigroup_node_form_fix_parents(&$elements, $multigroups) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ // Check if the current element is child of a multigroup. The #parents
+ // array for field values has, at least, 3 parent elements, being the
+ // first one the name of a multigroup.
+ if (count($elements[$key]['#parents']) >= 3 && isset($multigroups[$elements[$key]['#parents'][0]])) {
+
+ // Extract group name, delta and field name from the #parents array.
+ array_shift($elements[$key]['#parents']);
+ $delta = array_shift($elements[$key]['#parents']);
+ $field_name = array_shift($elements[$key]['#parents']);
+
+ // Now, insert field name and delta to the #parents array.
+ array_unshift($elements[$key]['#parents'], $field_name, $delta);
+ }
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_parents($elements[$key], $multigroups);
+ }
+ }
+}
+
+/**
+ * Update posting data to reflect delta changes in the form structure.
+ *
+ * The $_POST array is fixed in content_multigroup_node_form_transpose_elements().
+ */
+function content_multigroup_node_form_fix_post(&$elements) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ // Update the element copy of the $_POST array.
+ $elements[$key]['#post'] = $_POST;
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_post($elements[$key]);
+ }
+ }
+
+ // Update the form copy of the $_POST array.
+ $elements['#post'] = $_POST;
+}
+
+/**
+ * Make sure the '_weight' and '_remove' attributes of the element exist.
+ *
+ * @see content_multigroup_node_form_transpose_elements()
+ */
+function content_multigroup_fix_element_values($element, &$form_state) {
+ $field_name = $element['#field_name'];
+ $delta = $element['#delta'];
+ if (!isset($form_state['values'][$field_name][$delta]['_weight']) || !isset($form_state['values'][$field_name][$delta]['_remove'])) {
+ $value = array('_weight' => $element['#_weight'], '_remove' => $element['#removed']);
+ if (isset($form_state['values'][$field_name][$delta]) && is_array($form_state['values'][$field_name][$delta])) {
+ $value = array_merge($form_state['values'][$field_name][$delta], $value);
+ }
+ form_set_value($element, $value, $form_state);
+ }
+}
+
+/**
+ * Fix the value for fields that deal with multiple values themselves.
+ */
+function content_multigroup_fix_multivalue_fields($element, &$form_state) {
+ $field_name = $element['#field_name'];
+ $delta = $element['#delta'];
+ if (isset($form_state['values'][$field_name][$delta][0]) && is_array($form_state['values'][$field_name][$delta][0])) {
+ $value = array_merge($form_state['values'][$field_name][$delta][0], array('_remove' => $element['#removed']));
+ }
+ else {
+ $value = array('_remove' => $element['#removed']);
+ }
+ form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Add AHAH add more button, if not working with a programmed form.
+ */
+function content_multigroup_add_more(&$form, &$form_state, $group) {
+ $group_multiple = $group['settings']['multigroup']['multiple'];
+ if ($group_multiple != 1 || !empty($form['#programmed'])) {
+ return FALSE;
+ }
+
+ // Make sure the form is cached so ahah can work.
+ $form['#cache'] = TRUE;
+ $content_type = content_types($group['type_name']);
+ $group_name = $group['group_name'];
+ $group_name_css = str_replace('_', '-', $group_name);
+
+ $form_element = array();
+ $form_element[$group_name .'_add_more'] = array(
+ '#type' => 'submit',
+ '#name' => $group_name .'_add_more',
+ '#value' => t('Add more values'),
+ '#weight' => $group_multiple + 1,
+ '#submit' => array('content_multigroup_add_more_submit'),
+ '#ahah' => array(
+ 'path' => 'content_multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name,
+ 'wrapper' => $group_name_css .'-items',
+ 'method' => 'replace',
+ 'effect' => 'fade',
+ ),
+ // When JS is disabled, the content_multigroup_add_more_submit handler will
+ // find the relevant group information using these entries.
+ '#group_name' => $group_name,
+ '#type_name' => $group['type_name'],
+ '#item_count' => $form[$group_name]['#item_count'],
+ );
+
+ // Add wrappers for the group and 'more' button.
+ $form_element['#prefix'] = '
';
+
+ return $form_element;
+}
+
+/**
+ * Submit handler to add more choices to a content form. This handler is used when
+ * JavaScript is not available. It makes changes to the form state and the
+ * entire form is rebuilt during the page reload.
+ */
+function content_multigroup_add_more_submit($form, &$form_state) {
+ // Set the form to rebuild and run submit handlers.
+ node_form_submit_build_node($form, $form_state);
+ $group_name = $form_state['clicked_button']['#group_name'];
+ $type_name = $form_state['clicked_button']['#type_name'];
+
+ // Make the changes we want to the form state.
+ if (isset($form_state['clicked_button']['#item_count'])) {
+ $form_state['item_count'][$group_name] = $form_state['clicked_button']['#item_count'] + 1;
+ }
+}
+
+/**
+ * Menu callback for AHAH addition of new empty widgets.
+ *
+ * Adapted from content_add_more_js to work with groups instead of fields.
+ */
+function content_multigroup_add_more_js($type_name_url, $group_name) {
+ $content_type = content_types($type_name_url);
+ $groups = fieldgroup_groups($content_type['type']);
+ $group = $groups[$group_name];
+
+ if (($group['settings']['multigroup']['multiple'] != 1) || empty($_POST['form_build_id'])) {
+ // Invalid request.
+ drupal_json(array('data' => ''));
+ exit;
+ }
+
+ // Retrieve the cached form.
+ $form_state = array('submitted' => FALSE);
+ $form_build_id = $_POST['form_build_id'];
+ $form = form_get_cache($form_build_id, $form_state);
+ if (!$form) {
+ // Invalid form_build_id.
+ drupal_json(array('data' => ''));
+ exit;
+ }
+
+ // We don't simply return a new empty widget to append to existing ones, because
+ // - ahah.js won't simply let us add a new row to a table
+ // - attaching the 'draggable' behavior won't be easy
+ // So we resort to rebuilding the whole table of widgets including the existing ones,
+ // which makes us jump through a few hoops.
+
+ // The form that we get from the cache is unbuilt. We need to build it so that
+ // _value callbacks can be executed and $form_state['values'] populated.
+ // We only want to affect $form_state['values'], not the $form itself
+ // (built forms aren't supposed to enter the cache) nor the rest of $form_data,
+ // so we use copies of $form and $form_data.
+ $form_copy = $form;
+ $form_state_copy = $form_state;
+ $form_copy['#post'] = array();
+ form_builder($_POST['form_id'], $form_copy, $form_state_copy);
+ // Just grab the data we need.
+ $form_state['values'] = $form_state_copy['values'];
+ // Reset cached ids, so that they don't affect the actual form we output.
+ form_clean_id(NULL, TRUE);
+
+ // Sort the $form_state['values'] we just built *and* the incoming $_POST data
+ // according to d-n-d reordering.
+ unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']);
+ foreach ($_POST[$group_name] as $delta => $item) {
+ $form_state['values'][$group_name][$delta]['_weight'] = $item['_weight'];
+ $form_state['values'][$group_name][$delta]['_remove'] = isset($item['_remove']) ? $item['_remove'] : 0;
+ }
+ $group['multiple'] = $group['settings']['multigroup']['multiple'];
+ $form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]);
+ $_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]);
+
+ // Build our new form element for the whole group, asking for one more element.
+ $delta = max(array_keys($_POST[$group_name])) + 1;
+ $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1);
+ $form_element = content_multigroup_group_form($form, $form_state, $group, $delta);
+
+ // Rebuild weight deltas to make sure they all are equally dimensioned.
+ foreach ($form_element[$group_name] as $key => $item) {
+ if (is_numeric($key) && isset($item['_weight']) && is_array($item['_weight'])) {
+ $form_element[$group_name][$key]['_weight']['#delta'] = $delta;
+ }
+ }
+ // Add the new element at the right place in the (original, unbuilt) form.
+ content_set_form_element($group_name, $content_type['type'], $form, $form_element[$group_name], TRUE);
+
+ // Save the new definition of the form.
+ $form_state['values'] = array();
+ form_set_cache($form_build_id, $form, $form_state);
+
+ // Build the new form against the incoming $_POST values so that we can
+ // render the new element.
+ $_POST[$group_name][$delta]['_weight'] = $delta;
+ $form_state = array('submitted' => FALSE, 'multigroup_add_more' => TRUE);
+ $form += array(
+ '#post' => $_POST,
+ '#programmed' => FALSE,
+ );
+ $form = form_builder($_POST['form_id'], $form, $form_state);
+
+ // Render the new output.
+ $group_form = content_get_form_element($group_name, $content_type['type'], $form, TRUE);
+ // We add a div around the new content to receive the ahah effect.
+ $group_form[$delta]['#prefix'] = '