1

I'm in the process of creating my first D7 module, partly based on the tutorial found here. My module creates (among other things) a public page to show some content, and it also has a configuration page (in the Drupal admin) that allows the user to choose the path at which this page must be displayed (e.g. "mypage" or "content/mymodule" etc).

My problem is that saving this value through the configuration form is not enough: you also have to clear the Menu cache, otherwise the old path remains active, and the new one gives you a "page not found" error.

I need that to occur automatically every time this value is changed (or every time the form is submitted). I tried using menu_rebuild() in my validation function, but it's obviously not the right place, because it is triggered before the new data is saved, and so the menu is rebuilt with the obsolete data.

How should I proceed if I want the Menu cache to be automatically cleared after the data has been validated and saved?

Here's my implementation of the pages, via hook_menu:

<?php

/**
 * Implements hook_menu().
 */
function mymodule_menu() {
  $items = array();

  // config page
  $items['admin/config/services/mymodule'] = array(
    'title' => 'mymodule',
    'description' => 'Configuration for the mymodule module',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
  );

  // public page
  $m_title = variable_get('mymodule_title', 'Mymodule');
  $m_title = $m_title ? $m_title : 'Mymodule';

  $m_path = variable_get('mymodule_path', 'mymodule');
  $m_path = $m_path ? $m_path : 'mymodule';

  $items[$m_path] = array(
      'title' => $m_title,
      'page callback' => '_mymodule_page',
      'access arguments' => array('access mymodule content'),
      'type' => MENU_NORMAL_ITEM,
  );

  return $items;
}


/**
 * Page callback: mymodule settings
 *
 * @see mymodule_menu()
 */
function mymodule_form($form, &$form_state) {
  $form['mymodule_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title displayed on the mymodule public page'),
    '#default_value' => variable_get('mymodule_title', 'Mymodule'),
    '#required' => TRUE,
  );
  $form['mymodule_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Path for the mymodule public page'),
    '#default_value' => variable_get('mymodule_path', 'mymodule'),
    '#required' => TRUE,
  );
  $form['#submit'][] = 'mymodule_form_submit';

  return system_settings_form($form);
}


/**
 * Add a submit handler/function to the form.
 */
function mymodule_form_submit($form, &$form_state) {
  drupal_set_message(t('TEST: The form has been submitted.'));
  menu_rebuild();
}


/**
 * Implements validation from the Form API.
 *
 * @param $form
 *   A structured array containing the elements and properties of the form.
 * @param $form_state
 *   An array that stores information about the form's current state
 *   during processing.
 */
function mymodule_form_validate($form, &$form_state) {
  // page path validation
  $m_path = $form_state['values']['mymodule_path'];
  $m_path_db = variable_get('mymodule_path', '');
  if ($m_path_db != $m_path) {

    if (drupal_lookup_path('source', $m_path) || menu_get_item($m_path)) {
      form_set_error('mymodule_path', t('This path is already used.'));
    } else {

      // attempting to validate path value
      // see: http://drupal.stackexchange.com/questions/167923
      $m_path_parts = explode('/', $m_path);
      $has_invalid_chars = false;
      foreach ($m_path_parts as $key => $part) {
        if (urlencode($part) != $part) {
          $has_invalid_chars = true;
        }
      }
      if ($has_invalid_chars) {
        form_set_error('mymodule_path', t('This path contains invalid characters.'));
      }
    }
  }
}

?>

Edit: more code provided.

  • Just a note, variable_get's second parameter (e.g. 'Mymodule') is the default value to return if the variable is not set. You don't need the other line immediately after it: $m_title = $m_title ? $m_title : 'Mymodule'; because variable_get already does it for you. – Beebee Aug 10 '15 at 15:53
  • Right. Actually I was aware of that, I just wasn't sure if a variable could be defined with an empty value (""), so I added this check to make sure the title or path was never empty. – s427 Aug 10 '15 at 16:01
1

This is to do with the order of execution.

mymodule_form_submit is being called earlier than system_settings_form_submit because you're adding your own submit handler into the form array before calling system_settings_form which will add it's own submit handler (system_settings_form_submit) after your submit handler.

Essentially, your submit handler does a menu rebuild with the old variables, and then the new variables get saved to the database.

There's couple ways you can fix it as far as I know. These aren't tested.

First method is using a variable in your submit handler instead of directly rebuilding the menu:

/**
 * Add a submit handler/function to the form.
 */
function mymodule_form_submit($form, &$form_state) {
  drupal_set_message(t('TEST: The form has been submitted.'));
  // Set a variable to let menu_execute_active_handler() know that menu rebuild is required
  variable_set('menu_rebuild_needed', TRUE);
}

Or alternatively, you can add your own submit handler after system_settings_form has done its thing:

/**
 * Page callback: mymodule settings
 *
 * @see mymodule_menu()
 */
function mymodule_form($form, &$form_state) {
  $form['mymodule_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title displayed on the mymodule public page'),
    '#default_value' => variable_get('mymodule_title', 'Mymodule'),
    '#required' => TRUE,
  );
  $form['mymodule_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Path for the mymodule public page'),
    '#default_value' => variable_get('mymodule_path', 'mymodule'),
    '#required' => TRUE,
  );

  $form = system_settings_form($form);
  // Add custom submit handler after system_settings_form_submit
  $form['#submit'][] = 'mymodule_form_submit';

  return $form;
}

Hope this helps.

  • 1
    Thanks for your help. First I tried the second solution you suggested: it worked, but it caused the "The configuration options have been saved" message to appear twice, which made me feel it was not optimal. So I canceled this change and then tried your first solution: it works perfectly as far as I can tell (no double message this time). (I actually moved the variable_set('menu_rebuild_needed', TRUE); line in the validation function, so it only executes if the updated values are different than the ones stored in the database. No need to clear the cache if those values haven't changed. :)) – s427 Aug 10 '15 at 11:30
1

Your form code would be helpful to see here to determine if you are using a system_settings_form() function (which seems likely since you are creating a module configuration form) or just a simple form. Regardless, the answer for both is to add a submit_handler to your form.

Basic Form with Submit Handler: https://www.drupal.org/node/717740

If you are not using system_settings_form, than you can add your post-submit actions to a function named after your form definition function, or

/**
 * Add a submit handler/function to the form.
 */
function my_form_submit($form, &$form_state) {
  <YOUR menu_rebuild STUFF HERE>
}

and Form API will automatically find it. If you are using system_settings_form() for your admin form, than you can add your submit handler by adding this before calling system_settings_form()

$form['#submit'][] = 'my_form_submit';
  • Oh, I see. So it seems the only thing I was missing was the my_form_submit function. I'll have to wait until Monday before I can test that out, but thanks for your answer already. ^^ – s427 Aug 7 '15 at 23:30
  • OK I tested your suggestion. The submit function is called (I used a drupal_set_message() to make sure of that), but with the same problem as when I tried clearing the cache during validation: after the menu cache is cleared, the menu still uses the values from before the form was submitted, instead of using the updated values. So if I save the form twice in a row (by clicking the submit button), then the new path is recognized. But not if I save the form only once. Any idea how to solve that? I've edited my question to include more of my code. – s427 Aug 10 '15 at 10:18
  • Never mind, problem solved. :) – s427 Aug 10 '15 at 11:32
  • Mind sharing what it was that resolved the problem? – schnippy Aug 10 '15 at 15:30
  • Sure. The trick was to use variable_set('menu_rebuild_needed', TRUE); in order to let Drupal know that it needed to rebuild the menu cache later on. I was able to put that in my validation function, so that the clearing only occurs when really needed. You can check the accepted answer and my comment after it for more details. – s427 Aug 10 '15 at 15:58
0

Drupal 7

for Drush using

drush eval 'menu_rebuild();'

memcached

If you are using memcached, you have to restart it.

On Linux: /etc/init.d/memcached restart

On OS X: killall memcached

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.