Magento 404 error on language store specific URLs - magento

I have a magento(enterprise) site with 2 language stores. Each having their own dedicated url to a given item or resource be it a static page or a product page.
I am using the URL rules via the CMS to manage my SEF URLs for all resources.
The problem is the following scenario:
Site defaults to LANG #1.
When user switches from LANG#1 to LANG#2, switch happens with no issues - content switches to specific lang (_store=lang">http://www.sitename.com/?_store=lang)
But regardless of what lang store I am in, if I have enter a url from the other lang store into my current language store, I get a 404 error.
What I want the system checking the current store for the resource requested. If not found it should route to the next store and check for the resource in there. If found, store should switch to the lang store the item was found and url redirected.
What class should I extend in order to achieve this ( i am quite new to magento).
I went as far as inspecting if I can extend this class to do what I want: /app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php
But not sure if I am in the right location for such requirements.
Any help will be appreciated!
Thanks

I was able to sort this issue out. All I did was extend the /app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php file.
So I overode the loadByRequestPath function to serve the request in the current store, if not found, get a list of all the other available stores, loop through and find if the item exists. If found redirect to the store in which the URL key exists for the product and whola.
If none of the stores have it THEN return the 404 error.
And for static pages, the URL rewrite manager will be able to sort out the issue for you when you switch stores/languages.
I hope this helps someone!

I don't think mine is the most elegant of the solutions but I will post it anyway as it worked for me.
Basically I am searching in all stores the path that I am looking for in stead of only the specified store. Any suggestion would be more than welcome.
My config.xml
<?xml version="1.0"?>
<config>
<global>
<modules>
<Soipo_UrlRewrite>
<version>0.1</version>
</Soipo_UrlRewrite>
</modules>
<models>
<soipo_urlrewrite>
<class>Soipo_UrlRewrite_Model</class>
</soipo_urlrewrite>
<core_mysql4>
<rewrite>
<url_rewrite>Soipo_UrlRewrite_Model_Mage_Core_Model_Mysql4_Url_Rewrite</url_rewrite>
</rewrite>
</core_mysql4>
</models>
</global>
</config>
Rewrite.php
<?php
class Soipo_UrlRewrite_Model_Mage_Core_Model_Mysql4_Url_Rewrite extends Mage_Core_Model_Mysql4_Url_Rewrite{
/**
* This function get an array of store ids, containing the Admin store.
* #return array
*/
public function getStoreIds(){
$allStores = Mage::app()->getStores();
$storeIds = array();
$storeIds[] = Mage_Core_Model_App::ADMIN_STORE_ID;
foreach ($allStores as $_eachStoreId => $val)
{
$_storeId = Mage::app()->getStore($_eachStoreId)->getId();
$storeIds[] = $_storeId;
}
return $storeIds;
}
/**
* Load rewrite information for request
* If $path is array - we must load all possible records and choose one matching earlier record in array
*
* #param Mage_Core_Model_Url_Rewrite $object
* #param array|string $path
* #return Mage_Core_Model_Resource_Url_Rewrite
*/
public function loadByRequestPath(Mage_Core_Model_Url_Rewrite $object, $path)
{
if (!is_array($path)) {
$path = array($path);
}
$pathBind = array();
foreach ($path as $key => $url) {
$pathBind['path' . $key] = $url;
}
$storeIds = $this->getStoreIds();
// Form select
$adapter = $this->_getReadAdapter();
$select = $adapter->select()
->from($this->getMainTable())
->where('request_path IN (:' . implode(', :', array_flip($pathBind)) . ')')
->where('store_id IN(?)', $storeIds);
$items = $adapter->fetchAll($select, $pathBind);
// Go through all found records and choose one with lowest penalty - earlier path in array, concrete store
$mapPenalty = array_flip(array_values($path)); // we got mapping array(path => index), lower index - better
$currentPenalty = null;
$foundItem = null;
foreach ($items as $item) {
if (!array_key_exists($item['request_path'], $mapPenalty)) {
continue;
}
$penalty = $mapPenalty[$item['request_path']] << 1 + ($item['store_id'] ? 0 : 1);
if (!$foundItem || $currentPenalty > $penalty) {
$foundItem = $item;
$currentPenalty = $penalty;
if (!$currentPenalty) {
break; // Found best matching item with zero penalty, no reason to continue
}
}
}
// Set data and finish loading
if ($foundItem) {
$object->setData($foundItem);
}
// Finish
$this->unserializeFields($object);
$this->_afterLoad($object);
return $this;
}
}

Related

Magento Product Page redirection

Which controller in Magento do I need to override if I want to redirect a certain product view page to another url?
I'd like to use the following code for redirection. Is there any more Magento-like way of doing it?
if($this->getProductId() == 10) {
header("Location: /mymodule");
die();
}
Magento Admin has a feature for this under Catalog > URL Rewrite Management. Create a new product rewrite with these steps:
Click the Add URL Rewrite button at top right.
Select “Custom” from Create URL Rewrite.
Choose the appropriate Store.
Set Request Path to something like this catalog/product/view/id/10 (assuming 10 is the ID of your product).
Set Target Path to whatever route or URL you want to send the request to.
Set Redirect to probably “Temporary (302)”.
Found a solution. Might not be the best one but it works:
Observer.php:
class Namespace_Mymodule_Model_Observer {
public function redirect(){
//request object
$request = Mage::app()->getRequest();
//only redirect in the frontend/catalog
$storeId = Mage::app()->getStore()->getId();
if(in_array($request->getModuleName(), array("catalog")) && $storeId != 0) {
$response = Mage::app()->getResponse();
$params = Mage::app()->getRequest()->getParams();
//check the product id
if($params[id] == 10) {
return $response->setRedirect("/mymodule", 301)->sendHeaders();
}
}
}
}
And the config.xml in the global/events section:
<controller_action_predispatch>
<observers>
<namespace_mymodule_observer>
<type>singleton</type>
<class>namespace_mymodule/observer</class>
<method>redirect</method>
</namespace_mymodule_observer>
</observers>
</controller_action_predispatch>
I believe a better approach is to actually utilize the observer event since all you need is included here already. Also, the answer does not differentiate between products and categories since in_array($request->getModuleName(), array("catalog")) would be true for both.
class Namespace_Mymodule_Model_Observer {
public function redirect(Varien_Event_Observer $observer) {
$actionName = $observer->getEvent()->getControllerAction()->getFullActionName();
$productId = $observer->getEvent()->getControllerAction()->getRequest()->getParam('id');
if($actionName == 'catalog_product_view' && $productId == 10) {
$response = $observer->getEvent()->getControllerAction()->getResponse();
$response->setRedirect('/mymodule', 301)->sendHeaders();
}
}
}

Prevent automatic URL rewrites created that include category URL key in product URL

Whenever I create a new product, Magento automatically creates unnecessary URL rewrites that include each category and subcategory combination, that use the categories' URL keys in the product path. For example, for a product product-name with the categories:
category
category > subcategory
category > subcategory > third
...Magento will automatically create URL rewrites with the following request paths:
/category/product-name
/category/subcategory/product-name
/category/subcategory/third/product-name
...as well as also creating the in-use URL rewrite with request path:
/product-name
My question is, even though I have the setting Use Categories Path for Product URLs set to No in:
System > Configuration > Catalog > Search Engine Optimizations
...how do I stop these additional URL rewrites from being created automatically?
Now, again, I realize that the site isn't linking to these additional paths anywhere on the site, but if for some reason a search engine picked up:
http://example.com/category/subcategory/third/product-name
...this would load! I'm nervous this will result in duplicate content being indexed by search engines. Since the Use Categories Path for Product URLs setting is set to No, and all links to the product on the site are pointing to:
http://example.com/product-name
...I want to stop Magento from creating these unnecessary URL rewrites automatically.
For reference, I tried truncating the core_url_rewrite table to zero (basically emptying it out) and reindexing the Catalog URL Rewrites in System > Index Management. This still results in Magento automatically creating these unnecessary URL rewrites.
Also, for reference, I am using Magento Community 1.9.1.
Please advise! Your help is much appreciated.
Its not only about canonical links the problem is mainly another: crawling budget. You dont want to waste your crawling budget so the unnecessary urls need to go.
You should modify every entry in core_url_rewrite by shell script which:
is_system = 1
product_id not null
category_id not null
there you set:
target_path = direct product url
options = RP
Now you created 301 redirects to the real page and only have one problem left:
If a product has no category-product-urls no other urls will be created if the feature is turned off via backend config settings, this is what we want.
But if a product yet has category-product-urls and you add this product to a category still a new category-product-url would be created. So you need to change one method by rewriting/extending Mage_Catalog_Model_Url :
/**
* Refresh product rewrite
*
* #param Varien_Object $product
* #param Varien_Object $category
* #return Mage_Catalog_Model_Url
*/
protected function _refreshProductRewrite(Varien_Object $product, Varien_Object $category)
{
//FIX: DONT ADD CATEGORY-PRODUCT-URL - MIGHT HAPPEN IF CATEGORY-PRODUCT-URL EXIST YET FOR THIS PRODUCT
if (Mage::getStoreConfigFlag('catalog/seo/product_use_categories')) {
if ($category->getId() && $product->getId()) {
return $this;
}
}
parent::_refreshProductRewrite($product, $category);
}
I suggest that rather than trying to disable this built-in functionality, instead turn on canonical links. If you have an older version of Magento without this option, there are other ways to implement it.
However, if one were still inclined to remove it, they could probably create an extension that extends Mage_Catalog_Model_Url to do something like this:
class My_Catalog_Model_Url extends Mage_Catalog_Model_Url
{
public function refreshProductRewrite($productId, $storeId = null)
{
if (is_null($storeId)) {
foreach ($this->getStores() as $store) {
$this->refreshProductRewrite($productId, $store->getId());
}
return $this;
}
$product = $this->getResource()->getProduct($productId, $storeId);
if ($product) {
$store = $this->getStores($storeId);
$storeRootCategoryId = $store->getRootCategoryId();
// List of categories the product is assigned to, filtered by being within the store's categories root
// CUSTOMIZATION: Ignore product categories if the 'catalog/seo/product_use_categories' config setting is false.
if (Mage::getStoreConfigFlag('catalog/seo/product_use_categories', $storeId)) {
$categories = $this->getResource()->getCategories($product->getCategoryIds(), $storeId);
} else {
$categories = array();
}
$this->_rewrites = $this->getResource()->prepareRewrites($storeId, '', $productId);
// Add rewrites for all needed categories
// If product is assigned to any of store's categories -
// we also should use store root category to create root product url rewrite
if (!isset($categories[$storeRootCategoryId])) {
$categories[$storeRootCategoryId] = $this->getResource()->getCategory($storeRootCategoryId, $storeId);
}
// Create product url rewrites
foreach ($categories as $category) {
$this->_refreshProductRewrite($product, $category);
}
// Remove all other product rewrites created earlier for this store - they're invalid now
$excludeCategoryIds = array_keys($categories);
$this->getResource()->clearProductRewrites($productId, $storeId, $excludeCategoryIds);
unset($categories);
unset($product);
} else {
// Product doesn't belong to this store - clear all its url rewrites including root one
$this->getResource()->clearProductRewrites($productId, $storeId, array());
}
return $this;
}
}

Magento 1.9 - Checkbox per product in cart - Save to quote

I like to add checkboxes for every product in the cart, so the client can decide wich product (balloons) he want to befill with helium. I'm unable to save the infomation to quote respectively to the sales_flat_order.. tables.
I followed this tutorial: http://www.atwix.com/magento/custom-product-attribute-quote-order-item/
By clicking on "Refrech Cart" the function _updateShoppingCart() from my CartController is called.
protected function _updateShoppingCart()
{
$cartData = $this->getRequest()->getParam('cart');
if (is_array($cartData)) {
$cart = $this->_getCart();
$quote = $cart->getQuote();
foreach ($cartData as $index => $data) {
if (isset($data['qty'])) {
$cartData[$index]['qty'] = $data['qty'];
}
if (isset($data['ballo_add_gas']) && $data['ballo_add_gas'] == 1) {
$cartData[$index]['ballo_add_gas'] = $data['ballo_add_gas'];
}
}
$cartData = $cart->suggestItemsQty($cartData);
MAGE::log($cartData);
$cart->updateItems($cartData)
->save();
}
$this->_getSession()->setCartWasUpdated(true);
}
This function is called, properly. The Informations in $cartData are according to the log file are:
[70] => Array
(
[qty] => 5
[ballo_add_gas] => 1
[before_suggest_qty] => 5
)
So that's ok. If I set a fix value for qty in this function , everything works as espected-> the cart is updated with the fix quantity. So my direction should be ok.
Unfortunately the ballo_add_gas value isn't saved to quote eg. database.
I've tried to save the value in app\code\local\Mage\Checkout\Model\Cart.php in the function updateItems($data).
$item->setData('ballo_add_gas', $itemInfo['ballo_add_gas']);
But nothing happens.
Next, in Gas\Model\Observer.php I've created an function.
public function salesQuoteItemSetCustomAttribute($observer)
{
$quoteItem = $observer->getQuoteItem();
$product = $observer->getProduct();
$quoteItem->setBallo_add_gas($product->getBallo_add_gas());
//$quoteItem->setBallo_add_gas(1); THIS WORKS
}
This function should copy the informations. See config.xml
<sales_quote_item_set_product>
<observers>
<XXX_Gas_Model_Observer>
<class>Mexan_Gas_Model_Observer</class>
<method>salesQuoteItemSetCustomAttribute</method>
</XXX_Gas_Model_Observer>
</observers>
</sales_quote_item_set_product>
</events>
And again config.xml
<fieldsets>
<sales_convert_quote_item>
<ballo_add_gas>
<to_order_item>*</to_order_item>
</ballo_add_gas>
</sales_convert_quote_item>
<sales_convert_order_item>
<ballo_add_gas>
<to_quote_item>*</to_quote_item>
</ballo_add_gas>
</sales_convert_order_item>
</fieldsets>
When I change $product->getBallo_add_gas() to a fixed value 1 everything works fine. So I assume when I'm able to save the value in _updateShoppingCart() properly. Than everything should be ok.
By the way. I've created the column ballo_add_gas in the database tables sales_flat_order_item and sales_flat_quote_item.
--
Another approach I followed was saving the Information directly into the DB.
private function saveGas($item_id, $gas)
{
$cartItem = Mage::getSingleton('checkout/cart')->getQuote()->getItemById($item_id);
$cartItem->setBalloAddGas($gas);
$cartItem->save();
$sales_flat_quote_item_table = Mage::getSingleton('core/resource')->getTableName('sales_flat_quote_item');
$db = Mage::getSingleton('core/resource')->getConnection('core_write');
$qry = "UPDATE " . $sales_flat_quote_item_table . " SET ballo_add_gas=" . $gas . " WHERE item_id=".$item_id;
MAGE::log($qry);
$db->query($qry);
}
The information was saved to the DB, but disappeared after reloading the cart, and well there should be an easier way?
Any ideas?
Thanks!

Magento Admin Create Order not showing Custom Options

I have simple products with custom options in my store. They work perfectly from the front end, but if I try to add an order from the admin section, The custom options do not show up.
I only have this problem if the type of custom option is a dropdown, multi select, radio buttons, or check boxes. If it is a text field, date or anything else, it works fine.
I am assumming i need to make some changes to something in the /www/app/design/adminhtml/default/default/template/sales/order/create area, but no clue what i should try.
Looking a bit further, I found this /www/app/code/core/Mage/Adminhtml/Block/Sales/Order/Create/Items/grid.php
/**
* Get Custom Options of item
*
* #param Mage_Sales_Model_Quote_Item $item
* #return array
*/
public function getCustomOptions(Mage_Sales_Model_Quote_Item $item)
{
$optionStr = '';
$this->_moveToCustomerStorage = true;
if ($optionIds = $item->getOptionByCode('option_ids')) {
foreach (explode(',', $optionIds->getValue()) as $optionId) {
if ($option = $item->getProduct()->getOptionById($optionId)) {
$optionValue = $item->getOptionByCode('option_' . $option->getId())->getValue();
$optionStr .= $option->getTitle() . ':';
$quoteItemOption = $item->getOptionByCode('option_' . $option->getId());
$group = $option->groupFactory($option->getType())
->setOption($option)
->setQuoteItemOption($quoteItemOption);
$optionStr .= $group->getEditableOptionValue($quoteItemOption->getValue());
$optionStr .= "\n";
}
}
}
return $optionStr;
}
The best way to find the correct template path is to turn on admin template hints.
By default magento does not provide a way to accomplish from the admin, but you can easily accomplish this using one of these methods Enable template path hint in admin pages - Magento

Magento - How I can Run Store by Country by GeoIP?

I want run store by IP of the customer.
In the backend of Magento, the user may configure the concret Store to load per country.
Taking a glance, I see the method at class Mage_Core_Model_App
public function run($params)
{
$options = isset($params['options']) ? $params['options'] : array();
$this->baseInit($options);
if ($this->_cache->processRequest()) {
$this->getResponse()->sendResponse();
} else {
$this->_initModules();
$this->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS);
if ($this->_config->isLocalConfigLoaded()) {
//$scopeCode = isset($params['scope_code']) ? $params['scope_code'] : '';
//===============custom scope by country======================
$scopeCode = Mage::helper('custom/module')->getStoreByGeoip();
//===============custom scope by country======================
$scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store';
$this->_initCurrentStore($scopeCode, $scopeType);
$this->_initRequest();
Mage_Core_Model_Resource_Setup::applyAllDataUpdates();
}
$this->getFrontController()->dispatch();
}
return $this;
}
In my progress to get a good solution, I thought another alternative.
In the index.php write the next code:
Mage::app();
Mage::Helper('custom/helper')->getRunCodeByGeoio();
Mage::run($mageRunCode, $mageRunType);
I thinks this haven´t dangerous of performance because this method only create object if you not have before
/**
* Get initialized application object.
*
* #param string $code
* #param string $type
* #param string|array $options
* #return Mage_Core_Model_App
*/
public static function app($code = '', $type = 'store', $options = array())
{
if (null === self::$_app) {
self::$_app = new Mage_Core_Model_App();
self::setRoot();
self::$_events = new Varien_Event_Collection();
self::$_config = new Mage_Core_Model_Config();
Varien_Profiler::start('self::app::init');
self::$_app->init($code, $type, $options);
Varien_Profiler::stop('self::app::init');
self::$_app->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS);
}
return self::$_app;
}
And my question is......
Are this the best approach for get the solution??
I think is very dangerous modify Mage_Core_Model_App even using rewrite
I don´t have any event at tier
Another option is made the business in the index.php but lost the management by the backend
Searching...., found a extension that cover many of my requirements,
http://www.mageworx.com/store-and-currency-auto-switcher-magento-extension.html
then I'll buy this or made a similar extension.
You shyould never touch any core files when developing with Magento, or any other application if you can avoid it.
Doing this will mean possible future upgrades will overwrite your changes and break your store.
The simplest way would be to do everything index.php as this is the entry point where the store is selected anyway, all you are doing is selecting the store on different criteria (ie IP address).
One simple way would be to use a free library, such as maxmind GeoLite: http://dev.maxmind.com/geoip/geolite
You can either load an apache module, or via a pecl extensions, or even plain PHP.
This will return you the iso country code for the country of your visitor.
You could then name your stores with a country iso code for the store code, and this will make it really simple to load the correct store depending on IP
something simple like this:
$countryCode = getUsersCountryCode(); // which ever method you use in here...
$stores = array(
'gb',
'us',
'fr',
);
if(in_array(countryCode, $stores)) {
Mage::run(countryCode, 'store');
}
else {
Mage::run($mageRunCode, $mageRunType);
}
You could of course make it into a Magento extensions, but this is by far the simplest way. You could even get a list of the countries/stores from Magento rather than hard coding them if you required.

Resources