I want to make a phtml file witch shows products from a specific brand.
The url needs to look like:
brands/Subway
brands/Mcdonalds
brands/Lego
brands/Barbie
brands/Duplo
brands/.....
How can i make this url work where the brand is a variable.
I have tried to make a module but in that case I get an url like brands/index/brands/Subway
I also don't want to make a rewrite for every brand (100+)
You can create a custom router in your module to handle routes like brands/Subway.
Here is what you need.
First you need to have the pages working without rewrites.
Let's assume that at this url mysite.com/brands/index/index - you have the brand list
and at the url mysite.com/brands/index/view/id/12 - you have the brand with id 12.
In your config.xml you need to add this inside the <global> tag:
<events>
<controller_front_init_routers>
<observers>
<[namespace]_[module]>
<class>[Namespace]_[Module]_Controller_Router</class>
<method>initControllerRouters</method>
</[namespace]_[module]>
</observers>
</controller_front_init_routers>
</events>
This will add a new router to the existing ones.
Now the router class app/code/local/[Namespace]/[Module]/Controller/Router.php:
<?php
class [Namespace]_[Module]_Controller_Router extends Mage_Core_Controller_Varien_Router_Abstract{
public function initControllerRouters($observer){
$front = $observer->getEvent()->getFront();
$front->addRouter('[module]', $this);
return $this;
}
public function match(Zend_Controller_Request_Http $request){
if (!Mage::isInstalled()) {
Mage::app()->getFrontController()->getResponse()
->setRedirect(Mage::getUrl('install'))
->sendResponse();
exit;
}
$urlKey = trim($request->getPathInfo(), '/');
$check = array();
$parts = explode('/', $urlKey);
if ($parts[0] == 'brands'){//if url key starts with 'brands'
if (!isset($parts[1])){ //if the url is just 'brands' - then redirect to brand list
return false;//do nothing...the request will be handled by the default router and you will see `brands/index/index/`.
}
elseif ($parts[1] == 'index'){//if your controller is named differently change 'index' to your controller name
return false; //do nothing...the request will be handled by the default router and you will see `brands/index/index/`.
}
else {
//if a name is specified
//get the entities with that name.
$collection = Mage::getModel('[module]/[entity]')->getCollection()
//if the field is named differently change 'name' to your field name.
->addAttributeToFilter('name', $parts[1]);
$collection->getSelect()->limit(1);//limit to only one
$itemId = (int)$collection->getFirstItem()->getId();
//redirect to 'brands/index/view/id/$itemId'
$request->setModuleName('brands')
->setControllerName('index') //if your controller is named differently change 'index' to your controller name
->setActionName('view') //if your action is named differently change 'view' to action name
->setParam('id', $itemId);
$request->setAlias(
Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,
$urlKey
);
return true;
}
}
return false;
}
}
That's it. Clear the cache and give it a try.
You could create a rewrite rule in your .htaccess (the one that's in the root directory).
Options +FollowSymLinks
RewriteEngine on
RewriteRule ^brand/(.+)$ /brands/index/brands?brand=$1 [L,R=301,QSA]
With this if your url is:
www.yoursite.com/brands/index/brands?brand=MyBrandName
You'll see:
www.yoursite.com/brand/MyBrandName
Hope it helps!
Related
OK, I think I am probably (hopefully) going to be told I am going about this in the wrong way.
Currently, if I go to root of CI web I call a function which reads a predefined location with map_directory(). I then iterate it out in a view as a simple directory listing.
I then want to click on one of these directories to see what's inside. When I do that I call a different controller function called browse.
So if I click on one link I go to
www.mysite.com/dir1
(which is Routed to www.mysite.com/controller/browse/$1 - where $1 in this instance = dir1).
Now I am presented with a dir listing of dir1. I have configured the links of the displayed listings to now go to:
www.mysite.com/dir1/subdir1 etc.
What I want to do and this might be the bit where I am cheating/going wrong, is capture everything after
www.mysite.com/
and pass it to
www.mysite.com/controller/browser/$1
So example:
www.mysite.com/dir1/subdir1/ => www.mysite.com/controller/browse/"dir1/subdir1"
I know I can't have '"' in there, but that's the bit I am trying to pass to the map_directory() function...so it goes /.$var (where $var would = $1 = "dir1/subdir1".
So far I have tried in CI Routes.php:
$route['(.+)$'] = 'controller/browse/$1';
$route['([a-zA-Z0-9\/]*)'] = 'controller/browse/$1';
$route['(:any)'] = 'controller/browse/$1';
...but they all only ever seem to capture "dir1" never anything beyond that.
I hope that makes sense to someone....
You can just use the special _remap() method in your controller to override the default routing behavior:
class Controller extends CI_Controller
{
protected function browse($path)
{
// ... do something with $path here
}
public function _remap($method, $params = array())
{
if ($method === 'browse')
{
$params = implode('/', $params);
return $this->browse($params);
}
elseif (method_exists($this, $method))
{
return call_user_func_array(array($this, $method), $params);
}
show_404();
}
}
I have made a custom module with layout in magento. its shows customer's information of a particular customer group.
its url is
www.mysite.com/profile/index/index/mayank
Where profile is my modulename, index is controller name, 2nd index is index controller's method and mayank is customer's username (a unique field or attribute).
What i need is.
Make this url like this:-
www.mysite.com/mayank
Firstly, Magento URLs tend to put the field name before the value. So I expect the long URL to actually look like this:
www.mysite.com/profile/index/index/name/mayank
This would allow for a neat retrieval in the controller,
public function indexAction() {
$name = $this->getRequest()->get('name');
}
“Is there anyway we can do like 'if no route found then magento calls our module's controller..?”
I've wrestled with routers before and in my opinion they are unnecessary unless absolutely necessary. When the number of possible URLs is finite then it is easier to save a rewrite in advance. The best time to do this is whenever a username changes.
To listen for a change add this to your module's config.xml:
<global>
<events>
<customer_save_after>
<observers>
<mayank_profile>
<type>singleton</type>
<class>mayank_profile/observer</class>
<method>customerSaveAfter</method>
</mayank_profile>
</observers>
</customer_save_after>
<customer_delete_after>
<observers>
<mayank_profile>
<type>singleton</type>
<class>mayank_profile/observer</class>
<method>customerDeleteAfter</method>
</mayank_profile>
</observers>
</customer_delete_after>
</events>
</global>
In your module's Model folder create Observer.php:
class Mayank_Profile_Model_Observer {
public function customerSaveAfter(Varien_Event_Observer $observer) {
$customer = $observer->getCustomer();
// username is not a standard attribute, hopefully you have created it
if ($customer->dataHasChangedFor('username')) {
$this->remove($customer->getOrigData('username'));
$this->add($customer->getData('username'));
}
}
public function customerDeleteAfter(Varien_Event_Observer $observer) {
$customer = $observer->getCustomer();
$this->remove($customer->getOrigData('username'));
}
protected function remove($username) {
// this does nothing if rewrite doesn't exist
Mage::getModel('core/url_rewrite')
->loadByIdPath('profile/'.$username);
->delete();
}
protected function add($username) {
if ($this->isValidUsername($username)) {
$storeId = Mage::app()->getDefaultStoreView()->getId();
Mage::getModel('core/url_rewrite')
->setIsSystem(0)
->setStoreId($storeId)
// IdPath is used by remove() function
->setIdPath('profile/'.$username)
// TargetPath is your controller/action
->setTargetPath('profile/index/index/name/'.$username)
// RequestPath is what the browser sees
->setRequestPath($username)
->save();
}
}
protected function isValidUsername($username) {
// feel free to add more checks here
// start with existing rewrites
$rewrite = Mage::getModel('core/url_rewrite')
->loadByRequestPath($username);
if (!$rewrite->isObjectNew()) {
return false;
}
// scan known frontNames
$frontNames = Mage::getConfig()->getXPath('*/routers/*/args/frontName');
foreach ($frontNames as $name) {
if ($username == (string) $name) {
return false;
}
}
return true;
}
}
It's quite a bit of work but the advantage here is the following code correctly looks up the path earlier saved as "RequestPath":
echo Mage::getUrl('profile', array(
'name' => $username,
'_use_rewrite' => true
));
Here is a much simpler way which only works for URLs that cannot clash with other modules. Add this to your config.xml:
<global>
<rewrite>
<mayank_profile>
<from><![CDATA[#^/profile/#]]></from>
<to><![CDATA[/profile/index/index/name/]]></to>
<complete>1</complete>
</mayank_profile>
</rewrite>
</global>
When outputting an URL you need to concatenate the path yourself. e.g.
echo Mage::getUrl('', array(
// Use _direct here to avoid a trailing solidus
'_direct' => 'profile/' . $username
));
You can try to solve this with a htaccess rewriterule
Or
create an observer on controller_action_layout_load_before and fetch the REQUEST_URI and use it for your case.
Good luck
My current router / FrontController is setup to dissect URL's in the format:
http://localhost/controller/method/arg1/arg2/etc...
However, I'm not sure how to get certain requests to default to the IndexController so that I can type:
http://localhost/contact
or
http://localhost/about/portfolio
Instead of:
http://localhost/index/contact
or
http://localhost/index/about/portfolio
How is this accomplished?
<?php
namespace framework;
class FrontController {
const DEFAULT_CONTROLLER = 'framework\controllers\IndexController';
const DEFAULT_METHOD = 'index';
public $controller = self::DEFAULT_CONTROLLER;
public $method = self::DEFAULT_METHOD;
public $params = array();
public $model;
public $view;
function __construct() {
$this->model = new ModelFactory();
$this->view = new View();
}
// route request to the appropriate controller
public function route() {
// get request path
$basePath = trim(substr(PUBLIC_PATH, strlen($_SERVER['DOCUMENT_ROOT'])), '/') . '/';
$path = trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/');
if($basePath != '/' && strpos($path, $basePath) === 0) {
$path = substr($path, strlen($basePath));
}
// determine what action to take
#list($controller, $method, $params) = explode('/', $path, 3);
if(isset($controller, $method)) {
$obj = __NAMESPACE__ . '\\controllers\\' . ucfirst(strtolower($controller)) . 'Controller';
$interface = __NAMESPACE__ . '\\controllers\\' . 'InterfaceController';
// make sure a properly implemented controller and corresponding method exists
if(class_exists($obj) && method_exists($obj, $method) && in_array($interface, class_implements($obj))) {
$this->controller = $obj;
$this->method = $method;
if(isset($params)) {
$this->params = explode('/', $params);
}
}
}
// make sure we have the appropriate number of arguments
$args = new \ReflectionMethod($this->controller, $this->method);
$totalArgs = count($this->params);
if($totalArgs >= $args->getNumberOfRequiredParameters() && $totalArgs <= $args->getNumberOfParameters()) {
call_user_func_array(array(new $this->controller, $this->method), $this->params);
} else {
$this->view->load('404');
}
}
}
You can use your URLs by one of two methods:
Establish the controllers the way your routing defines them
example.com/contact => Have a "contact" controller with default or index action
example.com/about/portfolio => Have an "about" controller with a "portfolio" action
Because your currently available routing says your URL is treated like "/controller/method", there is no other way.
Establish dynamic routing to allow multiple URLs to be handled by a single controller
Obviously this needs a bit of configuration because one cannot know which URLs are valid and which one should be redirected to the generic controller, and which ones should not. This is somehow a replacement for any of the rewriting or redirecting solutions, but as it is handled on the PHP level, change might be easier to handle (some webserver configurations do not offer .htaccess because of performance reasons, and it generally is more effort to create these).
Your configuration input is:
The URL you want to be handled and
The controller you want the URL passed to, and it's action.
You'll end up having an array structure like this:
$specialRoutes = array(
"/contact" => "IndexController::indexAction",
"/about/portfolio" => "IndexController::indexAction"
);
What's missing is that this action should get the current URL passed as a parameter, or that the path parts become designated parameters within your URL schema.
All in all this approach is a lot harder to code. To get an idea, try to look at the routing of common MVC frameworks, like Symfony and Zend Framework. They offer highly configurable routing, and because of this, the routing takes place in multiple classes. The main router only reads the configuration and then passes the routing of any URL to the configured routers if a match is detected.
Based on your code snippet I'd do it like this (pseudo php code):
$handler = get_controller($controller);
if(!$handler && ($alias = lookup_alias($path))) {
list($handler, $method) = $alias;
}
if(!$handler) error_404();
function lookup_alias($path) {
foreach(ALL_CONTROLLERS as $controller) {
if(($alias = $controller->get_alias($path))) {
return $alias;
}
}
return null;
}
So basically in case there is no controller to handle a certain location you check if any controller is configured to handle the given path as an alias and if yes return that controller and the method it maps to.
You can create a rewrite in your webserver for these exceptions. For example:
RewriteRule ^contact$ /index/contact
RewriteRule ^about/portfolio$ /about/portfolio
This will allow you to have simplified URLs that map to your regular structure.
You could have a dynamic rule if you are able to precisely define what should be rewritten to /index. For example:
RewriteRule ^([a-z]+)$ /index/$1
Try this dynamic htaccess rewrite rule:
RewriteRule ^(.+)/?$ /index/$1 [QSA]
The QSA flag in the above rule allows you to also add a query string to the end if you want, like this:
http://localhost/contact?arg1=1&arg2=2
EDIT: This rule would also handle cases such as /about/portfolio:
RewriteRule ^(.+)/?(.+)?$ /index/$1 [QSA]
I created a module and there exists a default controller inside that. Now I can access the index action (default action) in the default controller like /mymodule/. For all other action i need to specify the controller id in the url like /mymodule/default/register/ . I would like to know is it possible to eliminate the controller id from url for the default controller in a module.
I need to set url rule like this:
before beautify : www.example.com/index.php?r=mymodule/default/action/
after beautify : www.example.com/mymodule/action/
Note: I want this to happen only for the default controller.
Thanks
This is a little tricky because the action part might be considered as a controller or you might be pointing to an existing controller. But you can get away with this by using a Custom URL Rule Class. Here's an example (I tested it and it seems to work well):
class CustomURLRule extends CBaseUrlRule
{
const MODULE = 'mymodule';
const DEFAULT_CONTROLLER = 'default';
public function parseUrl($manager, $request, $pathInfo, $rawPathInfo)
{
if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
// Make sure the url has 2 or more segments (e.g. mymodule/action)
// and the path is under our target module.
if (count($matches) != 4 || !isset($matches[1]) || !isset($matches[3]) || $matches[1] != self::MODULE)
return false;
// check first if the route already exists
if (($controller = Yii::app()->createController($pathInfo))) {
// Route exists, don't handle it since it is probably pointing to another controller
// besides the default.
return false;
} else {
// Route does not exist, return our new path using the default controller.
$path = $matches[1] . '/' . self::DEFAULT_CONTROLLER . '/' . $matches[3];
return $path;
}
}
return false;
}
public function createUrl($manager, $route, $params, $ampersand)
{
// #todo: implement
return false;
}
}
I'm trying to implement canonical URLs and combine it with custom route-classes.
The URL-scheme is something like this:
/category-x/article/123
/category-y/article/123
I create a custom route-class extending Zend_Controller_Router_Route_Regex and checks that the article 123 exists and that the URL includes the correct category-name. If article 123 belongs in category-x and the user is accessing category-y I want to redirect to the correct URL.
But the routes does not have any obvious possibility to do this directly. What's the best practice approach here?
I often do this in my action controller. Something like this...
// assuming GET /category-y/article/123
// $article->url is generated, and contains /category-x/article/123
if (this->_request->getRequestUri() != $article->url) {
return $this->_helper->redirector->goToUrl($article->url);
}
In this example, $article->url would need to be generated from your database data. I often use this to verify a correct slug, when I also pull in the object id.
You could also potentially move this to your routing class, if you wanted to use a custom one instead of using Regex (you could subclass it).
I ended up with this solution:
The custom route-class creates the canonical URL in its match()-method like this:
public function match($path, $partial = false) {
$match = parent::match($path, $partial);
if (!empty($match)) {
$article = $this->backend->getArticle($match['articleId']);
if (!$article) {
throw new Zend_Controller_Router_Exception('Article does not exist', 404);
}
$match['canonicalUrl'] = $this->assemble(array(
'title' => $article->getTitle(),
'articleId' => $article->getId()
));
}
return $match;
}
$article is populated inside match() if the parent::match() returns array.
I've created a front controller plugin which hooks on the routeShutdown() like this:
public function routeShutdown(Zend_Controller_Request_Abstract $request) {
if ($request->has('canonicalUrl')){
$canonicalUrl = $request->getBaseUrl() . '/' . $request->get('canonicalUrl');
if ($canonicalUrl != $request->getRequestUri()) {
$this->getResponse()->setRedirect($canonicalUrl, 301);
}
}
}
It simply checks if the route(custom or native Zend) created a canonical URL and if the requested URL does not match, redirect to the correct canonical URL.