I have built a simple portfolio using CakePHP and it has urls like: domain.com/portfolio/82/This_is_an_item
What I want to do is remove the ID from the url. How would I do this?
Here is my controller code for the view:
function view ( $id, $slug )
{
$post = $this->Portfolio->read(null, $id));
$this->set(compact('post'));
}
and here is the link generator:
<?php echo $this->Html->link($post['Portfolio']['title'],
array('admin' => false, 'controller' => 'portfolio', 'action' => 'view', $post['Portfolio']['id'], Inflector::slug($post['Portfolio']['title'])),
array('title' => $post['Portfolio']['title'])); ?>
I'm guessing I need to change the controller method to do some sort of find on the title?
Any help would be much appreciated. Thanks
I would save the slug in the database, along with the title. That way you only have to create it once. Also you may or may not be able to get a unique link from slug to title, so it's best not to try.
For easy processing you can use the Sluggable behaviour, see https://gist.github.com/338096 (or just Google).
Update
If you use the sluggable behaviour from https://gist.github.com/338096 (save as sluggable.php in app/model/behaviors), you would only need to do a few steps:
In your Profile model class add var $actsAs = array('Sluggable'); or
var $actsAs = array(
'Sluggable' => array(
'fields' => 'title',
'scope' => false,
'conditions' => false,
'slugfield' => 'slug',
'separator' => '-',
'overwrite' => false,
'length' => 256,
'lower' => true
)
);
if you want to override settings
In the database, add a column slug in the profiles table.
When you save a profile, it will automagically add fill in the slug field, you do not need to take any special actions.
You can eliminate the id completely, but you'll have to make sure the slugs are unique (specify inUnique validation rule is a option). When saving the post, use Inflector::slug() on the 'title' field (you might want to save it to a 'slug' field, if you want to keep the title intact:
$this->data['Portfolio']['slug'] = Inflector::slug($this->data['Portfolio']['title'])
function view ($slug ){
$post = $this->Portfolio->find('first', array('conditions'=>array('Portfolio.slug'=>$slug))));
$this->set(compact('post'));
}
and for the link:
<?php echo $this->Html->link($post['Portfolio']['title'],
array('admin' => false, 'controller' => 'portfolio', 'action' => 'view', $post['Portfolio']['slug']),
array('title' => $post['Portfolio']['title']));
Related
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']));
I am having problems trying to get my cake application to display items from the database on a single page based on the url of the page... For example I want to display items that are in the "Party" category and I type "mydomain.com/All/party" in the address bar and I get a list of all the items in that category on that page.
This is my code but the pages I am getting from the route is blank:
My routes.php:
Router::connect('/categories/:name', array(
'controller' => 'All', 'action' => 'categories'
),
array(
'pass' => 'catname',
'catname' => '[a-zA-Z0-9]+'
));
my AllController.php:
function categories($catname = null) {
$options = array(
'conditions' => array('Category.name'=>$catname)
);
$Category = $this->Category->find('all', $options);
$this->set('Category', $Category);
$this ->render('/All/categories');
}
Any help would be appreciated.
I don't get at all the code you just posted, but what you want to achieve is quite trivial:
Router::connect(
'/products/:category',
['controller' => 'products', 'action' => 'categorized'],
['pass' => ['category']]
);
In ProductsController:
function categorized($category) {
$conditions = ['Category.name' => $category];
$this->set('products', $this->Prodcut->find('all', compact('conditions')));
}
That's it. You may want to reuse the index action if you don't need a separate view template for that, in that case just set the conditions (for example to the Paginator component) and call $this->setAction('index')
I found a much easier way to do what I wanted. I just remember that I had a database search option that allows persons to search for products and categories. What I did was simply do a search for the categories and then copy the URL of the page I got and set that as a link to the category in my application.
Example code:
<ul class="filter-options">
<li><a style="color:#fff" href="http://mydomain.com/products/search?q=parties">Parties</a></li>
<li><a style="color:#fff" href="http://mydomain.com/products/search?q=festivals">Festivals</a></li>
<li><a style="color:#fff" href="http://mydomain.com/products/search?q=theatres">Cinemas/Theatres</a></li>
Everything looks good on your side just change your routes.php as follows
Router::connect('/categories/:catname', array(
'controller' => 'all',
'action' => 'categories'),
array('pass' => array('catname'),
'catname' => '[a-zA-Z0-9]+'));
I am using Zend\Paginator to construct a paginated result set. This works fine, however, after adding a search form, I cannot get the two to play nicely together.
The URL produced by the search form on the page is:
user/index/?searchTerm=hello
How do I amend the Zend paginator configuration so that it retains the searchTerm in the URLs produced?
I was hoping for something like:
user/index/page/4/?searchTerm=hello
What am I missing?
The module config route is defined as follows:
'user' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/user[/[:action[/]]][[id/:id]][/[page/:page]]',
'defaults' => array(
'controller' => 'Application\Controller\User',
'action' => 'index',
'id' => null,
),
// the below was added to try and get the searchTerm query to be retained
'may_terminate' => true,
'child_routes' => array(
'searchTerm' => array(
'type' => 'Query',
),
),
),
),
The pagination is constructed using this in the view:
echo $this->paginationControl(
$this->users, 'sliding', array('paginator', 'User'), array('route' => 'user', 'action' => 'index')
);
Pagination template snippet:
<li>
<a href="<?php echo $this->url($this->route, array('action' => $this->action, 'page' => $this->next), true); ?>">
Next ยป
</a>
</li>
(I was under the impression that passing true as the third parameter to url() would retain the query params.)
I now see what that third parameter to url() is doing. I can simplify the pagination links and remove the 'action' key as follows:
<a href="<?php echo $this->url($this->route, array('page' => $this->next), true); ?>">
The page's action was matched as part of the URL (due to that third param being true) which is why that works. By the same token I can change the route to this:
'route' => '/user[/[:action[/]]][[id/:id]][/[page/:page]][/[search/:search]]',
And then the search will be retained in the pagination links.
If I amend the search form to submit via JavaScript, I can construct the search URL and direct the user to it.
Simple jQuery example for that approach:
$(".search-form").submit(function() {
var baseUrl = $(this).attr('action'),
search = $(this).find('.search').val();
window.location = baseUrl + '/search/' + search;
return false;
});
Another option would be to redirect to the current/route/search/term route in the controller if it receives a searchTerm query.
I'm posting this as an answer but I am open to better solutions.
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?
I have a table that's been created by a module. I need to include some of its fields into an existing view.
I tried using the table wizard module, but all it does is create a separate view for that table. I'd like to be able to choose fields from that table to be added into an existing view as additional fields, or through relationships or something like that. Is there a workaround to do what I'm trying to do?
Ah. Views. Took me a while as well. This answer is for Drupal 6 and in the abstract shows how to define fields as well as using a relationship to allow the fields to link to the node table.
Inside modulename.module, you want a function that goes:
function modulename_views_api() {
return array(
'api' => 2,
);
}
Then you want to make a file called modulename.views.inc and define a function like this:
function modulename_views_data() {
$data['modulename_table'] = array(
'table' => array(
'group' => 'ModuleName',
'title' => 'Module name title',
),
'join' => array(
// to join to node, we'll use a field in modulename_table called 'nid'
'node' => array(
'left_field' => 'nid',
'field' => 'nid',
),
),
);
// now we define the fields in the table like this
// check out modules/views/handlers to see more specific handlers
$data['modulename_table']['fieldname'] = array(
'title' => 'fieldname',
'help' => 'fieldname description',
'field' => array(
'handler' => 'views_handler_field',
),
);
$data['modulename_table']['nid'] = array(
'title' => 'related node',
'help' => 'the field that relates back to {node}',
// here we implement a relationship to nid
'relationship' => array(
'base' => 'node',
'field' => 'nid',
'handler' => 'views_handler_relationship',
'label' => 'modulename row node',
),
// this relationship can be turned on in views
);
return $data;
}
You can use hook_views_data to define your table in code. As long as you don't want views to do special manipulations, it's almost as simple as defining the table with the schema API.
Your other option is to use table wizard to expose the tables to the database and then use the migrate module to create the views. http://drupal.org/project/migrate
I have found that the Views Custom Field module lets me do just about anything I need as far as adding oddball fields to views .. maybe it'd help ..