Kohana Hierarchical Routes with Subpages - php

I'd like to set up some kind of hierarchical routing in Kohana 3.0.9. The routing should support pages and sub-pages, which could look something like this:
URL called: http://example.com/company/contact/
Possible Route: <page>(/<subpage>(/<action>))
The problem with above mentioned route is that it only supports a hierarchy with two levels. If the site needed to be able to handle "sub-sub-pages" like http://example.com/company/contact/sub, I would have to change the route.
Also, I'd like one single page controller to handle the request. It could, for example, accept company/contact/sub as a string parameter. I'm aware of the <directory> key and I'm not planning to use it.
Is there any way to make the route "compatible" with unlimited nested pages?
Thanks in advance for your answers.

If you look at the ROUTING section, you can create custom REGEX patterns.
(read here) http://kohanaframework.org/guide/kohana/routing
By default, routing "parameters" removes punctuation.
You can setup a regex to accept "/".
Then you can grab the last parameter and explode it with
$pieces = explode("/", $pizza);
Example:
http://example.com/company/contact/subpage/subsubpage/action
Route::set('multilevel', '(<controller>(/<page_levels>/<action>))'
, array('page_levels' => '.*'))
->defaults(array(
'controller' => 'page_controller',
'action' => 'index',
));
Doublecheck the REGEX, but basically it grabs all "/" except the very last one (which should be your ACTION parameter separator.
In your ACTION_[action] function (in Controller_Contact for this example),
you would then call
$page_levels = Request::instance()->param('page_levels');
$page_array = explode("/",$page_levels);
This should get you
$page_array = array ( [1] => 'contact',
[2] => 'subpage',
[3] => 'subsubpage')

Related

CakePHP routes always pointing to same action

I am facing some problems in routing under cakephp
there are three actions in my controller
they are as below
www.example.com/photos/newphotos
www.example.com/photos/random
www.example.com/photos/popular
I want them as
www.example.com/newphotos
www.example.com/random
www.example.com/popular
so i routes file under config file I wrote as
Router::connect('/:newphotos', array('controller' => 'photos', 'action' => 'newphotos'));
Router::connect('/:popular', array('controller' => 'photos', 'action' => 'popular'));
Router::connect('/:random', array('controller' => 'photos', 'action' => 'random'));
its working fine when I hit the url
www.example.com/newphotos
but when I hit url www.example.com/random or www.example.com/popular , its again point to action newphotos.
so how can I solve it
(In other words I need to remove controller name "photos" from url for every action)
Many thanks
Why not remove the : from the routes?
If you want to stick with /: paths, then you would need to supply a third parameter to Router::connect() in which to specify patterns for the added options. That is, if you have /:popular as the first parameter, you would need array('popular' => 'popular') as the third parameter, making the rule look like:
Router::connect('/:popular', array('controller' => 'photos', 'action' => 'popular'), array('popular' => 'popular'));
This means that :popular will be matched against the given regex, that is the literal 'popular'. See CakePHP's docs for more info.
Nevertheless, this is useless and silly, so you should stick with paths without colons.
Just delete the colon from the first parameter. They are kind of "capturing variables", so now you basically are routing all / with some parameters to photos/newphotos, and the parameters being captured to :newphotos. As it always will match the first route, then it will not look for the others.

Custom lithium routing scenario

I've been tasked with rewriting an existing website with large pre-existing link catalog. For argument's sake, let's assume we can't do anything that would change the link catalog. Here's a few examples of the link structure we're working with:
An item page would be:
www.domain.com/widgets/some-totally-awesome-large-purple-widget
A category sub page page would be:
www.domain.com/widgets/purple-widgets
A category parent page page would be:
www.domain.com/widgets/
A custom page may be:
www.domain.com/some-random-page
The various page types are too numerous to write individual Routers for.
Using Router::connect I can easily account for the first and second scenarios using something like:
Router::connect('/{:pageroot}/{:pagekey}', 'Pages::index');
In turn, the Pages::index method looks for entries in our database with the "key" of '/widgets/purple-widgets'.
However, the framework defaults to the '/{:controller}/{:action}/{:args}' route for pages like the third and fourth. I know that this is the correct behavior for the framework. Also, best practice would state that I should write the site to match this behavior. But, that isn't an option here.
What I need is a Router that would allow the third and fourth examples to function the same as the first. All examples should be sent to the Pages::index controller, which in turn queries a database using the URL path as a key.
If you don't have any convention in the URL for what is what, between page, item and category. I'd go with a very generic router.
Router::connect('/{:category}/{:page}/{:item}', 'Pages::any');
Router::connect('/{:category}/{:page}', array('Pages::any', 'item' => null));
Router::connect('/{:category}', array('Pages::any', 'page' => null, 'item' => null));
And in Pages::any() to search for the correct stuff. Is that category a page after all (example 4)? Is that page an item (example 1)?
or
You store the URL somewhere (e.g. a mapping table in the database) and use the pattern version of a lithium Route.
Router::connect(new Route(array(
'pattern' => '#^/(?<path>.+)$#',
'params' => array('controller' => 'pages', 'action' => 'any'),
'keys' => array('path' => 'path'),
// extra stuff, if the path is `tata`, it skips this route and uses
// any of the following ones that matches.
'handler' => function($request) {
if ($request->params['path'] == 'tata') {
return false;
} else {
return $request;
}
}
)));
From that point, you'll get the full URL.
You probably should write a smart Router Helper which is maybe able to process your request based on your db defined routes.
Take a look into: net/http/Router.php
especially connect(), parse() and match()
I would start to write some kind of anonymous function and progress it to a testable Class which is located in /extension.. ?

Zend Framework - Zend_Controller_Router_Route_Regex - Concatinate Regex matches

I would like to be able to match a URL like this
www.site.com/cc/[action-name-first-part].12345.[action-name-second-part]
Basically the resulting action would be a concatenation of the 2 action name.
My current route looks like this
$ccRoute = new Zend_Controller_Router_Route_Regex(
'cc/([^.]).([^.]).([^.]*)',
array('controller' => 'cc'),
array('action-first-part' => 1, 'arbitrary-number' => 2, 'action-second-part' => 3)
);
this partner matches fine. but with the third argument (mapping argument), how can I concat the 1 and 3 index and just pass the correct action to the controller.
Thanks.
As far as I remember, you cannot do this with the regex routes. However, it's quite trivial to make a new route class that can do this.
Here is a quick answer on how to make your own route class.
how to get dynamic URL like mydomain.com/username using zend framework

Zend_Router omitting param-key

I've got a question considering Zend_Controller_Router. I'm using a a modular-structure in my application. The application is built upon Zend-Framework. The normal Routes are like this:
/modulename/actionname/
Since I always use an IndexController within my modules, it's not necessary to provide it in the url. Now I am able to append params like this:
/modulename/actionname/paramkey/paramvalue/paramkey/paramvalue
So this is normal in ZF, I guess. But in some cases I don't want to provide a paramkey within the url. For example I want a blog-title to be shown within the url. Of course this is intended for SEO:
/blog/show/id/6/this-is-the-blog-title
In this case, blog is the module, show is the action. id is a paramkey and 6 is the id of the blogpost I want to show. this-is-the-blog-title is of course the headline of the blogpost with the id 6. The problem is, that if I do use the assemble()-method of the router like this:
assemble(array('module' =>'blog',
'action' => 'show',
'id' => $row['blog_id'],
$row['blog_headline_de'] . '.html'));
the url results in:
blog/show/id/6/0/this-is-the-blog-title.html
As you can see a 0 is inserted as a key. But I want this 0 to be omitted. I tried this by using the blogtitle as key, like this:
assemble(array('module' =>'blog',
'action' => 'show',
'id' => $row['blog_id'],
$row['blog_headline_de'] . '.html' => ''));
This results in:
blog/show/id/6/this-is-the-blog-title.html/
Now the 0 is omitted, but I've got the slash at the end.
Do you have any solution to get an url without 0 as key and without an ending slash?
Regards,
Alex
You might want to use a custom route for this:
$router->addRoute(
'blogentry',
new Zend_Controller_Router_Route('blog/show/:id/:title',
array('controller' => 'index', 'module' => 'blog'
'action' => 'info'))
);
And call your assemble with the route as second parameter. See the Zend_Controller_Router_Route section of the documentation for more details (they even provide examples with assemble).
Or in a more general way:
$router->addRoute(
'generalseo',
new Zend_Controller_Router_Route(':module/:action/:id/:title',
array('controller' => 'index'))
);

cakephp routing for cushycms preview link

I have a set of rather static pages wich I moved to the views/pages folder. The resulting *.ctp files are editable by my customer through CushyCMS (simplistic cms perfect for dummy proof editing). However CushyCMS generated preview links that obviously don't take CakePHP into account. I would like to solve this little problem with custom routing, but can't get my head around the details..
How can I dynamically connect the url http://localhost:8888/cake125/app/views/pages/test.ctp to http://localhost:8888/cake125/pages/test?
I added the following in my routes.php:
Router::connect('/pages/test.ctp', array(
'controller' => 'pages',
'action' => 'display', 'test'));
This works ok for connecting: http://localhost:8888/cake125/pages/test.ctp to http://localhost:8888/cake125/pages/test. Somehow following snibbet doesn't do the trick:
Router::connect('/app/views/pages/test.ctp', array(
'controller' => 'pages',
'action' => 'display', 'test'));
Ideally I'd like to have a single Router::connect statement which connects all /app/views/pages/*.ctp requests to the right place.
Finally I would also like to correctly handle google search results for the old version of the site. Like so:
Router::connect('/test.html', array(
'controller' => 'pages',
'action' => 'display', 'test'));
This works ok but I'd rather have anypage.html connect to /pages/anypage. Can anyone help with this?
Thanks in advance!
First, by virtue of having Cake in a subdirectory (/cake125), I think you may need to connect the /cake125/:controller/:action, rather than how you have it. Not 100%, though; Cake might be robust enough to handle that use case. If you have weird errors, I'd check that.
On with my answer:
I think you are somewhat misunderstanding how the Router class works. You connect URLs, not relative filesystem paths, using Router::connect. By default (which you may have erased, but it's pretty simple to fix), Cake will route requests to /pages/* to the PagesController::display() function, passing it one argument (the action listed in the http request).
So, to have the pages controller map /pages/one to the app/views/pages/one.ctp element, simply make sure that the following (default, i.e. Cake normally has this setup) line is in the routes config (and make sure that lines above it do not match that pattern):
Router::connect( '/pages/:action', array( 'controller' => 'pages', 'action' => 'display', :action);
This should ensure that PagesController::display( $action ) is invoked during the request, which is (I think) what you're after.
If your CMS generates preview links that you want to correctly re-route, I'd suggest adding a new route. E.g., if your CMS generates links like http://somesite.com/cms/preview/newly_edited_file, you can route it like this:
Router::connect( '/cms/preview/:action', array( 'controller' => 'pages', 'action' => 'display', :action );
For your second question: have a default rule in your routes (make it the last rule, and have it match *). It will then be configured to route all not found requests to your controller/action pair as requested. Try this:
Router::connect( '/:action', array( 'controller' => 'pages', 'action' => 'display', :action );
Major caveat this will break your existing routes. You will need to manually add an entry for each of your existing controllers (Router::connect( '/users/:action', ...etc...). If you google around you can find some clever solutions, such as having that list generated at runtime for you. But you will need to address "normal" routing, once you've added that catch-all (and make sure your catch-all is at the end of the routing file).
Also, if you want to parse URLs like /test.html, simply add a call to Router::parseExtensions(...) so that Cake will register .html as an extension for it to parse. Check the manual on that function for more info.
As others have pointed out how CakePHP Router works, I'll leave it at that.
For the second part of your question (handling old links), I'd suggest adding this to the end of your Routes list:
Router::connect( '/:page',
array (
'controller' => 'pages',
'action' => 'display',
),
array (
'pass' => array ('page'), // to pass the page as first arg to action
'page' => '.+\.html$', // to verify that it ends with .html
)
);
You'd unfortunately have to parse out the .html yourself though
How can I dynamically connect the url http://localhost:8888/cake125/app/views/pages/test.ctp to http://localhost:8888/cake125/pages/test?
Well, the thing is, you don't. :-)
What I mean by that is, you do not connect a URL to another URL. What you really do is, you make certain URLs trigger certain Controller functions (or Actions for short) which in turn may (or may not) render certain Views. By default it's all straight forward through naming conventions. The URL /foo/bar triggers the Controller Foo's Action bar and renders the View /views/foo/bar.ctp.
The PagesController is already a special case. The URL /pages/foo triggers the Controller Pages's Action display, passes it the parameter foo, which renders the View /views/pages/foo.ctp. Notice the difference in which Action is triggered.
Since there are a lot of steps inbetween, it's not a given that a certain URL corresponds to a particular file on the hard disk. The URL /foo/bar might trigger Controller Baz' Action doh which renders the View /views/narf/glob.ctp.
This makes translating http://localhost:8888/cake125/app/views/pages/test.ctp to render the file /views/pages/test.ctp somewhere between an uncertainty and a pain in the rear.
Edit:
Having said that, the particular problem in your case is that the base URL is http://localhost:8888/cake125/app/. You can invoke a Cake app from http://localhost:8888/cake125/, http://localhost:8888/cake125/app/ or http://localhost:8888/cake125/app/webroot. All three URLs will be handled by the same file cake125/app/webroot/index.php, if you use one of the shorter URLs the request will be "forwarded" (rewritten) via .htaccess rules.
So the Route you're trying to connect, the Route that Cake sees, is actually /views/pages/test.ctp.
Actually, my mistake, this might not be the problem, but it depends on your .htaccess files and server configuration.
It doesn't seem to make much sense in a CMS though, since every newly created page would need its own rule. So I'd recommend against trying to do so and rather hack Cushy to properly construct URLs using the Cake HtmlHelper or Router::url(). Failing that, connect all URLs with a catch-all rule to some Action, parse the URL there and render the correct View "manually".
Alternatively, use .htaccess files and rewrite rules to actually rewrite the URL into a normal Cake URL, so Cake doesn't have to worry about it. As said above though, this can be very fragile.

Categories