Hi,

I am currently trying to migrate content from a proprietary system into a fresh Drupal 7 installation. I have enabled the Link module (http://drupal.org/project/link) and added a field of type Link to a content type.

Now I am trying to create nodes of the new type using the drupal_form_submit() method.

All works fine until it comes to the link field. I am using this code:

$form_state['values']['field_label_link']['und'][0]['url'] = 'http://www.someurl.com';
$form_state['values']['field_label_link']['und'][1]['url'] = 'http://www.anotherurl.com';  

field_label_link is the name of the field in my content type, $form_state the form state variable passed into drupal_form_submit(). Apart from this I am setting the title, the body and some taxonomy terms, which all works fine.

After executing drupal_form_submit(), the node gets created, but only the first link is included. The second one does not show up.

What do I need to do to include the second link when creating the node in this way?

Thanks for any help,
best regards,
Arngrim

Comments

Arngrim’s picture

As expected, the issue is correlated to the fact that the Link field (like other fields allowing an unlimited number of values) creates new form fields upon request using Ajax. This process does not happen when using the drupal_form_submit method with a two-dimensional array for the Link data.

As a workaround I have limited the number of allowed values for my input type so that all form fields get generated when first calling the dialog - hence the above notation works when trying to add multiple links to a new node.

Without having dug too deep into the internals I assume that the form only processes values for which input fields are available, so without the ajax request in the original form (which creates a second (third, etc) field for the Link field) only one field exists and thus the other submitted values are ignored.

In order to get an answer to my original question, the question therefore needs to be rephrased: how to programmatically update the form to include more than one link field, i.e. simulate the ajax request when creating a new node this way?

siharris’s picture

I hope you sorted this out, just replying as this was the first result on google when I got stuck.

You can't use drupal_form_submit directly, you have to copy the code from inside drupal_form_submit and then manipulate the form_state before processing. Remembering to set $form_id and $form_state['build_info']['args'] manually, you need:

  if (!isset($form_state['build_info']['args'])) {
    $form_state['build_info']['args'] = array($node);
  }
  // Merge in default values.
  $form_state += form_state_defaults();

  // Populate $form_state['input'] with the submitted values before retrieving
  // the form, to be consistent with what drupal_build_form() does for
  // non-programmatic submissions (form builder functions may expect it to be
  // there).
  $form_state['input'] = $form_state['values'];

  $form_state['programmed'] = TRUE;
  $form = drupal_retrieve_form($form_id, $form_state);
  // Programmed forms are always submitted.

Then the following, taken from field_add_more_submit:

  // Go one level up in the form, to the widgets container.
  $element = drupal_array_get_nested_value($form, array('YOUR_FIELD_NAME','und'));
  $field_name = $element['#field_name'];
  $langcode = $element['#language'];
  $parents = $element['#field_parents'];

  // Increment the items count.
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
  $field_state['items_count'] = DESIRED_NUMBER_OF_FIELDS;
  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);

  $form = drupal_rebuild_form($form_id, $form_state, $form);

Then the rest of the drupal_form_submit code.

  // Reset form validation.
  $form_state['submitted'] = TRUE;
  $form_state['must_validate'] = TRUE;
  form_clear_error();

  drupal_prepare_form($form_id, $form, $form_state);
  drupal_process_form($form_id, $form, $form_state);

Hope this helps.

Steve

spouilly’s picture

Thanks a lot steve, your explanation was very useful ... saved me tons of time.

Cheers!

S.

leandroman’s picture

I have lots of custom fields and I didn't want to hard code my fieldnames "YOUR_FIELD_NAME" etc... I needed this to work across the board.

The below code is the "Extra" (middle) code siharris added in siharris's copy of drupal_form_submit(). I automatically add fields based on the form_values.

Works good for me.

/***
   * How to add multiple Link-field entries programmatically using drupal_form_submit
   * https://drupal.org/node/1066786#comment-5174564
   * @START
   */
  
    // Loop through fields to discover multiple fields
    foreach(element_children($form_state['values']) as $fieldname) {
      // Look for fields with mmore than one value
      if( count($form_state['values'][$fieldname]['und']) > 1 ) {
        // Taken from https://drupal.org/node/1066786#comment-5174564
        $element = drupal_array_get_nested_value($form, array($fieldname,'und'));
        $field_name = $element['#field_name'];
        $langcode = $element['#language'];
        $parents = $element['#field_parents'];
        
        // Increment the items count.
        $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
        $field_state['items_count'] = count($form_state['values'][$fieldname]['und']) - 1;
        field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);

        $form = drupal_rebuild_form($form_id, $form_state, $form);
      }
    }
  
  /***
   * #END
   */
ashepherd’s picture

You can still call drupal_form_submit() by using this technique:

1) setup your $form_values with multiple values

$form_state['values']['field_label_link']['und'][0]['url'] = 'http://www.someurl.com';
$form_state['values']['field_label_link']['und'][1]['url'] = 'http://www.anotherurl.com';  

2) call drupal_form_submit() with the form_id of a custom function:

for content type called 'mynode'

drupal_form_submit('mymodule_mynode_node_form', $form_state, $node);
...

function mymodule_mynode_node_form($array, &$form_state, $node){
  $form = drupal_retrieve_form('mynode_node_form', $form_state);
  //increment items_count
  $form_state['field']['field_label_link']['und']['items_count'] = 2; //YOUR_NUMBER_OF VALUES
  
  $form = drupal_rebuild_form('mynode_node_form', $form_state, $form);
  return $form;
}
retiredpro’s picture

This solution is perfect for my situation as well. Thanks!

Dennis
oh-soyummy.com

hypertext200’s picture

Thanks, this works fine.

Heshan Wanigasooriya
Github

a.d2000’s picture

This solution doesn't work for me.
I have Commerce Product entity in which I have field collection.

After performing some action, When user redirects to Add Product form, based on some predefined values, I want to display User a Field collection pre-filled.

If I have 5 predefined values then 5 Field-collection items would be displayed to User and by clicking on save that product would be saved with that field collection item values.

By clicking on 'Add another item', User can add more as well. Above field collection items won't be saved until User clicks on Save form button.

May be I am doing something wrong.

I tried below code :

if (!isset($form_state['build_info']['args'])) {
                          $form_state['build_info']['args'] = array($product);
                        }
                        // Merge in default values.
                        $form_state += form_state_defaults();
                        // Populate $form_state['input'] with the submitted values before retrieving
                        // the form, to be consistent with what drupal_build_form() does for
                        // non-programmatic submissions (form builder functions may expect it to be
                        // there).
                        $form_state['input'] = $form_state['values'];
                        $form_state['programmed'] = TRUE;
                        $form = drupal_retrieve_form($form_id, $form_state);
                        // Programmed forms are always submitted.
                        
                        // Go one level up in the form, to the widgets container.
                        $element = drupal_array_get_nested_value($form, array('YOUR_FIELD_NAME','und'));
                        $field_name = $element['#field_name'];
                        $langcode = $element['#language'];
                        $parents = $element['#field_parents'];
                        // Increment the items count.
                        $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
                        $field_state['items_count'] = DESIRED_NUMBER_OF_FIELDS;
                        field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
                        $form = drupal_rebuild_form($form_id, $form_state, $form);

drupal_rebuild_form() is not working for me in Add Product form load. It goes into infinite loop.

Please help me here. How can I resolve the issue ?

lahode’s picture

Maybe this code can help someone

function generate_multiform_link(&$form, &$form_state) {
  $link_form_name = $form['delta']['#value'];
  $links = variable_get($link_form_name,  NULL);

  $form['settings']['links_fieldset'] = array(
    '#type' => 'fieldset',
    '#title' => t('Liste des liens'),
    '#prefix' => '<div id="links_form">',
    '#suffix' => '</div>',
    '#tree' => TRUE,
  );
  
  // Build the number of link_field form indicated by $form_state['num_links']
  if (empty($form_state['num_links'])) {
    $form_state['num_links'] = count($links) > 1 ? count($links) : 1;
  }
  for ($i = 0; $i < $form_state['num_links']; $i++) {    
    $form['settings']['links_fieldset'][$link_form_name][$i] = array(
      '#type' => 'link_field',
      '#delta' => 0,
      '#default_value' => isset($links[$i]) ? $links[$i] : NULL,
      '#field_parents' => NULL,
      '#field_name' => '',
      '#language' => '',
    );
  }

  $form['settings']['links_fieldset']['add_more'] = array(
    '#type' => 'submit',
    '#value' => t('Add another item'), 
    '#submit' => array('generate_add_more'),
    '#ajax' => array(
      'callback' => 'generate_callback',
      'wrapper' => 'links_form',
    ),
  );
  if ($form_state['num_links'] > 1) {
    $form['settings']['links_fieldset']['remove_item'] = array(
      '#type' => 'submit',
      '#value' => t('Retirer le dernier élément'),
      '#submit' => array('generate_remove_item'),
      '#ajax' => array(
        'callback' => 'generate_callback',
        'wrapper' => 'links_form',
      ),
    );
  }
  
  $form['#submit'][] = 'generate_block_submit';
}

/**
 * Callback for ajax-enabled buttons
 *
 * Appel ajax de l'ajout et suppression des liens 
 */
function generate_callback($form, $form_state) {
  return $form['settings']['links_fieldset'];
}

/**
 * Submit handler for the "add_more" button.
 *
 * Ajoute un lien
 */
function generate_add_more($form, &$form_state) {
  $form_state['num_links']++;
  $form_state['rebuild'] = TRUE;
}

/**
 * Submit handler for the "remove_item" button.
 *
 * Supprime un lien
 */
function generate_remove_item($form, &$form_state) {
  if ($form_state['num_links'] > 1) {
    $form_state['num_links']--;
  }
  $form_state['rebuild'] = TRUE;
}

/**
 * Implements hook_block_save().
 *
 * Sauvegarde des liens configuré au sein du bloc "Liens pratiques"
 */
function generate_block_submit(&$form, &$form_state) {
  $link_form_name = $form['delta']['#value'];
  $edit = $form_state['values'];
  if (isset($edit['links_fieldset'][$link_form_name]) && is_array($edit['links_fieldset'][$link_form_name])) {
    foreach($edit['links_fieldset'][$link_form_name] as $links) {
      $array[] = array('title' => $links['title'], 'url' => $links['url']);
    }
    variable_set($link_form_name, $array);    
  }
}

Fredrik Lahode
Développeur et Formateur web indépendant

fjgarlin’s picture

Probably it helped more people along the way, but I wanted to say that, almost 10 years after this comment was made, it did help someone... me!

Thanks so much.