Note: If you've been editing products by PHP code, then re-index them in the admin after, save your self hours trying to work out why they not showing like me below …
I am going round in circles trying to work out how is_salable
is set for a product, and thus work out why my products are now showing.
There's only one place in the code I can find that sets it:
$salable = $this->isAvailable();
but I can't work out how or where in gets this from, as when I follow isAvailable
it just seems to loop back around ….
/app/code/core/Mage/Catalog/Model/Product.php
public function isSalable()
{
Mage::dispatchEvent('catalog_product_is_salable_before', array(
'product' => $this
));
$salable = $this->isAvailable();
$object = new Varien_Object(array(
'product' => $this,
'is_salable' => $salable
));
Mage::dispatchEvent('catalog_product_is_salable_after', array(
'product' => $this,
'salable' => $object
));
return $object->getIsSalable();
}
following $this->isAvailable() from here it goes a few lines:
**public function isAvailable()
{
return $this->getTypeInstance(true)->isSalable($this);
}**
this then calls app/code/core/Mage/Catalog/Model/Product/Type/Configurable.php's isSalable
public function isSalable($product = null)
{
$salable = parent::isSalable($product);
if ($salable !== false) {
$salable = false;
if (!is_null($product)) {
$this->setStoreFilter($product->getStoreId(), $product);
}
foreach ($this->getUsedProducts(null, $product) as $child) {
if ($child->isSalable()) {
$salable = true;
break;
}
}
}
return $salable;
}
which calls the parent: /app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php's isSalable :
public function isSalable($product = null)
{
$salable = $this->getProduct($product)->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_ENABLED;
if ($salable && $this->getProduct($product)->hasData('is_salable')) {
$salable = $this->getProduct($product)->getData('is_salable');
}
elseif ($salable && $this->isComposite()) {
$salable = null;
}
return (boolean) (int) $salable;
}
which just does a has/get data call on the is_saleable value?!? Did I track that right? Where is this value coming from?
I issued a recursive grep on my installation for is_salable, surely this should show any lines where it is set but I don't see any right away:
grep -r is_salable *
app/code/core/Mage/CatalogInventory/Model/Stock/Status.php: $object = new Varien_Object(array('is_in_stock' => $product->getData('is_salable')));
app/code/core/Mage/XmlConnect/Block/Wishlist.php: $itemXmlObj->addChild('is_salable', (int)$item->getProduct()->isSalable());
app/code/core/Mage/XmlConnect/Block/Catalog/Product.php: $item->addChild('is_salable', (int)$product->isSalable());
app/code/core/Mage/XmlConnect/Block/Cart/Crosssell.php: $itemXmlObj->addChild('is_salable', 0);
app/code/core/Mage/XmlConnect/Block/Cart/Crosssell.php: $itemXmlObj->addChild('is_salable', (int)$product->isSalable());
app/code/core/Mage/Catalog/Model/Product.php: Mage::dispatchEvent('catalog_product_is_salable_before', array(
app/code/core/Mage/Catalog/Model/Product.php: 'is_salable' => $salable
app/code/core/Mage/Catalog/Model/Product.php: Mage::dispatchEvent('catalog_product_is_salable_after', array(
app/code/core/Mage/Catalog/Model/Product.php: if ($this->hasData('is_salable')) {
app/code/core/Mage/Catalog/Model/Product.php: return $this->getData('is_salable');
app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php: if ($salable && $this->getProduct($product)->hasData('is_salable')) {
app/code/core/Mage/Catalog/Model/Product/Type/Abstract.php: $salable = $this->getProduct($product)->getData('is_salable');
FOUND:
grep -r setIsSalable *
app/code/core/Mage/CatalogInventory/Model/Stock/Status.php: $product->setIsSalable($stockStatus);
app/code/core/Mage/CatalogInventory/Model/Stock/Status.php: $product->setIsSalable($status);
It was setIsSalable that I did not think/know to look for rather than just setIsSalable.
Best Answer
isAvailable()
looks like this:This means that the result of the method depends on the product type.
Each product type has a
isSalable()
method:Mage_Catalog_Model_Product_Type_Grouped::isSalable()
- for grouped productsMage_Catalog_Model_Product_Type_Configurable::isSalable()
- for configurable productsMage_Catalog_Model_Product_Type_Abstract::isSalable()
- for the rest of product types since all the product types extendMage_Catalog_Model_Product_Type_Abstract
.I think that the call
$this->getTypeInstance(true)
confuses you. The methodgetTypeInstance()
does not return an instance of the product model, but an instance of a product type.[EDIT]
For a simple product this is called
Mage_Catalog_Model_Product_Type_Grouped::isSalable()
. This method checks if the product is enabled. If not thenfalse
is returned. If it's enabled, then it checks if it has a propertyis_salable
that can be set by one of your observers.If it does not have such a property then it checks if the product type instance
$this->isComposite()
. If it is then it's not salable.For configurable products it checks if the conditions above are met and if there is a simple product associated to the configurable one that is salable (Again with the conditions above)
The same is done for the grouped product but in a different way.
In conclusion
is_salable
doesn't have to exist. But just in case you have an observer that sets that property it's taken into consideration when checking if the product can be sold.Here is an example from the core: The method
Mage_CatalogInventory_Model_Stock_Status::assignProduct()
cals$product->setIsSalable()
Same goes for
Mage_CatalogInventory_Model_Stock_Status::addStockStatusToProducts
.The last one is called by the event
catalog_product_collection_load_after
.