3

I'm in the process of creating my first D7 module, partly based on the tutorial found here. It's a pretty simple module that creates both a block and a page to show some content.

To go a little further, I want to allow the user to change the path of this page, via a configuration page.

This raises two questions regarding the validation of this data:

  1. how to avoid collision with already existing paths. I already use two function calls (see code below) to check if the alias is already used, but I'm wondering if I'm doing it right because it feels a little bit like a hack, and I'm not sure I'm covering every possible cases.
  2. more importantly, how should I validate the data entered by the user? I suppose some (a lot of) characters are not allowed in a URL (or a least strongly advised against). Is there a Drupal function for that, or should I do it from scratch?

Here is the code I have (simplified, and only the parts relevant to the question) :

<?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', 'My module');
  $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 page'),
    '#default_value' => variable_get('mymodule_title', 'My module'),
    '#required' => TRUE,
  );
  $form['mymodule_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Path for the mymodule page'),
    '#default_value' => variable_get('mymodule_path', 'mymodule'),
    '#description' => t('<p>Relative path to your Drupal installation. Default value: <code>mymodule</code><br>Do not include a trailing slash. In case of multilingual sites, do not enter the language prefix either.</p>'),
    '#required' => TRUE,
  );

  return system_settings_form($form);
}


/**
 * 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 {

      // check if value contains non-standard characters
      // while still allowing for path fragments separators ("/")
      $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.'));
      } else {
        variable_set('menu_rebuild_needed', TRUE);
      }
    }
  }

  $m_title_db = variable_get('mymodule_title', 'My module');
  $m_title = $form_state['values']['mymodule_title'];
  if ($m_title_db != $m_title) {
    variable_set('menu_rebuild_needed', TRUE);
  }
}

?>

Edit: In the meantime, I tried to implement some kind of "manual" validation based on the urlencode function. If a part of the path contains characters that are modified by urlencode, then it is considered not valid. I've updated my code. Of course I'm still curious to know if there's a better way of handling that.

  • Conflicts are easy, relatively. Just need to obtain menu and see if anything is there. User input validation is more fun :) +1 from me, later I'll try to answer. – Mołot Aug 5 '15 at 16:54
  • @Mołot "Just need to obtain menu" -> so using menu_get_item() is correct then? But what if a path is present, but not included in a menu? (That's why I combined it with drupal_lookup_path()) – s427 Aug 6 '15 at 9:18
  • all paths are defined in hook menu, so I would expect everything to be in some menu. Sadly, Drupal mixes menu with routing. I'll try to post answer if I'll have time, but please don't poke me, if I'm not answering, it means I can't, simple as that. – Mołot Aug 6 '15 at 9:23
1

You could use drupal_valid_path() to determine if the URL exists in Drupal. I would recommend running this as user 1 to avoid any weird permissions issues with the URL.

Link: https://api.drupal.org/api/drupal/includes%21path.inc/function/drupal_valid_path/7

| improve this answer | |
  • Thanks for your answer. I can't check it right now, so I'm not entirely sure, but I think I did test that and I got inconsistent results. I'll test it again next week. – s427 Aug 19 '15 at 16:39

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.