Stopping product saving process in observer - php

I am currently developing a module working with the product edit in the backend.
Its purpose is to retrieve categories the product belongs to and populate an attribute (the Brand attribute) with the list of selected categories.
It is mandatory for the admin to select at least one category.
My module works as expected except that I don't know how to stop the saving process if the admin hasn't selected any category while editing a product.
Here is the workflow
Administrator selects categories in the category tab in the product edit page
Admin clicks on "Save"
My module "observes" and gathers all categories
--> If there are selected categories
My module's observer does its stuff to update the Brand attribute
--> Else
My module's observer adds an error to the admin session
My module's observer should tell Magento to stop saving the product. But how do I do that ?
The generic question would maybe be : how to pass a "stop save" argument to an observer ?
Here are a sample of my config.xml file and the method that deals with the workflow I explained above.
Thanks a lot for your help and have fun Magentoing !
config.xml
<catalog_product_prepare_save>
<observers>
<brands_product_save_observer>
<type>singleton</type>
<class>brands/observer</class>
<method>saveProductBrand</method>
</brands_product_save_observer>
</observers>
</catalog_product_prepare_save>
Observer.php
public function saveProductBrand($observer) {
$product = $observer->getProduct();
$categoryIds = $product->getCategoryIds();
if (isset($categoryIds)) {
foreach ($categoryIds as $categoryId) {
$isBrandCategory = Mage::getModel('brands/navigation')->isBrandCategory($categoryId);
if ($isBrandCategory)
$brandCategories[] = $categoryId;
}
if (isset($brandCategories)) {
$brandId = Mage::getModel('brands/navigation')->getBrand($brandCategories[0]);
if ($brandId) {
$attribute = Mage::getModel('eav/config')->getAttribute('catalog_product', 140);
foreach ($attribute->getSource()->getAllOptions(true, true) as $option) {
$attributeArray[$option['label']] = $option['value'];
}
$categoryName = Mage::getModel('catalog/category')->load($brandId)->getName();
$product->setData('brand', $attributeArray[$categoryName]);
}
} else {
Mage::getSingleton('adminhtml/session')->addError(Mage::helper('catalog')->__('Please add this product to a brand in the "Categories" tab.'));
HERE SOME CODE TO TELL MAGENTO TO STOP SAVING THE PRODUCT
return;
}
}
}

It's always a crap shoot as to which sections of Magento support this, but throwing an exception is often the prescribed way of telling Magento that something went wrong. The layers higher up the stack are set to catch these exceptions and use them to go back to the form and display an error message. Give this a try
Mage::throwException(Mage::helper('adminhtml')->__('You totally failed at that.'));
If you take a look at the Adminhtml module's Catalog/ProductController.php (1.5, but I'd assume a similar format in previous versions)
function saveAction()
{
...
$product = $this->_initProductSave();
try {
$product->save();
$productId = $product->getId();
...
}
The _initProductSave method is where the catalog_product_prepare_save event is fired. Since this is outside the saveAction's try/catch block, the exception won't be caught (as described in the comments below).
You'll need to move you validation code into the product model's before save event (catalog_product_save_before). Doing that should let you throw an exception and have the admin display the error message and represent the form for editing.

A couple of thoughts. Do you actually want to stop the save, or would "undoing" the changes be enough? If your observer detects that the required information is missing, then just iterate through the changed data and set it back to the original and allow the save to proceed. You should be able to do $product->getOrigData() to compare which has changed?
Alternatively, why not make the category attribute mandatory? You should be able to do that in your config xml (not exactly sure how off the top of my head)
Finally, you could bind to the controller_action_predispatch_catalog_product_save Event instead, and if you detect the error state, set a no-dispatch flag, add your error message to the session and redirectReferrer. Check out my earlier answer here for details.
=========EDIT=========
I found a new way. In your Observer, change the _dataSaveAllowed value on the object to false. Mage_Core_Model_Abstract::save() checks that value before proceeding with the save.
HTH,
JD

In case you need to prevent the save method to execute for a core model (i.e. Catalog/Product), you can use reflection to set "$_dataSaveAllowed" to false:
public function catalogProductSaveBefore($observer)
{
try {
$product = $observer->getProduct();
$reflectionClass = new ReflectionClass('Mage_Catalog_Model_Product');
$reflectionProperty = $reflectionClass->getProperty('_dataSaveAllowed');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($product, false);
} catch (Exception $e) {
Mage::log($e->getMessage());
}
return $this;
}

I don't think you can you stop execution the way you want via an observer, Magento doesn't pay attention to anything that you might return or set via your observer.
One idea would be too rewrite the save method of the product model. You could check for any error flag that you set in your observer and then prevent the save from that point. Something like this:
function save()
{
if( $error_detection )
{
return $this;
}
else
{
return parent::save();
}
}

One of the possible approaches can be this one as well - it's a hack though
Mage::app()->getRequest()->setPost("YOUR_KEY",false);

Related

Get websiteId from observer event

Given that I have multiple websites in my Magento instance, how do I identify the website where a particular event happened? For example, observing the checkout_cart_add_product_complete event lets me catch all Add to Cart events. Let's say I wanted to get the website Id of the website where this Add to Cart event happened, how do you I do that?
public function addToCart(Varien_Event_Observer $observer) {
$product = $observer->getEvent()->getProduct();
$websiteId = $observer->getEvent()->get ??? ();
}
I know that I can get the websiteIds of the product that was added to the cart, by doing the following
$websiteIds = $observer->getEvent()->getProduct()->getWebsiteIds();
But that is not what I want, because if the product belongs to more than one website, it will give me all the websites and not the one where the Add to Cart event happened.
Thanks
Have you tried:
Mage::app()->getStore()->getId()
within your observer? That should give you the current store id..
You can directly get the StoreId from Mage, not from the observer object.
StoreId:
Mage::app()->getStore()->getStoreId();
WebsiteId:
Mage::app()->getStore()->getWebsiteId();

How prevent PrestaShop from updating product quantities after order validation

I am new to prestashop and I am trying to make a payment module where I need to duplicate an order for statistical issues. My problem is that the duplicate order also substracts from product stock and I need to know where, after an order validation, does prestashop update stock to avoid calling the corresponding function. In few words, I call validateOrder() twice but I need that StockAvailable be updated once.
By the way, I examined the whole validateOrder() function searching for the update section / function but I haven't been able to find it.
The only related code that I have been able to find was this:
// updates stock in shops
if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
{
$product_list = $order->getProducts();
foreach ($product_list as $product)
{
// if the available quantities depends on the physical stock
if (StockAvailable::dependsOnStock($product['product_id']))
{
// synchronizes
StockAvailable::synchronize($product['product_id'], $order->id_shop);
}
}
}
but this is only works when advanced stock management is enabled.
Thanks.
The code you're looking for is located in the OrderDetail::create method which uses OrderDetail::checkProductStock which in turn calls StockAvailable::updateQuantity
$update_quantity = StockAvailable::updateQuantity(...
What's interesting is that before updating the quantity there's this condition
if (!StockAvailable::dependsOnStock($product['id_product']))
I suggest that you override this method and return true when you need to.
You might set a global flag before duplicating the order and then check that flag and if it's true return true to prevent updating the stock.
the override code in override/classes/stock/StockAvailable.php
class StockAvailable extends StockAvailableCore
{
public static function dependsOnStock($id_product, $id_shop = null)
{
$no_quantity_update = isset($GLOBALS['no_quantity_update']) && $GLOBALS['no_quantity_update'];
if ($no_quantity_update)
return true;
else return parent::dependsOnStock($id_product, $id_shop = null);
}
}
To make this override effective, delete the file cache/class_index.php to refresh the overrides list
Your module code:
//set to false just to be sure
$GLOBALS['no_quantity_update'] = false;
$this->module->validateOrder($cart->id, Configuration...
//set to true to prevent quantity update
$GLOBALS['no_quantity_update'] = true;
$this->module->validateOrder($cart->id, Configuration...
You can modify directly on the core code but that's not recommended

Magento custom price rules for product

Hello i need to use magento to sell a product that will have custom price rules.
the rule will depend by the quantity of this product sold.
I know that magento can make special rule, if a customer by ++ quantity of this product, but me i need to apply different rule and i cant find the way.
For example, product bought from customers first 100 times, price is :100$
product bought 200-500 times, price is 400$
500-1000times, product is 800$
1000 - > product is fixed price 1000$
is it possible for magento to do this?
Thank you
You can do this by creating a module that uses the checkout_cart_product_add_after and checkout_cart_update_items_after observers.
Then inside your observer class you can put the following function in to set the price:
public function yourAddToCartFunction($observer) {
if ($p = $observer->getQuoteItem()->getParentItem()) {
$discount_amount = $your_discount_logic;
$p->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #configs
} else {
$p = $observer->getQuoteItem();
$discount_amount = $your_discount_logic;
$p->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #simple products
}
}
You will more than likely need a different function for the cart update observer call. The one above is for the cart_add_after event. The function for the cart update is nearly identical except you have to go through the cart object to get the logic.
public function yourCartUpdateFunction($observer) {
$cart = $observer->cart;
$quote = $cart->getQuote();
foreach($quote->getAllVisibleItems() as $item) {
if ($p = $item->getParentItem()) {
$discount_amount = $your_discount_logic;
$p->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #configs
} else {
$discount_amount = $your_discount_logic;
$item->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #simple products
}
}
}
The reason you would want the second function is if the logic needs to happen again if the cart is updated. If the price is going to stick forever no matter what happens once it is in the cart, you can probably exclude the second function.
MASSIVE EDIT:
Ok this is how I would do this. I am assuming you are starting with a basic, functional module.
The first thing you want to do is setup your observers in your modules config.xml file. In this case you are going to use be using the checkout_cart_product_add_after and checkout_cart_update_items_after observers. You will set that up by adding the following to your config.xml file:
<config>
...
<global>
...
<events>
<checkout_cart_product_add_after>
<observers>
<call_this_something_unique>
<type>singleton</type>
<class>Ocaff_Custompricing_Model_Cart_Observer</class>
<method>calculateAddToCart</method>
</call_this_something_unique>
</observers>
</checkout_cart_product_add_after>
<checkout_cart_update_items_after>
<observers>
<call_this_something_unique_2>
<type>singleton</type>
<class>Ocaff_Custompricing_Model_Cart_Observer</class>
<method>calculateCartUpdate</method>
</call_this_something_unique_2>
</observers>
</checkout_cart_update_items_after>
</events>
...
</global>
...
</config>
Basically what this does is it tells Magento that when the checkout_cart_product_add_after event is triggered, run the calculateAddToCart method of the Ocaff_Custompricing_Model_Cart_Observer class and also to run the calculateCartUpdate when the checkout_cart_update_items_after event is triggered.
Ok now that you have your configuration put together you can create your model. In the case of this example the class would be located in the /app/code/local/Ocaff/Custompricing/Model/Cart/Observer.php file (however you will put the class that matches your module)
Ok now in your Observer.php file you are going to put the following code:
<?php
class Ocaff_Custompricing_Model_Cart_Observer {
public function calculateAddToCart($observer) {
if ($p = $observer->getQuoteItem()->getParentItem()) {
$discount_amount = Mage::helper('custompricing')->calculatePrice($p);
$p->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #configs
} else {
$p = $observer->getQuoteItem();
$discount_amount = Mage::helper('custompricing')->calculatePrice($p);
$p->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #simple products
}
}
public function calculateCartUpdate($observer) {
$cart = $observer->cart;
$quote = $cart->getQuote();
foreach($quote->getAllVisibleItems() as $item) {
if ($p = $item->getParentItem()) {
$discount_amount = Mage::helper('custompricing')->calculatePrice($prpoduct);
$p->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #configs
} else {
$discount_amount = Mage::helper('custompricing')->calculatePrice($p);
$item->setCustomPrice($discount_amount)->setOriginalCustomPrice($discount_amount); #simple products
}
}
}
}
Ok a few notes here. both functions take a single paramater that I have called observer (and pretty much everywhere else calls it that too). This is a single variable that holds all the information that Magento passes along to all the functions that handle the event. In the case of the calculateAddToCart function we are only grabbing the Quote Item (which is confusing since in checkout with Magento there are quite a few different objects that kind of all represent the same thing if you are looking at it from 50,000 feet). What this function does is takes the quote item and determines if the product that was added to cart is a simple or configurable product and takes that appropiate action.
The calculateCartUpdate function basically does the exact same thing, but it has to instead iterate through all the products in the cart since in Magento you update the entire cart all at once, not every line item.
In both functions you will notice that I am updating the Custom Price and the Original Custom Price field. I am not 100% sure why you have to update both (and really why ask questions if it works :))
Ok now you may or may not notice that I have done something a little less than conventional for figuring out the $discount_amount variable. I am calling a helper function to get the price that the product should be set to. I put this into a helper because you may want to access that logic elsewhere in the system and I generally prefer to put that kind of logic into a Helper (in fact I am pretty sure you are going to want to use this on the product and category pages since you are modifying the way pricing is working for some products and customers get mad when prices change up unexpectadly).
Ok now onto the helper function. Since I am not 100% sure what logic you really want in the end, we are just going to keep this function simple and have it return $2. This will make everything that goes into the cart $2 no matter what its price actually is. This is going to be where you figure out your logic for pricing.
<?php
class Ocaff_Custompricing_Helper_Data {
public function calculatePrice($product=null) {
// Your custom logic will go here. Whatever number you come up with and return will be the price that the item is set to
return 2;
}
}
For the most part this should get you about 90% of where you want to be. Try this out and post questions and I'll do my best to help you.
Another Edit: Activating Helpers in config.xml
Put this code inside your config.xml file right after your observers block detailed above. This will tell Magento the location of the helper files for your module.
<helpers>
<custompricing>
<class>Ocaff_Custompricing_Helper</class>
</custompricing>
</helpers>
This is a tier prices feature, you don't need to create a catalog price rule. Check in the product edit page when you manage your products in the backend, the tab Prices and see the option "Tier Prices"

Magento - customer_save_after always fired twice

I am using the customer_save_after event in magento, and all is working fine apart from 1 annoying thing - it is always fired twice.
There are no other modules rewriting this and I can find no other reason for this happening. When I look through all of the events getting fired at this time and this event is definately getting fired twice.
Anyone explain this?
I am writing a web service that hooks into this and its turning out to be quite inefficient to duplicate things.
I've noticed this double-save behaviour too. The way to prevent issue with your observer is to set a flag in the request that can be checked e.g.
if(Mage::registry('customer_save_observer_executed')){
return $this; //this method has already been executed once in this request (see comment below)
}
...execute arbitrary code here....
/* Customer Addresses seem to call the before_save event twice,
* so we need to set a variable so we only process it once, otherwise we get duplicates
*/
Mage::register('customer_save_observer_executed',true);
I ran into this as well and did a stack trace in the observer for each method, and can tell you at least ONE reason why it fires twice (there may be others):
When a new user creates an account, createPostAction() runs when the form is submitted. This action does a save() on the customer.
THEN, after the customer has been created, setCustomerAsLoggedIn() is called by createPostAction(). This in turn calls setCustomer(), which has this little bit of code:
if ((!$customer->isConfirmationRequired()) && $customer->getConfirmation()) {
$customer->setConfirmation(null)->save(); // here is the second save
$customer->setIsJustConfirmed(true);
}
Those are the two save()s which dispatch the save event. I only know this for sure for account creation in Magento 1.5. I doubt if it gets fired twice when creating users in the Admin area, or when a user edit's their information... but I don't know for sure.
I hope this helps!
Be careful with Jonathans solution, 'customer_save_observer_executed' stays in the session, so event will not be fired again in the browser session. So it's generally a bad idea, because it will not allow to register two or more customers in a row(actually, it will, but events will not be fired)
I suggest the following solution:
public function customerRegister(Varien_Event_Observer $observer)
{
$customer = $observer->getEvent()->getCustomer();
if (!$customer->getId())
return $this;
if(Mage::registry('customer_save_observer_executed_'.$customer->getId()))
return $this;
//your code goes here
Mage::register('customer_save_observer_executed_'.$customer->getId(),true);
}
I used a static var:
private static $_handleCustomerFirstSearchCounter = 1;
public function Savest($observer) {
if (self::$_handleCustomerFirstSearchCounter > 1) {
return $this;
}
$customerData = Mage::getSingleton('customer/session')->getCustomer();
$model = Mage::getModel('customerst/customerst')
->setQueryText(Mage::app()->getRequest()->getParam('q'))
->setCustomerId($customerData->getId())
->setCustomerName($customerData->getName())
->save();
self::$_handleCustomerFirstSearchCounter++;
}
The difference between these 2 events is one of them can't get customer info, while the other can. So the solution is
public function email_CustomerRegister(Varien_Event_Observer $observer){
$customer = Mage::getSingleton('customer/session')->getCustomer();
$customer_email = $customer->getEmail();
if(empty($customer_email)){
return;
}
// do something
}

How do I edit a product attribute using PHP during a cart checkout in Magento?

I have a two part question about customizing my Magento store.
When someone buys a downloadable product, I want to generate a licence code and include it in the invoice.
I have added a product attribute called ‘license_code’ to my product’s default attribute set and I want to set its value with php when a customer checks out.
What is the event to observe that will let me access the products in the cart just after they are purchased but before the invoice is created?
I also need to know what script to use to set a product’s attribute value during that event.
Thank you for your help!
Possible events are sales_order_place_before or sales_convert_quote_*.
You cannot save your 'license_code' attribute because that will affect all products, a product does not store it's values when ordered. Instead a better idea would be to manipulate the options of an order item.
function salesConvertQuoteItemToOrderItem(Varien_Event_Observer $observer)
{
$orderItem = $observer->getOrderItem();
$options = $orderItem->getProductOptions();
$options['licence_code'] = YOUR-DOWNLOADABLE-CODE-HERE;
$orderItem->setProductOptions($options);
}
Retrieving the code later is essentially the same process with getProductOptions(), the order item objects are already used on the order view pages so are easy to find and use in your theme.
Ok I think I got it figured out.
I set up my event observers as follows:
<events>
<sales_order_item_save_before>
<observers>
<downloadable_observer>
<class>Licensing_Catalog_Model_Observer</class>
<method>generate_licenses</method>
</downloadable_observer>
</observers>
</sales_order_item_save_before>
</events>
and then my observing function as:
public function generate_licenses($observer)
{
$orderItem = $observer->getEvent()->getItem();
$options = $orderItem->getProductOptions();
$options['licence_code'] = 'YOUR-DOWNLOADABLE-CODE-HERE';
$orderItem->setProductOptions($options);
return $this;
}
Thank you so much for the help, clockworkgeek!

Categories