Disable full page caching for specific block - magento

I'm working with magento EE that has full page caching feature. There is a block that is updated dynamically, but I can't seem to disable its caching.
What I want to achieve ideally: disable caching only for particular block so it would be rendered again each time the page loads.
Things I tried:
Include unsetData to layout file
<action method="unsetData"><key>cache_lifetime</key></action>
<action method="unsetData"><key>cache_tags</key></action>
Set function _saveCache to return false
protected function _saveCache($data, $id, $tags = array(), $lifetime = null) {
return false;
}
Set different values for cache_lifetime
public function __construct()
{
$this->addData(array(
‘cache_lifetime’ => 0,
‘cache_tags’ => array(Mage_Catalog_Model_Product::CACHE_TAG),
));
}
Perhaps I'm missing something in full page caching mechanics?

Well, I found a couple of good posts and implement my caching with etc/cache.xml, that wraps my block with container object.
My cache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<placeholders>
<namespace_block_unique_node>
<block>module/block_class</block>
<name>name_of_block_in_my_layout</name>
<template>path/to/my/template</template>
<placeholder>UNIQUE_PLACEHOLDER_HERE</placeholder>
<container>Namespace_Module_Model_Caching_Container_BlockName</container>
<cache_lifetime>86400</cache_lifetime>
</namespace_block_unique_node>
</placeholders>
</config>
I used here as block the block that should not be cached, as name name of block in my layout, and as container I've choose my container.
Code for container:
<?php
class Namespace_Module_Model_Caching_Container_BlockName extends Enterprise_PageCache_Model_Container_Abstract
{
protected function _getCacheId()
{
return 'NAMESPACE_MODULE_BLOCKNAME' . $this->_getIdentifier();
}
protected function _getIdentifier()
{
return microtime();
}
protected function _renderBlock()
{
$blockClass = $this->_placeholder->getAttribute('block');
$template = $this->_placeholder->getAttribute('template');
$block = new $blockClass;
$block->setTemplate($template);
$layout = Mage::app()->getLayout();
$block->setLayout($layout);
return $block->toHtml();
}
protected function _saveCache($data, $id, $tags = array(), $lifetime = null) { return false;}
}
Here I putmicrotime() function to identify block, but inside my module I used cookie variables related to my module. I believe that saves redundant reloading of a block when nothing was really changed.
The thing that I didn't found in other tutorials is that I had to create layout variable and assign it to my block, otherwise I was getting only my block instead of whole page.

Here is the solution for disabling FPC for a specific controller (could be extended to specific action as well).
First create an Observer to listen on the controller_action_predispatch event:
public function processPreDispatch(Varien_Event_Observer $observer)
{
$action = $observer->getEvent()->getControllerAction();
// Check to see if $action is a Product controller
if ($action instanceof Mage_Catalog_ProductController)
{
$request = $action->getRequest();
$cache = Mage::app()->getCacheInstance();
// Tell Magento to 'ban' the use of FPC for this request
$cache->banUse('full_page');
}
}
Then add the following to your config.xml file for the module. This goes in the section:
<events>
<controller_action_predispatch>
<observers>
<YOUR_UNIQUE_IDENTIFIER>
<class>YOURMODULE/observer</class>
<method>processPreDispatch</method>
</YOUR_UNIQUE_IDENTIFIER>
</observers>
</controller_action_predispatch>
</events>
Now Magento will serve up your page every time and bypass FPC for the request.
And You can also refer: http://mikebywaters.wordpress.com/2011/12/14/disable-magento-full-page-cache-on-a-per-controller-basis/

cache_lifetime must be set to null to disable cache for this block
public function __construct()
{
$this->addData(array(
‘cache_lifetime’ => null,
));
}

Related

Magento - check if customer attribute already exists

I have a custom module which is called on the customer_save_observer_executed event and specifically when a customer updates their details in their account page (name, password, email etc) I have added a custom attribute called display_name.
When a customer submits this form i need to check if the display_name currently exists for any other customer. If it doesn't then setDisplayName(...) else do nothing/display error message.
I was hoping to do this with this snippet:
$customer_check = Mage::getModel('customer/customer')
->getCollection()
->addAttributeToSelect('display_name')
->addAttributeToFilter('display_name',$new_name)->load();
if ( is_object($customer_check) && count($customer_check) >= 2) {
// dont allow - duplicate customer displayname
}
else {
// allow update....
}
My current code in Model -> Observer.php
class xxxxxxx_Model_Observer
{
public function xxxxxxxx(Varien_Event_Observer $observer)
{
// to stop event being fired twice
if(Mage::registry('customer_save_observer_executed')){
return $this;
}
$postData = Mage::app()->getRequest()->getPost();
$customer = $observer->getCustomer();
// if updating NOT a new customer
if($customer instanceof Mage_Customer_Model_Customer && !$customer->isObjectNew()) {
// check display name is posted
if(!empty($postData['display_name'])){
$current_name = $customer->getDisplayName();
$new_name = $postData['display_name'];
// duplicate check
$customer_check = Mage::getModel('customer/customer')
->getCollection()
->addAttributeToSelect('display_name')
->addAttributeToFilter('display_name',$new_name)->load();
if ( is_object($customer_check) && count($customer_check) >= 2) {
// dont allow - duplicate customer displayname
}
else {
if( $postData['display_name'] !== $current_name ) {
$customer->setDisplayName($postData['display_name']);
}
}
}
}
Mage::register('customer_save_observer_executed',true);
}
}
but this just updates the display_name even if i deliberately set it to a duplicate of another customers
UPDATE
Looking into this further it looks like the module function itself is not being run or nothing inside it is taking affect, as whatever i put in it doesn't work. It is infact the default behaviour that is setting the displayname not my module. My module is activated and my config file uses the customer_save_commit_after event as shown below:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<xxx_xxxxx>
<version>0.0.1</version>
</xxx_xxxxx>
</modules>
<global>
<models>
<xxx_xxxxx>
<class>xxx_xxxxx_Model</class>
</xxx_xxxxx>
</models>
<events>
<customer_save_commit_after>
<observers>
<xxx_xxxxx>
<class>xxx_xxxxx/observer</class>
<method>xxxxxx</method>
<type>singleton</type>
</xxx_xxxxx>
</observers>
</customer_save_commit_after>
</events>
</global>
</config>
This probably is the case because all data from the form submitted is set on the customer model in Magento's Customer module (code taken from Mage_Customer_AccountController)
/**
* Forgot customer account information page
*/
public function editAction()
{
$this->loadLayout();
$this->_initLayoutMessages('customer/session');
$this->_initLayoutMessages('catalog/session');
$block = $this->getLayout()->getBlock('customer_edit');
if ($block) {
$block->setRefererUrl($this->_getRefererUrl());
}
$data = $this->_getSession()->getCustomerFormData(true);
$customer = $this->_getSession()->getCustomer();
if (!empty($data)) {
$customer->addData($data);
}
if ($this->getRequest()->getParam('changepass') == 1) {
$customer->setChangePassword(1);
}
$this->getLayout()->getBlock('head')->setTitle($this->__('Account Information'));
$this->getLayout()->getBlock('messages')->setEscapeMessageFlag(true);
$this->renderLayout();
}
Here addData() is called on the customer model. To prevent this rename your form's input to something temporary, like <input name="display_name_tmp"/>, then in your controller do something like
public function xxxxxxxx(Varien_Event_Observer $observer)
{
// to stop event being fired twice
if(Mage::registry('customer_save_observer_executed')){
return $this;
}
$postData = Mage::app()->getRequest()->getPost();
$customer = $observer->getCustomer();
// if updating NOT a new customer
if($customer instanceof Mage_Customer_Model_Customer &&
!$customer->isObjectNew()) {
// check display name is posted
if(!empty($postData['display_name_tmp'])){
$current_name = $customer->getDisplayName();
$new_name = $postData['display_name_tmp'];
// duplicate check
$customer_check = Mage::getModel('customer/customer')
->getCollection()
->addAttributeToSelect('display_name')
->addAttributeToFilter('display_name',$new_name)->load();
if ( is_object($customer_check) && count($customer_check) >= 2) {
// dont allow - duplicate customer displayname
}
else {
if( $postData['display_name'] !== $current_name ) {
$customer->setDisplayName($postData['display_name_tmp']);
}
}
}
}
Mage::register('customer_save_observer_executed',true);
}

Magento Custom Router Lost Layout Object

It seems like everything is being properly configured and I can get the output that I want but I would prefer not echoing the layout object directly from my controller:
Here is what Im working with
config.xml
<config>
<modules>
<ALS_Bestselling>
<version>0.1.0</version>
</ALS_Bestselling>
</modules>
<global>
<models>
<bestselling>
<class>ALS_Bestselling_Model</class>
</bestselling>
</models>
<blocks>
<bestselling>
<class>ALS_Bestselling_Block</class>
</bestselling>
</blocks>
<helpers>
<bestselling>
<class>ALS_Bestselling_Helper</class>
</bestselling>
</helpers>
</global>
<frontend>
<layout>
<updates>
<als_bestselling>
<file>bestselling.xml</file>
</als_bestselling>
</updates>
</layout>
</frontend>
<default>
<web>
<routers>
<bestselling_router>
<area>frontend</area>
<class>ALS_Bestselling_Controller_Router</class>
</bestselling_router>
</routers>
</web>
<shorturls>
</shorturls>
</default>
</config>
and
#File: Controller/Router.php
<?php
class ALS_Bestselling_Controller_Router extends Mage_Core_Controller_Varien_Router_Abstract
{
private static $_module = 'bestsellers';
private static $_realModule = 'ALS_Bestselling';
private static $_controller = 'index';
private static $_controllerClass = 'ALS_Bestselling_Controller_Index';
private static $_action = 'view';
public function initControllerRouters($observer)
{
$front = $observer->getEvent()->getFront();
$front->addRouter('bestselling', $this);
}
public function collectRoutes()
{
// nothing to do here
}
public function match(Zend_Controller_Request_Http $request)
{
$this->_request = $request;
$front = $this->getFront();
$identifier = trim($request->getPathInfo(), '/');
if(!substr($identifier,0,strlen('bestsellers')) == 'bestsellers'){
return false;
}else{
//$rewrite = Mage::getModel('core/url_rewrite');
$route_params = str_replace ( "bestsellers/" , "" , $identifier );
$rewrite = Mage::getModel('core/url_rewrite');
$rewrite->setStoreId(1);
$rewrite->loadByRequestPath($route_params);
$category_route = $rewrite->getIdPath();
//If no route exists for category send to a different router
if(!$category_route != ""){
return false;
}//Otherwise send the parameters to the request
else{
$id = str_replace ( "category/" , "" , $category_route );
$this->_request->setParam('id',$id);
}
$this->_setRequestRoute();
$this->_dispatch();
return true;
}
}
protected function _setRequestRoute()
{
$this->_request->setModuleName(self::$_module);
$this->_request->setControllerName(self::$_controller);
$this->_request->setActionName(self::$_action);
$this->_request->setControllerModule(self::$_realModule);
}
protected function _dispatch()
{
$this->_request->setDispatched(true);
$controller = Mage::getControllerInstance(self::$_controllerClass, $this->_request, $this->_response);
$controller->dispatch(self::$_action);
}
}
and
File: Controller/Index.php
class ALS_Bestselling_Controller_Index extends Mage_Core_Controller_Front_Action{
public function viewAction(){
$layout = Mage::app()->getLayout();
$layout->generateXml()->generateBlocks();
$render = $layout->getBlock('root')->toHtml();
echo $render;
}
}
The previous works but the following:
$update = $this->getLayout()->getUpdate();
$update->addHandle('default');
$this->renderLayout();
throws an error Call to a member function appendBody() on a non-object.
Is this how I am suppose to do this or is there something missing from the recipe?
Custom routing classes are on the fringe of what most developers do with Magento — the standard approach would be to setup a standard module controller with a frontname of bestselling, or create your feature in a non-seo friendly way and then create a rewrite entity/object to SEO-ify it.
I, however, have a soft spot for custom routing objects, even if there's little in the way of best community practices behind them. Without a line number, it sounds like your exception
Call to a member function appendBody() on a non-object
comes from the following code
#File: app/code/core/Mage/Core/Controller/Varien/Action.php
$this->getResponse()->appendBody($output);
Given the definition for getResponse
public function getResponse()
{
return $this->_response;
}
It sounds like your controller object doesn't have a proper response object set.
Looking at your controller instantiation code
$controller = Mage::getControllerInstance(self::$_controllerClass, $this->_request, $this->_response);
You're referencing a $this->_response property — but your router class and the abstract router class don't have this property. It's impossible to say based on what you've posted, but this is probably your problem. Take a look at how the standard router's match method does it.
#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
//...
$controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse());
So, use the front-controller object to grab the response object, pass that into the getCongrollerInstance factory method, and you should be good to go (or at least onto the next problem)

Magento: Add Widget tool onto product and category WYSIWYG

On the WYSIWYG editor for CMS pages in Magento there's a tool to add Magento widgets to the editor. I'd like this also to be available for the WYSIWYG on the product and category descriptions.
I'm struggling to find where the editor is even loaded at the moment. Could anyone let me know what I might have to do or at least point me in the right direction?
Thanks in advance.
After enabling add_widgets and add_variables in class Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg_Content as per #David Manner's answer, you will likely find that whilst this will certainly enable these in the WYSIWYG editor and function properly, it will only render the raw widget/variable code in the front end (rather than the appropriate markup).
You can fix with the following:-
Navigate to /app/design/frontend/package/theme/template/catalog/category/view.phtml
Find <?php if($_description=$this->getCurrentCategory()->getDescription()): ?>
Add the following below this line:-
<?php
$helper = Mage::helper('cms');
$processor = $helper->getPageTemplateProcessor();
$_description = $processor->filter($_description);
?>
This will render in the frontend correctly then.
Under the class Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg_Content there are two flags in the config array 'add_widgets' and 'add_variables'. Both of these are set to false by default.
Setting these to true will then be caught in the Mage_Widget_Model_Observer class function prepareWidgetsPluginConfig on the event cms_wysiwyg_config_prepare.
I would suggest rewriting the Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg_Content to fit your needs, but setting add_widgets and add_variables to true should work for both categories and products.
After read all answers I found elegant solution. This solution rewrite only one Block class and doesn't change any template file.
Rewrite Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg_Content
<config>
...
<global>
<blocks>
...
<adminhtml>
<rewrite>
<catalog_helper_form_wysiwyg_content>Agere_Wysiwyg_Block_Widget_Anywhere</catalog_helper_form_wysiwyg_content>
</rewrite>
</adminhtml>
</blocks>
...
</global>
</config>
Change only two flags in the config array 'add_widgets' and 'add_variables' to true
class Agere_Wysiwyg_Block_Widget_Anywhere extends Mage_Adminhtml_Block_Catalog_Helper_Form_Wysiwyg_Content {
protected function _prepareForm() {
//return parent::_prepareForm();
$form = new Varien_Data_Form(array('id' => 'wysiwyg_edit_form', 'action' => $this->getData('action'), 'method' => 'post'));
$config['document_base_url'] = $this->getData('store_media_url');
$config['store_id'] = $this->getData('store_id');
$config['add_variables'] = true;
$config['add_widgets'] = true;
$config['add_directives'] = true;
$config['use_container'] = true;
$config['container_class'] = 'hor-scroll';
$form->addField($this->getData('editor_element_id'), 'editor', array(
'name' => 'content',
'style' => 'width:725px;height:460px',
'required' => true,
'force_load' => true,
'config' => Mage::getSingleton('cms/wysiwyg_config')->getConfig($config)
));
$this->setForm($form);
return $this;
}
}
Create handler that will process content from category or product
class Agere_Wysiwyg_Helper_Filter extends Mage_Core_Helper_Abstract {
public function categoryAttribute($mainHelper, $result, $params) {
return $this->process($result);
}
public function productAttribute($mainHelper, $result, $params) {
return $this->process($result);
}
public function process($result) {
/** #var Mage_Cms_Helper_Data $helperCms */
$helperCms = Mage::helper('cms');
$processor = $helperCms->getPageTemplateProcessor();
return $processor->filter($result);
}
}
Finally create Observer wich add handlers for out wysiwyg
class Agere_Wysiwyg_Model_Observer extends Varien_Event_Observer {
public function addWysiwygHandler(Varien_Event_Observer $observer) {
/** #var Mage_Catalog_Helper_Output $_helperOutput */
/** #var Agere_Wysiwyg_Helper_Filter $_helperFilter */
$_helperOutput = Mage::helper('catalog/output');
$_helperFilter = Mage::helper('agere_wysiwyg/filter');
$_helperOutput->addHandler('categoryAttribute', $_helperFilter);
$_helperOutput->addHandler('productAttribute', $_helperFilter);
}
}
Full snap of code see by link https://github.com/popovsergiy/magento-wysiwyg
Think a better way is to create a new observer that listening on the same event, and make the module depending on Mage_Widget. Then our observer will be running after Mage_Widget

capture Order completion status in observer in magento

Hi I want to catpture the order information when the oder is completed or close.I try different events like (sales_order_place_after) but didn't complete my requirements.I am completing orders form admin side and i create observer for capturing the information like
Xml :
<events>
<sales_order_place_after>
<observers>
<extra_options>
<class>My_Module_Model_Observer</class>
<method>salesConvertQuoteItemToOrderItem</method>
</extra_options>
</observers>
</sales_order_place_after>
</events>
Observer :
public function salesConvertQuoteItemToOrderItem($observer)
{
$order = $observer->getOrder();
$orders = $observer->getEvent()->getOrder();
if($order->getState() == Mage_Sales_Model_Order::STATE_COMPLETE){
echo "<pre>";
print_r($orders);exit;
}
}
Can anyone help ? Thanks in advance
You are doing everything right except you are listening to wrong event. You have to use sales_order_save_after instead.
Using sales_order_save_after is still good, but it just requires you also to check for the state (as Mischa suggests):
public function salesOrderSaveAfter($observer)
{
$order = $observer->getEvent()->getOrder();
if($order->getState() != Mage_Sales_Model_Order::STATE_COMPLETE) {
return $this;
}
if($order->getData('state') == $order->getOrigData('state')) {
return $this;
}
// do your stuff
return $this;
}
This works fine for me.

Magento Payment Redirect Order

Is there any functionality in magento payment extention to stop the creation of an order before the success status from payment gateway is returned?
The Extension is designed as a payment redirect (with getOrderPlaceRedirectUrl) but really in the correct controller action, I do post from the action with params, to the gateway and return success if all OK, and return failure if not.
But the order is already created so I must cancel this order, but it should not create this order in the first place.
Maybe if I can design it as a gateway, I can use some payment method to do this?
I thought about the validate or prepareSave methods, but both of them are called twice - on accept payment method and on place order.
Also I thought about events - maybe I can use some event to do this post action and on failture just throw exception?
But I really think that in the payment methods, there has to be some standard functionality that I can use.
This is quite a common issue during Payment Module development.
Magento offers two hooks for payment method classes to provide redirect URL's, one before the order is created, one after.
If a payment method model implements getOrderPlaceRedirectUrl() the customer will be redirected after the confirmation step of the one page checkout, the order entity will be created.
If a payment method model implements the getCheckoutRedirectUrl() method, the customer will be redirected after the payment step of the one page checkout, and no order entity is created.
This is not ideal, but thats what Magento offers out of the box.
How about extending the _validate() method on Mage_Sales_Model_Service_Quote, and throw an error there so that it never gets to the "$transaction->save();" bit.
public function submitOrder()
{
$this->_deleteNominalItems();
// do some check here
$this->_validate();
// End checks
$quote = $this->_quote;
$isVirtual = $quote->isVirtual();
........
try {
$transaction->save();
$this->_inactivateQuote();
Mage::dispatchEvent('sales_model_service_quote_submit_success', array('order'=>$order, 'quote'=>$quote));
} catch (Exception $e) {
...........
}
...........
return $order;
}
Validate function looks like this:
protected function _validate()
{
$helper = Mage::helper('sales');
if (!$this->getQuote()->isVirtual()) {
$address = $this->getQuote()->getShippingAddress();
$addressValidation = $address->validate();
if ($addressValidation !== true) {
Mage::throwException(
$helper->__('Please check shipping address information. %s', implode(' ', $addressValidation))
);
}
$method= $address->getShippingMethod();
$rate = $address->getShippingRateByCode($method);
if (!$this->getQuote()->isVirtual() && (!$method || !$rate)) {
Mage::throwException($helper->__('Please specify a shipping method.'));
}
}
$addressValidation = $this->getQuote()->getBillingAddress()->validate();
if ($addressValidation !== true) {
Mage::throwException(
$helper->__('Please check billing address information. %s', implode(' ', $addressValidation))
);
}
if (!($this->getQuote()->getPayment()->getMethod())) {
Mage::throwException($helper->__('Please select a valid payment method.'));
}
return $this;
}
The extended function can look like this:
public function __construct(Mage_Sales_Model_Quote $quote)
{
$this->_quote = $quote;
parent::__construct($quote);
}
protected function _validate()
{
// Code to test comes here
Mage::throwException(Mage::helper('payment')->__('unsuccessfull.....'));
// Code ends, now call parent
return parent::_validate();
}
As I said - giving sample that I used for this solution at final.
I prefered to observe event to do post request. Really if you'll use method presented here
you will take the same effect, but I prefer to use event observer. So:
First add some data to config.xml to create event observer in frontend section
<events>
<sales_model_service_quote_submit_before>
<observers>
<lacpaycs>
<type>singleton</type>
<class>OS_LacPayCS_Model_Observer</class>
<method>lacpaycs_payment_send</method>
</lacpaycs>
</observers>
</sales_model_service_quote_submit_before>
</events>
then we must create Observer class in OS/LacPayCS/Mode/Observer.php:
class OS_LacPayCS_Model_Observer {
protected $_code = 'lacpaycs';
// Here some our additional functions
/**
* #param Varien_Object $observer
*/
public function lacpaycs_payment_send(Varien_Object $observer)
{
/**
* #var Mage_Sales_Model_Order $order
* #var Mage_Sales_Model_Quote $quote
*/
$order = $observer->getOrder();
$quote = $observer->getQuote();
$payment = $order->getPayment();
if ($payment->getMethodInstance()->getCode() != $this->_code) {
return;
}
$helper = Mage::helper('lacpaycs');
try {
// Here we prepare data and sending request to gateway, and getting response
if (!$this->_validateResponse($response)) {
Mage::throwException('Error '.$this->errorMsg);
}
} catch (Exception $e) {
Mage::throwException($e->getMessage());
}
}
}
So in two words what we doing here $_code is the same that in our payment model and with it we checking in observer if we catched event when customer using our payment method
All another code is simple, so I think it's no need to comment it

Resources