How to change field cardinality per form either programatically or by rule? I've a multipage form (where each page is determined by field group) where on the first page user type number of people in text field and on the second one I would like to show X number of field collection instances based on the given number. So if user selects 5 people, I expect to see 5 field collection placeholders to fill for the user and no more Add another item buttons (especially when field cardinality is set to Unlimited).

Is there any solution which can achieve setting up cardinality per form state by a rule or from the code?
There are the following hooks which allow to alter the widget form such as hook_field_widget_form_alter and hook_field_widget_WIDGET_TYPE_form_alter, but they only apply to widget form element.

As for workaround, it's possible to set the field to Unlimited and use hook_form_alter to remove add_more element manually like:

$form[$field_name][LANGUAGE_NONE][0]['remove_button']['#access'] = FALSE;
$form[$field_name][LANGUAGE_NONE]['add_more']['#access'] = FALSE;


or overriding/removing #theme:

unset($form['field_foo'][LANGUAGE_NONE][0]['#theme']);


for certain conditions.



Using Rules, it's possible to use Rules Forms Support module which can deal with form elements, so you can change the items_count. Here is sample rule dealing with a multi value field in Entityform:

/**
 * Implements hook_action_info().
 */
function mymodule_entityform_rules_rules_action_info() {
  return array(
    'foo_entityform_rules_set_cardinality_value' =&gt; array(
      'label' =&gt; t('Change items count of a multi value field'),
      'group' =&gt; t('Rules Forms'),
      'parameter' =&gt; array(
        'form' =&gt; array('type' =&gt; 'form', 'label' =&gt; t('Form')),
        'form_state' =&gt; array('type' =&gt; 'form_state', 'label' =&gt; t('Form State')),
        'element' =&gt; array('type' =&gt; 'form_element', 'label' =&gt; t('Target Element')),
        'value' =&gt; array('type' =&gt; 'form_element', 'label' =&gt; t('Value from Element')),
      ),
      'base' =&gt; 'foo_entityform_rules_set_cardinality_value',
      'callbacks' =&gt; array(
        'access' =&gt; 'rules_forms_integration_access',
        'form_alter' =&gt; 'rules_forms_element_form_alter',
      ),
    ),
  );
}

/**
 * Implementation of rule callback.
 */
function mymodule_entityform_rules_set_cardinality_value($form, $form_state, $element, $value) {
  module_load_include("module", "rules_forms", 'rules_forms');
  $val_elem = &amp;_rules_forms_get_element($form, $value);
  if (!isset($form_state['field'])) {
    return;
  }

  $element = substr($element, strpos($element, ':') + 1);
  $path = explode(':', $element);
  if (count($path) == 1) {
    $path[] = LANGUAGE_NONE;
  }

  $field_state = &amp;drupal_array_get_nested_value($form_state['field'], $path);
  if (!$field_state) {
    return;
  }

  $limit = !empty($val_elem['#value']) ? $val_elem['#value'] : (!empty($val_elem['#default_value']) ? $val_elem['#default_value'] : NULL);
  $field_state['items_count'] = $limit;
  $field_state['field']['cardinality'] = $limit; // Won't work for field_multiple_value_form().
}


This works when field is set to Unlimited, however Add another and Remove buttons still needs to be removed from form_alter as shown below:

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&amp;$form, &amp;$form_state, $form_id) {

    // Check cardinality.
    // If items_count for a field has reach its cardinality limit,
    // then remove Add more and Remove buttons.
    foreach ($form as $key =&gt; $item) {
      if (is_array($item) &amp;&amp; !empty($item['#type']) &amp;&amp; !empty($item['#language']) &amp;&amp; $item['#type'] == 'container') {
        $field = &amp;$form[$key];
        $langcode = $field['#language'] ?: LANGUAGE_NONE;
        $field_state = field_form_get_state($field[$langcode]['#field_parents'], $field[$langcode]['#field_name'], $langcode, $form_state);
        if ($field_state['items_count'] == $field_state['field']['cardinality']) {
          $field[$langcode]['add_more']['#access'] = FALSE;
          foreach (array_column($field[$langcode], 'remove_button') as $key2 =&gt; $rm_item) {
            $field[$langcode][$key2]['remove_button']['#access'] = FALSE;
          }
        }
      }
    } // end: foreach
  }
}




It's also worth to try some patches from Allow cardinality to be restricted (overridden and reduced) in the field instance settings or  Add hook alter to override field_multiple_value_form().

There is also Field instance cardinality module which allows the cardinality of specific instances of fields to be overridden.

Some other proposed workaround from here by @donquixote (without actually hacking Drupal core) could include:


a new widget type with FIELD_BEHAVIOR_CUSTOM with widget settings to change a cardinality limit, the name of another ("decorated") field widget type and other settings for the decorated field widget type,
add a number field in the instance settings form and use hook_field_widget_properties_alter to replace $instance['widget']['module'] (keep original in $instance['widget']['_module']) and replace override $instance['widget']['type'] with own FIELD_BEHAVIOR_CUSTOM and save the original in $instance['widget']['_type'], so you can do the same decorator trick in hook_field_widget_form as above.




Modules

Related modules:


Multivalue Extras


  Enhances settings for multiple value fields.