Pixelastic

You can clip our wings but we will always remember what it was like to fly.

Posts tagged with "Router"

Protecting a directory using HTTP Auth on Dreamhost with cakePHP

One can protect the browsing of a special directory with a simple set of login/password by using appropriate .htaccess/.htpasswd files.

The classic way

Just create an .htaccess in the directory you want to protect with the following lines :

AuthName "Restricted Access"
AuthType Basic
AuthUserFile /full/path/to/your/.htpasswd
<Limit GET POST PUT>
Require valid-user
</Limit>

And to create the .htpasswd file, run the following command :

htpasswd -c /full/path/to/your/.htpasswd username

The -c modifier will create the file, omit it if you only want to add a new user. Also change the path to your .htpasswd file (moving it out of the webdir could be a good idea) and change username to any login you want.

You'll then be prompted to enter the password (twice) and your file will be generated.

cakePHP and Dreamhost fixed

I had an issue when protecting a folder in my app/webroot/ folder on Dreamhost. I'm not sure it is completly cake related nor Dreamhost related but the two together made it quite hard to debug.

Anyway, it appears that when issuing an HTTP Auth, Dreamhost redirect to a file named /failed_auth.html (this is the file you're supposed to see when your Auth fails, obviously).

But as I didn't have such a file in my app, everytime I tried to access my protected dir, I got my custom 404 error page.

To finally fix that, all I had to do was to create a real failed_auth.html page, or in my case, create a Route that redirect failed_auth.html to a custom failed auth page.

I guess just dropping a failed_auth.html file in app/webroot/ could have done the trick too.

Using Router::connectNamed without breaking pagination

In cakePHP, you can pass all sort of parameters to your urls by following the www.domain.com/controllers/view/foo:bar/foo2:baz syntax.

You could then access $this->params['foo'] and $this->params['foo2'] in your Controller::view() method.

Using Router::connectName()

This does not play nice with default routing. I mean, if you define a route to add a vanity url like www.domain.com/vanity is routed to Controller:view(), you'll write something like this :

Router::connect('/vanity', array('controller' => 'controllers', 'action' => 'view'));

This will work as long as you don't specify any additional parameters. Once you started to add any parameters, the Router won't be able to parse your url and instead of returning www.domain.com/vanity/foo:bar/foo2:baz it will return the default www.domain.com/controllers/view/foo:bar/foo2:baz

If you do want your custom parameters to be taken into account by your Router rules, you have to manually add them, using Router::connectNamed(array('foo', 'foo2')).

Custom connectNamed()

I'll let you browse the connectNamed() documentation page for further details on how to use it properly. But one important thing not to overlook is that if you ever have to define a custom Router::connectNamed(), do not forget to add a second parameter of array('default' => true), this will allow all your paginated links to keep working.

Paginated search results and custom url

I wanted for this blog a search feature, but I had some prerequisites for it :

  • The search url could be bookmarked
  • It should be paginated
  • It should play well with my custom url starting with /blog

Defining custom urls

Here are the two routes I defined in my routes.php

Router::connect('/blog/search/:keyword',
    array('controller' => 'posts', 'action' => 'search'),
    array(
        'pass' => array('keyword'),
        'keyword' => '[^/]+'
    )
);
Router::connect('/blog/search/*', array('controller' => 'posts', 'action' => 'search'));

Going to /blog/search/*keyword* will start a search on the keyword, while going to /blog/search/ would display a search form.

Writing the method

I started by creating a search action in my PostController, then creating a form submitting to this action, with a keyword input field.

In the search method, the first thing I do is checking if some POST data is submitted (coming from the search form). If so, I redirect to the same page, but passing the keyword as first parameter.

If no keyword is passed nor data submitted, I'll display a simple search form.

And finally if a keyword is specified, I'll do a paginated search on every posts whose name or text contains the keyword.

function search() {
// We redirect to get it in GET mode
if (!empty($this->data)) {
return $this->redirect(array('keyword' => urlencode($this->data['Post']['keyword'])));
}

// Search index
if (empty($keyword)) {
return $this->render('search_index');
}

// Adding conditions to name and text
$keyword = urldecode($keyword);
$this->paginate = Set::merge(
$this->paginate,
array(
'conditions' => array(
'AND' => array(
'OR' => array(
'Post.name LIKE' => '%'.$keyword.'%',
'Post.text LIKE' => '%'.$keyword.'%'
)
)
)
)
);
// Getting paginated result
$itemList = $this->paginate();

$this->set(array(
'keyword' => $keyword,
'itemList' => $itemList
));
}