I would like to make a portion of a page available for getting via AJAX. I have in mind to use a URL parameter, bare, that would tell Magento to present a page with a different template applied to the root block. The bare template looks like this:
<?php echo $this->getChildHtml('content'); ?>
That's it! The idea is that a JavaScript method could grab just the content of another page and insert it into the DOM where appropriate. (I don't want this to be possible with just any page – only pages that have been marked to do so in layout xml.)
I've read elsewhere that I should avoid conditional layout xml. The only other approach I can think of is to override the Page/Html block itself, creating a modified setTemplate method, like below. Instinctively, I'm concerned about overriding such a core part of Magento.
public function setTemplate($template, $bareTemplate='')
{
$bareMode = Mage::app()->getRequest()->getParam('bare');
$targetTemplate = (!empty($bareTemplate) && $bareMode === '1') ? $bareTemplate : $template;
return parent::setTemplate($targetTemplate);
}
What better approaches haven't I thought of?
The key to getting what you want is removing root as an output block, replacing it with content. Output blocks are just entry points for renderLayout();
To do this in Magento without include-path-hacking Mage_Core_Controller_Varien_Action, observe the controller_action_layout_render_before_$this->getFullActionName() scoped events which are fired in the base action controller class (ref Mage_Core_Controller_Varien_Action::renderLayout() method).
First configure your model class group and frontend event observer. You'll need to determine the full action name of any route that needs this logic. See Mage_Core_Controller_Varien_Action::renderLayout(). Example config below.
<?xml version="1.0"?>
<config>
<global>
<models>
<your_classgroup>
<class>Your_Classgroup_Model</class>
</your_classgroup>
</models>
</global>
<frontend>
<events>
<controller_action_layout_render_before_FULL_ACTION_NAME...>
<observers>
<your_observer_config>
<type>model</type>
<class>your_classgroup/observer</class>
<method>makeContentBlockTheOutputBlock</method>
</your_observer_config>
</observers>
</controller_action_layout_render_before_FULL_ACTION_NAME...>
</events>
</frontend>
</config>
The event observer logic is easy. Do this:
public function makeContentBlockTheOutputBlock($observer)
{
//Edit: action not passed in to this event; passed in generic generate_blocks event
if( Mage::app()->getRequest()->getParam('bare') )
{
Mage::app()->getLayout()->removeOutputBlock('root')->addOutputBlock('content');
}
}
HTH.
Related
I have a custom block that I would like to append under a existing core block in the order view page in the admin panel of Magento.
I developed my custom module.
In order to avoid modification of a core template phtml file to load my custom block, I try to follow the best practices and I built an observer on the core_block_abstract_to_html_after
if you want to know why Read more two excellent articles above
http://www.atwix.com/magento/best-practices/
http://inchoo.net/magento/how-you-could-build-your-magento-extensions-without-view-files/
However while the block, that I want to append, will contain lots of html I want to put this html in a custom phtml file and not directly in the php, to make it more easily customizable for designers.
I created so a phtm file in the following folder
app\design\adminhtml\default\default\template\custommodulefolder\customhtmlfileforadminorderview.phtml
But how to load this phtml content from the observer ?
<adminhtml>
<events>
<core_block_abstract_to_html_after>
<observers>
<custommodule>
<class>NameSpace_CustomModule_Model_Observer</class>
<method>RenderBlockCustomdAdmin</method>
</referencefield>
</custommodule>
</core_block_abstract_to_html_after>
</events>
</adminhtml>
And the method in my Observer.php
public function RenderBlockCustomdAdmin($observer = NULL)
{
if (!$observer) {
return;
}
if ('order_info' == $observer->getEvent()->getBlock()->getNameInLayout()) {
if (!Mage::getStoreConfig('advanced/modules_disable_output/'.self::MODULE_NAME)) {
$transport = $observer->getEvent()->getTransport();
// here I would like to find a way to load the content of a custom of phtml
$htmfromablock= function_which_will_allow-me_to_get_content_phtmlfile()
$transportOldHtml =$transport->getHtml();
$transport->setHtml($transportOldHtml.'<br />'.$htmfromablock);
}
}
return $this;
}
Creating a block is fairly easy when you get the layout (it is just a matter of calling createBlock() with the block type you want and setTemplate() with the template you want to use).
And you can always get the layout from another block.
So in your case :
$observer->getEvent()
->getBlock()
->getLayout()
->createBlock('adminhtml/template')
->setTemplate('custommodulefolder/customhtmlfileforadminorderview.phtml')
->toHtml();
Still learning magento coding. I wonder is there a way I can print out all the values in the collections on any page I load on magento? This would be assuming I don't know the names of the collections being used.
This would be very helpful if it is possible.
First, a word of warning - if you are var_dump'ing collections, let alone every collection loaded for a given request, then you are most likely going to run in to problems - collections contain huge amounts of data.
What is it exactly that you require from each collection that you would need to do this?
Anyway, the only way to get this data that comes to mind would be to use an observer subscribed to:
core_collection_abstract_load_after
So as a head start...
Your config.xml would look similar to this...
<?xml version="1.0"?>
<config>
<modules>
<YourCompany_YourModule>
<version>1.0.0</version>
</YourCompany_YourModule>
</modules>
<frontend>
<events>
<core_collection_abstract_load_after>
<observers>
<yourmodule>
<class>YourCompany_YourModule_Model_Observer</class>
<method>core_collection_abstract_load_after</method>
</yourmodule>
</observers>
</core_collection_abstract_load_after>
</events>
</frontend>
<global>
<models>
<yourmodule>
<class>YourCompany_YourModule_Model</class>
</yourmodule>
</models>
</global>
</config>
Your observer would look like this...
<?php
class YourCompany_YourModule_Model_Observer
{
public function core_collection_abstract_load_after(Varien_Event_Observer $observer)
{
$collection = $observer->getEvent()->getCollection();
//Do what you want with each collection here
}
}
A much simpler approach and be it your new to Magento I would suggest using this:
https://github.com/madalinoprea/magneto-debug
As it will offer other insight into the internal workings as well as collection and SQL info about the actual collection.
A view in action:
http://www.youtube.com/watch?v=aqvgrmebcu4
So, horray - I'm attempting to create a new custom Payment Gateway. It's designed to do auth/capture via a third-party API, but does NOT need to redirect to the third-party site.
From my understanding, when an order is placed/finalized in Magento, and the gateway is set to "Authorize and Capture", it should be firing off the 'capture' method from the gateway model. Currently, it's not doing this.
Of course, if I specifically capture from the Admin Order view, it'll attempt to capture, but this needs to happen immediately upon checkout (and again, it's my understanding that it already should).
In my gateway Model, I have the following (truncated for readability):
<?php
class Example_Gateway_Model_Payment extends Mage_Payment_Model_Method_Cc
{
protected $_code = 'example';
protected $_isGateway = true;
protected $_canAuthorize = true;
protected $_canCapture = true;
protected $_canUseInternal = true;
protected $_canUseCheckout = true;
// This is an empty block class that extends Mage_Payment_Block_Form_Cc
protected $_formBlockType = 'example/form_example';
public function authorize(Varien_Object $payment, $amount)
{
Mage::log('Authorizing!');
}
public function capture(Varien_Object $payment, $amount)
{
Mage::log('** Capturing **');
// Third-party API stuff would go here, with exceptions being thrown if the gateway determines they've provided an invalid card, etc.
}
public function assignData($data)
{
Mage::log('Assigning Data');
}
}
This Payment model itself definitely works - I get logging output for assignData() and validate(), as well as __construct() if I add it. But no matter what I do, neither the capture or authorize methods fire when actually placing the order.
My config.xml reads somewhat like this:
<?xml version="1.0"?>
<config>
<modules>
<Example_Gateway>
<version>0.0.5</version>
</Example_Gateway>
</modules>
<global>
<blocks>
<gateway>
<class>Example_Gateway_Block</class>
</gateway>
</blocks>
<models>
<gateway>
<class>Example_Gateway_Model</class>
</gateway>
</models>
<helpers>
<gateway>
<class>Example_Gateway_Helper</class>
</gateway>
</helpers>
</global>
<frontend>
<!-- Snip.. Nothing special here -->
</frontend>
<default>
<payment>
<gateway>
<sort_order>0</sort_order>
<model>gateway/payment</model>
<enabled>1</enabled>
<order_staus>processing</order_status>
<payment_action>authorize_capture</payment_action>
<cctypes>VI,MC,AE,DI</cctypes>
<useccv>1</useccv>
</gateway>
</payment>
</default>
</config>
I don't believe there's a need for a resource model as I don't need any additional tables; my expectation is that it will simply use sales_flat_order_payment and related tables to store any gateway-related/-provided data (txn id, etc)
Similarly, I'm simply extending the default CC block to get the standard payment form.
What am I missing? It has to be something small and simple that I'm overlooking.
Thanks in advance!
UPDATE:
So far, I have implemented a workaround that uses an observer to the checkout_type_onepage_save_order event that calls the capture() method manually - but I'm pretty sure this is not the right way to go.
I'm not wrong in thinking that Magento should automatically call capture() upon the initial order save, if the gateway is set to authorize_capture, right..?
Solved! You need this:
protected $_isInitializeNeeded = false;
I have NO IDEA why this is necessary, but at this point I've given up trying to figure out the why of magento in favor of actually getting things done. I had the exact same problem as you, and when I traced it through the source code I found that Payment.php was not calling _authorize when isInitializeNeeded returned true. So, stick that line in your model, and it will work.
I think the method should be : "authorize_capture" and not "capture" as stated in the config
<payment_action>authorize_capture</payment_action>
like that:
public function authorize_capture(Varien_Object $payment, $amount)
{
Mage::log('** Capturing **');
// Third-party API stuff would go here, with exceptions being thrown if the gateway determines they've provided an invalid card, etc.
}
i had a similar problem that the "authorize" method was not fired at all because "authorize_action" was empty. I was able to solve this by hardcoding it in the method itself. "getConfigPaymentAction" is called to get the authorize method.
public function getConfigPaymentAction() {
return 'authorize';
}
Well, I used an observer to manually call the capture method.
Not the most elegant solution, but it works well enough.
How can I tell if the current request is for a backend or frontend page? This check will be done inside an observer, so I do have access to the request object if that helps.
I considered checking Mage::getSingleton('admin/session')->getUser(), but I don't think that's a very reliable method. I'm hoping for a better solution.
This is one of those areas where there's no good answer. Magento itself doesn't provide an explicit method/API for this information, so with any solution you'll need to examine the environment and infer things.
I was using
Mage::app()->getStore()->isAdmin()
for a while, but it turns out there are certain admin pages (the Magento Connect Package manager) where this isn't true. For some reason this page explicitly sets the store id to be 1, which makes isAdmin return as false.
#File: app/code/core/Mage/Connect/controllers/Adminhtml/Extension/CustomController.php
public function indexAction()
{
$this->_title($this->__('System'))
->_title($this->__('Magento Connect'))
->_title($this->__('Package Extensions'));
Mage::app()->getStore()->setStoreId(1);
$this->_forward('edit');
}
There may be other pages with this behavior,
Another good bet is to check the "area" property of the design package.
This seems less likely to be overridden for a page that's in the admin, since the area impacts the path to the admin areas design templates and layout XML files.
Regardless of what you choose to infer from the environment, create new Magento module, and add a helper class to it
class Namespace_Modulename_Helper_Isadmin extends Mage_Core_Helper_Abstract
{
public function isAdmin()
{
if(Mage::app()->getStore()->isAdmin())
{
return true;
}
if(Mage::getDesign()->getArea() == 'adminhtml')
{
return true;
}
return false;
}
}
and then whenever you need to check if you're in the admin, use this helper
if( Mage::helper('modulename/isadmin')->isAdmin() )
{
//do the thing about the admin thing
}
This way, when/if you discover holes in your admin checking logic, you can correct everything in one centralized place.
If you're able to use an observer, you can limit it to the 'adminhtml' event area.
<config>
...
<adminhtml>
<events>
<core_block_abstract_prepare_layout_after>
<observers>
<mynamespace_mymodule_html_before>
<type>singleton</type>
<class>mynamespace_mymodule/observer</class>
<method>adminPrepareLayoutBefore</method>
</mynamespace_mymodule_html_before>
</observers>
</core_block_abstract_prepare_layout_after>
</events>
</adminhtml>
</config>
Have a look at the methods inside Mage/Core/Model/Store.php you'll want to use:
Mage::app()->getStore()->isAdmin()
In conjunction with
Mage::getDesign()->getArea() == 'adminhtml'
To act as a fallback where the store ID isn't set as you expect (Magento connect etc.)
I like beep logic's answer - it makes sense in the context of observers. I also like Alan's point that there is no way to know the admin state in all contexts, which is a function of "the admin" being a state which is entered after the app and front controller are initialized.
Magento's admin state is effectively created from the control dispatching to an admin action controller; see Mage_Adminhtml_Controller_Action::preDispatch(). This is the method which fires the adminhtml_controller_action_predispatch_start event, which is consumed by Mage_Adminhtml_Model_Observer::bindStore(), which is where the admin store is initially "set". In fact, the observer configuration areas (adminhtml vs frontend) "works" because of the main action controller class - see Mage_Core_Controller_Varien_Action::preDispatch(), specifically Mage::app()->loadArea($this->getLayout()->getArea()); - just note that the layout object has its area information set in the adminhtml predispatch.
No matter how you slice it, the admin behavior on which we rely in so many contexts - even something as high-level as the event observer system - relies on the command control structure.
<config>
<!-- ... -->
<adminhtml>
<events>
<core_block_abstract_prepare_layout_after>
<observers>
<mynamespace_mymodule_html_after>
<type>singleton</type>
<class>mynamespace_mymodule/observer</class>
<method>adminPrepareLayoutAfter</method>
</mynamespace_mymodule_html_after>
</observers>
</core_block_abstract_prepare_layout_after>
</events>
</adminhtml>
<frontend>
<events>
<core_block_abstract_prepare_layout_after>
<observers>
<mynamespace_mymodule_html_after>
<type>singleton</type>
<class>mynamespace_mymodule/observer</class>
<method>frontendPrepareLayoutAfter</method>
</mynamespace_mymodule_html_after>
</observers>
</core_block_abstract_prepare_layout_after>
</events>
</frontend>
</config>
In your observer definition:
class Mynamepace_Mymodule_Model_Observer
{
public function adminPrepareLayoutAfter()
{
$this->_prepareLayoutAfter('admin');
}
public function frontendPrepareLayoutAfter()
{
$this->_prepareLayoutAfter('frontend');
}
protected function _prepareLayoutAfter($area)
{
switch($area){
case 'admin':
// do admin things
break;
case 'frontend':
// do frontend things
break;
default:
// i'm a moron
}
}
}
tl;dr: Use an observer, even use the same observer model, but pass in the context by specifying a different calling method.
I have also included some example code using the config from beeplogic's answer as a starting point.
Whether I'm wrong or not (but I've tested it), some events (like controller_front_init_before) can only be overwritten inside global node. As a result, this override will affect both frontend and backend.
Then come Alan's and benmark's solution to the rescue to specify if you want to apply the observer on frontend only or backend only.
Greetings, in Magento I want to trigger an event, once an order has been set to processing (by gateway confirmation or manually) that, example: If a general customer (id 1) spends over 100$ and the payment has been confirmed, set his group id to 4 (silver VIP, which by promotion rule gets 2% discount globally)
I would give a bounty to this, but I'd like the answer before 2 days O_o
EDIT: the answer I received so far is only a partial answer, also I find the links very confusing, I'm not clear on what is the minimal setup, what do i have to configure create etc... Also I'm trying to find out how to get the paying customers id/model.
You should start by creating your own module in app/code/local.
Create for example the directories Moak/Vip. It will be the root of your module.
In order for Magento to know it exists, create a file named Moak_Vip.xml in etc/modules, with the following content :
<?xml version="1.0"?>
<config>
<modules>
<Moak_Vip>
<active>true</active>
<codePool>local</codePool>
<self_name>Moak VIP module</self_name>
</Moak_Vip >
</modules>
</config>
Then, in your module directory, you need the following structure and files :
etc/config.xml
Model/Observer.php
The config.xml defines your module and declares your event listener for a given event (checkout_onepage_controller_success_action is sent when onepage checkout process is complete, sales_order_payment_pay is sent when the payment has been confirmed).
You don't need any DB setup since you will not save any new entity.
So your config file should look something like the following :
<?xml version="1.0"?>
<config>
<modules>
<Moak_Vip>
<version>0.1.0</version>
</Moak_Vip>
</modules>
<global>
<models>
<moak>
<class>Moak_Vip_Model</class>
</moak>
</models>
<events>
<sales_order_payment_pay>
<observers>
<moak_observer>
<type>singleton</type>
<class>moak/observer</class>
<method>checkVipCustomer</method>
</moak_observer>
</observers>
</sales_order_payment_pay >
</events>
</global>
</config>
Now, your Observer method checkVipCustomer should receive an event object from which you can retrieve all information about the order, the customer... and perform the modifications you like.
Have a look at Magento model classes in app/code/core/Mage/.../Model/...
to see how to navigate through those objects.
Example :
<?php
class Moak_Vip_Model_Observer
{
public function checkVipCustomer($event)
{
$order = $event->getInvoice()->getOrder(); // Mage_Sales_Model_Order
/*
- Check order amount
- Get customer object
- Set Group id
- $customer->save();
*/
return $this;
}
}
Note I've not tested any of the code I wrote here, so handle with care !
Hope it helped, Magento has a hard learning curve...
Good luck !
You can create an observer for the "sales_order_payment_pay" event. Here is a cheatsheet of the events in magento 1.3.
And an explanation of how to create observer methods. Links courtesy of the excellent activecodeline and inchoo sites.