API page: http://api.drupal.org/api/drupal/includes--path.inc/function/drupal_look...

Describe the problem you have found:

drupal_lookup_path($action, $path = '', $path_language = NULL)

Try as I might, this function returns FALSE for me no matter what $path is passed in. I am using an up-to-date WAMP stack with a muti-site config and testing with the Devel "Execute PHP" page

For example:

$x = drupal_lookup_path('wipe','http://example.com/admin');
dprint_r($x==FALSE?'nothing':$x);

$x = drupal_lookup_path('source','http://example.com/admin');
dprint_r($x==FALSE?'nothing':$x);

$x = drupal_lookup_path('alias','http://example.com/admin');
dprint_r($x==FALSE?'nothing':$x);

In a normal Drupal install, these pages obviously do exist, but this function returns FALSE for most everything I try, including http://www.example.com/?=node/19 (which, for me, also exists!).

I would sincerely appreciate it if a LAMP stack developer would test this to confirm this bug is not confined to Windoze... This problem also manifests itself in Drupal 6.x

BTW, I encountered this problem while developing a module that implements hook_menu(). I was a bit surprised when the following code snippet did not generate an error:

function mymodule_menu() {
  // let's try something stupid...
  $items['admin'] = array(
    'title' => 'Ha ha - replace the the admin path',
    'page callback' => 'hello_world',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;  
}

It did nothing instead, which is good, I suppose, but it may not be the best practice ;-)

Comments

Mark Theunissen’s picture

What are you trying to achieve?

That function will lookup the path alias for a system path. So if you edit node/1, and assign it a URL path of 'foobar', then calling:

print drupal_lookup_path('alias', 'node/1');

Will return 'foobar'.

Passing it absolute URLs like http://example.com/admin doesn't make sense.

Diogenes’s picture

My apologies, a careful re-reading of the documentation, and it seems that my first impressions were wrong.

I assumed that most drupal pages were aliases of one type or another and that any drupal paths like /admin, admin/config, etc., were system urls. They are common to all drupal sites, after all.

I've developed a module with a hook_menu that uses a path that appears in a form - i.e. the site admin can specify a custom path. When specifying a new path, the validate functions checks to see if the path is available - i.e. /admin should NOT be available, therefore is not valid.

I assumed drupal_lookup_path( ... ) would be a fast lookup to see if that path is taken. That approach did not work, of course. Now I am using drupal_http_request() for the path validation, with a pass if a 404 code is encountered.

I'm not proud of this, it's an ugly hack. If you could advise of a better method, I would be grateful. ;-)

And how are path conflicts resolved in hook_menus of different contributed modules?

Mark Theunissen’s picture

Title: Problem with drupal_lookup_path() » How do I determine if a path is in use already?
Category: bug » support

You could use a combination of approaches. Have a look at the menu system and maybe menu_get_item:

http://api.drupal.org/api/drupal/includes--menu.inc/function/menu_get_it...

That returns the menu item for the system path, if it returns false, it doesn't exist.

Diogenes’s picture

Status: Active » Closed (fixed)

Thank you for that reply Mark - sorry I'm so late in replying.

Diogenes’s picture

Version: 7.4 » 7.10
Status: Closed (fixed) » Active

OK - so I did some real testing with menu_get_item() in the context I was using.

I have a module that creates a page at some arbitrary url, defaults to 'members'.

To test this function I created a basic page at /node/122 and assigned it the url 'members' before enabling the module.

After enabling the module, the '/node/122' page is rendered up instead of the page I was expecting.

I tried every variation of $path in menu_get_item($path). NULL was returned in every case.

So I am reverting to drupal_http_request() to determine if a url is already in use.

Diogenes’s picture

Title: How do I determine if a path is in use already? » How do I determine if a url or path alias is being used?

Change to Issue title

Mark Theunissen’s picture

Could you please post the exact code and what you are actually getting as opposed to what you expect?

Say you want to test whether "/members" is a path used by Drupal... you would do:


$path = 'members';
$internal_path = drupal_lookup_path('source', $path); // are you using a multilingual site? if so, don't forget language parameter.

if (!$internal_path) {
  // Nothing found in aliases, so "members" is not an alias. Check if it's handled by a menu hook (router item).
  $menu_item = menu_get_item($path);
  if (!$menu_item) {
    return TRUE; // Hooray, we can use this as it's available.
  }
  else {
    return FALSE; // Internal path is in use.
  }
}
else {
  // We found an internal path for the alias, so therefore we can't use this.
  return FALSE;
}
Diogenes’s picture

Well that was a curve ball. Thank you Mark, for the response and the code sample.

It has been an interesting day. I tried a whole bunch of stuff.

First, the use case:

I have a module that creates a custom page. A feature of this module is that the admin can specify the url or page alias for the page the module creates. This location is stored as a site variable. This is easy to do:

/**
 * Implements hook_menu().
 */
function members_menu() {
  $user_path = variable_get('members_register_url', 'register/user');
  $items[$user_path] = array(
    'title' => 'Create new account',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('members_user_register_form'),
    'access callback' => 'user_is_anonymous',
    'type' => MENU_NORMAL_ITEM,
  );

  $members_path = variable_get('members_page_url', 'members');
  $items[$members_path] = array(
    'title' => 'Members',
    'description' => 'Login or register a username.',
    'page callback' => 'members_page',
    'access callback' => TRUE,
    'menu_name' => 'main-menu',
    'type' => MENU_SUGGESTED_ITEM,
  );

Now this module, to properly install itself, must determine if the default url for the variable is an available location, otherwise it tries another similar string until it find a url that is free.

The code for drupal_get_path() is not small or simple. And the code for menu_get_item() is hardly intuitive either. I simply tried what I thought might work without really trying to figure out how these two functions worked.

Here is the result of my trial and error testing:

In Drupal 7, I fell back to using drupal_http_request() before my test case installed correctly. I never could get menu_get_item() or drupal_http_request() to work the way I expected.

Then I switched to Drupal 8. The drupal_http_request() that worked in D7 method gave me a WSOD in D8. I also tried wrapping the code you provided above in a function. It also failed but did return a page. It tried about 1600 different urls before it gave up.

So I wrote another function that uses the php curl library. It works quite well and seems to be the fastest of any method I have tried.

This module (Members Page) is a sandbox project waiting for approval. The D8 version has all the latest test code in a file called members.inc, including a couple of test functions that did not work.

The default url's for the Members Page module are 'members' and 'register/user'.

To test, I did a fresh D8 install, then created some bogus pages and gave then url's of 'members' and 'register/user' respectively. Since these pages now existed, a successful install would create create the default pages at members2 and register/user2 instead.

The code below works for me with D8. I haven't tested yet with D7 or D6 yet but I think this may work across all versions since its basically just php and drupal independent.

To summarize: A function - say drupal_check_url_free($url) - is needed that provides a simple TRUE/FALSE return for pages that implement a relocatable menu router item. Something like this:

/**
 * Checks if the $url is available.
 *
 * @param string $url
 *  The url or path alias to be set.  This should be a simple string without any
 *  protocol (http:) leader, and without leading or trailing /'s.
 * @returns boolean TRUE | FALSE
 */
function drupal_check_url_free($url) {
  $url = 'http://' . $_SERVER['SERVER_NAME'] . '/' . $url;
  $cptr = curl_init();                    
  curl_setopt($cptr, CURLOPT_URL, $url);     // set url
  curl_setopt($cptr, CURLOPT_NOBODY, true);  // no body, just header
  curl_setopt($cptr, CURLOPT_CONNECTTIMEOUT, 2);
  $content = curl_exec($cptr);
  $info = curl_getinfo($cptr);
  $errs = curl_error($cptr);
  if (!empty($errs)){ 
    echo "<pre>"; print_r($errs); echo "</pre>"; 
  }
  curl_close($cptr);
  return $info['http_code'] == '404' ? TRUE : FALSE;
}
Mark Theunissen’s picture

Is there any reason you're not doing this the most straightforward way? You want to provide the ability for people to change the members page url and the members register url. So use aliases for that.

Set your menu items to be hardcoded to 'members' and 'register/user', and then allow admins to alias these paths, so they can select whatever they like. If you're worrying about people visiting the original, just put a redirect in place that redirects to the alias when it detects that someone is trying to access it directly.

And if you really must allow the system path to be configurable, then menu_get_item() will work. You haven't really shown how you've tried to use menu_get_item() as I specified above, and why it's not working for you.

Diogenes’s picture

Is there any reason you're not doing this the most straightforward way?

The reason is simple - it's a rather effective security by obscurity technique.

As an example - every drupal website has a url '/user/register'. That is where every web bot first goes to try to register so they can leave spam.

Set up another separate page, say at /register/user (or wherever) and leave the original in place as a kind of honey trap for the web bots. Create a link to the real page that a real user would use. Web bots have to be far more clever to find the real page. If it's a moving target and maybe different at other websites, all the better.

The D8 version of Members Page has all the test code I tried, including a function that had the same code you had suggested. It would take all of five minutes (if you already have a D8 test bed set up) to try an install/uninstall cycle with the two different functions. The differences are quite stark.

Mark Theunissen’s picture

So I think then that the solution to your issue, assuming that the function menu_get_item() isn't working for you, is to create a system URL e.g. /user/register and have your honey pot on the system URL. In your code, check if the user is accessing the form through the system url directly or if they are using an alias. If they're using an alias, you can accept the submission, otherwise it's rejected.

Mark Theunissen’s picture

Status: Active » Fixed

Assume the question is answered.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.