The CMS i'm developing using Cakephp 2.0 has two main Controllers:
Pages
Categories
I'm trying to set the route.php to have the following behavior:
If the user request a Page, the URL should be something like:
http://www.something.com/pages-alias/article-name/id/whatever
If the user address a Category, the URL should be something like:
http://www.something.com/categories-alias/category-name/id/whatever
Please notice that following categories and pages i've used "alias".
To clarify with an example, the URLs for a website of a local restaurant will be:
http://www.something.com/course/wild-boar/68/2013-07-18
Where "course" will substitute "page". And
http://www.something.com/menu/valentine-day/8/2014-01-30
Where "menu" will substitute "category".
The View should not be explicited in the URL nor the Routing rules.
Both the cases will have the view automatically choosen by the controller after some internal check (having subcategory, having only one page or more pages, and so on) so that will be overridden by the controller.
I've got some clues about the use of sort-of "alias" to build the routing rules but unfortunately the documentation was not clear enough to me about how to manage the object to create my own custom route.
So, can someone try to explain it with some example different from the ones available in the CakePhP 2.x documentation?
Thanks in advance to anyone that can be helpful.
For reference i'll paste here the links i've already read:
Routing - Cakephp 2.0 Documentation
Bakery Article from Frank (i suppose this is for the v1.3)
That is what you want probably:
Router::connect(
'/:category_alias/:category_name/:id/:whatever',
array('controller' => 'Article', 'action' => 'view'),
array('pass' => array('category_alias','category_name','id','whatever'),
'id' => '[0-9]+')
);
ofc you can delete this validator for id.. or add more validators :)
Then you can use function in ArticleController.php
public function view($category_alias, $category_name, $id, $whatever) {}
Related
I'm building a web application and have been banging my head against a brick wall on a certain routing rule.
Here is the URL I am trying to achieve.
localhost/admin/location/123/pages/index
I am using the admin prefix which is working fine by uncommenting the following in app/config/core.php
Configure::write('Routing.prefixes', array('admin'));
'location/123' I want to essentially pass as parameters, so location with id of 123, 'pages' is the controller, 'edit' is the action.
There are other controllers that could replace 'pages' so this needs to be dynamic/wildcard. For example, a location could have pages, posts, users, etc.
Can anyone help me on how to write the Router::connect statement for this? Everything I try from the documentation doesn't seem to work.
Many thanks!
James
Router::connect(
'/admin/location/:location/:controller/:action/*',
array('admin' => 'true'),
array('location' => '[0-9]+')
);
If you go to /admin/location/123/pages/display/home, for example, it will go to PagesController, display action and home parameter.
In $this->request->params, this route will send admin = true and location = 123
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.. ?
I'm not really even sure how exactly to search for this but I have a URL
site.com/forum/controller/action
Where forum is a plugin and I currently have it routing to the plugin forum successfully with
Router::connect('/forum', array('plugin' => 'forum', 'controller' => 'home', 'action' => 'index'));
However, I want to add a route that will connect any top-level subdirectory to the plugin forum. For example,
site.com/fish/controller/action
site.com/bird/controller/action
would both route to the forum plugin. Similarly,
site.com/bird
would also route to the forum plugin. This is the best I have been able to come up with and it has no effect (I get a "FishController could not be found":
Router::connect('/*/:controller/:action/*', array('plugin' => 'forum'));
The closest answer I could find basically says this might not be possible? http://cakephp.1045679.n5.nabble.com/Routes-with-wildcards-missing-controller-errors-td1263632.html
EDIT: After some more trial & error I tried this:
Router::connect('/:site/:controller/:action/*', array('plugin' => 'forum'));
And it works. Could someone explain this?
The documentation at http://api.cakephp.org/class/router#method-Routerconnect does a great job at explaining this.
What you've done is created a custom parameter. Cake uses an array to keep track of the parameters and that how it know which controller, action and other parameters have been passed. The Router will convert any URLs with 3 slashes (/) to $param['site'], $param['controller'] and $param['action'].
From your controller, you can retrieve the value of :site by using $this->params['site'].
Im developing a simple application with CakePHP v1.3.7 Stable. I want to generate a simple user profile page, accessible by the url: my.domain/u/id, where id is the id of the user in database.
So i wrote this (and only this) in app/config/routes.php:
Router::connect('/u/:id',
array('controller' => 'Users', 'action' => 'profile')
,array('pass'=>array('id'),'id'=>'[0-9]+')
);
The above code works fine, when i put my.domain/u/120 in the browser, it shows the profile of user 120.
But, when i try to create a link to this page using the Html helper:
// some code in a view
$this->html->link('Test', array('controller'=>'Users', 'action'=>'profile', 120))
The html helper (doing inverse routing, i think) generates the url in the defaut cakephp form: Test
Based on the configuration in routes.php, it should be: Test, right?
I'm missing something?
Thanks.
Try with this:
$this->html->link('Test', array('controller'=>'Users', 'action'=>'profile', 'id'=>120))
I hope the missed 'id' will fix it.
Finally, thanks to dogmatic69, I deleted the id related code of the route.
I end with this in routes.php:
Router::connect('/u/*',
array('controller' => 'users', 'action' => 'profile')
);
Now, the html helper works just as expected:
echo $this->html->link('Test',array(
'controller'=>'users',
'action'=>'profile',
100
))
// renders: Test
The drawback is that I can't take advantage of the regexp filter of the router, as in the initial route code.
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.