CakePHP: PagesController with Admin Routing
Posted by Dave | Tags: admin, cake, CakePHP, pagescontroller, routing, tutorialFor small Cake websites with admin routing enabled, I like to use the Auth component to require a login for all admin routes, and allow access to everything else using the following beforeFilter in the AppController superclass:
function beforeFilter(){
$admin = Configure::read('Routing.admin');
if (isset($this->params[$admin]) and $this->params[$admin]){
$this->layout = 'admin';
}
else {
$this->Auth->allow();
}
}
The problem with this however is static pages handled by the pages controller cannot be password protected. To resolve this problem, I had to overload the PagesController class that Cake comes with, and add in the required functionality. Part of the reason for doing this for me was to allow a setup where there was a password protected admin welcome page or control panel located at my_app_URL/admin, so I’ll show you the necessary routing to achieve that too.
Adding a route and overloading PagesController
First of all, copy your_app_dir/cake/libs/controller/pages_controller.php into your_app_dir/app/controllers with the rest of your application’s controllers. Then fire up an editor and take a look at your newly copied version. You’ll see there is a function called display, this is what the pages controller uses to display pages. There is a route in app/config/routes.php that maps /pages/* to /pages/display/* to make the URL easier on the eyes, so if we’re gonna have /admin/pages/* working properly, we’ll need a similar route. Open up app/config/routes.php, and underneath the line
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
add in the following route:
Router::connect('/admin/pages/*', array('controller' => 'pages', 'action' => 'display', 'admin' => true));
Now, when admin routing is enabled, Cake looks for controller actions appened with “admin_”, so we’d better add in the function to handle this in PagesController. Open it up, and underneath the display function, add the following:
function admin_display() {
$path = func_get_args();
$temp = null;
$count = count($path);
if ($path[0] != 'admin') {
//This adds admin to the beginning of the path so the pages controller will look in the 'admin' folder in pages directory
$path = array_merge((array)'admin', $path);
} else {
//This removes admin from the beginning if it's there already, and sends the request round again so we end up with URLs that look like app/admin/pages/x
//when app/admin/pages/admin/x is requested somehow.
$path = array_slice($path, 1);
$this->redirect(array_merge(array('controller' => 'pages', 'action' => 'display', 'admin' => true), $path));
}
if (!$count) {
$this->redirect('/');
}
$page = $subpage = $title = null;
if (!empty($path[0])) {
$page = $path[0];
}
if (!empty($path[1])) {
$subpage = $path[1];
}
if (!empty($path[$count - 1])) {
$title = Inflector::humanize($path[$count - 1]);
}
$this->set(compact('page', 'subpage', 'title'));
$this->render(join('/', $path));
}
As you can see it’s fairly similar to the display function, with a few extra lines added in that handle admin pages. There are a few subtleties here which I will explain in a second. Before that however, we also require a slight change to the existing, non-admin display function. Look for the if statement below:
if (!empty($path[0])) {
$page = $path[0];
}
and change it to:
if (!empty($path[0])) {
$page = $path[0];
if ($page == 'admin') {
//Sends admin page requests to their proper place to stop sneaky access attempts
$this->redirect(array_merge(array('controller' => 'pages', 'action' => 'display', 'admin' => true), $path));
}
}
Ok so what have we done?
- The added nested if statement in the display() function redirects requests for /pages/admin/x to their proper place, /admin/pages/x.
- The ‘else’ clause of the if ($path[0] == ‘admin’) in admin_display redirects requests for /admin/pages/admin/x to /admin/pages/x, which just tidies up a URL aesthetics issue.
- Finally, the first part of the same if statement is what handles the /admin/pages/x requests proper – it adds the ‘admin’ part back to the beginning of the $path variable that point 2 removes. This is actually just exploiting a subtlety of the pages controller, ‘subpages’ (this seems to be quite hard to find in the documentation actually) – requests sent to /pages/a/b will display a page b stored in the folder your_app_dir/app/views/pages/a, rather than a page b stored in the pages root. Adding this additional logic helps organisation a bit by storing all admin pages in you_app_dir/app/views/pages/admin/.
So now this is all in place, everything should work correctly. Requests to /pages/admin/x and /admin/pages/admin/x both get sent to /admin/pages/x, and these require proper authentication.
The final step is to add in a route that allows my original use for this whole setup to work properly – displaying an admin homepage or control panel that requires authentication when a user visits your_app_URL/admin, i.e. without referring to any controllers or actions. First, create a page in your_app_dir/app/views/pages/admin/, called home.ctp that contains the content you want. You can now access this from your_app_URL/admin/pages/home, but the shorter URL works after adding the route:
Router::connect('/admin', array('controller' => 'pages', 'action' => 'display', 'admin' => true, 'home'));
Hooray! We now have an admin homepage. Hope everything worked for you, hit me with a comment if you have an issues and I’ll try and help out.
Hey man,
this post save my life!
congrats!
[...] is back with a tutorial on how to use the pages controller with admin routing. What I like most about their code is the lack of indention. It’s like they’re tossing [...]
[...] Password protected static pages Today I came across a nice tutorial on how to protect a static page with a password Click here [...]
Hi. I was trying to make it work in cakephp 1.2, but it didn’t. I did exactly the example in a test app but no succeded.
I would like to make an admin home page with login in my app.
Thank you.
Do you get any errors or anything? And did you have the beforeFilter in the AppController class given right at the start?
Thank you you saved my live with this code, thank you very much and god bless you for your kindness
with cake 1.3 there is no more routing.admin
http://book.cakephp.org/view/1561/Migrating-from-CakePHP-1-2-to-1-3
you have to change the control in the beforeFilter function
$routing_prefixes = Configure::read(‘Routing.prefixes’);
if(in_array(“admin”, $routing_prefixes)){
$admin =’admin’;
}
Hi — I know this is completely unrelated to your post here, but your \LaTeX renderer on your “PhysiWiki” appears to have gone down. I’m a current third year about to sit the B1, B2, and B3 papers and I’ve found your site incredibly useful for them — please could you fix it!
A thank you from St. Hugh’s!
Hi,
In an ideal scenario, you would not override the PagesController, or repeat the code by copying and pasting it. I solved this problem by adding this to routes.php:
Router::connect('/admin/adm_pages/*', array('controller' => 'adm_pages', 'action' => 'display', 'admin' => 1));
Then i created this class, adm_pages_controller.php:
and then added my view to app/views/adm_pages/help.ctp.
This way, if you need to update your cake version, as long as the file structure and display method prototype stays the same, it will work
No idea why the class code didnt show, i’ll try again without the php tags
require_once '../../cake/libs/controller/pages_controller.php';
class AdmPagesController extends PagesController {
var $name = 'AdmPages';
public function admin_display(){
// parent::display(func_get_args());
$vArgs = func_get_args();
call_user_func_array(array('parent', 'display'), $vArgs);
}
}
weweewewewew wewew wew ewew
At first: GREAT post – I was looking something like this
Thank You
!!!
I did like You described, but it doesn’t work for me. When I try to get access to foo/admin it asks me to log in – that’s ok. But when I give correct login and passord it still gives me information that I’m not authorised user, and shows me login screen again. But then I want to show foo/admin/users and type this address in web browser – page is shown correctly. No idea what can cause this behaviour.
Anny suggestions ?
Thanks,
Radek
For cakephp 2.0 I adapted this article:
http://nuts-and-bolts-of-cakephp.com/2011/03/15/dealing-with-static-pages-v2-or-3/
for the admin static pages, in this way:
$staticAdminPages = array(
‘tools’
);
$staticAdminList = implode(‘|’, $staticAdminPages);
Router::connect(‘/admin/:static’, array(
‘plugin’ => false,
‘controller’ => ‘pages’,
‘action’ => ‘display’, ‘admin’ => true), array(
‘static’ => $staticAdminList,
‘pass’ => array(‘static’)
)
);
Obviously I wrote the admin_display function but I left these lines unchanged:
if (!empty($path[0])) {
$page = $path[0];
}
Now I am able to connect to this url:
http://server/site/admin/tools
Enjoy it!