Magento 1.7+: How to use the page layout handle - magento

After trying to debug for hours I'm out of ideas and hope for some clarification (I guess I missunderstood a concept at some point).
The backstory: Some base categories need an "overview page" which should be generated automatically from child categories and products. So my approach was to add a sub category to every base category and create a custom page layout which is being used from all these sub categories. For my client this would be very easy to manage in the Magento backend since he would only need to change the value in one drop down. So I created a simple module defining the new page layout. Within the backend I was able to select this one as well.
The module config:
<?xml version="1.0"?>
<config>
<modules>
<Company_Layouts>
<version>0.1.0</version>
</Company_Layouts>
</modules>
<global>
<page>
<layouts>
<company_category_overview module="page" translate="label">
<label>Kategorie-Übersicht</label>
<template>page/1column.phtml</template>
<layout_handle>company_category_overview</layout_handle>
</company_category_overview>
</layouts>
</page>
</global>
<frontend>
<layout>
<updates>
<company_layouts>
<file>company_layouts.xml</file>
</company_layouts>
</updates>
</layout>
</frontend>
</config>
Since these special overview pages require some layout changes I was hoping to reference the layout in a specific layout file (company_layouts.xml)... and here my logic is leaving me:
With <layout_handle>company_category_overview</layout_handle> I was hoping to define a handle which I can use to change the layout only when this specific page template is being used. Exactly this is not the case. My layout updates which are inside the handle company_category_overview are just being ignored.
After digging deeper I realized, it doesn't seem to be my code but more like a general issue. In an old Magento 1.4 installation the page layout handle is being carried to all sites, like page_one_column. In Magento 1.7 and (what I'm using now) 1.8 this is only on the home page the case. I'm using Commerce Bug for debugging. I just tried this with a fresh 1.7 and a freh 1.8 installation.
Is this some concept I don't understand or just a plain bug?
Also, I'm aware that layout updates can be achieved within the backend but this would only be my last option since I feel it's much cleaner having this in a seperate file without the need of copy/pasting such stuff.

Is this some concept I don't understand or just a plain bug?
Both? Neither? The information in the <page><layout>...</layout></page> node is used by both the category pages and CMS pages, but each system uses the the information differently, and neither system uses it in a way you'd expect. Here's a rundown on how category pages use this information.
The category page is rendered by the following controller action
#File: app/code/core/Mage/Catalog/controllers/CategoryController.php
public function viewAction()
{
...
}
This controller action doesn't have the standard loadLayout and renderLayout method calls. Instead, there's a lot of extra code in this method for adding layout handles and doing things between generating the blocks and rendering the final layout. The section we're interested in is this
$design = Mage::getSingleton('catalog/design');
$settings = $design->getDesignSettings($category);
#...other stuff we don't care about...
if ($settings->getPageLayout()) {
$this->getLayout()->helper('page/layout')->applyTemplate($settings->getPageLayout());
}
When you save a category with your "Page Layout" in the Custom Design tab, the getPageLayout method call above should return company_category_overview. On category pages, Magento doesn't use this to apply a handle, instead it passes the values to the applyTemplate method. Here's that method in full.
#File: app/code/core/Mage/Page/Helper/Layout.php
public function applyTemplate($pageLayout = null)
{
if ($pageLayout === null) {
$pageLayout = $this->getCurrentPageLayout();
} else {
$pageLayout = $this->_getConfig()->getPageLayout($pageLayout);
}
if (!$pageLayout) {
return $this;
}
if ($this->getLayout()->getBlock('root') &&
!$this->getLayout()->getBlock('root')->getIsHandle()) {
// If not applied handle
$this->getLayout()
->getBlock('root')
->setTemplate($pageLayout->getTemplate());
}
return $this;
}
The pertinent parts are this line,
$pageLayout = $this->_getConfig()->getPageLayout($pageLayout);
which will load the information from your configuration
<label>Kategorie-Übersicht</label>
<template>page/1column.phtml</template>
<layout_handle>company_category_overview</layout_handle>
as a Varien_Object. Then, it will use this information to apply a template to the root block.
$this->getLayout()
->getBlock('root')
>setTemplate($pageLayout->getTemplate());
So, for category pages, the information in the <layout_handle/> node is never used. That's why your layout updates aren't being applied — Magento actually applies your handle.

Related

Redirect in CheckOut

I currently work on a magento payment module which has to check if certain fields in a users profile/account are set.
If they do not exist/have no content, I want to either
1. redirect to /customer/account/edit/
2. display a notice that the fields in question has to be filled
or the other way around.
This is initialized by an AJAX call from Magentos core OnePage Checkout.
I tried several things like
Mage::app()->getFrontController()->getResponse()->setRedirect(Mage::getUrl('customer/account/edit/'));
Mage::app()->getResponse()->sendResponse();
exit;
and other things.
From the observer I somehow get a redirect but always end at /default/checkout/cart (which is default?)
In the model I see in chromes network tab that the correct page gets loaded but somehow it wont redirect to the page.
Please let me know if you need more Infos on the problem.
Edit 1: got rid of the second Mage::getURL, still no redirect.
Edit 2: I guess that if I could somehow 'hijack' the response for opcheckout.js it could do the redirect. Or should I maybe just add an extra js which listens as well? I think because the hole thing is triggered by an AJAX call the redirect just doesn't take place at the correct place. More ideas are welcome.
Edit 3: I try to figure at the moment out how to build a response fo the mentioned opcheckout.js which does not trigger the next step but does trigger redirect
Solved it
Solution:
Create an an Override for
/app/code/core/Mage/Checkout/controllers/OnepageController.php
This should be for example
app/code/local/{NameSpace}/{ModuleName}/controllers/OnepageController.php
Pay special attention to the "s" the folder is plural not singular. Took me ten minutes to see why my controller was not working.
The controller should be build like this:
require_once Mage::getModuleDir('controllers', "Mage_Checkout").DS."OnepageController.php";
class MOOD_BonimaScoreIdent_OnepageController extends Mage_Checkout_OnepageController {
public function indexAction(){
parent::indexAction();
}
public function savePaymentAction(){
//just an example
$result['redirect']=Mage::getUrl('customer/account/edit/');
$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
return;
}
}
The config.xml part to include the controller override is simply:
<frontend>
<routers>
<checkout>
<args>
<modules>
<MyModuleName before="Mage_Checkout">Namespace_MyModuleName</MyModuleName>
</modules>
</args>
</checkout>
</routers
</frontend>

Magento : how to load product details on homepage

I've been trying to show product details on homepage. After long hours searching on google and stackoverflow, i've come with this solution:
Mage::register('product',Mage::getModel('catalog/product')->load('17'));
$block = Mage::app()->getLayout()
->createBlock('catalog/product_view')
->setData('area','frontend')
->setTemplate('catalog/product/view.phtml');
echo $block->toHtml();
It does render the catalog/product_view block but no its children, i'd like to understand how to render the children too ?
Note: my homepage as the layout "myviewer.phtml" and the code above is in "myviewer.phtml".
You are currently manually creating the product view block and not using the standard layout xml, so you are not getting any of the default child blocks that would be loaded on the product page via the product page layout handle;
catalog_product_view
Viewing this layout handle in the catalog.xml file will show you which blocks are loaded, and importantly which child blocks are added to the block named 'product.info' which usually uses the template section you are trying to render.
If you view the product page template 'catalog/product/view.phtml' that you are rendering, you will see it calls blocks that will not be available such as;
<?php echo $this->getChildHtml('addtocart') ?>
So to fix this you have two options,
Manually initialize and add each of the child blocks to the product block you are creating using the append() method.
Or
Update the controller action that was used to load and render the page to include the product page layout handle. Call the following in the controller,
$update = $this->getLayout()->getUpdate();
$update->addHandle('catalog_product_view');
This will cause the layout to include all of the correct product page child blocks, then in the template just call;
$_product = Mage::getModel('catalog/product')->load('17');
Mage::register('product',$_product);
Mage::register('current_product',$_product);
$block = Mage::app()->getLayout()->getBlock('product.info');
echo $block->toHtml();
Adding the handle could be done in the controller which would need to be overridden using a rewrite in a custom module (there are already lots of articles on this).
Use an event observer for any event fired before the controller action.
Magento creates its block hierarchy based on its layout XML configuration. For example, the catalog/product_view block is used in the catalog.xml layout file (usually found in app/design/frontend/base/default/layout/catalog.xml). This file also defines all of the children blocks of the catalog/product_view block.
Magento decides which layout instructions to carry out depending on the active layout handles. For example, the catalog.xml file references the catalog_product_view layout handle, which corresponds to the catalog module, product controller, view action. Whenever that specific controller action is invoked, that layout is applied.
In your case, the home page is probably a CMS page, which doesn't apply the catalog_product_view layout handle (it typically applies the cms_page handle). As such, the block hierarchy is not defined correctly for you to utilize the catalog/product_view block.
There are many ways of resolving this issue, but first you have to think about exactly what you're trying to do. If you try to use the catalog/product_view block, your home page will look almost exactly like a regular product page, which may or may not be what you want. One way of achieving that is to use the <update/> tag, which takes another layout handle and merges it with the current layout handle.
I recommend reading some more into Magento's layout XML system. Alan Storm has some great resources on the subject, such as this blog post (warning, it's a bit outdated), and his book on Magento's layout system.
you can do it by inserting in CMS > Pages > Home
{{block type="catalog/product_list" name="home.catalog.product.list" alias="products_homepage" LATEST="0" template="catalog/product/list.phtml"}}
In this case you have to change in template/catalog/product/list.phtml
Find this code around in lines 74 , 133 and 180
<?php
$_nameAfterChildren = $this->getChild('name.after')->getSortedChildren();
foreach($_nameAfterChildren as $_nameAfterChildName):
Replace adding a if statement , should be something like this :
<?php
$_nameAfter = $this->getChild('name.after');
// New if here
if($_nameAfter):
$_afterChildren = $this->getChild('name.after')->getSortedChildren();
foreach($_afterChildren as $_afterChildName):

Set homepage from theme's layout

I am trying to set a CMS homepage via a theme's local.xml layout update file in the <cms_index_index> node. I swear I've seen functions to change the store configuration temporarily within a layout node (but maybe I dreamt it), but I'm having trouble finding the layout function in classes like Mage_Core_Block_Abstract and its children classes.
For reference, I've checked in Mage_Cms_IndexController and found the function which renders the homepage:
public function indexAction($coreRoute = null)
{
$pageId = Mage::getStoreConfig(Mage_Cms_Helper_Page::XML_PATH_HOME_PAGE);
if (!Mage::helper('cms/page')->renderPage($this, $pageId)) {
$this->_forward('defaultIndex');
}
}
Or, am I doing this completely the wrong way? What would be best practice for a problem like this? I do not want to add a store view for the new theme, as the new theme is for mobile platforms and requires the same settings from the store view. Thanks guys!
This is not possible. The layout configuration is not invoked until after checks occur to see if there is a valid page which has been specified; because these checks fail, the Default router will match and (by default) the application will display the 404 page.

Can't modify response in a Magento event observer, even though observer is firing

I'm attempting to use an observer to modify the response of the add to cart controller action, but only in the context of an AJAX request.
My observer is called and my JS is retrieving data fine, I have verified this by putting a die() in my observer function cartAdd() and verifying the response developer console, which I am using to see the result of my response from Magento. So JS isn't the issue here.
My primary problem is that I can't seem to modify the response through the normal functions. I get the request by using $observer->getEvent()->getControllerAction()->getResponse() and then make changes to it by setHeader(), or setBody(), or any other function that modifies the response, but there is absolutely no effect to the response!
Does anybody have any clue as to why I'm not able to modify the response in my observer?
In /app/code/local/mynamespace/mymodule/etc/config.xml:
<frontend>
....
<events>
<controller_action_predispatch_checkout_cart_add>
<observers>
<mymodule_cart_add>
<type>singleton</type>
<class>mymodule/observer</class>
<method>cartAdd</method>
</mymodule_cart_add>
</observers>
</controller_action_predispatch_checkout_cart_add>
</events>
</frontend>
In /app/code/local/mynamespace/mymodule/Model/Observer.php:
public function cartAdd(Varien_Event_Observer $observer)
{
$controllerAction = $observer->getEvent()->getControllerAction();
if($controllerAction->getRequest()->isAjax()) {
$response = $controllerAction->getResponse();
// I've even tried using:
// $response = Mage::app()->getResponse();
$response->setHeader('HTTP/1.1','403 Forbidden'); //using this because i will need it in my final code and it will make it immediatly obvious the response has been changed
$response->setHeader('Content-type', 'application/json');
$response->setBody('hello world!!!!');
// this is to stop the product from being added to the cart
$controllerAction->setFlag('', Mage_Core_Controller_Varien_Action::FLAG_NO_DISPATCH, true);
}
}
Please note: I know this code isn't going to at all AJAXify adding to cart (which is my end goal). At the moment I am just trying to resolve this issue
I end up just getting the contents of the page that you would end up on as a result of running an add to cart action:
When a product is added to cart there is admin-configurable behavior to send a person to the cart page or to redirect them back to the product page. See the System > Configuration > Checkout > Shopping Cart: After Adding a Product Redirect to Shopping Cart field.
This redirect behavior is accomplished via a redirect which will displace any redirect set in the dynamic controller_action_predispatch_checkout_cart_add event; ref. the final bit of logic from the Mage_Checkout_CartController::addAction(). Have no fear, though! Magento core developers have the need to override this behavior as well, so it is possible to inform the Mage_Checkout cart controller's addAction() method to bypass the normal redirect behavior if a flag has been set on the checkout/session object. Not only is there a hook and supporting logic to make it work, but there is actually a working example from the core - always a good thing for developers.
Just prior to the final redirect logic in the addAction() method, the cart controller's addAction() method dispatches the checkout_cart_add_product_complete event. This event is observed by the Mage_Wishlist observer. A quick review of relevant final logic from the Mage_Wishlist_Model_Observer::processAddToCart() method shows how to prevent the cart controller's addAction() method from redirecting - namely by setting the no_cart_redirect flag on checkout/session object, which preserves the redirect set on the response object.
There is one more consideration in this case. It's likely that the Mage_Wishlist observers behavior should be preserved, namely: after adding a product to cart from the wishlist, a customer may be redirected to the next product in their wishlist. This is one of the instances when observer processing order matters. To make sure that the Mage_Wishlist module's add to cart behavior is preserved, other modules which consume the checkout_cart_add_product_complete event should fire before the Mage_Wishlist observer. In the declaration file for the custom module, the Mage_Wishlist module should be set as dependent on the custom module, which will ensure that the custom module's observer will fire before the Mage_Wishlist module:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<Custom_Module>
<active>true</active>
<codePool>local</codePool>
</Custom_Module>
<Mage_Wishlist>
<depends>
<Custom_Module />
</depends>
</Mage_Wishlist>
</modules>
</config>
If the Mage_Wishlist module were not a factor, the better targeted event to consume would be the dynamically-generated controller_action_postdispatch_checkout_cart_add event, which is the last targeted event before the generic controller_front_send_response_before event.

Magento read-only and hidden product attributes

I would like to have some Magento product attributes that are not editable from the admin interface and some that are not visible at all in that interface (as a method of storing some persistent information about a product that should not be viewed by human users.. it's the only way of doing this that i can think of, any other suggestions are welcome).
So my question is: Do all Magento attributes have to be visible and editable from the admin interface? If not, how can they be made read-only or hidden?
I noticed that in the admin interface there are some read-only fields, so it must be possible to do this one way or another. After searching stackoverflow for this I found a possible solution involving JavaScript, but I would like to not go down that path if it's at all possible.
OK, it looks like it can be done after all. After adding an observer for the catalog_product_load_after event, the lockAttribute method of the Mage_Catalog_Model_Abstract class may be used to make a product attribute read-only. Here is the code for the observer method:
public function lockAttributes($observer) {
$event = $observer->getEvent();
$product = $event->getProduct();
$product->lockAttribute('attribute_code');
}
Since the catalog_product_load_after event is dispatched for every product load, the attributes supplied in the lock_attributes method are locked after every product load. This could have unexpected results: it is not possible to change the value of the attributes in the lock_attributes method without explicitly unlocking them.
Instead of using the catalog_product_load_after event, it suffices to add an observer for the catalog_product_edit_action event: this event is dispatched only when editing a product in the admin interface.
I think Aad Mathijssen and Epicurus combined have the best answer to the question, with a little clarification. As Aad points out, catalog_product_load_after is called after every product load and that means on the FrontEnd as well!
If we are looking to protect attribute fields only in the admin panels, catalog_product_edit_action is the more appropriate choice.
Your etc/config.xml will then be something like this:
<catalog_product_edit_action>
<observers>
<lock_attributes>
<class>yourmodule/observers</class>
<method>lockAttributes</method>
</lock_attributes>
</observers>
</catalog_product_edit_action>
No i guess its not possible from the attribute manager.
A easy quick and dirty solution would be to use css to hide the input and label.
I have developed exactly such extension that works for products, categories and CMS pages. You just have to define some rules and choose which attributes you want to show as read-only.
Extension URL: https://www.bubbleshop.net/magento-admin-readonly.html
Using this thread and some more digging around; the lockAttribute method is from an abstract class which means that it's possible to also be used on category attributes as well. I caught the 'catalog_category_load_after' observer and used it to lock my desired category attributes:
public function lockCategoryAttributes($observer) {
$event = $observer->getEvent();
$c = $event->getCategory();
$c->lockAttribute('attribute_code');
}
I'm not sure if that's the right observer to use but it works.
So yes it is possible to lock category attributes or make them readonly.
etc\adminhtml\events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_load_after">
<observer name="product_lock_attributes" instance="Vendor\Module\Observer\Lock"/>
</event>
</config>
Observer\Lock.php
namespace Vendor\Module\Observer;
class Lock implements \Magento\Framework\Event\ObserverInterface
{
public function execute(\Magento\Framework\Event\Observer $observer)
{
$event = $observer->getEvent();
$product = $event->getProduct();
$product->lockAttribute('attribute_code');
}
}

Resources