Score:0

hook_forms() not working

br flag

I'm trying to add a button to all node forms via callback in hook_forms. I get a similar error. Can someone help.

Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'my_dossier_form' not found in drupal_retrieve_form() (line 844 of /var/www/includes/form.inc).
Notice: Array to string conversion in drupal_prepare_form() (line 1094 of /var/www/includes/form.inc).
Notice: Array to string conversion in drupal_prepare_form() (line 1108 of /var/www/includes/form.inc).
Notice: Array to string conversion in drupal_prepare_form() (line 1128 of /var/www/includes/form.inc).

Code

function my_dossier_forms($form_id, $args) {
  $forms = array();
  if ($types = node_type_get_types()) {
    foreach (array_keys($types) as $type) {
      $forms[$type . '_node_form']['callback'] = 'my_dossier_form';
    }
  }

  return $forms;
}

function my_dossier_form($form, &$form_state) {
  $form['delete'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
    '#weight' => 10,
  );
  return $form;
}
beltouche avatar
cn flag
Are sure you don't want `hook_form_alter`?
Павел Герасюта avatar
br flag
According to the task, I need to use hook_forms specifically.
Jaypan avatar
de flag
Your code looks ok, but it's not finding `my_dossier_form()`. What file is this function in? You may need to explicitly include it.
Павел Герасюта avatar
br flag
In the my_dossier.module file.
Павел Герасюта avatar
br flag
I was thinking about using module_load_include
apaderno avatar
us flag
@beltouche I agree. The form builder set with `hook_forms()` (a.k.a. the *callback* value) should build the full form, not just a submission button. It seems the OP's task is altering the node edit forms, for which `hook_form_BASE_FORM_ID()` is the hook to implement.
Score:1
us flag

What node_type_get_types() returns is an associative array of node type objects, keyed by the type. The description of the value returned is more understandable in the _node_types_build() documentation.

The hook implementation done from a Drupal core module, for example comment_forms(), helps to understand the correct code.

function comment_forms() {
  $forms = array();
  foreach (node_type_get_types() as $type) {
    $forms["comment_node_{$type->type}_form"]['callback'] = 'comment_form';
  }
  return $forms;
}

In your case, the code should be similar to the following one.

function my_dossier_forms($form_id, $args) {
  $forms = array();
  foreach (node_type_get_types() as $type) {
    $forms["my_dossier_node_{$type->type}_form"]['callback'] = 'my_dossier_form';
  }

  return $forms;
}

This code isn't much different from the code shown in the question, and it would not cause, alone, the class 'my_dossier_form' not found error.

The line 844 is the following one.

$form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);

$callback is initialized from the following code.

if (!isset($forms) || !isset($forms[$form_id])) {
  $forms = module_invoke_all('forms', $form_id, $args);
}
$form_definition = $forms[$form_id];
if (isset($form_definition['callback arguments'])) {
  $args = array_merge($form_definition['callback arguments'], $args);
}
if (isset($form_definition['callback'])) {
  $callback = $form_definition['callback'];
  $form_state['build_info']['base_form_id'] = isset($form_definition['base_form_id']) ? $form_definition['base_form_id'] : $callback;
}

Line 844 could not think my_dossier_form is a class, except in the case $callback contains an array like array('my_dossier_form', 'mymodule_form');, or it contains a string like 'my_dossier_form::methodName'.

The warnings about an array to string conversion means Drupal is getting an array when it expects a string, for example from the following line (line 1094).

    elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) {

I would just avoid returning a form ID that starts with $type as that is probably creating conflicts with other modules implementing hook_forms(), which are expected to return a unique form ID each. See module_invoke_all() to understand what happens when two hook_forms() implementations return information for the same form ID.

  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    if (function_exists($function)) {
      $result = call_user_func_array($function, $args);

      // In the case of hook_forms(), $result contains an array.
      if (isset($result) && is_array($result)) {
        $return = array_merge_recursive($return, $result);
      }

      elseif (isset($result)) {
        $return[] = $result;
      }
    }
  }

To be more exact, two modules returning values for the same form ID could at least cause the call_user_func_array() expects parameter 1 to be a valid callback, class '[callback]' not found when they both set the value for the same callback. See the output of the following code, to understand what exactly happens.

$return["type_node_form"]["callback"] = "my_dossier_form";
$result["type_node_form"]["callback"] = "mymodule_form";
print_r(array_merge_recursive($return, $result));

The callback value becomes an array of two strings, which call_user_func_array() interprets as an array containing a class name and a method name.

Array
(
    [type_node_form] => Array
        (
            [callback] => Array
                (
                    [0] => my_dossier_form
                    [1] => mymodule_form
                )

        )

)

In this case, the conflict is with the Node module, which uses the following code for its hook_forms() implementation.

function node_forms() {
  $forms = array();
  if ($types = node_type_get_types()) {
    foreach (array_keys($types) as $type) {
      $forms[$type . '_node_form']['callback'] = 'node_form';
    }
  }
  return $forms;

As side note, the hook_forms() purpose is providing the same form builder for a group of forms whose ID follow a schema, such as in the case of comment edit forms, whose IDs are comment_node_[node type]_form. When provided, the form builder callback (passed as callback value) should build the full form, not part of it.
The fact my_dossier_form() builds just a submission button, and my_dossier_forms() is using the same code used by node_forms() makes me think the code purpose is altering the node edit forms, which should be accomplished by implementing hook_form_BASE_FORM_ID_alter(). That's what the Book module does with book_form_node_form_alter(), which is invoked for the node edit form of every content type.

function book_form_node_form_alter(&$form, &$form_state, $form_id) {
  $node = $form['#node'];
  $access = user_access('administer book outlines');
  if (!$access) {
    if (user_access('add content to books') && (!empty($node->book['mlid']) && !empty($node->nid) || book_type_is_allowed($node->type))) {

      // Already in the book hierarchy, or this node type is allowed.
      $access = TRUE;
    }
  }
  if ($access) {
    _book_add_form_elements($form, $form_state, $node);

    // Since the "Book" dropdown can't trigger a form submission when
    // JavaScript is disabled, add a submit button to do that. book.css hides
    // this button when JavaScript is enabled.
    $form['book']['pick-book'] = array(
      '#type' => 'submit',
      '#value' => t('Change book (update list of parents)'),
      '#submit' => array(
        'book_pick_book_nojs_submit',
      ),
      '#weight' => 20,
    );
  }
}

If then the hook needs to use the content type name, that is available in $form['#node']->type.

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.