4

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;
        }
    }
}
Jonathan Day
  • 18,519
  • 10
  • 84
  • 137
Hervé Guétin
  • 4,392
  • 4
  • 29
  • 36

5 Answers5

15

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.

Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • definitely worth a try. I looked through the `_beforeSave()` methods of Mage_Catalog_Model_Product, its parents and associated resource classes and they aren't checking for any Exceptions before proceeding with the save :( – Jonathan Day Mar 25 '11 at 01:48
  • Guys, I tried that and it "fails" because it throws an exception on a beautiful error report page instead of reloading the product edit page with the "You totally failed" message. Many thanks anyway. I will keep on digging and let you know if I'm successful! – Hervé Guétin Mar 25 '11 at 08:32
  • @vrnet I looked at the problem a bit more and updated the answer with more information. If you move your validation to the catalog_product_save_before event you should be set. – Alana Storm Mar 25 '11 at 14:52
  • vrnet::getHappiness('applauses')->thankAlanTonsOfTimes(); - Man this is huge! Thanks so much! The only nano downside is that the data in the product edition are not kept when reloading. The admin must retype all info... He/she will be careful for the next product :) Thanks again. – Hervé Guétin Mar 25 '11 at 17:54
  • @vrnet To preserve the data you can call `Mage::getSingleton('adminhtml/session')->set[MODULE]Data($this->getData());` before throwing the exception in your Model, then call `$form->setValues(Mage::getSingleton('adminhtml/session')->get[MODULE]Data());` at the end of your `_prepareForm` function in Form.php – ABailiss Nov 24 '11 at 11:44
2

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

Community
  • 1
  • 1
Jonathan Day
  • 18,519
  • 10
  • 84
  • 137
  • Hey Jonathan - thanks a lot for that but it seems that Magento doesn't care about the no-dispatch. I have dumped the Mage::app()->getFrontController()->getAction()->getFlag($action) and it properly returns Array ( [save] => Array ( [check_url_settings] => 1 [no-dispatch] => 1 ) ). But the product save still happens... Arg. – Hervé Guétin Mar 25 '11 at 09:05
1

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;
}
0

One of the possible approaches can be this one as well - it's a hack though

Mage::app()->getRequest()->setPost("YOUR_KEY",false);

srgb
  • 4,783
  • 6
  • 29
  • 45
0

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();
    }
}
Josh
  • 2,820
  • 23
  • 15
  • 2
    I would be very wary of rewriting the Product model... Many of the custom modules rewrite that model and it ends up causing lots of conflicts. Avoid if possible! – Jonathan Day Mar 25 '11 at 01:47
  • Yeah, that's a very good point. It was just the first thought that came to mind, since I don't think Magento will catch a thrown exception in this case. Though I could be wrong about that. – Josh Mar 25 '11 at 02:35
  • I agree too and won't go on that path :) – Hervé Guétin Mar 25 '11 at 08:33