I want to make my url seo friendly. www.example.com/posts/view/1 change for www.example.pl/:slug-:id. Everything works fine, but probably I'm doing something wrong with routing, because when after clicking the urls in paginator, the url is correct, it looks like www.example.pl/:slug-:id , but it appears an error
"The requested address 'www.example.pl/:slug-:id' was not found on this server."
I don't know what's wrong. Here's my code:
Router::connect(
'/:slug-:id',
array(
'controller' => 'posts',
'action' => 'view'
),
array(
'pass' => array('slug' , 'id'),
'id' => '[0-9]+'
)
);
in paginator view:
echo $this->Html->link($ad['Post']['title'], array(
'controller' => 'posts',
'action' => 'view',
'slug' => Inflector::slug($post['Post']['title'],'-'),
'id'=>$post['Post']['id'])
);
I solved the problem.
Its too simple i'll give you an example from my project ..
in your routes.php
Router::connect(
'/:slug-:id',
array('controller'=>'posts','action'=>'view'),
array('pass'=>array('slug','id'),'slug'=>'[a-zA-Z0-9 -]+','id'=>'[0-9]+')
);
your link in views should be like .
$this->Html->link(__('link desu'),array('controller'=>'posts','action'=>'view','id'=>$post['Post']['id'],'slug'=>$post['Post']['slug']));
and your PostsController.php
public function view($slug,$id){
$this->Post->id = $id;
// ....
}
Quick tip : try to create an array in your PostModel to avoid creating it every time in your view .
example :
Post.php
class Post extends AppModel{
// ....
public function afterFind($results,$primary = false){
foreach ($results as $key => $value) {
if(isset($value[$this->alias]['id'])){
$results[$key][$this->alias]['url'] = array(
'controller'=>'posts',
'action'=>'view',
'id'=>$results[$key][$this->alias]['id'],
'slug'=>$results[$key][$this->alias]['slug']
);
}
// ....
}
return $results;
}
}
}
so you can call it in your view simply like that
$this->Html->link(__('link desu'),$post['Post']['url']);
It's probably a problem with the regex on the route. Your slug contain hyphens - which you also use to separate between the slug and the id. i.e.:
example.com/my-slug-has-hyphens-1
The regex is not smart enough to know that the "last" hyphen separates the slug from the id.
To test if this is the problem, try using a route like this '/:slug__:id', just to see if it works.
I solved the problem. In the posts controller my view function was wrong. Here's right correct:
function view($id = null, $slug = null) {
$this->Post->id = $this->params['post'];
$this->set('post', $this->Post->read());
Pass is order sensitive
In the question the route is as follows:
Router::connect(
'/:slug-:id',
array(
'controller' => 'posts',
'action' => 'view'
),
array(
'pass' => array('slug' , 'id'), # <-
'id' => '[0-9]+'
)
);
That means the post function will recieve:
public function view($slug, $id)
As indicated by the edited question, the code is expecting the id to be the first parameter. The easiest solution is simply to specify the passed parameters in the order that they are expected:
...
'pass' => array('id', 'slug'), # <-
Router::connect(
'/:slug/:id',
array(
'controller' => 'posts',
'action' => 'view'
),
array(
'pass' => array('slug' , 'id'),
'id' => '[0-9]+'
)
);
the above code will create correct link as www.example.com/posts/view/title/1
echo $this->Html->link($post['Post']['title'], array('controller' => 'posts', 'action' => 'view', Inflector::slug($post['Post']['title'],'-'),$post['Post']['id']));
Related
I am using CakePHP 1.3 and have some troubles with prefix routing.
I configured routes like that:
Router::connect(
'/listing/*',
array(
'controller' => 'dsc_dates',
'action' => 'listing',
)
);
Router::connect(
'/modular/listing/*',
array(
'controller' => 'dsc_dates',
'action' => 'listing',
'prefix' => 'modular'
)
);
in my controller there are two functions:
function modular_listing($order = null,$orderDirection = null, $items=null, $location_id=null) {
$this->layout='module';
$this->setAction('listing',$order, $orderDirection, $items, $location_id);
}
function listing($order = null,$orderDirection = null, $items=null, $location_id=null){...}
The prefix action should just change some things and then operate like the normal 'listing' method. Until here it works fine.
But if i create relative links (with HTML Helper) Router::url() uses 'modular_listing' as action which does not fit into my routes. It should be 'listing' instead of 'modular_listing'.
The controller params are correct with 'listing' as action but the router params still says 'modular_listing'.
So relative links:
$this->Html->link('example',array('parameter'));
will end up in:
/dsc_dates/modular_listing/parameter
How can I get the correct links so that the router uses 'listing' as action?
UPDATE:
It is not an alternative to add 'controller' and 'action' to the url array of the link generation. In fact I have problems with the automatically generated relative links from the paginator.
I couldn't tell if you wanted the generated Html->link() routes with the leading controller or not, so I did both:
Controller (note the renderer):
// DscDatesController.php
public function listing($param = null) {
$this->set('param', $param);
$this->render('listing');
}
public function modular_listing($param = null) {
//
$this->setAction('listing', $param);
}
Routes:
// routes.php
Router::connect(
// notice no leading DS
'listing/*',
array(
'controller' => 'DscDates',
'action' => 'listing'
)
);
Router::connect(
'/modular/listing/*',
array(
'controller' => 'DscDates',
'action' => 'listing'
)
);
View:
// DscDates/listing.ctp
<?php
// generates /dsc_dates/listing/:param
echo $this->Html->link(
'example',
array('controller'=>'dsc_dates', 'action'=>'listing', $param));
// generates /listing/:param
echo $this->Html->link(
'example',
array('action'=>'listing', $param));
About wildcards, DS and routing order:
CakePHP broken index method
HTH :)
There was a similar thread but it didn't address this exact problem so I thought I would create a new thread to make this clear.
Code/problem explained:
This is my SystemsController.
Here I have viewsystemAction(). This is loaded, and from the view a script is called to load ajaxviewsystemAction().
In the viewsystemAction(), I use the params function to get the "id" parameter from my route. For example systems/viewsystems/222 ( <-- 222 is id. ). I echo this out just to show that it is correct, and it gives the correct id for the page.
The ajaxviewsystemsAction is kind of where the problem is. This is because the route of ajaxviewsystemsAction id is not the same as viewsystemsAction. If I echo the id from the params here, it displays as 0.
Although if i go to this page in the browser for example ajaxviewsystemsAction/222 then it is working perfectly. However, nothing is rendered here except the ajax table, as remember it is called in viewsystemAction from a script.
What i need to do, is somehow pass the id route value from viewsystemAction into ajaxviewsystemAction, to both use this same route id.
This is so when someone clicks on the link, it loads viewsystems/222, the correct id is executed in both viewsystemsAction and ajaxviewsystemsAction.
Is this possible? If not how could i make something similar like this. I am using zftable which integrates ajax. I need to pass this id parameter through into a query.
private function getSourceViewAllSystems($id)
{
return $this->getSystemsTable()->fetchViewAllSystems($id); //paramater to model which executes sql ->where system = $id
}
public function viewsystemAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
echo $id; //i see the correct id for example 220 from the route in the browser
}
public function ajaxviewsystemAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
echo $id; //to see the id of the route with the ajax page
//displays 0 and not the route id from the viewsystemAction
$table = new TableExample\Advance();
$table->setAdapter($this->getDbAdapter())
->setSource($this->getSourceViewAllSystems($id))
->setParamAdapter($this->getRequest()->getPost())
;
return $this->htmlResponse($table->render('custom' , 'custom-b2'));
}
--- update ---
<?php
return array(
'controllers' => array(
'invokables' => array(
'Systems\Controller\Systems' => 'Systems\Controller\SystemsController',
),
),
// The following section is new and should be added to your file
'router' => array(
'routes' => array(
'systems' => array(
'type' => 'segment',
'options' => array(
'route' => '/systems[/][:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Systems\Controller\Systems',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'systems' => __DIR__ . '/../view',
),
),
);
I've got two or more routes that will be going to the same controller and action. This is fine until I want to use a helper such as the form helper or pagination on the page.
What happens is that the current url changes to whatever is declared first in my routes.php file.
I see there is a way to promote a router with Router::promote but I'm not sure if I can do it based on the current url or router being used or if there's a bett way to do this.
Here's an example of what my router.php looks like:
Router::connect('/cars-for-sale/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/new-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/used-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Let's say for example that I'm at the url domain.com/used-cars/results/ and I'm using the form helper or pagination helper, the url that is being put in the action or href is domain.com/cars-for-sale/results/.
Any help or info would be appreciated.
Routes should be unique and identifiable!
The problem with these Routes is that, basically, you created duplicate URLs not only does this cause problems with CakePHP picking the right route, Google doesn't like that as well; duplicated content will have a negative effect on your SEO ranking!
In order to pick the right URL (Route), CakePHP should be able to do so, based on its parameters; your current Routes do not offer any way to distinguish them.
And neither does your application!
All these URLs will present the same data;
/cars-for-sale/results/
/new-cars/results/
/used-cars/results/
Solution 1 - separate actions
If your application is limited to these three categories, the easiest solution is to create three actions, one per category;
Controller:
class ListingsController extends AppController
{
const CATEGORY_NEW = 1;
const CATEGORY_USED = 2;
const CATEGORY_FOR_SALE = 3;
public $uses = array('Car');
public function forSaleCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_FOR_SALE)));
}
public function newCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_NEW)));
}
public function usedCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_USED)));
}
}
Routes.php
Router::connect(
'/cars-for-sale/results/*',
array('controller' => 'listings', 'action' => 'forSaleCars')
);
Router::connect(
'/new-cars/results/*',
array('controller' => 'listings', 'action' => 'newCars')
);
Router::connect(
'/used-cars/results/*',
array('controller' => 'listings', 'action' => 'usedCars')
);
Solution 2 - Pass the 'category' as parameter
If the list of URLs to be used for the 'listings' will not be fixed and will expand, it may be better to pass the 'filter' as a parameter and include that in your routes;
routes.php
Router::connect(
'/:category/results/*',
array(
'controller' => 'listings',
'action' => 'results',
),
array(
// category: lowercase alphanumeric and dashes, but NO leading/trailing dash
'category' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
// Mark category as 'persistent' so that the Html/PaginatorHelper
// will automatically use the current category to generate links
'persist' => array('category'),
// pass the category as parameter for the 'results' action
'pass' => array('category'),
)
);
Read about the Router API
In your controller:
class ListingsController extends AppController
{
public $uses = array('Car');
/**
* Shows results for the specified category
*
* #param string $category
*
* #throws NotFoundException
*/
public function results($category = null)
{
$categoryId = $this->Car->Category->field('id', array('name' => $category));
if (!$categoryId) {
throw new NotFoundException(__('Unknown category'));
}
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => $categoryId)));
}
}
And, to create a link to a certain category;
$this->Html->link('New Cars',
array(
'controller' => 'listings',
'action' => 'results',
'category' => 'new-cars'
)
);
Firstly, Kohana's documentation is terrible, before people go "read the docs" I have read the docs and they don't seem to make much sense, even copying and pasting some of the code doesn't work for some things in the documentation.
With that in mind, I have a route like so:
//(enables the user to view the profile / photos / blog, default is profile)
Route::set('profile', '<userid>(/<action>)(/)', array( // (/) for trailing slash
"userid" => "[a-zA-Z0-9_]+",
"action" => "(photos|blog)"
))->defaults(array(
'controller' => 'profile',
'action' => 'view'
))
This enables me to go http://example.com/username and be taken to the users profile, http://example.com/username/photos to be taken to view the users photos and http://example.com/username/blog to view the blog.
If somebody goes http://example.com/username/something_else I want it to default to the action view for the user specified in <userid> but I can't seem to find any way of doing this.
I could do it like this:
Route::set('profile', '<userid>(/<useraction>)(/)', array(
"userid" => "[a-zA-Z0-9_]+",
"useraction" => "(photos|blog)"
))->defaults(array(
'controller' => 'profile',
'action' => 'index'
))
then in the controller do this:
public function action_index(){
$method = $this->request->param('useraction');
if ($method && method_exists($this, "action_{$method}")) {
$this->{"action_{$method}"}();
} else if ($method) {
// redirect to remove erroneous method from url
} else {
$this->action_view(); // view profile
}
}
(it might be better in the __construct() function but you get the gist of it.)
I'd rather not do that though if there is a better method available (which there really should be)
I think the answer might be in the regex but the following does not work:
$profile_functions = "blog|images";
//(enables the user to view the images / blog)
Route::set('profile', '<id>/<action>(/)', array(
"id" => "[a-zA-Z0-9_]+",
"action" => "($profile_functions)",
))->defaults(array(
'controller' => 'profile'
));
Route::set('profile_2', '<id>(<useraction>)', array(
"id" => "[a-zA-Z0-9_]+",
"useraction" => "(?!({$profile_functions}))",
))->defaults(array(
'controller' => 'profile',
'action' => 'view'
));
although it does match when nothing is after the ID.
I would set up the route like this:
Route::set('profile', '<userid>(/<action>)(/)', array(
"userid" => "[a-zA-Z0-9_]+",
"action" => "[a-zA-Z]+"
))->defaults(array(
'controller' => 'profile',
'action' => 'index'
))
And then in the controllers before() method:
if(!in_array($this->request->_action, array('photos', 'blog', 'index')){
$this->request->_action = 'view';
}
Or somethig similiar, just validate the action in the controller...
EDIT:
This could also work:
if(!is_callable(array($this, 'action_' . $this->request->_action))){
$this->request->_action = 'view';
}
I am using CakePHP 1.3. I have a Product model. on the DB table among others there are id and slug fields.
If I have a product that is id:37 and slug:My-Product-Title I want the URL for the product to be:
products/37/My-Product-Title
Instead of the standard:
products/view/37
I created a route that looks like this:
Router::connect(
'/products/:id/:slug',
array('controller' => 'products', 'action' => 'view'),
array('pass' => array('id'), 'id' => '[0-9]+')
);
Now I can go to http://server/products/37/My-Product-Title and it takes me to the right place.
But How do I get reverse routing to automatically build the correct URL in $HtmlHelper->link?
When I use:
echo $html->link(
'Product 37',
array('controller'=>'products', 'action' => 'view', 37)
);
It still outputs the standard products/view/37 url.
I don't believe that it's possible to be done auto-magically. The helper is just an "helper" who builds the link from the given parameters.
So the easiest method is to add another parameter in your link like so:
echo $html->link(
'Product 37',
array('controller'=>'products', 'action' => 'view', 37, $slug)
);
where the $slug is the data from the slug field.
Probably it could be done your idea, but you need to break the MVC pattern very badly :)
Edit:
Reading your question again I understood it well. See how should be done:
in your router.php add the following rule:
Router::connect(
'/product/*',
array('controller' => 'products', 'action' => 'view')
);
Please note that it's /product/* rather than /products/*
Your link should be done like this:
echo $html->link(
'Product 37',
array('controller'=>'products', 'action' => 'view', 37, 'my-product-title')
);
and the link would look like:
http://yourdomain.com/product/37/my-product-title
For me doing your suggestion is bad practice. Also I don't think it's good from SEO point of view redirecting always the user.
For routing:
Router::connect(
'/products/:id/:slug',
array('controller' => 'products', 'action' => 'view'),
array('pass' => array('id'), 'id' => '[0-9]+')
);
Your links should look like this:
echo $html->link(
'Product 37',
array('controller'=>'products', 'action' => 'view', 'id' => 37, 'slug' => 'my-product-title')
);
You have to add additional (key => value) to your array for each :param in your routing. Then magic will work
You should look at the following post regarding custom route classes.
The slug data doesn't need to be involved with the database at all - the field is a fake field used to simplify logic and lookups. This solution allows you to reverse route slugs, without needing a slug field in the models table.
http://42pixels.com/blog/slugs-ugly-bugs-pretty-urls
I am not sure how bad this is but with the following code in the ProductsController:
function view($id)
{
if( isset($_SERVER) && stristr($_SERVER["REQUEST_URI"],'view/') )
{
$this->Product->id = $id;
$slug = $this->Product->field('slug');
$this->redirect($id.'/'.$slug);
}
$data = $this->Product->find('first', array('conditions' => array('Product.id' => $id)));
$this->set("data", $data);
}
If the page is accesses via /view/id it automatically redirects them to the current page using /id/slug
Now I can just use the default link scheme:
echo $html->link(
'Product 37',
array('controller'=>'products', 'action' => 'view', 37)
);
and they will be redirected to the right URL.
Only problem is I am not sure how bad it is to have a redirect happening every time a user visits a product page?