I have set up friendly URLs for a few search result pages, using a custom route for each:
friendly_search_resultpage:
url: /products/my-friendly-alias
param:
module: products
action: search
querystring: searchattribute
querystring2: searchattribute2
This works fine, but when doing the search directly (i.e. browsing to /products/search?querystring=search...) I want to set a <link rel="canonical"> containing the matching friendly URL. This will help Google understand the relation and that there isn't duplicate content.
I put my friendly URL route at the top of routes.yml and hoped for a magic match, but URL parameters aren't recognised in the checking done by symfony. I have dug into sfRoute, with no luck. Is it possible?
I ended up writing a custom routing class to use for these routes, which is executed when url_for() is called. Here is the code:
<?php
class mySearchFriendlyRoute extends sfRoute
{
public function matchesParameters($params, $context = array())
{
// I can't find symfony sorting parameters into order, so I do
// (so that foo=x&bar=y is treated the same as bar=y&foo=x)
ksort($params);
$mine = $this->defaults;
ksort($mine);
if ($params == $mine) {
return true;
}
return false;
}
}
To use, add class: mySearchFriendlyRoute to the routes.yml entry.
Try to use Your own Route:
friendly_search_resultpage:
class: YourRouteClassName
...
And overload sfRoute::generate() there by concrete cannonicalization (return the canonical URL).
This redirects with 301 in my project upon of last sf1.4 revision.
Related
Im trying to get the layered navigation of a category links from a php that is outside magento.
I can create all of the category html, but the problem is that the layered links are created with the params of frontcontroller (at the same way of the toolbar links).
If i have a filter selected the creation of layered links doesnt take account of it, and also the layered links havent the category url...
I try to recreate the frontcontroller of the category page inside magento on my php ouside that, but i haven success... Even i recreate the $_SERVER, but the controller seems to not find a router...
In the php if i use mage::run, it do the operation of calcule correctly but mage::run makes the response and isnt i want because i need an xml output only of layered navigation.
If i use mage::app i can get the category html, but the controler isnt calculated correctly although is the same $_SERVER[request_uri] and havent correct links. The front controller havent action...
In the frontcontroller request i see two differences: in magento dispathed is true, but in the php no, and in magento de request_uri are rewrited to catalog/category/view/id/7?color=99 while in the php not hombre.html?color=99
Im missing anything, i need to initialize the front controller? o reinitialize???
Or there a different way to get the layered navigation from outside magento??
I found the problem and the solution...
On start magento it calls to rewrite the URL with the aim to get converted friendly url on a route url...
hombre.html?color=99 is converted on catalog/category/view/id/7?color=99
So the first is to call Mage::getModel('core/url_rewrite')->rewrite(); and now our frontcontroller request have the URL converted correctly.
Before that the frontendcontroller is inited executing the function match in all of its routers to try to find a controller and an action...
$frCont = Mage::app()->getFrontController();
foreach ($frCont->getRouters() as $router) {
if ($router->match($frCont->getRequest())) {
break;
}
}
With that two steps i have the frontcontroller initialized like if i access to the url "hombre.html?color=99" but in fact im on another php in my services folder.
But the match function after initialize the frontcontroller and get the router, action, and route, dispatch the action by default, so it generate all the html output and cannot work with layouts... So i create a local copy of the class Mage_Core_Controller_Varien_Router_Standard and i have updated the function match:
public function match(Zend_Controller_Request_Http $request){
.
.
.
$controllerInstance->dispatch($action);
return true;
}
for this one
public function match(Zend_Controller_Request_Http $request, $dispatchAction = true){
.
.
.
if($dispatchAction==true){
$controllerInstance->dispatch($action);
}
return true;
}
So in my service php i have that code:
Mage::getModel('core/url_rewrite')->rewrite();
$frCont = Mage::app()->getFrontController();
foreach ($frCont->getRouters() as $router) {
if ($router->match($frCont->getRequest(),false)) {
break;
}
}
And i have the frontcontroller initialized and the action,router,params, etc assigned like if i access from the catalog URL but withouth dispatching the action, and then i can write the objects i want with the layout object.
Hope it helps anyone.
I'm working on a Symfony 1.4 project and when I do this:
url_for('');
I'm expecting to get the URL for the index.php controller or at least http://domain/ but I'm getting this:
/sfTCPDF
sfTCPDF is a plugin that I have in this project that in my config/ProjectConfiguration.class.php is used like:
class ProjectConfiguration extends sfProjectConfiguration {
public function setup() {
$this->enablePlugins('sfTCPDFPlugin');
}
}
When I disable the plugin I get the expected result:
$url = url_for('');
> /
Or when I use it like this (Doesn't matter if the plugin is enabled or not):
$url = url_for('/');
> /
I don't understand why the plugin is causing (or even if it's the plugin fault).
Any suggestions? I can search & replace the entire project for url_for('') and put the '/'
But I really want to understand why this is happening.
After some research:
I found out that when the url_for function is called, the procedure is the next:
lib/vendor/symfony/lib/helper/UrlHelper.php url_for()
lib/vendor/symfony/lib/helper/UrlHelper.php url_for2()
lib/vendor/symfony/lib/helper/UrlHelper.php url_for1()
lib/vendor/symfony/lib/controller/sfWebController.class.php gen_url()
lib/vendor/symfony/lib/routing/sfRoute.class.php generate()
And in the last one it gets the $this->pattern here is where the pattern is sfTCPDF/:action
which means that some routing is playing. Continuing my research i found out that the plugin has a routing.yml with:
sfTCPDF:
url: /sfTCPDF/:action
param: { module: sfTCPDF, action: test }
If I delete this routing everything works as expected.
Now the question is: when do the plugin creates a routing object with the pattern of sfTCPDF and why?
I never use the url_for with an empty string.
In my routes file I use to have:
homepage:
url: /
param: { module: home, action: index }
So if I want the route for this, I use:
url_for('#homepage');
url_for uses the route name you provide to find the proper route in the cached routes table. If you provide an empty string it will take the 0 index from the table (which you can find in the file: cache/app/env/config/config_routing.yml.php.
When the TCPDF plugin is on its' route is being added as the first one that's why you get it as the url.
The solution - don't use an empty string for url_for. If you want to get the url of the homepage always use url_for('/') or url_for('#homepage').
Not sure if I properly wrote the subject but anyway.
Since you can create specific routes with different parameters for eg:
_search:
pattern: /page/{category}/{keyword}
defaults: { _controller: Bundle:Default:page, category: 9, keyword: null }
is there any way from a form with GET method to get to that route specific url format?
At the moment the url is like /page?category=2?keyword=some+keyword
As such is not passing to the route format as you may noticed.
What do I need to do to get it working through this specific format? I really have no idea how to rewrite the page url to match the route settings for the specific url. Even in plain php was stumbled on this ...
Thanks in advance.
It's the default behavior of HTML forms with GET method. You will need to build that URL yourself.
Backend way
Drawback: It makes two requests to the server instead of one
Advantage: It's more maintainable because the URL is built using the routing service
Your routing file
_search:
pattern: /page/{category}/{keyword}
defaults: { _controller: Bundle:Default:page, category: 9, keyword: null }
_search_endpoint:
pattern: /page
defaults: { _controller: Bundle:Default:redirect }
Your controller
public function redirectAction()
{
$category = $this->get('request')->query->get('category');
$keyword = $this->get('request')->query->get('keyword');
// You probably want to add some extra check here and there
// do avoid any kind of side effects or bugs.
$url = $this->generateUrl('_search', array(
'category' => $category,
'keyword' => $keyword,
));
return $this->redirect($url);
}
Frontend way
Using Javascript, you can build the URL yourself and redirect the user afterwards.
Drawback: You don't have access to the routing service (although you could use the FOSJsRoutingBundle bundle)
Advantage: You save one request
Note: You will need to get your own query string getter you can find a Stackoverflow thread here, below I'll use getQueryString on the jQuery object.
(function (window, $) {
$('#theFormId').submit(function (event) {
var category, keyword;
event.preventDefault();
// You will want to put some tests here to make
// sure the code behaves the way you are expecting
category = $.getQueryString('category');
keyword = $.getQueryString('keyword');
window.location.href = '/page/' + category + '/' + keyword;
}):
})(window, jQuery);
You could add a second route that will just match /page
then in the controller you can get the defaults. and merge them with any that are passed.
Take a look at a similar question I answered for some code examples.
KendoUI Grid parameters sending to a symfony2 app
I've too encountered this issue and I managed to resolve it with a slightly different solution.
You could also reroute like #Thomas Potaire suggested, but in the same controller, beginning your controller with :
/**
* #Route("/myroute/{myVar}", name="my_route")
*/
public function myAction(Request $request, $myVar = null)
{
if ($request->query->get('myVar') !== null) {
return $this->redirectToRoute('my_route', array(
'myVar' => str_replace(' ','+',$request->query->get('myVar')) // I needed this modification here
));
}
// your code...
}
Can I get the controller action from given URL?
In my project, I will have different layout used for admin and normal users. i.e.
something.com/content/list - will show layout 1.
something.com/admin/content/list - will show layout 2.
(But these need to be generated by the same controller)
I have added filter to detect the pattern 'admin/*' for this purpose. Now I need to call the action required by the rest of the URL ('content/list' or anything that will appear there). Meaning, there could be anything after admin/ it could be foo/1/edit (in which case foo controller should be called) or it could be bar/1/edit (in which case bar controller should be called). That is why the controller name should be generated dynamically from the url that the filter captures,
So, I want to get the controller action from the URL (content/list) and then call that controller action from inside the filter.
Can this be done?
Thanks to everyone who participated.
I just found the solution to my problem in another thread. HERE
This is what I did.
if(Request::is('admin/*')) {
$my_route = str_replace(URL::to('admin'),"",Request::url());
$request = Request::create($my_route);
return Route::dispatch($request)->getContent();
}
I could not find these methods in the documentation. So I hope, this will help others too.
You can use Request::segment(index) to get part/segment of the url
// http://www.somedomain.com/somecontroller/someaction/param1/param2
$controller = Request::segment(1); // somecontroller
$action = Request::segment(2); // someaction
$param1 = Request::segment(3); // param1
$param2 = Request::segment(3); // param2
Use this in your controller function -
if (Request::is('admin/*'))
{
//layout for admin (layout 2)
}else{
//normal layout (layout 1)
}
You can use RESTful Controller
Route:controller('/', 'Namespace\yourController');
But the method have to be prefixed by HTTP verb and I am not sure whether it can contain more url segment, in your case, I suggest just use:
Route::group(array('prefix' => 'admin'), function()
{
//map certain path to certain controller, and just throw 404 if no matching route
//it's good practice
Route::('content/list', 'yourController#yourMethod');
});
There will be several high profile links for customers to focus on, for example:
Contact Us # domain.com/home/contact
About the Service # domain.com/home/service
Pricing # domain.com/home/pricing
How It Works # domain.com/home/how_it_works
Stuff like that. I would like to hide the home controller from the URL so the customer only sees /contact/, not /home/contact/. Same with /pricing/ not /home/pricing/
I know I can setup a controller or a route for each special page, but they will look the same except for content I want to pull from the database, and I would rather keep my code DRY.
I setup the following routes:
Route::get('/about_us', 'home#about_us');
Route::get('/featured_locations', 'home#featured_locations');
Which work well, but I am afraid of SEO trouble if I have duplicate content on the link with the controller in the URL. ( I don't plan on using both, but I have been known to do dumber things.)
So then made routes like these:
Route::get('/about_us', 'home#about_us');
Route::get('/home/about_us', function()
{
return Redirect::to('/about_us', 301);
});
Route::get('/featured_locations', 'home#featured_locations');
Route::get('/home/featured_locations', function()
{
return Redirect::to('/featured_locations', 301);
});
And now I have a redirect. It feels dumb, but it appears to be working the way I want. If I load the page at my shorter URL, it loads my content. If I try to visit the longer URL I get redirected.
It is only for about 8 or 9 special links, so I can easily manage the routes, but I feel there must be a smart way to do it.
Is this even an PHP problem, or is this an .htaccess / web.config problem?
What hell have I created with this redirection scheme. How do smart people do it? I have been searching for two hours but I cannot find a term to describe what I am doing.
Is there something built into laravel 4 that handles this?
UPDATE:
Here is my attempt to implement one of the answers. This is NOT working and I don't know what I am doing wrong.
application/routes.php
Route::controller('home');
Route::controller('Home_Controller', '/');
(you can see the edit history if you really want to look at some broken code)
And now domain.com/AboutYou and domain.com/aboutUs are returning 404. But the domain.com/home/AboutYou and domain.com/home/aboutUs are still returning as they should.
FINAL EDIT
I copied an idea from the PongoCMS routes.php (which is based on Laravel 3) and I see they used filters to get any URI segment and try to create a CMS page.
See my answer below using route filters. This new way doesn't require that I register every special route (good) but does give up redirects to the canonical (bad)
Put this in routes.php:
Route::controller('HomeController', '/');
This is telling you HomeController to route to the root of the website. Then, from your HomeController you can access any of the functions from there. Just make sure you prefix it with the correct verb. And keep in mind that laravel follows PSR-0 and PSR-1 standards, so methods are camelCased. So you'll have something like:
domain.com/aboutUs
In the HomeController:
<?php
class HomeController extends BaseController
{
public function getAboutUs()
{
return View::make('home.aboutus');
}
}
I used routes.php and filters to do it. I copied the idea from the nice looking PongoCMS
https://github.com/redbaron76/PongoCMS-Laravel-cms-bundle/blob/master/routes.php
application/routes.php
// automatically route all the items in the home controller
Route::controller('home');
// this is my last route, so it is a catch all. filter it
Route::get('(.*)', array('as' => 'layouts.locations', 'before' => 'checkWithHome', function() {}));
Route::filter('checkWithHome', function()
{
// if the view isn't a route already, then see if it is a view on the
// home controller. If not, then 404
$response = Controller::call('home#' . URI::segment(1));
if ( ! $response )
{
//didn't find it
return Response::error('404');
}
else
{
return $response;
}
});
They main problem I see is that the filter basically loads all the successful pages twice. I didn't see a method in the documentation that would detect if a page exists. I could probably write a library to do it.
Of course, with this final version, if I did find something I can just dump it on the page and stop processing the route. This way I only load all the resources once.
applicaiton/controllers/home.php
public function get_aboutUs()
{
$this->view_data['page_title'] = 'About Us';
$this->view_data['page_content'] = 'About Us';
$this->layout->nest('content', 'home.simplepage', $this->view_data);
}
public function get_featured_locations()
{
$this->view_data['page_title'] = 'Featured Locations';
$this->view_data['page_content'] = 'Featured properties shown here in a pretty row';
$this->layout->nest('content', 'home.simplepage', $this->view_data);
}
public function get_AboutYou()
{
//works when I return a view as use a layout
return View::make('home.index');
}