*/ class Mage_CatalogIndex_Model_Price extends Mage_Core_Model_Abstract { protected function _construct() { $this->_init('catalogindex/price'); $this->_getResource()->setStoreId(Mage::app()->getStore()->getId()); $this->_getResource()->setRate(Mage::app()->getStore()->getCurrentCurrencyRate()); $this->_getResource()->setCustomerGroupId(Mage::getSingleton('customer/session')->getCustomerGroupId()); } public function getMaxValue($attribute, $entityIdsFilter) { return $this->_getResource()->getMaxValue($attribute, $entityIdsFilter); } public function getCount($attribute, $range, $entitySelect) { return $this->_getResource()->getCount($range, $attribute, $entitySelect); } public function getFilteredEntities($attribute, $range, $index, $entityIdsFilter) { return $this->_getResource()->getFilteredEntities($range, $index, $attribute, $entityIdsFilter); } public function applyFilterToCollection($collection, $attribute, $range, $index) { return $this->_getResource()->applyFilterToCollection($collection, $attribute, $range, $index); } public function addMinimalPrices(Mage_Catalog_Model_Resource_Product_Collection $collection) { $minimalPrices = $this->_getResource()->getMinimalPrices($collection->getLoadedIds()); foreach ($minimalPrices as $row) { $item = $collection->getItemById($row['entity_id']); if ($item) { $item->setData('minimal_price', $row['value']); $item->setData('minimal_tax_class_id', $row['tax_class_id']); } } } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogIndex * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Index resource model abstraction * * @category Mage * @package Mage_CatalogIndex * @author Magento Core Team */ class Mage_CatalogIndex_Model_Resource_Abstract extends Mage_Core_Model_Resource_Db_Abstract { protected $_storeId = 0; protected $_websiteId = null; /** * Initialize model * * @return Mage_Core_Model_Resource_Db_Abstract */ protected function _construct() { return parent::_construct(); } /** * storeId setter * * @param int $storeId */ public function setStoreId($storeId) { $this->_storeId = $storeId; } /** * storeId getter * * @return int */ public function getStoreId() { return $this->_storeId; } /** * websiteId getter * * @return int */ public function getWebsiteId() { if (is_null($this->_websiteId)) { $result = Mage::app()->getStore($this->getStoreId())->getWebsiteId(); $this->_websiteId = $result; } return $this->_websiteId; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogIndex * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Price index resource model * * @category Mage * @package Mage_CatalogIndex * @author Magento Core Team */ class Mage_CatalogIndex_Model_Resource_Price extends Mage_CatalogIndex_Model_Resource_Abstract { /** * Enter description here ... * * @var unknown */ protected $_rate = 1; /** * Enter description here ... * * @var unknown */ protected $_customerGroupId; /** * Enter description here ... * * @var unknown */ protected $_taxRates = null; /** * Enter description here ... * */ protected function _construct() { $this->_init('catalogindex/price', 'index_id'); } /** * Enter description here ... * * @param unknown_type $rate */ public function setRate($rate) { $this->_rate = $rate; } /** * Enter description here ... * * @return unknown */ public function getRate() { if (!$this->_rate) { $this->_rate = 1; } return $this->_rate; } /** * Enter description here ... * * @param unknown_type $customerGroupId */ public function setCustomerGroupId($customerGroupId) { $this->_customerGroupId = $customerGroupId; } /** * Enter description here ... * * @return unknown */ public function getCustomerGroupId() { return $this->_customerGroupId; } /** * Enter description here ... * * @param unknown_type $attribute * @param unknown_type $entitySelect * @return unknown */ public function getMaxValue($attribute, $entitySelect) { $select = clone $entitySelect; $select->reset(Zend_Db_Select::COLUMNS); $select->reset(Zend_Db_Select::ORDER); $select->reset(Zend_Db_Select::LIMIT_COUNT); $select->reset(Zend_Db_Select::LIMIT_OFFSET); $response = new Varien_Object(); $response->setAdditionalCalculations(array()); $select->join(array('price_table'=>$this->getMainTable()), 'price_table.entity_id=e.entity_id', array()); if ($attribute->getAttributeCode() == 'price') { $select->where('price_table.customer_group_id = ?', $this->getCustomerGroupId()); $args = array( 'select'=>$select, 'table'=>'price_table', 'store_id'=>$this->getStoreId(), 'response_object'=>$response, ); Mage::dispatchEvent('catalogindex_prepare_price_select', $args); } $select ->columns("MAX(price_table.value".implode('', $response->getAdditionalCalculations()).")") ->where('price_table.website_id = ?', $this->getWebsiteId()) ->where('price_table.attribute_id = ?', $attribute->getId()); return $this->_getReadAdapter()->fetchOne($select)*$this->getRate(); } /** * Enter description here ... * * @param unknown_type $range * @param unknown_type $attribute * @param unknown_type $entitySelect * @return unknown */ public function getCount($range, $attribute, $entitySelect) { $select = clone $entitySelect; $select->reset(Zend_Db_Select::COLUMNS); $select->reset(Zend_Db_Select::ORDER); $select->reset(Zend_Db_Select::LIMIT_COUNT); $select->reset(Zend_Db_Select::LIMIT_OFFSET); $select->join(array('price_table'=>$this->getMainTable()), 'price_table.entity_id=e.entity_id', array()); $response = new Varien_Object(); $response->setAdditionalCalculations(array()); if ($attribute->getAttributeCode() == 'price') { $select->where('price_table.customer_group_id = ?', $this->getCustomerGroupId()); $args = array( 'select'=>$select, 'table'=>'price_table', 'store_id'=>$this->getStoreId(), 'response_object'=>$response, ); Mage::dispatchEvent('catalogindex_prepare_price_select', $args); } $fields = array('count'=>'COUNT(DISTINCT price_table.entity_id)', 'range'=>"FLOOR(((price_table.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()})/{$range})+1"); $select->columns($fields) ->group('range') ->where('price_table.website_id = ?', $this->getWebsiteId()) ->where('price_table.attribute_id = ?', $attribute->getId()); $result = $this->_getReadAdapter()->fetchAll($select); $counts = array(); foreach ($result as $row) { $counts[$row['range']] = $row['count']; } return $counts; } /** * Enter description here ... * * @param unknown_type $range * @param unknown_type $index * @param unknown_type $attribute * @param unknown_type $entityIdsFilter * @param unknown_type $tableName * @return unknown */ public function getFilteredEntities($range, $index, $attribute, $entityIdsFilter, $tableName = 'price_table') { $select = $this->_getReadAdapter()->select(); $select->from(array($tableName=>$this->getMainTable()), $tableName . '.entity_id'); $response = new Varien_Object(); $response->setAdditionalCalculations(array()); $select ->distinct(true) ->where($tableName . '.entity_id in (?)', $entityIdsFilter) ->where($tableName . '.website_id = ?', $this->getWebsiteId()) ->where($tableName . '.attribute_id = ?', $attribute->getId()); if ($attribute->getAttributeCode() == 'price') { $select->where($tableName . '.customer_group_id = ?', $this->getCustomerGroupId()); $args = array( 'select'=>$select, 'table'=>$tableName, 'store_id'=>$this->getStoreId(), 'response_object'=>$response, ); Mage::dispatchEvent('catalogindex_prepare_price_select', $args); } $select->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) >= ?", ($index-1)*$range); $select->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) < ?", $index*$range); return $this->_getReadAdapter()->fetchCol($select); } /** * Enter description here ... * * @param unknown_type $collection * @param unknown_type $attribute * @param unknown_type $range * @param unknown_type $index * @param unknown_type $tableName * @return Mage_CatalogIndex_Model_Resource_Price */ public function applyFilterToCollection($collection, $attribute, $range, $index, $tableName = 'price_table') { /** * Distinct required for removing duplicates in case when we have grouped products * which contain multiple rows for one product id */ $collection->getSelect()->distinct(true); $tableName = $tableName.'_'.$attribute->getAttributeCode(); $collection->getSelect()->joinLeft( array($tableName => $this->getMainTable()), $tableName .'.entity_id=e.entity_id', array() ); $response = new Varien_Object(); $response->setAdditionalCalculations(array()); $collection->getSelect() ->where($tableName . '.website_id = ?', $this->getWebsiteId()) ->where($tableName . '.attribute_id = ?', $attribute->getId()); if ($attribute->getAttributeCode() == 'price') { $collection->getSelect()->where($tableName . '.customer_group_id = ?', $this->getCustomerGroupId()); $args = array( 'select'=>$collection->getSelect(), 'table'=>$tableName, 'store_id'=>$this->getStoreId(), 'response_object'=>$response, ); Mage::dispatchEvent('catalogindex_prepare_price_select', $args); } $collection->getSelect()->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) >= ?", ($index-1)*$range); $collection->getSelect()->where("(({$tableName}.value".implode('', $response->getAdditionalCalculations()).")*{$this->getRate()}) < ?", $index*$range); return $this; } /** * Enter description here ... * * @param unknown_type $ids * @return unknown */ public function getMinimalPrices($ids) { if (!$ids) { return array(); } $select = $this->_getReadAdapter()->select(); $select->from(array('price_table'=>$this->getTable('catalogindex/minimal_price')), array('price_table.entity_id', 'value'=>"(price_table.value)", 'tax_class_id'=>'(price_table.tax_class_id)')) ->where('price_table.entity_id in (?)', $ids) ->where('price_table.website_id = ?', $this->getWebsiteId()) ->where('price_table.customer_group_id = ?', $this->getCustomerGroupId()); return $this->_getReadAdapter()->fetchAll($select); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog inventory module observer * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Observer { /** * Product qty's checked * data is valid if you check quote item qty and use singleton instance * * @deprecated after 1.4.2.0-rc1 * @var array */ protected $_checkedProductsQty = array(); /** * Product qty's checked * data is valid if you check quote item qty and use singleton instance * * @var array */ protected $_checkedQuoteItems = array(); protected $_itemsForReindex = array(); /** * Array, indexed by product's id to contain stockItems of already loaded products * Some kind of singleton for product's stock item * * @var array */ protected $_stockItemsArray = array(); /** * Add stock information to product * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function addInventoryData($observer) { $product = $observer->getEvent()->getProduct(); if ($product instanceof Mage_Catalog_Model_Product) { $productId = intval($product->getId()); if (!isset($this->_stockItemsArray[$productId])) { $this->_stockItemsArray[$productId] = Mage::getModel('cataloginventory/stock_item'); } $productStockItem = $this->_stockItemsArray[$productId]; $productStockItem->assignProduct($product); } return $this; } /** * Remove stock information from static variable * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function removeInventoryData($observer) { $product = $observer->getEvent()->getProduct(); if (($product instanceof Mage_Catalog_Model_Product) && $product->getId() && isset($this->_stockItemsArray[$product->getId()])) { unset($this->_stockItemsArray[$product->getId()]); } return $this; } /** * Add information about producs stock status to collection * Used in for product collection after load * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function addStockStatusToCollection($observer) { $productCollection = $observer->getEvent()->getCollection(); if ($productCollection->hasFlag('require_stock_items')) { Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection); } else { Mage::getModel('cataloginventory/stock_status')->addStockStatusToProducts($productCollection); } return $this; } /** * Add Stock items to product collection * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function addInventoryDataToCollection($observer) { $productCollection = $observer->getEvent()->getProductCollection(); Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection); return $this; } /** * Saving product inventory data. Product qty calculated dynamically. * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function saveInventoryData($observer) { $product = $observer->getEvent()->getProduct(); if (is_null($product->getStockData())) { if ($product->getIsChangedWebsites() || $product->dataHasChangedFor('status')) { Mage::getSingleton('cataloginventory/stock_status') ->updateStatus($product->getId()); } return $this; } $item = $product->getStockItem(); if (!$item) { $item = Mage::getModel('cataloginventory/stock_item'); } $this->_prepareItemForSave($item, $product); $item->save(); return $this; } /** * Copy product inventory data (used for product duplicate functionality) * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function copyInventoryData($observer) { /** @var Mage_Catalog_Model_Product $currentProduct */ $currentProduct = $observer->getEvent()->getCurrentProduct(); /** @var Mage_Catalog_Model_Product $newProduct */ $newProduct = $observer->getEvent()->getNewProduct(); $newProduct->unsStockItem(); $stockData = array( 'use_config_min_qty' => 1, 'use_config_min_sale_qty' => 1, 'use_config_max_sale_qty' => 1, 'use_config_backorders' => 1, 'use_config_notify_stock_qty'=> 1 ); if ($currentStockItem = $currentProduct->getStockItem()) { $stockData += array( 'use_config_enable_qty_inc' => $currentStockItem->getData('use_config_enable_qty_inc'), 'enable_qty_increments' => $currentStockItem->getData('enable_qty_increments'), 'use_config_qty_increments' => $currentStockItem->getData('use_config_qty_increments'), 'qty_increments' => $currentStockItem->getData('qty_increments'), ); } $newProduct->setStockData($stockData); return $this; } /** * Prepare stock item data for save * * @param Mage_CatalogInventory_Model_Stock_Item $item * @param Mage_Catalog_Model_Product $product * @return Mage_CatalogInventory_Model_Observer */ protected function _prepareItemForSave($item, $product) { $item->addData($product->getStockData()) ->setProduct($product) ->setProductId($product->getId()) ->setStockId($item->getStockId()); if (!is_null($product->getData('stock_data/min_qty')) && is_null($product->getData('stock_data/use_config_min_qty'))) { $item->setData('use_config_min_qty', false); } if (!is_null($product->getData('stock_data/min_sale_qty')) && is_null($product->getData('stock_data/use_config_min_sale_qty'))) { $item->setData('use_config_min_sale_qty', false); } if (!is_null($product->getData('stock_data/max_sale_qty')) && is_null($product->getData('stock_data/use_config_max_sale_qty'))) { $item->setData('use_config_max_sale_qty', false); } if (!is_null($product->getData('stock_data/backorders')) && is_null($product->getData('stock_data/use_config_backorders'))) { $item->setData('use_config_backorders', false); } if (!is_null($product->getData('stock_data/notify_stock_qty')) && is_null($product->getData('stock_data/use_config_notify_stock_qty'))) { $item->setData('use_config_notify_stock_qty', false); } $originalQty = $product->getData('stock_data/original_inventory_qty'); if (strlen($originalQty)>0) { $item->setQtyCorrection($item->getQty()-$originalQty); } if (!is_null($product->getData('stock_data/enable_qty_increments')) && is_null($product->getData('stock_data/use_config_enable_qty_inc'))) { $item->setData('use_config_enable_qty_inc', false); } if (!is_null($product->getData('stock_data/qty_increments')) && is_null($product->getData('stock_data/use_config_qty_increments'))) { $item->setData('use_config_qty_increments', false); } return $this; } /** * Removes error statuses from quote and item, set by this observer * * @param Mage_Sales_Model_Quote_Item $item * @param int $code * @return Mage_CatalogInventory_Model_Observer */ protected function _removeErrorsFromQuoteAndItem($item, $code) { if ($item->getHasError()) { $params = array( 'origin' => 'cataloginventory', 'code' => $code ); $item->removeErrorInfosByParams($params); } $quote = $item->getQuote(); $quoteItems = $quote->getItemsCollection(); $canRemoveErrorFromQuote = true; foreach ($quoteItems as $quoteItem) { if ($quoteItem->getItemId() == $item->getItemId()) { continue; } $errorInfos = $quoteItem->getErrorInfos(); foreach ($errorInfos as $errorInfo) { if ($errorInfo['code'] == $code) { $canRemoveErrorFromQuote = false; break; } } if (!$canRemoveErrorFromQuote) { break; } } if ($quote->getHasError() && $canRemoveErrorFromQuote) { $params = array( 'origin' => 'cataloginventory', 'code' => $code ); $quote->removeErrorInfosByParams(null, $params); } return $this; } /** * Check product inventory data when quote item quantity declaring * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function checkQuoteItemQty($observer) { $quoteItem = $observer->getEvent()->getItem(); /* @var $quoteItem Mage_Sales_Model_Quote_Item */ if (!$quoteItem || !$quoteItem->getProductId() || !$quoteItem->getQuote() || $quoteItem->getQuote()->getIsSuperMode()) { return $this; } /** * Get Qty */ $qty = $quoteItem->getQty(); /** * Check item for options */ if (($options = $quoteItem->getQtyOptions()) && $qty > 0) { $qty = $quoteItem->getProduct()->getTypeInstance(true)->prepareQuoteItemQty($qty, $quoteItem->getProduct()); $quoteItem->setData('qty', $qty); $stockItem = $quoteItem->getProduct()->getStockItem(); if ($stockItem) { $result = $stockItem->checkQtyIncrements($qty); if ($result->getHasError()) { $quoteItem->addErrorInfo( 'cataloginventory', Mage_CatalogInventory_Helper_Data::ERROR_QTY_INCREMENTS, $result->getMessage() ); $quoteItem->getQuote()->addErrorInfo( $result->getQuoteMessageIndex(), 'cataloginventory', Mage_CatalogInventory_Helper_Data::ERROR_QTY_INCREMENTS, $result->getQuoteMessage() ); } else { // Delete error from item and its quote, if it was set due to qty problems $this->_removeErrorsFromQuoteAndItem( $quoteItem, Mage_CatalogInventory_Helper_Data::ERROR_QTY_INCREMENTS ); } } foreach ($options as $option) { $optionValue = $option->getValue(); /* @var $option Mage_Sales_Model_Quote_Item_Option */ $optionQty = $qty * $optionValue; $increaseOptionQty = ($quoteItem->getQtyToAdd() ? $quoteItem->getQtyToAdd() : $qty) * $optionValue; $stockItem = $option->getProduct()->getStockItem(); if ($quoteItem->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) { $stockItem->setProductName($quoteItem->getName()); } /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ if (!$stockItem instanceof Mage_CatalogInventory_Model_Stock_Item) { Mage::throwException( Mage::helper('cataloginventory')->__('The stock item for Product in option is not valid.') ); } /** * define that stock item is child for composite product */ $stockItem->setIsChildItem(true); /** * don't check qty increments value for option product */ $stockItem->setSuppressCheckQtyIncrements(true); $qtyForCheck = $this->_getQuoteItemQtyForCheck( $option->getProduct()->getId(), $quoteItem->getId(), $increaseOptionQty ); $result = $stockItem->checkQuoteItemQty($optionQty, $qtyForCheck, $optionValue); if (!is_null($result->getItemIsQtyDecimal())) { $option->setIsQtyDecimal($result->getItemIsQtyDecimal()); } if ($result->getHasQtyOptionUpdate()) { $option->setHasQtyOptionUpdate(true); $quoteItem->updateQtyOption($option, $result->getOrigQty()); $option->setValue($result->getOrigQty()); /** * if option's qty was updates we also need to update quote item qty */ $quoteItem->setData('qty', intval($qty)); } if (!is_null($result->getMessage())) { $option->setMessage($result->getMessage()); $quoteItem->setMessage($result->getMessage()); } if (!is_null($result->getItemBackorders())) { $option->setBackorders($result->getItemBackorders()); } if ($result->getHasError()) { $option->setHasError(true); $quoteItem->addErrorInfo( 'cataloginventory', Mage_CatalogInventory_Helper_Data::ERROR_QTY, $result->getQuoteMessage() ); $quoteItem->getQuote()->addErrorInfo( $result->getQuoteMessageIndex(), 'cataloginventory', Mage_CatalogInventory_Helper_Data::ERROR_QTY, $result->getQuoteMessage() ); } else { // Delete error from item and its quote, if it was set due to qty lack $this->_removeErrorsFromQuoteAndItem($quoteItem, Mage_CatalogInventory_Helper_Data::ERROR_QTY); } $stockItem->unsIsChildItem(); } } else { $stockItem = $quoteItem->getProduct()->getStockItem(); /* @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ if (!$stockItem instanceof Mage_CatalogInventory_Model_Stock_Item) { Mage::throwException(Mage::helper('cataloginventory')->__('The stock item for Product is not valid.')); } /** * When we work with subitem (as subproduct of bundle or configurable product) */ if ($quoteItem->getParentItem()) { $rowQty = $quoteItem->getParentItem()->getQty() * $qty; /** * we are using 0 because original qty was processed */ $qtyForCheck = $this->_getQuoteItemQtyForCheck( $quoteItem->getProduct()->getId(), $quoteItem->getId(), 0 ); } else { $increaseQty = $quoteItem->getQtyToAdd() ? $quoteItem->getQtyToAdd() : $qty; $rowQty = $qty; $qtyForCheck = $this->_getQuoteItemQtyForCheck( $quoteItem->getProduct()->getId(), $quoteItem->getId(), $increaseQty ); } $productTypeCustomOption = $quoteItem->getProduct()->getCustomOption('product_type'); if (!is_null($productTypeCustomOption)) { // Check if product related to current item is a part of grouped product if ($productTypeCustomOption->getValue() == Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE) { $stockItem->setIsChildItem(true); } } $result = $stockItem->checkQuoteItemQty($rowQty, $qtyForCheck, $qty); if ($stockItem->hasIsChildItem()) { $stockItem->unsIsChildItem(); } if (!is_null($result->getItemIsQtyDecimal())) { $quoteItem->setIsQtyDecimal($result->getItemIsQtyDecimal()); if ($quoteItem->getParentItem()) { $quoteItem->getParentItem()->setIsQtyDecimal($result->getItemIsQtyDecimal()); } } /** * Just base (parent) item qty can be changed * qty of child products are declared just during add process * exception for updating also managed by product type */ if ($result->getHasQtyOptionUpdate() && (!$quoteItem->getParentItem() || $quoteItem->getParentItem()->getProduct()->getTypeInstance(true) ->getForceChildItemQtyChanges($quoteItem->getParentItem()->getProduct()) ) ) { $quoteItem->setData('qty', $result->getOrigQty()); } if (!is_null($result->getItemUseOldQty())) { $quoteItem->setUseOldQty($result->getItemUseOldQty()); } if (!is_null($result->getMessage())) { $quoteItem->setMessage($result->getMessage()); if ($quoteItem->getParentItem()) { $quoteItem->getParentItem()->setMessage($result->getMessage()); } } if (!is_null($result->getItemBackorders())) { $quoteItem->setBackorders($result->getItemBackorders()); } if ($result->getHasError()) { $quoteItem->addErrorInfo( 'cataloginventory', Mage_CatalogInventory_Helper_Data::ERROR_QTY, $result->getMessage() ); $quoteItem->getQuote()->addErrorInfo( $result->getQuoteMessageIndex(), 'cataloginventory', Mage_CatalogInventory_Helper_Data::ERROR_QTY, $result->getQuoteMessage() ); } else { // Delete error from item and its quote, if it was set due to qty lack $this->_removeErrorsFromQuoteAndItem($quoteItem, Mage_CatalogInventory_Helper_Data::ERROR_QTY); } } return $this; } /** * Get product qty includes information from all quote items * Need be used only in sungleton mode * * @deprecated after 1.4.2.0-rc1 * @param int $productId * @param float $itemQty */ protected function _getProductQtyForCheck($productId, $itemQty) { $qty = $itemQty; if (isset($this->_checkedProductsQty[$productId])) { $qty += $this->_checkedProductsQty[$productId]; } $this->_checkedProductsQty[$productId] = $qty; return $qty; } /** * Get product qty includes information from all quote items * Need be used only in sungleton mode * * @param int $productId * @param int $quoteItemId * @param float $itemQty * @return int */ protected function _getQuoteItemQtyForCheck($productId, $quoteItemId, $itemQty) { $qty = $itemQty; if (isset($this->_checkedQuoteItems[$productId]['qty']) && !in_array($quoteItemId, $this->_checkedQuoteItems[$productId]['items'])) { $qty += $this->_checkedQuoteItems[$productId]['qty']; } $this->_checkedQuoteItems[$productId]['qty'] = $qty; $this->_checkedQuoteItems[$productId]['items'][] = $quoteItemId; return $qty; } /** * Subtract qtys of quote item products after multishipping checkout * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function checkoutAllSubmitAfter(Varien_Event_Observer $observer) { $quote = $observer->getEvent()->getQuote(); if (!$quote->getInventoryProcessed()) { $this->subtractQuoteInventory($observer); $this->reindexQuoteInventory($observer); } return $this; } /** * Subtract quote items qtys from stock items related with quote items products. * * Used before order placing to make order save/place transaction smaller * Also called after every successful order placement to ensure subtraction of inventory * * @param Varien_Event_Observer $observer */ public function subtractQuoteInventory(Varien_Event_Observer $observer) { $quote = $observer->getEvent()->getQuote(); // Maybe we've already processed this quote in some event during order placement // e.g. call in event 'sales_model_service_quote_submit_before' and later in 'checkout_submit_all_after' if ($quote->getInventoryProcessed()) { return; } $items = $this->_getProductsQty($quote->getAllItems()); /** * Remember items */ $this->_itemsForReindex = Mage::getSingleton('cataloginventory/stock')->registerProductsSale($items); $quote->setInventoryProcessed(true); return $this; } /** * Revert quote items inventory data (cover not success order place case) * @param $observer */ public function revertQuoteInventory($observer) { $quote = $observer->getEvent()->getQuote(); $items = $this->_getProductsQty($quote->getAllItems()); Mage::getSingleton('cataloginventory/stock')->revertProductsSale($items); // Clear flag, so if order placement retried again with success - it will be processed $quote->setInventoryProcessed(false); } /** * Adds stock item qty to $items (creates new entry or increments existing one) * $items is array with following structure: * array( * $productId => array( * 'qty' => $qty, * 'item' => $stockItems|null * ) * ) * * @param Mage_Sales_Model_Quote_Item $quoteItem * @param array &$items */ protected function _addItemToQtyArray($quoteItem, &$items) { $productId = $quoteItem->getProductId(); if (!$productId) return; if (isset($items[$productId])) { $items[$productId]['qty'] += $quoteItem->getTotalQty(); } else { $stockItem = null; if ($quoteItem->getProduct()) { $stockItem = $quoteItem->getProduct()->getStockItem(); } $items[$productId] = array( 'item' => $stockItem, 'qty' => $quoteItem->getTotalQty() ); } } /** * Prepare array with information about used product qty and product stock item * result is: * array( * $productId => array( * 'qty' => $qty, * 'item' => $stockItems|null * ) * ) * @param array $relatedItems * @return array */ protected function _getProductsQty($relatedItems) { $items = array(); foreach ($relatedItems as $item) { $productId = $item->getProductId(); if (!$productId) { continue; } $children = $item->getChildrenItems(); if ($children) { foreach ($children as $childItem) { $this->_addItemToQtyArray($childItem, $items); } } else { $this->_addItemToQtyArray($item, $items); } } return $items; } /** * Refresh stock index for specific stock items after succesful order placement * * @param $observer */ public function reindexQuoteInventory($observer) { // Reindex quote ids $quote = $observer->getEvent()->getQuote(); $productIds = array(); foreach ($quote->getAllItems() as $item) { $productIds[$item->getProductId()] = $item->getProductId(); $children = $item->getChildrenItems(); if ($children) { foreach ($children as $childItem) { $productIds[$childItem->getProductId()] = $childItem->getProductId(); } } } if( count($productIds)) { Mage::getResourceSingleton('cataloginventory/indexer_stock')->reindexProducts($productIds); } // Reindex previously remembered items $productIds = array(); foreach ($this->_itemsForReindex as $item) { $item->save(); $productIds[] = $item->getProductId(); } Mage::getResourceSingleton('catalog/product_indexer_price')->reindexProductIds($productIds); $this->_itemsForReindex = array(); // Clear list of remembered items - we don't need it anymore return $this; } /** * Return creditmemo items qty to stock * * @param Varien_Event_Observer $observer */ public function refundOrderInventory($observer) { /* @var $creditmemo Mage_Sales_Model_Order_Creditmemo */ $creditmemo = $observer->getEvent()->getCreditmemo(); $items = array(); foreach ($creditmemo->getAllItems() as $item) { /* @var $item Mage_Sales_Model_Order_Creditmemo_Item */ $return = false; if ($item->hasBackToStock()) { if ($item->getBackToStock() && $item->getQty()) { $return = true; } } elseif (Mage::helper('cataloginventory')->isAutoReturnEnabled()) { $return = true; } if ($return) { $parentOrderId = $item->getOrderItem()->getParentItemId(); /* @var $parentItem Mage_Sales_Model_Order_Creditmemo_Item */ $parentItem = $parentOrderId ? $creditmemo->getItemByOrderId($parentOrderId) : false; $qty = $parentItem ? ($parentItem->getQty() * $item->getQty()) : $item->getQty(); if (isset($items[$item->getProductId()])) { $items[$item->getProductId()]['qty'] += $qty; } else { $items[$item->getProductId()] = array( 'qty' => $qty, 'item'=> null, ); } } } Mage::getSingleton('cataloginventory/stock')->revertProductsSale($items); } /** * Cancel order item * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function cancelOrderItem($observer) { $item = $observer->getEvent()->getItem(); $children = $item->getChildrenItems(); $qty = $item->getQtyOrdered() - max($item->getQtyShipped(), $item->getQtyInvoiced()) - $item->getQtyCanceled(); if ($item->getId() && ($productId = $item->getProductId()) && empty($children) && $qty) { Mage::getSingleton('cataloginventory/stock')->backItemQty($productId, $qty); } return $this; } /** * Update items stock status and low stock date. * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function updateItemsStockUponConfigChange($observer) { Mage::getResourceSingleton('cataloginventory/stock')->updateSetOutOfStock(); Mage::getResourceSingleton('cataloginventory/stock')->updateSetInStock(); Mage::getResourceSingleton('cataloginventory/stock')->updateLowStockDate(); return $this; } /** * Update Only product status observer * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function productStatusUpdate(Varien_Event_Observer $observer) { $productId = $observer->getEvent()->getProductId(); Mage::getSingleton('cataloginventory/stock_status') ->updateStatus($productId); return $this; } /** * Catalog Product website update * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function catalogProductWebsiteUpdate(Varien_Event_Observer $observer) { $websiteIds = $observer->getEvent()->getWebsiteIds(); $productIds = $observer->getEvent()->getProductIds(); foreach ($websiteIds as $websiteId) { foreach ($productIds as $productId) { Mage::getSingleton('cataloginventory/stock_status') ->updateStatus($productId, null, $websiteId); } } return $this; } /** * Add stock status to prepare index select * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function addStockStatusToPrepareIndexSelect(Varien_Event_Observer $observer) { $website = $observer->getEvent()->getWebsite(); $select = $observer->getEvent()->getSelect(); Mage::getSingleton('cataloginventory/stock_status') ->addStockStatusToSelect($select, $website); return $this; } /** * Add stock status limitation to catalog product price index select object * * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function prepareCatalogProductIndexSelect(Varien_Event_Observer $observer) { $select = $observer->getEvent()->getSelect(); $entity = $observer->getEvent()->getEntityField(); $website = $observer->getEvent()->getWebsiteField(); Mage::getSingleton('cataloginventory/stock_status') ->prepareCatalogProductIndexSelect($select, $entity, $website); return $this; } /** * Lock DB rows for order products * * We need do it for resolving problems with inventory on placing * some orders in one time * @deprecated after 1.4 * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function lockOrderInventoryData($observer) { $order = $observer->getEvent()->getOrder(); $productIds = array(); /** * Do lock only for new order */ if ($order->getId()) { return $this; } if ($order) { foreach ($order->getAllItems() as $item) { $productIds[] = $item->getProductId(); } } if (!empty($productIds)) { Mage::getSingleton('cataloginventory/stock')->lockProductItems($productIds); } return $this; } /** * Register saving order item * * @deprecated after 1.4 * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function createOrderItem($observer) { $item = $observer->getEvent()->getItem(); /** * Before creating order item need subtract ordered qty from product stock */ $children = $item->getChildrenItems(); if (!$item->getId() && empty($children)) { Mage::getSingleton('cataloginventory/stock')->registerItemSale($item); } return $this; } /** * Back refunded item qty to stock * * @deprecated after 1.4 * @param Varien_Event_Observer $observer * @return Mage_CatalogInventory_Model_Observer */ public function refundOrderItem($observer) { $item = $observer->getEvent()->getCreditmemoItem(); if ($item->getId() && $item->getBackToStock() && ($productId = $item->getProductId()) && ($qty = $item->getQty()) ) { Mage::getSingleton('cataloginventory/stock')->backItemQty($productId, $qty); } return $this; } /** * Reindex all events of product-massAction type * * @param Varien_Event_Observer $observer */ public function reindexProductsMassAction($observer) { Mage::getSingleton('index/indexer')->indexEvents( Mage_Catalog_Model_Product::ENTITY, Mage_Index_Model_Event::TYPE_MASS_ACTION ); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Stock resource model * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Resource_Stock extends Mage_Core_Model_Resource_Db_Abstract { /** * Is initialized configuration flag * * @var boolean */ protected $_isConfig; /** * Manage Stock flag * * @var boolean */ protected $_isConfigManageStock; /** * Backorders * * @var boolean */ protected $_isConfigBackorders; /** * Minimum quantity allowed in shopping card * * @var int */ protected $_configMinQty; /** * Product types that could have quantities * * @var array */ protected $_configTypeIds; /** * Notify for quantity below _configNotifyStockQty value * * @var int */ protected $_configNotifyStockQty; /** * Ctalog Inventory Stock instance * * @var Mage_CatalogInventory_Model_Stock */ protected $_stock; /** * Define main table and initialize connection * */ protected function _construct() { $this->_init('cataloginventory/stock', 'stock_id'); } /** * Lock product items * * @param Mage_CatalogInventory_Model_Stock $stock * @param int|array $productIds * @return Mage_CatalogInventory_Model_Resource_Stock */ public function lockProductItems($stock, $productIds) { $itemTable = $this->getTable('cataloginventory/stock_item'); $select = $this->_getWriteAdapter()->select() ->from($itemTable) ->where('stock_id=?', $stock->getId()) ->where('product_id IN(?)', $productIds) ->forUpdate(true); /** * We use write adapter for resolving problems with replication */ $this->_getWriteAdapter()->query($select); return $this; } /** * Get stock items data for requested products * * @param Mage_CatalogInventory_Model_Stock $stock * @param array $productIds * @param bool $lockRows * @return array */ public function getProductsStock($stock, $productIds, $lockRows = false) { if (empty($productIds)) { return array(); } $itemTable = $this->getTable('cataloginventory/stock_item'); $productTable = $this->getTable('catalog/product'); $select = $this->_getWriteAdapter()->select() ->from(array('si' => $itemTable)) ->join(array('p' => $productTable), 'p.entity_id=si.product_id', array('type_id')) ->where('stock_id=?', $stock->getId()) ->where('product_id IN(?)', $productIds) ->forUpdate($lockRows); return $this->_getWriteAdapter()->fetchAll($select); } /** * Correct particular stock products qty based on operator * * @param Mage_CatalogInventory_Model_Stock $stock * @param array $productQtys * @param string $operator +/- * @return Mage_CatalogInventory_Model_Resource_Stock */ public function correctItemsQty($stock, $productQtys, $operator = '-') { if (empty($productQtys)) { return $this; } $adapter = $this->_getWriteAdapter(); $conditions = array(); foreach ($productQtys as $productId => $qty) { $case = $adapter->quoteInto('?', $productId); $result = $adapter->quoteInto("qty{$operator}?", $qty); $conditions[$case] = $result; } $value = $adapter->getCaseSql('product_id', $conditions, 'qty'); $where = array( 'product_id IN (?)' => array_keys($productQtys), 'stock_id = ?' => $stock->getId() ); $adapter->beginTransaction(); $adapter->update($this->getTable('cataloginventory/stock_item'), array('qty' => $value), $where); $adapter->commit(); return $this; } /** * add join to select only in stock products * * @param Mage_Catalog_Model_Resource_Product_Link_Product_Collection $collection * @return Mage_CatalogInventory_Model_Resource_Stock */ public function setInStockFilterToCollection($collection) { $manageStock = Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK); $cond = array( '{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=1 AND {{table}}.is_in_stock=1', '{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=0', ); if ($manageStock) { $cond[] = '{{table}}.use_config_manage_stock = 1 AND {{table}}.is_in_stock=1'; } else { $cond[] = '{{table}}.use_config_manage_stock = 1'; } $collection->joinField( 'inventory_in_stock', 'cataloginventory/stock_item', 'is_in_stock', 'product_id=entity_id', '(' . join(') OR (', $cond) . ')' ); return $this; } /** * Load some inventory configuration settings * */ protected function _initConfig() { if (!$this->_isConfig) { $this->_isConfig = true; $this->_isConfigManageStock = (int)Mage::getStoreConfigFlag(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK); $this->_isConfigBackorders = (int)Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_BACKORDERS); $this->_configMinQty = (int)Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MIN_QTY); $this->_configNotifyStockQty = (int)Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_NOTIFY_STOCK_QTY); $this->_configTypeIds = array_keys(Mage::helper('catalogInventory')->getIsQtyTypeIds(true)); $this->_stock = Mage::getModel('cataloginventory/stock'); } } /** * Set items out of stock basing on their quantities and config settings * */ public function updateSetOutOfStock() { $this->_initConfig(); $adapter = $this->_getWriteAdapter(); $values = array( 'is_in_stock' => 0, 'stock_status_changed_auto' => 1 ); $select = $adapter->select() ->from($this->getTable('catalog/product'), 'entity_id') ->where('type_id IN(?)', $this->_configTypeIds); $where = sprintf('stock_id = %1$d' . ' AND is_in_stock = 1' . ' AND ((use_config_manage_stock = 1 AND 1 = %2$d) OR (use_config_manage_stock = 0 AND manage_stock = 1))' . ' AND ((use_config_backorders = 1 AND %3$d = %4$d) OR (use_config_backorders = 0 AND backorders = %3$d))' . ' AND ((use_config_min_qty = 1 AND qty <= %5$d) OR (use_config_min_qty = 0 AND qty <= min_qty))' . ' AND product_id IN (%6$s)', $this->_stock->getId(), $this->_isConfigManageStock, Mage_CatalogInventory_Model_Stock::BACKORDERS_NO, $this->_isConfigBackorders, $this->_configMinQty, $select->assemble() ); $adapter->update($this->getTable('cataloginventory/stock_item'), $values, $where); } /** * Set items in stock basing on their quantities and config settings * */ public function updateSetInStock() { $this->_initConfig(); $adapter = $this->_getWriteAdapter(); $values = array( 'is_in_stock' => 1, ); $select = $adapter->select() ->from($this->getTable('catalog/product'), 'entity_id') ->where('type_id IN(?)', $this->_configTypeIds); $where = sprintf('stock_id = %1$d' . ' AND is_in_stock = 0' . ' AND stock_status_changed_auto = 1' . ' AND ((use_config_manage_stock = 1 AND 1 = %2$d) OR (use_config_manage_stock = 0 AND manage_stock = 1))' . ' AND ((use_config_min_qty = 1 AND qty > %3$d) OR (use_config_min_qty = 0 AND qty > min_qty))' . ' AND product_id IN (%4$s)', $this->_stock->getId(), $this->_isConfigManageStock, $this->_configMinQty, $select->assemble() ); $adapter->update($this->getTable('cataloginventory/stock_item'), $values, $where); } /** * Update items low stock date basing on their quantities and config settings * */ public function updateLowStockDate() { $this->_initConfig(); $adapter = $this->_getWriteAdapter(); $condition = $adapter->quoteInto('(use_config_notify_stock_qty = 1 AND qty < ?)', $this->_configNotifyStockQty) . ' OR (use_config_notify_stock_qty = 0 AND qty < notify_stock_qty)'; $currentDbTime = $adapter->quoteInto('?', $this->formatDate(true)); $conditionalDate = $adapter->getCheckSql($condition, $currentDbTime, 'NULL'); $value = array( 'low_stock_date' => new Zend_Db_Expr($conditionalDate), ); $select = $adapter->select() ->from($this->getTable('catalog/product'), 'entity_id') ->where('type_id IN(?)', $this->_configTypeIds); $where = sprintf('stock_id = %1$d' . ' AND ((use_config_manage_stock = 1 AND 1 = %2$d) OR (use_config_manage_stock = 0 AND manage_stock = 1))' . ' AND product_id IN (%3$s)', $this->_stock->getId(), $this->_isConfigManageStock, $select->assemble() ); $adapter->update($this->getTable('cataloginventory/stock_item'), $value, $where); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Stock item resource model * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Resource_Stock_Item extends Mage_Core_Model_Resource_Db_Abstract { /** * Define main table and initialize connection * */ protected function _construct() { $this->_init('cataloginventory/stock_item', 'item_id'); } /** * Loading stock item data by product * * @param Mage_CatalogInventory_Model_Stock_Item $item * @param int $productId * @return Mage_CatalogInventory_Model_Resource_Stock_Item */ public function loadByProductId(Mage_CatalogInventory_Model_Stock_Item $item, $productId) { $select = $this->_getLoadSelect('product_id', $productId, $item) ->where('stock_id = :stock_id'); $data = $this->_getReadAdapter()->fetchRow($select, array(':stock_id' => $item->getStockId())); if ($data) { $item->setData($data); } $this->_afterLoad($item); return $this; } /** * Retrieve select object and join it to product entity table to get type ids * * @param string $field * @param mixed $value * @param Mage_CatalogInventory_Model_Stock_Item $object * @return Varien_Db_Select */ protected function _getLoadSelect($field, $value, $object) { $select = parent::_getLoadSelect($field, $value, $object) ->join(array('p' => $this->getTable('catalog/product')), 'product_id=p.entity_id', array('type_id') ); return $select; } /** * Add join for catalog in stock field to product collection * * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection * @return Mage_CatalogInventory_Model_Resource_Stock_Item */ public function addCatalogInventoryToProductCollection($productCollection) { $adapter = $this->_getReadAdapter(); $isManageStock = (int)Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK); $stockExpr = $adapter->getCheckSql('cisi.use_config_manage_stock = 1', $isManageStock, 'cisi.manage_stock'); $stockExpr = $adapter->getCheckSql("({$stockExpr} = 1)", 'cisi.is_in_stock', '1'); $productCollection->joinTable( array('cisi' => 'cataloginventory/stock_item'), 'product_id=entity_id', array( 'is_saleable' => new Zend_Db_Expr($stockExpr), 'inventory_in_stock' => 'is_in_stock' ), null, 'left' ); return $this; } /** * Use qty correction for qty column update * * @param Mage_Core_Model_Abstract $object * @param string $table * @return array */ protected function _prepareDataForTable(Varien_Object $object, $table) { $data = parent::_prepareDataForTable($object, $table); if (!$object->isObjectNew() && $object->getQtyCorrection()) { $qty = abs($object->getQtyCorrection()); if ($object->getQtyCorrection() < 0) { $data['qty'] = new Zend_Db_Expr('qty-' . $qty); } else { $data['qty'] = new Zend_Db_Expr('qty+' . $object->getQtyCorrection()); } } return $data; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Stock item collection resource model * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Resource_Stock_Item_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Initialize resource model * */ protected function _construct() { $this->_init('cataloginventory/stock_item'); } /** * Add stock filter to collection * * @param mixed $stock * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ public function addStockFilter($stock) { if ($stock instanceof Mage_CatalogInventory_Model_Stock) { $this->addFieldToFilter('main_table.stock_id', $stock->getId()); } else { $this->addFieldToFilter('main_table.stock_id', $stock); } return $this; } /** * Add product filter to collection * * @param array $products * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ public function addProductsFilter($products) { $productIds = array(); foreach ($products as $product) { if ($product instanceof Mage_Catalog_Model_Product) { $productIds[] = $product->getId(); } else { $productIds[] = $product; } } if (empty($productIds)) { $productIds[] = false; $this->_setIsLoaded(true); } $this->addFieldToFilter('main_table.product_id', array('in' => $productIds)); return $this; } /** * Join Stock Status to collection * * @param int $storeId * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ public function joinStockStatus($storeId = null) { $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); $this->getSelect()->joinLeft( array('status_table' => $this->getTable('cataloginventory/stock_status')), 'main_table.product_id=status_table.product_id' . ' AND main_table.stock_id=status_table.stock_id' . $this->getConnection()->quoteInto(' AND status_table.website_id=?', $websiteId), array('stock_status') ); return $this; } /** * Add Managed Stock products filter to collection * * @param boolean $isStockManagedInConfig * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ public function addManagedFilter($isStockManagedInConfig) { if ($isStockManagedInConfig) { $this->getSelect()->where('(manage_stock = 1 OR use_config_manage_stock = 1)'); } else { $this->addFieldToFilter('manage_stock', 1); } return $this; } /** * Add filter by quantity to collection * * @param string $comparsionMethod * @param float $qty * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ public function addQtyFilter($comparsionMethod, $qty) { $methods = array( '<' => 'lt', '>' => 'gt', '=' => 'eq', '<=' => 'lteq', '>=' => 'gteq', '<>' => 'neq' ); if (!isset($methods[$comparsionMethod])) { Mage::throwException( Mage::helper('cataloginventory')->__('%s is not a correct comparsion method.', $comparsionMethod) ); } return $this->addFieldToFilter('main_table.qty', array($methods[$comparsionMethod] => $qty)); } /** * Initialize select object * * @return Mage_CatalogInventory_Model_Resource_Stock_Item_Collection */ protected function _initSelect() { return parent::_initSelect()->getSelect() ->join( array('cp_table' => $this->getTable('catalog/product')), 'main_table.product_id = cp_table.entity_id', array('type_id') ); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * CatalogInventory Stock Status per website Resource Model * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Resource_Stock_Status extends Mage_Core_Model_Resource_Db_Abstract { /** * Resource model initialization * */ protected function _construct() { $this->_init('cataloginventory/stock_status', 'product_id'); } /** * Save Product Status per website * * @param Mage_CatalogInventory_Model_Stock_Status $object * @param int $productId * @param int $status * @param float $qty * @param int $stockId * @param int|null $websiteId * @return Mage_CatalogInventory_Model_Resource_Stock_Status */ public function saveProductStatus(Mage_CatalogInventory_Model_Stock_Status $object, $productId, $status, $qty = 0, $stockId = 1, $websiteId = null) { $websites = array_keys($object->getWebsites($websiteId)); $adapter = $this->_getWriteAdapter(); foreach ($websites as $websiteId) { $select = $adapter->select() ->from($this->getMainTable()) ->where('product_id = :product_id') ->where('website_id = :website_id') ->where('stock_id = :stock_id'); $bind = array( ':product_id' => $productId, ':website_id' => $websiteId, ':stock_id' => $stockId ); if ($row = $adapter->fetchRow($select, $bind)) { $bind = array( 'qty' => $qty, 'stock_status' => $status ); $where = array( $adapter->quoteInto('product_id=?', (int)$row['product_id']), $adapter->quoteInto('website_id=?', (int)$row['website_id']), $adapter->quoteInto('stock_id=?', (int)$row['stock_id']), ); $adapter->update($this->getMainTable(), $bind, $where); } else { $bind = array( 'product_id' => $productId, 'website_id' => $websiteId, 'stock_id' => $stockId, 'qty' => $qty, 'stock_status' => $status ); $adapter->insert($this->getMainTable(), $bind); } } return $this; } /** * Retrieve product status * Return array as key product id, value - stock status * * @param int|array $productIds * @param int $websiteId * @param int $stockId * @return array */ public function getProductStatus($productIds, $websiteId, $stockId = 1) { if (!is_array($productIds)) { $productIds = array($productIds); } $select = $this->_getReadAdapter()->select() ->from($this->getMainTable(), array('product_id', 'stock_status')) ->where('product_id IN(?)', $productIds) ->where('stock_id=?', (int)$stockId) ->where('website_id=?', (int)$websiteId); return $this->_getReadAdapter()->fetchPairs($select); } /** * Retrieve product(s) data array * * @param int|array $productIds * @param int $websiteId * @param int $stockId * @return array */ public function getProductData($productIds, $websiteId, $stockId = 1) { if (!is_array($productIds)) { $productIds = array($productIds); } $result = array(); $select = $this->_getReadAdapter()->select() ->from($this->getMainTable()) ->where('product_id IN(?)', $productIds) ->where('stock_id=?', (int)$stockId) ->where('website_id=?', (int)$websiteId); $result = $this->_getReadAdapter()->fetchAssoc($select); return $result; } /** * Retrieve websites and default stores * Return array as key website_id, value store_id * * @return array */ public function getWebsiteStores() { $select = Mage::getModel('core/website')->getDefaultStoresSelect(false); return $this->_getReadAdapter()->fetchPairs($select); } /** * Retrieve Product Type * * @param array|int $productIds * @return array */ public function getProductsType($productIds) { if (!is_array($productIds)) { $productIds = array($productIds); } $select = $this->_getReadAdapter()->select() ->from( array('e' => $this->getTable('catalog/product')), array('entity_id', 'type_id') ) ->where('entity_id IN(?)', $productIds); return $this->_getReadAdapter()->fetchPairs($select); } /** * Retrieve Product part Collection array * Return array as key product id, value product type * * @param int $lastEntityId * @param int $limit * @return array */ public function getProductCollection($lastEntityId = 0, $limit = 1000) { $select = $this->_getReadAdapter()->select() ->from( array('e' => $this->getTable('catalog/product')), array('entity_id', 'type_id') ) ->order('entity_id ASC') ->where('entity_id > :entity_id') ->limit($limit); return $this->_getReadAdapter()->fetchPairs($select, array(':entity_id' => $lastEntityId)); } /** * Add stock status to prepare index select * * @param Varien_Db_Select $select * @param Mage_Core_Model_Website $website * @return Mage_CatalogInventory_Model_Resource_Stock_Status */ public function addStockStatusToSelect(Varien_Db_Select $select, Mage_Core_Model_Website $website) { $websiteId = $website->getId(); $select->joinLeft( array('stock_status' => $this->getMainTable()), 'e.entity_id = stock_status.product_id AND stock_status.website_id='.$websiteId, array('salable' => 'stock_status.stock_status') ); return $this; } /** * Add stock status limitation to catalog product price index select object * * @param Varien_Db_Select $select * @param string|Zend_Db_Expr $entityField * @param string|Zend_Db_Expr $websiteField * @return Mage_CatalogInventory_Model_Resource_Stock_Status */ public function prepareCatalogProductIndexSelect(Varien_Db_Select $select, $entityField, $websiteField) { $select->join( array('ciss' => $this->getMainTable()), "ciss.product_id = {$entityField} AND ciss.website_id = {$websiteField}", array() ); $select->where('ciss.stock_status = ?', Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK); return $this; } /** * Add only is in stock products filter to product collection * * @param Mage_Catalog_Model_Resource_Product_Collection $collection * @return Mage_CatalogInventory_Model_Resource_Stock_Status */ public function addIsInStockFilterToCollection($collection) { $websiteId = Mage::app()->getStore($collection->getStoreId())->getWebsiteId(); $joinCondition = $this->_getReadAdapter() ->quoteInto('e.entity_id = stock_status_index.product_id' . ' AND stock_status_index.website_id = ?', $websiteId ); $joinCondition .= $this->_getReadAdapter()->quoteInto( ' AND stock_status_index.stock_id = ?', Mage_CatalogInventory_Model_Stock::DEFAULT_STOCK_ID ); $collection->getSelect() ->join( array('stock_status_index' => $this->getMainTable()), $joinCondition, array() ) ->where('stock_status_index.stock_status=?', Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Stock model * * @method Mage_CatalogInventory_Model_Resource_Stock _getResource() * @method Mage_CatalogInventory_Model_Resource_Stock getResource() * @method string getStockName() * @method Mage_CatalogInventory_Model_Stock setStockName(string $value) * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Stock extends Mage_Core_Model_Abstract { const BACKORDERS_NO = 0; const BACKORDERS_YES_NONOTIFY = 1; const BACKORDERS_YES_NOTIFY = 2; /* deprecated */ const BACKORDERS_BELOW = 1; const BACKORDERS_YES = 2; const STOCK_OUT_OF_STOCK = 0; const STOCK_IN_STOCK = 1; const DEFAULT_STOCK_ID = 1; protected function _construct() { $this->_init('cataloginventory/stock'); } /** * Retrieve stock identifier * * @return int */ public function getId() { return self::DEFAULT_STOCK_ID; } /** * Add stock item objects to products * * @param collection $products * @return Mage_CatalogInventory_Model_Stock */ public function addItemsToProducts($productCollection) { $items = $this->getItemCollection() ->addProductsFilter($productCollection) ->joinStockStatus($productCollection->getStoreId()) ->load(); $stockItems = array(); foreach ($items as $item) { $stockItems[$item->getProductId()] = $item; } foreach ($productCollection as $product) { if (isset($stockItems[$product->getId()])) { $stockItems[$product->getId()]->assignProduct($product); } } return $this; } /** * Retrieve items collection object with stock filter * * @return unknown */ public function getItemCollection() { return Mage::getResourceModel('cataloginventory/stock_item_collection') ->addStockFilter($this->getId()); } /** * Prepare array($productId=>$qty) based on array($productId => array('qty'=>$qty, 'item'=>$stockItem)) * * @param array $items */ protected function _prepareProductQtys($items) { $qtys = array(); foreach ($items as $productId => $item) { if (empty($item['item'])) { $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); } else { $stockItem = $item['item']; } $canSubtractQty = $stockItem->getId() && $stockItem->canSubtractQty(); if ($canSubtractQty && Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { $qtys[$productId] = $item['qty']; } } return $qtys; } /** * Subtract product qtys from stock. * Return array of items that require full save * * @param array $items * @return array */ public function registerProductsSale($items) { $qtys = $this->_prepareProductQtys($items); $item = Mage::getModel('cataloginventory/stock_item'); $this->_getResource()->beginTransaction(); $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true); $fullSaveItems = array(); foreach ($stockInfo as $itemInfo) { $item->setData($itemInfo); if (!$item->checkQty($qtys[$item->getProductId()])) { $this->_getResource()->commit(); Mage::throwException(Mage::helper('cataloginventory')->__('Not all products are available in the requested quantity')); } $item->subtractQty($qtys[$item->getProductId()]); if (!$item->verifyStock() || $item->verifyNotification()) { $fullSaveItems[] = clone $item; } } $this->_getResource()->correctItemsQty($this, $qtys, '-'); $this->_getResource()->commit(); return $fullSaveItems; } /** * * @param unknown_type $items */ public function revertProductsSale($items) { $qtys = $this->_prepareProductQtys($items); $this->_getResource()->correctItemsQty($this, $qtys, '+'); return $this; } /** * Subtract ordered qty for product * * @param Varien_Object $item * @return Mage_CatalogInventory_Model_Stock */ public function registerItemSale(Varien_Object $item) { $productId = $item->getProductId(); if ($productId) { $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); if (Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { if ($item->getStoreId()) { $stockItem->setStoreId($item->getStoreId()); } if ($stockItem->checkQty($item->getQtyOrdered()) || Mage::app()->getStore()->isAdmin()) { $stockItem->subtractQty($item->getQtyOrdered()); $stockItem->save(); } } } else { Mage::throwException(Mage::helper('cataloginventory')->__('Cannot specify product identifier for the order item.')); } return $this; } /** * Get back to stock (when order is canceled or whatever else) * * @param int $productId * @param numeric $qty * @return Mage_CatalogInventory_Model_Stock */ public function backItemQty($productId, $qty) { $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($productId); if ($stockItem->getId() && Mage::helper('catalogInventory')->isQty($stockItem->getTypeId())) { $stockItem->addQty($qty); if ($stockItem->getCanBackInStock() && $stockItem->getQty() > $stockItem->getMinQty()) { $stockItem->setIsInStock(true) ->setStockStatusChangedAutomaticallyFlag(true); } $stockItem->save(); } return $this; } /** * Lock stock items for product ids array * * @param array $productIds * @return Mage_CatalogInventory_Model_Stock */ public function lockProductItems($productIds) { $this->_getResource()->lockProductItems($this, $productIds); return $this; } /** * Adds filtering for collection to return only in stock products * * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection $collection * @return Mage_CatalogInventory_Model_Stock $this */ public function addInStockFilterToCollection($collection) { $this->getResource()->setInStockFilterToCollection($collection); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog Inventory Stock Model * * @method Mage_CatalogInventory_Model_Resource_Stock_Item _getResource() * @method Mage_CatalogInventory_Model_Resource_Stock_Item getResource() * @method Mage_CatalogInventory_Model_Stock_Item setProductId(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setStockId(int $value) * @method float getQty() * @method Mage_CatalogInventory_Model_Stock_Item setQty(float $value) * @method Mage_CatalogInventory_Model_Stock_Item setMinQty(float $value) * @method int getUseConfigMinQty() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigMinQty(int $value) * @method int getIsQtyDecimal() * @method Mage_CatalogInventory_Model_Stock_Item setIsQtyDecimal(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setBackorders(int $value) * @method int getUseConfigBackorders() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigBackorders(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setMinSaleQty(float $value) * @method int getUseConfigMinSaleQty() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigMinSaleQty(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setMaxSaleQty(float $value) * @method int getUseConfigMaxSaleQty() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigMaxSaleQty(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setIsInStock(int $value) * @method string getLowStockDate() * @method Mage_CatalogInventory_Model_Stock_Item setLowStockDate(string $value) * @method Mage_CatalogInventory_Model_Stock_Item setNotifyStockQty(float $value) * @method int getUseConfigNotifyStockQty() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigNotifyStockQty(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setManageStock(int $value) * @method int getUseConfigManageStock() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigManageStock(int $value) * @method int getStockStatusChangedAutomatically() * @method Mage_CatalogInventory_Model_Stock_Item setStockStatusChangedAutomatically(int $value) * @method int getUseConfigQtyIncrements() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigQtyIncrements(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setQtyIncrements(float $value) * @method int getUseConfigEnableQtyIncrements() * @method Mage_CatalogInventory_Model_Stock_Item setUseConfigEnableQtyIncrements(int $value) * @method Mage_CatalogInventory_Model_Stock_Item setEnableQtyIncrements(int $value) * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Stock_Item extends Mage_Core_Model_Abstract { const XML_PATH_GLOBAL = 'cataloginventory/options/'; const XML_PATH_CAN_SUBTRACT = 'cataloginventory/options/can_subtract'; const XML_PATH_CAN_BACK_IN_STOCK = 'cataloginventory/options/can_back_in_stock'; const XML_PATH_ITEM = 'cataloginventory/item_options/'; const XML_PATH_MIN_QTY = 'cataloginventory/item_options/min_qty'; const XML_PATH_MIN_SALE_QTY = 'cataloginventory/item_options/min_sale_qty'; const XML_PATH_MAX_SALE_QTY = 'cataloginventory/item_options/max_sale_qty'; const XML_PATH_BACKORDERS = 'cataloginventory/item_options/backorders'; const XML_PATH_NOTIFY_STOCK_QTY = 'cataloginventory/item_options/notify_stock_qty'; const XML_PATH_MANAGE_STOCK = 'cataloginventory/item_options/manage_stock'; const XML_PATH_ENABLE_QTY_INCREMENTS = 'cataloginventory/item_options/enable_qty_increments'; const XML_PATH_QTY_INCREMENTS = 'cataloginventory/item_options/qty_increments'; const ENTITY = 'cataloginventory_stock_item'; /** * @var array */ private $_minSaleQtyCache = array(); /** * @var float|false */ protected $_qtyIncrements; /** * Prefix of model events names * * @var string */ protected $_eventPrefix = 'cataloginventory_stock_item'; /** * Parameter name in event * * In observe method you can use $observer->getEvent()->getItem() in this case * * @var string */ protected $_eventObject = 'item'; /** * Associated product instance * * @var Mage_Catalog_Model_Product */ protected $_productInstance = null; /** * Customer group id * * @var int|null */ protected $_customerGroupId; /** * Whether index events should be processed immediately * * @var bool */ protected $_processIndexEvents = true; /** * Initialize resource model * */ protected function _construct() { $this->_init('cataloginventory/stock_item'); } /** * Init mapping array of short fields to * its full names * * @resturn Varien_Object */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = array( 'stock_status_changed_automatically' => 'stock_status_changed_auto', 'use_config_enable_qty_increments' => 'use_config_enable_qty_inc' ); } /** * Retrieve stock identifier * * @todo multi stock * @return int */ public function getStockId() { return 1; } /** * Retrieve Product Id data wrapper * * @return int */ public function getProductId() { return $this->_getData('product_id'); } /** * Load item data by product * * @param mixed $product * @return Mage_CatalogInventory_Model_Stock_Item */ public function loadByProduct($product) { if ($product instanceof Mage_Catalog_Model_Product) { $product = $product->getId(); } $this->_getResource()->loadByProductId($this, $product); $this->setOrigData(); return $this; } /** * Subtract quote item quantity * * @param decimal $qty * @return Mage_CatalogInventory_Model_Stock_Item */ public function subtractQty($qty) { if ($this->canSubtractQty()) { $this->setQty($this->getQty()-$qty); } return $this; } /** * Check if is possible subtract value from item qty * * @return bool */ public function canSubtractQty() { return $this->getManageStock() && Mage::getStoreConfigFlag(self::XML_PATH_CAN_SUBTRACT); } /** * Add quantity process * * @param float $qty * @return Mage_CatalogInventory_Model_Stock_Item */ public function addQty($qty) { if (!$this->getManageStock()) { return $this; } $config = Mage::getStoreConfigFlag(self::XML_PATH_CAN_SUBTRACT); if (!$config) { return $this; } $this->setQty($this->getQty()+$qty); return $this; } /** * Retrieve Store Id (product or current) * * @return int */ public function getStoreId() { $storeId = $this->getData('store_id'); if (is_null($storeId)) { $storeId = Mage::app()->getStore()->getId(); $this->setData('store_id', $storeId); } return $storeId; } /** * Adding stock data to product * * @param Mage_Catalog_Model_Product $product * @return Mage_CatalogInventory_Model_Stock_Item */ public function assignProduct(Mage_Catalog_Model_Product $product) { if (!$this->getId() || !$this->getProductId()) { $this->_getResource()->loadByProductId($this, $product->getId()); $this->setOrigData(); } $this->setProduct($product); $product->setStockItem($this); $product->setIsInStock($this->getIsInStock()); Mage::getSingleton('cataloginventory/stock_status') ->assignProduct($product, $this->getStockId(), $this->getStockStatus()); return $this; } /** * Retrieve minimal quantity available for item status in stock * * @return float */ public function getMinQty() { return (float)($this->getUseConfigMinQty() ? Mage::getStoreConfig(self::XML_PATH_MIN_QTY) : $this->getData('min_qty')); } /** * Getter for customer group id * * @return int */ public function getCustomerGroupId() { return $this->_customerGroupId; } /** * Setter for customer group id * * @param int Value of customer group id * @return Mage_CatalogInventory_Model_Stock_Item */ public function setCustomerGroupId($value) { $this->_customerGroupId = $value; return $this; } /** * Retrieve Minimum Qty Allowed in Shopping Cart or NULL when there is no limitation * * @return float|null */ public function getMinSaleQty() { $customerGroupId = $this->getCustomerGroupId(); if (!$customerGroupId) { $customerGroupId = Mage::app()->getStore()->isAdmin() ? Mage_Customer_Model_Group::CUST_GROUP_ALL : Mage::getSingleton('customer/session')->getCustomerGroupId(); } if (!isset($this->_minSaleQtyCache[$customerGroupId])) { $minSaleQty = $this->getUseConfigMinSaleQty() ? Mage::helper('cataloginventory/minsaleqty')->getConfigValue($customerGroupId) : $this->getData('min_sale_qty'); $this->_minSaleQtyCache[$customerGroupId] = empty($minSaleQty) ? 0 : (float)$minSaleQty; } return $this->_minSaleQtyCache[$customerGroupId] ? $this->_minSaleQtyCache[$customerGroupId] : null; } /** * Retrieve Maximum Qty Allowed in Shopping Cart data wrapper * * @return float */ public function getMaxSaleQty() { return (float)($this->getUseConfigMaxSaleQty() ? Mage::getStoreConfig(self::XML_PATH_MAX_SALE_QTY) : $this->getData('max_sale_qty')); } /** * Retrieve Notify for Quantity Below data wrapper * * @return float */ public function getNotifyStockQty() { if ($this->getUseConfigNotifyStockQty()) { return (float) Mage::getStoreConfig(self::XML_PATH_NOTIFY_STOCK_QTY); } return (float) $this->getData('notify_stock_qty'); } /** * Retrieve whether Quantity Increments is enabled * * @return bool */ public function getEnableQtyIncrements() { return $this->getUseConfigEnableQtyIncrements() ? Mage::getStoreConfigFlag(self::XML_PATH_ENABLE_QTY_INCREMENTS) : (bool)$this->getData('enable_qty_increments'); } /** * Retrieve Quantity Increments data wrapper * * @return float|false */ public function getQtyIncrements() { if ($this->_qtyIncrements === null) { if ($this->getEnableQtyIncrements()) { $this->_qtyIncrements = (float)($this->getUseConfigQtyIncrements() ? Mage::getStoreConfig(self::XML_PATH_QTY_INCREMENTS) : $this->getData('qty_increments')); if ($this->_qtyIncrements <= 0) { $this->_qtyIncrements = false; } } else { $this->_qtyIncrements = false; } } return $this->_qtyIncrements; } /** * Retrieve Default Quantity Increments data wrapper * * @deprecated since 1.7.0.0 * @return int|false */ public function getDefaultQtyIncrements() { return Mage::getStoreConfigFlag(self::XML_PATH_ENABLE_QTY_INCREMENTS) ? (int)Mage::getStoreConfig(self::XML_PATH_QTY_INCREMENTS) : false; } /** * Retrieve backorders status * * @return int */ public function getBackorders() { if ($this->getUseConfigBackorders()) { return (int) Mage::getStoreConfig(self::XML_PATH_BACKORDERS); } return $this->getData('backorders'); } /** * Retrieve Manage Stock data wrapper * * @return int */ public function getManageStock() { if ($this->getUseConfigManageStock()) { return (int) Mage::getStoreConfigFlag(self::XML_PATH_MANAGE_STOCK); } return $this->getData('manage_stock'); } /** * Retrieve can Back in stock * * @return bool */ public function getCanBackInStock() { return Mage::getStoreConfigFlag(self::XML_PATH_CAN_BACK_IN_STOCK); } /** * Check quantity * * @param decimal $qty * @exception Mage_Core_Exception * @return bool */ public function checkQty($qty) { if (!$this->getManageStock() || Mage::app()->getStore()->isAdmin()) { return true; } if ($this->getQty() - $this->getMinQty() - $qty < 0) { switch ($this->getBackorders()) { case Mage_CatalogInventory_Model_Stock::BACKORDERS_YES_NONOTIFY: case Mage_CatalogInventory_Model_Stock::BACKORDERS_YES_NOTIFY: break; default: return false; break; } } return true; } /** * Returns suggested qty that satisfies qty increments and minQty/maxQty/minSaleQty/maxSaleQty conditions * or original qty if such value does not exist * * @param int|float $qty * @return int|float */ public function suggestQty($qty) { // We do not manage stock if ($qty <= 0 || !$this->getManageStock()) { return $qty; } $qtyIncrements = (int)$this->getQtyIncrements(); // Currently only integer increments supported if ($qtyIncrements < 2) { return $qty; } $minQty = max($this->getMinSaleQty(), $qtyIncrements); $divisibleMin = ceil($minQty / $qtyIncrements) * $qtyIncrements; $maxQty = min($this->getQty() - $this->getMinQty(), $this->getMaxSaleQty()); $divisibleMax = floor($maxQty / $qtyIncrements) * $qtyIncrements; if ($qty < $minQty || $qty > $maxQty || $divisibleMin > $divisibleMax) { // Do not perform rounding for qty that does not satisfy min/max conditions to not confuse customer return $qty; } // Suggest value closest to given qty $closestDivisibleLeft = floor($qty / $qtyIncrements) * $qtyIncrements; $closestDivisibleRight = $closestDivisibleLeft + $qtyIncrements; $acceptableLeft = min(max($divisibleMin, $closestDivisibleLeft), $divisibleMax); $acceptableRight = max(min($divisibleMax, $closestDivisibleRight), $divisibleMin); return abs($acceptableLeft - $qty) < abs($acceptableRight - $qty) ? $acceptableLeft : $acceptableRight; } /** * Checking quote item quantity * * Second parameter of this method specifies quantity of this product in whole shopping cart * which should be checked for stock availability * * @param mixed $qty quantity of this item (item qty x parent item qty) * @param mixed $summaryQty quantity of this product * @param mixed $origQty original qty of item (not multiplied on parent item qty) * @return Varien_Object */ public function checkQuoteItemQty($qty, $summaryQty, $origQty = 0) { $result = new Varien_Object(); $result->setHasError(false); if (!is_numeric($qty)) { $qty = Mage::app()->getLocale()->getNumber($qty); } /** * Check quantity type */ $result->setItemIsQtyDecimal($this->getIsQtyDecimal()); if (!$this->getIsQtyDecimal()) { $result->setHasQtyOptionUpdate(true); $qty = intval($qty); /** * Adding stock data to quote item */ $result->setItemQty($qty); if (!is_numeric($qty)) { $qty = Mage::app()->getLocale()->getNumber($qty); } $origQty = intval($origQty); $result->setOrigQty($origQty); } if ($this->getMinSaleQty() && $qty < $this->getMinSaleQty()) { $result->setHasError(true) ->setMessage( Mage::helper('cataloginventory')->__('The minimum quantity allowed for purchase is %s.', $this->getMinSaleQty() * 1) ) ->setErrorCode('qty_min') ->setQuoteMessage(Mage::helper('cataloginventory')->__('Some of the products cannot be ordered in requested quantity.')) ->setQuoteMessageIndex('qty'); return $result; } if ($this->getMaxSaleQty() && $qty > $this->getMaxSaleQty()) { $result->setHasError(true) ->setMessage( Mage::helper('cataloginventory')->__('The maximum quantity allowed for purchase is %s.', $this->getMaxSaleQty() * 1) ) ->setErrorCode('qty_max') ->setQuoteMessage(Mage::helper('cataloginventory')->__('Some of the products cannot be ordered in requested quantity.')) ->setQuoteMessageIndex('qty'); return $result; } $result->addData($this->checkQtyIncrements($qty)->getData()); if ($result->getHasError()) { return $result; } if (!$this->getManageStock()) { return $result; } if (!$this->getIsInStock() && !$this->getBackorders()) { $result->setHasError(true) ->setMessage(Mage::helper('cataloginventory')->__('This product is currently out of stock.')) ->setQuoteMessage(Mage::helper('cataloginventory')->__('Some of the products are currently out of stock')) ->setQuoteMessageIndex('stock'); $result->setItemUseOldQty(true); return $result; } if (!$this->checkQty($summaryQty) || !$this->checkQty($qty)) { $message = Mage::helper('cataloginventory')->__('The requested quantity for "%s" is not available.', $this->getProductName()); $result->setHasError(true) ->setMessage($message) ->setQuoteMessage($message) ->setQuoteMessageIndex('qty'); return $result; } else { if (($this->getQty() - $summaryQty) < 0) { if ($this->getProductName()) { if ($this->getIsChildItem()) { $backorderQty = ($this->getQty() > 0) ? ($summaryQty - $this->getQty()) * 1 : $qty * 1; if ($backorderQty > $qty) { $backorderQty = $qty; } $result->setItemBackorders($backorderQty); } else { $orderedItems = $this->getOrderedItems(); $itemsLeft = ($this->getQty() > $orderedItems) ? ($this->getQty() - $orderedItems) * 1 : 0; $backorderQty = ($itemsLeft > 0) ? ($qty - $itemsLeft) * 1 : $qty * 1; if ($backorderQty > 0) { $result->setItemBackorders($backorderQty); } $this->setOrderedItems($orderedItems + $qty); } if ($this->getBackorders() == Mage_CatalogInventory_Model_Stock::BACKORDERS_YES_NOTIFY) { if (!$this->getIsChildItem()) { $result->setMessage( Mage::helper('cataloginventory')->__('This product is not available in the requested quantity. %s of the items will be backordered.', ($backorderQty * 1)) ); } else { $result->setMessage( Mage::helper('cataloginventory')->__('"%s" is not available in the requested quantity. %s of the items will be backordered.', $this->getProductName(), ($backorderQty * 1)) ); } } elseif (Mage::app()->getStore()->isAdmin()) { $result->setMessage( Mage::helper('cataloginventory')->__('The requested quantity for "%s" is not available.', $this->getProductName()) ); } } } else { if (!$this->getIsChildItem()) { $this->setOrderedItems($qty + (int)$this->getOrderedItems()); } } } return $result; } /** * Check qty increments * * @param int|float $qty * @return Varien_Object */ public function checkQtyIncrements($qty) { $result = new Varien_Object(); if ($this->getSuppressCheckQtyIncrements()) { return $result; } $qtyIncrements = $this->getQtyIncrements(); if ($qtyIncrements && (Mage::helper('core')->getExactDivision($qty, $qtyIncrements) != 0)) { $result->setHasError(true) ->setQuoteMessage( Mage::helper('cataloginventory')->__('Some of the products cannot be ordered in the requested quantity.') ) ->setErrorCode('qty_increments') ->setQuoteMessageIndex('qty'); if ($this->getIsChildItem()) { $result->setMessage( Mage::helper('cataloginventory')->__('%s is available for purchase in increments of %s only.',$this->getProductName(), $qtyIncrements * 1) ); } else { $result->setMessage( Mage::helper('cataloginventory')->__('This product is available for purchase in increments of %s only.', $qtyIncrements * 1) ); } } return $result; } /** * Add join for catalog in stock field to product collection * * @param Mage_Catalog_Model_Entity_Product_Collection $productCollection * @return Mage_CatalogInventory_Model_Stock_Item */ public function addCatalogInventoryToProductCollection($productCollection) { $this->_getResource()->addCatalogInventoryToProductCollection($productCollection); return $this; } /** * Add error to Quote Item * * @param Mage_Sales_Model_Quote_Item $item * @param string $itemError * @param string $quoteError * @param string $errorIndex * @return Mage_CatalogInventory_Model_Stock_Item */ protected function _addQuoteItemError(Mage_Sales_Model_Quote_Item $item, $itemError, $quoteError, $errorIndex='error' ) { $item->setHasError(true); $item->setMessage($itemError); $item->setQuoteMessage($quoteError); $item->setQuoteMessageIndex($errorIndex); return $this; } /** * Before save prepare process * * @return Mage_CatalogInventory_Model_Stock_Item */ protected function _beforeSave() { // see if quantity is defined for this item type $typeId = $this->getTypeId(); if ($productTypeId = $this->getProductTypeId()) { $typeId = $productTypeId; } $isQty = Mage::helper('catalogInventory')->isQty($typeId); if ($isQty) { if (!$this->verifyStock()) { $this->setIsInStock(false) ->setStockStatusChangedAutomaticallyFlag(true); } // if qty is below notify qty, update the low stock date to today date otherwise set null $this->setLowStockDate(null); if ($this->verifyNotification()) { $this->setLowStockDate(Mage::app()->getLocale()->date(null, null, null, false) ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT) ); } $this->setStockStatusChangedAutomatically(0); if ($this->hasStockStatusChangedAutomaticallyFlag()) { $this->setStockStatusChangedAutomatically((int)$this->getStockStatusChangedAutomaticallyFlag()); } } else { $this->setQty(0); } return $this; } /** * Chceck if item should be in stock or out of stock based on $qty param of existing item qty * * @param float|null $qty * @return bool true - item in stock | false - item out of stock */ public function verifyStock($qty = null) { if ($qty === null) { $qty = $this->getQty(); } if ($this->getBackorders() == Mage_CatalogInventory_Model_Stock::BACKORDERS_NO && $qty <= $this->getMinQty()) { return false; } return true; } /** * Check if item qty require stock status notification * * @param float | null $qty * @return bool (true - if require, false - if not require) */ public function verifyNotification($qty = null) { if ($qty === null) { $qty = $this->getQty(); } return (float)$qty < $this->getNotifyStockQty(); } /** * Reindex CatalogInventory save event * * @return Mage_CatalogInventory_Model_Stock_Item */ protected function _afterSave() { parent::_afterSave(); /** @var $indexer Mage_Index_Model_Indexer */ $indexer = Mage::getSingleton('index/indexer'); if ($this->_processIndexEvents) { $indexer->processEntityAction($this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE); } else { $indexer->logEvent($this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE); } return $this; } /** * Retrieve Stock Availability * * @return bool|int */ public function getIsInStock() { if (!$this->getManageStock()) { return true; } return $this->_getData('is_in_stock'); } /** * Add product data to stock item * * @param Mage_Catalog_Model_Product $product * @return Mage_CatalogInventory_Model_Stock_Item */ public function setProduct($product) { $this->setProductId($product->getId()) ->setProductName($product->getName()) ->setStoreId($product->getStoreId()) ->setProductName($product->getName()) ->setProductTypeId($product->getTypeId()) ->setProductStatusChanged($product->dataHasChangedFor('status')) ->setProductChangedWebsites($product->getIsChangedWebsites()); $this->_productInstance = $product; return $this; } /** * Returns product instance * * @return Mage_Catalog_Model_Product|null */ public function getProduct() { return $this->_productInstance ? $this->_productInstance : $this->_getData('product'); } /** * Retrieve stock qty whether product is composite or no * * @return float */ public function getStockQty() { if (!$this->hasStockQty()) { $this->setStockQty(0); // prevent possible recursive loop $product = $this->_productInstance; if (!$product || !$product->isComposite()) { $stockQty = $this->getQty(); } else { $stockQty = null; $productsByGroups = $product->getTypeInstance(true)->getProductsToPurchaseByReqGroups($product); foreach ($productsByGroups as $productsInGroup) { $qty = 0; foreach ($productsInGroup as $childProduct) { if ($childProduct->hasStockItem()) { $qty += $childProduct->getStockItem()->getStockQty(); } } if (is_null($stockQty) || $qty < $stockQty) { $stockQty = $qty; } } } $stockQty = (float) $stockQty; if ($stockQty < 0 || !$this->getManageStock() || !$this->getIsInStock() || ($product && !$product->isSaleable()) ) { $stockQty = 0; } $this->setStockQty($stockQty); } return $this->getData('stock_qty'); } /** * Reset model data * @return Mage_CatalogInventory_Model_Stock_Item */ public function reset() { if ($this->_productInstance) { $this->_productInstance = null; } return $this; } /** * Set whether index events should be processed immediately * * @param bool $process * @return Mage_CatalogInventory_Model_Stock_Item */ public function setProcessIndexEvents($process = true) { $this->_processIndexEvents = $process; return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogInventory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * CatalogInventory Stock Status per website Model * * @method Mage_CatalogInventory_Model_Resource_Stock_Status _getResource() * @method Mage_CatalogInventory_Model_Resource_Stock_Status getResource() * @method int getProductId() * @method Mage_CatalogInventory_Model_Stock_Status setProductId(int $value) * @method int getWebsiteId() * @method Mage_CatalogInventory_Model_Stock_Status setWebsiteId(int $value) * @method int getStockId() * @method Mage_CatalogInventory_Model_Stock_Status setStockId(int $value) * @method float getQty() * @method Mage_CatalogInventory_Model_Stock_Status setQty(float $value) * @method int getStockStatus() * @method Mage_CatalogInventory_Model_Stock_Status setStockStatus(int $value) * * @category Mage * @package Mage_CatalogInventory * @author Magento Core Team */ class Mage_CatalogInventory_Model_Stock_Status extends Mage_Core_Model_Abstract { const STATUS_OUT_OF_STOCK = 0; const STATUS_IN_STOCK = 1; /** * Product Type Instances cache * * @var array */ protected $_productTypes; /** * Websites cache * * @var array */ protected $_websites; /** * Init resource model * */ protected function _construct() { $this->_init('cataloginventory/stock_status'); } /** * Retrieve Product Type Instances * as key - type code, value - instance model * * @return array */ public function getProductTypeInstances() { if (is_null($this->_productTypes)) { $this->_productTypes = array(); $productEmulator = new Varien_Object(); foreach (array_keys(Mage_Catalog_Model_Product_Type::getTypes()) as $typeId) { $productEmulator->setTypeId($typeId); $this->_productTypes[$typeId] = Mage::getSingleton('catalog/product_type') ->factory($productEmulator); } } return $this->_productTypes; } /** * Retrieve Product Type Instance By Product Type * * @param string $productType * @return Mage_Catalog_Model_Product_Type_Abstract */ public function getProductTypeInstance($productType) { $types = $this->getProductTypeInstances(); if (isset($types[$productType])) { return $types[$productType]; } return false; } /** * Retrieve website models * * @return array */ public function getWebsites($websiteId = null) { if (is_null($this->_websites)) { $this->_websites = $this->getResource()->getWebsiteStores(); } $websites = $this->_websites; if (!is_null($websiteId) && isset($this->_websites[$websiteId])) { $websites = array($websiteId => $this->_websites[$websiteId]); } return $websites; } /** * Retrieve Default website store Id * * @param int $websiteId * @return int */ public function getWebsiteDefaultStoreId($websiteId) { $websites = $this->getWebsites(); if (isset($websites[$websiteId])) { return $websites[$websiteId]; } return 0; } /** * Retrieve Catalog Product Status Model * * @return Mage_Catalog_Model_Product_Status */ public function getProductStatusModel() { return Mage::getSingleton('catalog/product_status'); } /** * Retrieve CatalogInventory empty Stock Item model * * @return Mage_CatalogInventory_Model_Stock_Item */ public function getStockItemModel() { return Mage::getModel('cataloginventory/stock_item'); } /** * Retrieve Product Status Enabled Constant * * @return int */ public function getProductStatusEnabled() { return Mage_Catalog_Model_Product_Status::STATUS_ENABLED; } /** * Change Stock Item status process * * @param Mage_CatalogInventory_Model_Stock_Item $item * @return Mage_CatalogInventory_Model_Stock_Status */ public function changeItemStatus(Mage_CatalogInventory_Model_Stock_Item $item) { $productId = $item->getProductId(); if (!$productType = $item->getProductTypeId()) { $productType = $this->getProductType($productId); } $status = (int)$item->getIsInStock(); $qty = (int)$item->getQty(); $this->_processChildren($productId, $productType, $qty, $status, $item->getStockId()); $this->_processParents($productId, $item->getStockId()); return $this; } /** * Assign Stock Status to Product * * @param Mage_Catalog_Model_Product $product * @param int $stockId * @param int $stockStatus * @return Mage_CatalogInventory_Model_Stock_Status */ public function assignProduct(Mage_Catalog_Model_Product $product, $stockId = 1, $stockStatus = null) { if (is_null($stockStatus)) { $websiteId = $product->getStore()->getWebsiteId(); $status = $this->getProductStatus($product->getId(), $websiteId, $stockId); $stockStatus = isset($status[$product->getId()]) ? $status[$product->getId()] : null; } $product->setIsSalable($stockStatus); return $this; } /** * Rebuild stock status for all products * * @param int $websiteId * @return Mage_CatalogInventory_Model_Stock_Status */ public function rebuild($websiteId = null) { $lastProductId = 0; while (true) { $productCollection = $this->getResource()->getProductCollection($lastProductId); if (!$productCollection) { break; } foreach ($productCollection as $productId => $productType) { $lastProductId = $productId; $this->updateStatus($productId, $productType, $websiteId); } } return $this; } /** * Update product status from stock item * * @param int $productId * @param string $productType * @param int $websiteId * @return Mage_CatalogInventory_Model_Stock_Status */ public function updateStatus($productId, $productType = null, $websiteId = null) { if (is_null($productType)) { $productType = $this->getProductType($productId); } $item = $this->getStockItemModel()->loadByProduct($productId); $status = self::STATUS_IN_STOCK; $qty = 0; if ($item->getId()) { $status = $item->getIsInStock(); $qty = $item->getQty(); } $this->_processChildren($productId, $productType, $qty, $status, $item->getStockId(), $websiteId); $this->_processParents($productId, $item->getStockId(), $websiteId); return $this; } /** * Process children stock status * * @param int $productId * @param string $productType * @param float $qty * @param int $status * @param int $stockId * @param int $websiteId * @return Mage_CatalogInventory_Model_Stock_Status */ protected function _processChildren($productId, $productType, $qty = 0, $status = self::STATUS_IN_STOCK, $stockId = 1, $websiteId = null) { if ($status == self::STATUS_OUT_OF_STOCK) { $this->saveProductStatus($productId, $status, $qty, $stockId, $websiteId); return $this; } $statuses = array(); $websites = $this->getWebsites($websiteId); foreach (array_keys($websites) as $websiteId) { /* @var $website Mage_Core_Model_Website */ $statuses[$websiteId] = $status; } if (!$typeInstance = $this->getProductTypeInstance($productType)) { return $this; } $requiredChildrenIds = $typeInstance->getChildrenIds($productId, true); if ($requiredChildrenIds) { $childrenIds = array(); foreach ($requiredChildrenIds as $groupedChildrenIds) { $childrenIds = array_merge($childrenIds, $groupedChildrenIds); } $childrenWebsites = Mage::getSingleton('catalog/product_website') ->getWebsites($childrenIds); foreach ($websites as $websiteId => $storeId) { $childrenStatus = $this->getProductStatusModel() ->getProductStatus($childrenIds, $storeId); $childrenStock = $this->getProductStatus($childrenIds, $websiteId, $stockId); $websiteStatus = $statuses[$websiteId]; foreach ($requiredChildrenIds as $groupedChildrenIds) { $optionStatus = false; foreach ($groupedChildrenIds as $childId) { if (isset($childrenStatus[$childId]) and isset($childrenWebsites[$childId]) and in_array($websiteId, $childrenWebsites[$childId]) and $childrenStatus[$childId] == $this->getProductStatusEnabled() and isset($childrenStock[$childId]) and $childrenStock[$childId] == self::STATUS_IN_STOCK ) { $optionStatus = true; } } $websiteStatus = $websiteStatus && $optionStatus; } $statuses[$websiteId] = (int)$websiteStatus; } } foreach ($statuses as $websiteId => $websiteStatus) { $this->saveProductStatus($productId, $websiteStatus, $qty, $stockId, $websiteId); } return $this; } /** * Process Parents by child * * @param int $productId * @param int $stockId * @param int $websiteId * @return Mage_CatalogInventory_Model_Stock_Status */ protected function _processParents($productId, $stockId = 1, $websiteId = null) { $parentIds = array(); foreach ($this->getProductTypeInstances() as $typeInstance) { /* @var $typeInstance Mage_Catalog_Model_Product_Type_Abstract */ $parentIds = array_merge($parentIds, $typeInstance->getParentIdsByChild($productId)); } if (!$parentIds) { return $this; } $productTypes = $this->getProductsType($parentIds); $item = $this->getStockItemModel(); foreach ($parentIds as $parentId) { $parentType = isset($productTypes[$parentId]) ? $productTypes[$parentId] : null; $item->setData(array('stock_id' => $stockId)) ->setOrigData() ->loadByProduct($parentId); $status = self::STATUS_IN_STOCK; $qty = 0; if ($item->getId()) { $status = $item->getIsInStock(); $qty = $item->getQty(); } $this->_processChildren($parentId, $parentType, $qty, $status, $item->getStockId(), $websiteId); } return $this; } /** * Save product status per website * if website is null, saved for all websites * * @param int $productId * @param int $status * @param float $qty * @param int $stockId * @param int|null $websiteId * @return Mage_CatalogInventory_Model_Stock_Status */ public function saveProductStatus($productId, $status, $qty = 0, $stockId = 1, $websiteId = null) { $this->getResource()->saveProductStatus($this, $productId, $status, $qty, $stockId, $websiteId); return $this; } /** * Retrieve Product(s) status * * @param int|array $productIds * @param int $websiteId * @param int $stockId * @return array */ public function getProductStatus($productIds, $websiteId, $stockId = 1) { return $this->getResource()->getProductStatus($productIds, $websiteId, $stockId); } /** * Retrieve Product(s) Data array * * @param int|array $productIds * @param int $websiteId * @param int $stockId * @return array */ public function getProductData($productIds, $websiteId, $stockId = 1) { return $this->getResource()->getProductData($productIds, $websiteId, $stockId); } /** * Retrieve Product Type * * @param int $productId * @return string|false */ public function getProductType($productId) { $types = $this->getResource()->getProductsType($productId); if (isset($types[$productId])) { return $types[$productId]; } return false; } /** * Retrieve Products Type as array * Return array as key product_id, value type * * @param array|int $productIds * @return array */ public function getProductsType($productIds) { return $this->getResource()->getProductsType($productIds); } /** * Add information about stock status to product collection * * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection $productCollection * @param int|null $websiteId * @param int|null $stockId * @return Mage_CatalogInventory_Model_Stock_Status */ public function addStockStatusToProducts($productCollection, $websiteId = null, $stockId = null) { if ($stockId === null) { $stockId = Mage_CatalogInventory_Model_Stock::DEFAULT_STOCK_ID; } if ($websiteId === null) { $websiteId = Mage::app()->getStore()->getWebsiteId(); if ((int)$websiteId == 0 && $productCollection->getStoreId()) { $websiteId = Mage::app()->getStore($productCollection->getStoreId())->getWebsiteId(); } } $productIds = array(); foreach ($productCollection as $product) { $productIds[] = $product->getId(); } if (!empty($productIds)) { $stockStatuses = $this->_getResource()->getProductStatus($productIds, $websiteId, $stockId); foreach ($stockStatuses as $productId => $status) { if ($product = $productCollection->getItemById($productId)) { $product->setIsSalable($status); } } } /* back compatible stock item */ foreach ($productCollection as $product) { $object = new Varien_Object(array('is_in_stock' => $product->getData('is_salable'))); $product->setStockItem($object); } return $this; } /** * Add stock status to prepare index select * * @param Varien_Db_Select $select * @param Mage_Core_Model_Website $website * @return Mage_CatalogInventory_Model_Stock_Status */ public function addStockStatusToSelect(Varien_Db_Select $select, Mage_Core_Model_Website $website) { $this->_getResource()->addStockStatusToSelect($select, $website); return $this; } /** * Add stock status limitation to catalog product price index select object * * @param Varien_Db_Select $select * @param string|Zend_Db_Expr $entityField * @param string|Zend_Db_Expr $websiteField * @return Mage_CatalogInventory_Model_Stock_Status */ public function prepareCatalogProductIndexSelect(Varien_Db_Select $select, $entityField, $websiteField) { if (Mage::helper('cataloginventory')->isShowOutOfStock()) { return $this; } $this->_getResource()->prepareCatalogProductIndexSelect($select, $entityField, $websiteField); return $this; } /** * Add only is in stock products filter to product collection * * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection $collection * @return Mage_CatalogInventory_Model_Stock_Status */ public function addIsInStockFilterToCollection($collection) { $this->_getResource()->addIsInStockFilterToCollection($collection); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * CatalogRule data helper */ class Mage_CatalogRule_Helper_Data extends Mage_Core_Helper_Abstract { /** * Algorithm for calculating price rule * * @param string $actionOperator * @param int $ruleAmount * @param float $price * @return float|int */ public function calcPriceRule($actionOperator, $ruleAmount, $price) { $priceRule = 0; switch ($actionOperator) { case 'to_fixed': $priceRule = min($ruleAmount, $price); break; case 'to_percent': $priceRule = $price * $ruleAmount / 100; break; case 'by_fixed': $priceRule = max(0, $price - $ruleAmount); break; case 'by_percent': $priceRule = $price * (1 - $ruleAmount / 100); break; } return $priceRule; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog Price rules observer model */ class Mage_CatalogRule_Model_Observer { /** * Store calculated catalog rules prices for products * Prices collected per website, customer group, date and product * * @var array */ protected $_rulePrices = array(); /** * Apply all catalog price rules for specific product * * @param Varien_Event_Observer $observer * @return Mage_CatalogRule_Model_Observer */ public function applyAllRulesOnProduct($observer) { $product = $observer->getEvent()->getProduct(); if ($product->getIsMassupdate()) { return; } $productWebsiteIds = $product->getWebsiteIds(); $rules = Mage::getModel('catalogrule/rule')->getCollection() ->addFieldToFilter('is_active', 1); foreach ($rules as $rule) { $websiteIds = array_intersect($productWebsiteIds, $rule->getWebsiteIds()); $rule->applyToProduct($product, $websiteIds); } return $this; } /** * Apply all price rules for current date. * Handle cataolg_product_import_after event * * @param Varien_Event_Observer $observer * * @return Mage_CatalogRule_Model_Observer */ public function applyAllRules($observer) { $resource = Mage::getResourceSingleton('catalogrule/rule'); $resource->applyAllRulesForDateRange($resource->formatDate(mktime(0,0,0))); Mage::getModel('catalogrule/flag')->loadSelf() ->setState(0) ->save(); return $this; } /** * Apply catalog price rules to product on frontend * * @param Varien_Event_Observer $observer * * @return Mage_CatalogRule_Model_Observer */ public function processFrontFinalPrice($observer) { $product = $observer->getEvent()->getProduct(); $pId = $product->getId(); $storeId = $product->getStoreId(); if ($observer->hasDate()) { $date = $observer->getEvent()->getDate(); } else { $date = Mage::app()->getLocale()->storeTimeStamp($storeId); } if ($observer->hasWebsiteId()) { $wId = $observer->getEvent()->getWebsiteId(); } else { $wId = Mage::app()->getStore($storeId)->getWebsiteId(); } if ($observer->hasCustomerGroupId()) { $gId = $observer->getEvent()->getCustomerGroupId(); } elseif ($product->hasCustomerGroupId()) { $gId = $product->getCustomerGroupId(); } else { $gId = Mage::getSingleton('customer/session')->getCustomerGroupId(); } $key = "$date|$wId|$gId|$pId"; if (!isset($this->_rulePrices[$key])) { $rulePrice = Mage::getResourceModel('catalogrule/rule') ->getRulePrice($date, $wId, $gId, $pId); $this->_rulePrices[$key] = $rulePrice; } if ($this->_rulePrices[$key]!==false) { $finalPrice = min($product->getData('final_price'), $this->_rulePrices[$key]); $product->setFinalPrice($finalPrice); } return $this; } /** * Apply catalog price rules to product in admin * * @param Varien_Event_Observer $observer * * @return Mage_CatalogRule_Model_Observer */ public function processAdminFinalPrice($observer) { $product = $observer->getEvent()->getProduct(); $storeId = $product->getStoreId(); $date = Mage::app()->getLocale()->storeDate($storeId); $key = false; if ($ruleData = Mage::registry('rule_data')) { $wId = $ruleData->getWebsiteId(); $gId = $ruleData->getCustomerGroupId(); $pId = $product->getId(); $key = "$date|$wId|$gId|$pId"; } elseif (!is_null($product->getWebsiteId()) && !is_null($product->getCustomerGroupId())) { $wId = $product->getWebsiteId(); $gId = $product->getCustomerGroupId(); $pId = $product->getId(); $key = "$date|$wId|$gId|$pId"; } if ($key) { if (!isset($this->_rulePrices[$key])) { $rulePrice = Mage::getResourceModel('catalogrule/rule') ->getRulePrice($date, $wId, $gId, $pId); $this->_rulePrices[$key] = $rulePrice; } if ($this->_rulePrices[$key]!==false) { $finalPrice = min($product->getData('final_price'), $this->_rulePrices[$key]); $product->setFinalPrice($finalPrice); } } return $this; } /** * Calculate price using catalog price rules of configurable product * * @param Varien_Event_Observer $observer * * @return Mage_CatalogRule_Model_Observer */ public function catalogProductTypeConfigurablePrice(Varien_Event_Observer $observer) { $product = $observer->getEvent()->getProduct(); if ($product instanceof Mage_Catalog_Model_Product && $product->getConfigurablePrice() !== null ) { $configurablePrice = $product->getConfigurablePrice(); $productPriceRule = Mage::getModel('catalogrule/rule')->calcProductPriceRule($product, $configurablePrice); if ($productPriceRule !== null) { $product->setConfigurablePrice($productPriceRule); } } return $this; } /** * Daily update catalog price rule by cron * Update include interval 3 days - current day - 1 days before + 1 days after * This method is called from cron process, cron is working in UTC time and * we should generate data for interval -1 day ... +1 day * * @param Varien_Event_Observer $observer * * @return Mage_CatalogRule_Model_Observer */ public function dailyCatalogUpdate($observer) { Mage::getResourceSingleton('catalogrule/rule')->applyAllRulesForDateRange(); return $this; } /** * Clean out calculated catalog rule prices for products */ public function flushPriceCache() { $this->_rulePrices = array(); } /** * Calculate minimal final price with catalog rule price * * @param Varien_Event_Observer $observer * @return Mage_CatalogRule_Model_Observer */ public function prepareCatalogProductPriceIndexTable(Varien_Event_Observer $observer) { $select = $observer->getEvent()->getSelect(); $indexTable = $observer->getEvent()->getIndexTable(); $entityId = $observer->getEvent()->getEntityId(); $customerGroupId = $observer->getEvent()->getCustomerGroupId(); $websiteId = $observer->getEvent()->getWebsiteId(); $websiteDate = $observer->getEvent()->getWebsiteDate(); $updateFields = $observer->getEvent()->getUpdateFields(); Mage::getSingleton('catalogrule/rule_product_price') ->applyPriceRuleToIndexTable($select, $indexTable, $entityId, $customerGroupId, $websiteId, $updateFields, $websiteDate); return $this; } /** * Check rules that contains affected attribute * If rules were found they will be set to inactive and notice will be add to admin session * * @param string $attributeCode * * @return Mage_CatalogRule_Model_Observer */ protected function _checkCatalogRulesAvailability($attributeCode) { /* @var $collection Mage_CatalogRule_Model_Mysql4_Rule_Collection */ $collection = Mage::getResourceModel('catalogrule/rule_collection') ->addAttributeInConditionFilter($attributeCode); $disabledRulesCount = 0; foreach ($collection as $rule) { /* @var $rule Mage_CatalogRule_Model_Rule */ $rule->setIsActive(0); /* @var $rule->getConditions() Mage_CatalogRule_Model_Rule_Condition_Combine */ $this->_removeAttributeFromConditions($rule->getConditions(), $attributeCode); $rule->save(); $disabledRulesCount++; } if ($disabledRulesCount) { Mage::getModel('catalogrule/rule')->applyAll(); Mage::getSingleton('adminhtml/session')->addWarning( Mage::helper('catalogrule')->__('%d Catalog Price Rules based on "%s" attribute have been disabled.', $disabledRulesCount, $attributeCode)); } return $this; } /** * Remove catalog attribute condition by attribute code from rule conditions * * @param Mage_CatalogRule_Model_Rule_Condition_Combine $combine * * @param string $attributeCode */ protected function _removeAttributeFromConditions($combine, $attributeCode) { $conditions = $combine->getConditions(); foreach ($conditions as $conditionId => $condition) { if ($condition instanceof Mage_CatalogRule_Model_Rule_Condition_Combine) { $this->_removeAttributeFromConditions($condition, $attributeCode); } if ($condition instanceof Mage_Rule_Model_Condition_Product_Abstract) { if ($condition->getAttribute() == $attributeCode) { unset($conditions[$conditionId]); } } } $combine->setConditions($conditions); } /** * After save attribute if it is not used for promo rules already check rules for containing this attribute * * @param Varien_Event_Observer $observer * * @return Mage_CatalogRule_Model_Observer */ public function catalogAttributeSaveAfter(Varien_Event_Observer $observer) { $attribute = $observer->getEvent()->getAttribute(); if ($attribute->dataHasChangedFor('is_used_for_promo_rules') && !$attribute->getIsUsedForPromoRules()) { $this->_checkCatalogRulesAvailability($attribute->getAttributeCode()); } return $this; } /** * After delete attribute check rules that contains deleted attribute * * @param Varien_Event_Observer $observer * @return Mage_CatalogRule_Model_Observer */ public function catalogAttributeDeleteAfter(Varien_Event_Observer $observer) { $attribute = $observer->getEvent()->getAttribute(); if ($attribute->getIsUsedForPromoRules()) { $this->_checkCatalogRulesAvailability($attribute->getAttributeCode()); } return $this; } public function prepareCatalogProductCollectionPrices(Varien_Event_Observer $observer) { /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection */ $collection = $observer->getEvent()->getCollection(); $store = Mage::app()->getStore($observer->getEvent()->getStoreId()); $websiteId = $store->getWebsiteId(); if ($observer->getEvent()->hasCustomerGroupId()) { $groupId = $observer->getEvent()->getCustomerGroupId(); } else { /* @var $session Mage_Customer_Model_Session */ $session = Mage::getSingleton('customer/session'); if ($session->isLoggedIn()) { $groupId = Mage::getSingleton('customer/session')->getCustomerGroupId(); } else { $groupId = Mage_Customer_Model_Group::NOT_LOGGED_IN_ID; } } if ($observer->getEvent()->hasDate()) { $date = $observer->getEvent()->getDate(); } else { $date = Mage::app()->getLocale()->storeTimeStamp($store); } $productIds = array(); /* @var $product Mage_Core_Model_Product */ foreach ($collection as $product) { $key = implode('|', array($date, $websiteId, $groupId, $product->getId())); if (!isset($this->_rulePrices[$key])) { $productIds[] = $product->getId(); } } if ($productIds) { $rulePrices = Mage::getResourceModel('catalogrule/rule') ->getRulePrices($date, $websiteId, $groupId, $productIds); foreach ($productIds as $productId) { $key = implode('|', array($date, $websiteId, $groupId, $productId)); $this->_rulePrices[$key] = isset($rulePrices[$productId]) ? $rulePrices[$productId] : false; } } return $this; } /** * Create catalog rule relations for imported products * * @param Varien_Event_Observer $observer */ public function createCatalogRulesRelations(Varien_Event_Observer $observer) { $adapter = $observer->getEvent()->getAdapter(); $affectedEntityIds = $adapter->getAffectedEntityIds(); if (empty($affectedEntityIds)) { return; } $rules = Mage::getModel('catalogrule/rule')->getCollection() ->addFieldToFilter('is_active', 1); foreach ($rules as $rule) { $rule->setProductsFilter($affectedEntityIds); Mage::getResourceSingleton('catalogrule/rule')->updateRuleProductData($rule); } } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract Rule entity resource model * * @category Mage * @package Mage_Rule * @author Magento Core Team */ abstract class Mage_Rule_Model_Resource_Abstract extends Mage_Core_Model_Resource_Db_Abstract { /** * Store associated with rule entities information map * * Example: * array( * 'entity_type1' => array( * 'associations_table' => 'table_name', * 'rule_id_field' => 'rule_id', * 'entity_id_field' => 'entity_id' * ), * 'entity_type2' => array( * 'associations_table' => 'table_name', * 'rule_id_field' => 'rule_id', * 'entity_id_field' => 'entity_id' * ) * .... * ) * * @var array */ protected $_associatedEntitiesMap = array(); /** * Prepare rule's active "from" and "to" dates * * @param Mage_Core_Model_Abstract $object * * @return Mage_Rule_Model_Resource_Abstract */ public function _beforeSave(Mage_Core_Model_Abstract $object) { $fromDate = $object->getFromDate(); if ($fromDate instanceof Zend_Date) { $object->setFromDate($fromDate->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); } elseif (!is_string($fromDate) || empty($fromDate)) { $object->setFromDate(null); } $toDate = $object->getToDate(); if ($toDate instanceof Zend_Date) { $object->setToDate($toDate->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); } elseif (!is_string($toDate) || empty($toDate)) { $object->setToDate(null); } parent::_beforeSave($object); return $this; } /** * Bind specified rules to entities * * @param array|int|string $ruleIds * @param array|int|string $entityIds * @param string $entityType * * @return Mage_Rule_Model_Resource_Abstract */ public function bindRuleToEntity($ruleIds, $entityIds, $entityType) { if (empty($ruleIds) || empty($entityIds)) { return $this; } $adapter = $this->_getWriteAdapter(); $entityInfo = $this->_getAssociatedEntityInfo($entityType); if (!is_array($ruleIds)) { $ruleIds = array((int) $ruleIds); } if (!is_array($entityIds)) { $entityIds = array((int) $entityIds); } $data = array(); $count = 0; $adapter->beginTransaction(); try { foreach ($ruleIds as $ruleId) { foreach ($entityIds as $entityId) { $data[] = array( $entityInfo['entity_id_field'] => $entityId, $entityInfo['rule_id_field'] => $ruleId ); $count++; if (($count % 1000) == 0) { $adapter->insertOnDuplicate( $this->getTable($entityInfo['associations_table']), $data, array($entityInfo['rule_id_field']) ); $data = array(); } } } if (!empty($data)) { $adapter->insertOnDuplicate( $this->getTable($entityInfo['associations_table']), $data, array($entityInfo['rule_id_field']) ); } $adapter->delete($this->getTable($entityInfo['associations_table']), $adapter->quoteInto($entityInfo['rule_id_field'] . ' IN (?) AND ', $ruleIds) . $adapter->quoteInto($entityInfo['entity_id_field'] . ' NOT IN (?)', $entityIds) ); } catch (Exception $e) { $adapter->rollback(); throw $e; } $adapter->commit(); return $this; } /** * Unbind specified rules from entities * * @param array|int|string $ruleIds * @param array|int|string $entityIds * @param string $entityType * * @return Mage_Rule_Model_Resource_Abstract */ public function unbindRuleFromEntity($ruleIds = array(), $entityIds = array(), $entityType) { $writeAdapter = $this->_getWriteAdapter(); $entityInfo = $this->_getAssociatedEntityInfo($entityType); if (!is_array($entityIds)) { $entityIds = array((int) $entityIds); } if (!is_array($ruleIds)) { $ruleIds = array((int) $ruleIds); } $where = array(); if (!empty($ruleIds)) { $where[] = $writeAdapter->quoteInto($entityInfo['rule_id_field'] . ' IN (?)', $ruleIds); } if (!empty($entityIds)) { $where[] = $writeAdapter->quoteInto($entityInfo['entity_id_field'] . ' IN (?)', $entityIds); } $writeAdapter->delete($this->getTable($entityInfo['associations_table']), implode(' AND ', $where)); return $this; } /** * Retrieve rule's associated entity Ids by entity type * * @param int $ruleId * @param string $entityType * * @return array */ public function getAssociatedEntityIds($ruleId, $entityType) { $entityInfo = $this->_getAssociatedEntityInfo($entityType); $select = $this->_getReadAdapter()->select() ->from($this->getTable($entityInfo['associations_table']), array($entityInfo['entity_id_field'])) ->where($entityInfo['rule_id_field'] . ' = ?', $ruleId); return $this->_getReadAdapter()->fetchCol($select); } /** * Retrieve website ids of specified rule * * @param int $ruleId * @return array */ public function getWebsiteIds($ruleId) { return $this->getAssociatedEntityIds($ruleId, 'website'); } /** * Retrieve customer group ids of specified rule * * @param int $ruleId * @return array */ public function getCustomerGroupIds($ruleId) { return $this->getAssociatedEntityIds($ruleId, 'customer_group'); } /** * Retrieve correspondent entity information (associations table name, columns names) * of rule's associated entity by specified entity type * * @param string $entityType * * @return array */ protected function _getAssociatedEntityInfo($entityType) { if (isset($this->_associatedEntitiesMap[$entityType])) { return $this->_associatedEntitiesMap[$entityType]; } $e = Mage::exception( 'Mage_Core', Mage::helper('rule')->__( 'There is no information about associated entity type "%s".', $entityType ) ); throw $e; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog rules resource model * * @category Mage * @package Mage_CatalogRule * @author Magento Core Team */ class Mage_CatalogRule_Model_Resource_Rule extends Mage_Rule_Model_Resource_Abstract { /** * Store number of seconds in a day */ const SECONDS_IN_DAY = 86400; /** * Store associated with rule entities information map * * @var array */ protected $_associatedEntitiesMap = array( 'website' => array( 'associations_table' => 'catalogrule/website', 'rule_id_field' => 'rule_id', 'entity_id_field' => 'website_id' ), 'customer_group' => array( 'associations_table' => 'catalogrule/customer_group', 'rule_id_field' => 'rule_id', 'entity_id_field' => 'customer_group_id' ) ); /** * Initialize main table and table id field */ protected function _construct() { $this->_init('catalogrule/rule', 'rule_id'); } /** * Add customer group ids and website ids to rule data after load * * @param Mage_Core_Model_Abstract $object * * @return Mage_CatalogRule_Model_Resource_Rule */ protected function _afterLoad(Mage_Core_Model_Abstract $object) { $object->setData('customer_group_ids', (array)$this->getCustomerGroupIds($object->getId())); $object->setData('website_ids', (array)$this->getWebsiteIds($object->getId())); return parent::_afterLoad($object); } /** * Bind catalog rule to customer group(s) and website(s). * Update products which are matched for rule. * * @param Mage_Core_Model_Abstract $object * * @return Mage_CatalogRule_Model_Resource_Rule */ protected function _afterSave(Mage_Core_Model_Abstract $object) { if ($object->hasWebsiteIds()) { $websiteIds = $object->getWebsiteIds(); if (!is_array($websiteIds)) { $websiteIds = explode(',', (string)$websiteIds); } $this->bindRuleToEntity($object->getId(), $websiteIds, 'website'); } if ($object->hasCustomerGroupIds()) { $customerGroupIds = $object->getCustomerGroupIds(); if (!is_array($customerGroupIds)) { $customerGroupIds = explode(',', (string)$customerGroupIds); } $this->bindRuleToEntity($object->getId(), $customerGroupIds, 'customer_group'); } parent::_afterSave($object); return $this; } /** * Update products which are matched for rule * * @param Mage_CatalogRule_Model_Rule $rule * * @return Mage_CatalogRule_Model_Resource_Rule */ public function updateRuleProductData(Mage_CatalogRule_Model_Rule $rule) { $ruleId = $rule->getId(); $write = $this->_getWriteAdapter(); $write->beginTransaction(); if ($rule->getProductsFilter()) { $write->delete( $this->getTable('catalogrule/rule_product'), array( 'rule_id=?' => $ruleId, 'product_id IN (?)' => $rule->getProductsFilter() ) ); } else { $write->delete($this->getTable('catalogrule/rule_product'), $write->quoteInto('rule_id=?', $ruleId)); } if (!$rule->getIsActive()) { $write->commit(); return $this; } $websiteIds = $rule->getWebsiteIds(); if (!is_array($websiteIds)) { $websiteIds = explode(',', $websiteIds); } if (empty($websiteIds)) { return $this; } Varien_Profiler::start('__MATCH_PRODUCTS__'); $productIds = $rule->getMatchingProductIds(); Varien_Profiler::stop('__MATCH_PRODUCTS__'); $customerGroupIds = $rule->getCustomerGroupIds(); $fromTime = strtotime($rule->getFromDate()); $toTime = strtotime($rule->getToDate()); $toTime = $toTime ? ($toTime + self::SECONDS_IN_DAY - 1) : 0; $sortOrder = (int)$rule->getSortOrder(); $actionOperator = $rule->getSimpleAction(); $actionAmount = $rule->getDiscountAmount(); $subActionOperator = $rule->getSubIsEnable() ? $rule->getSubSimpleAction() : ''; $subActionAmount = $rule->getSubDiscountAmount(); $actionStop = $rule->getStopRulesProcessing(); $rows = array(); try { foreach ($productIds as $productId) { foreach ($websiteIds as $websiteId) { foreach ($customerGroupIds as $customerGroupId) { $rows[] = array( 'rule_id' => $ruleId, 'from_time' => $fromTime, 'to_time' => $toTime, 'website_id' => $websiteId, 'customer_group_id' => $customerGroupId, 'product_id' => $productId, 'action_operator' => $actionOperator, 'action_amount' => $actionAmount, 'action_stop' => $actionStop, 'sort_order' => $sortOrder, 'sub_simple_action' => $subActionOperator, 'sub_discount_amount' => $subActionAmount, ); if (count($rows) == 1000) { $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows); $rows = array(); } } } } if (!empty($rows)) { $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows); } $write->commit(); } catch (Exception $e) { $write->rollback(); throw $e; } return $this; } /** * Get all product ids matched for rule * * @param int $ruleId * * @return array */ public function getRuleProductIds($ruleId) { $read = $this->_getReadAdapter(); $select = $read->select()->from($this->getTable('catalogrule/rule_product'), 'product_id') ->where('rule_id=?', $ruleId); return $read->fetchCol($select); } /** * Remove catalog rules product prices for specified date range and product * * @param int|string $fromDate * @param int|string $toDate * @param int|null $productId * * @return Mage_CatalogRule_Model_Resource_Rule */ public function removeCatalogPricesForDateRange($fromDate, $toDate, $productId = null) { $write = $this->_getWriteAdapter(); $conds = array(); $cond = $write->quoteInto('rule_date between ?', $this->formatDate($fromDate)); $cond = $write->quoteInto($cond.' and ?', $this->formatDate($toDate)); $conds[] = $cond; if (!is_null($productId)) { $conds[] = $write->quoteInto('product_id=?', $productId); } /** * Add information about affected products * It can be used in processes which related with product price (like catalog index) */ $select = $this->_getWriteAdapter()->select() ->from($this->getTable('catalogrule/rule_product_price'), 'product_id') ->where(implode(' AND ', $conds)) ->group('product_id'); $replace = $write->insertFromSelect( $select, $this->getTable('catalogrule/affected_product'), array('product_id'), true ); $write->query($replace); $write->delete($this->getTable('catalogrule/rule_product_price'), $conds); return $this; } /** * Delete old price rules data * * @param string $date * @param int|null $productId * * @return Mage_CatalogRule_Model_Resource_Rule */ public function deleteOldData($date, $productId = null) { $write = $this->_getWriteAdapter(); $conds = array(); $conds[] = $write->quoteInto('rule_dateformatDate($date)); if (!is_null($productId)) { $conds[] = $write->quoteInto('product_id=?', $productId); } $write->delete($this->getTable('catalogrule/rule_product_price'), $conds); return $this; } /** * Get DB resource statement for processing query result * * @param int $fromDate * @param int $toDate * @param int|null $productId * @param int|null $websiteId * * @return Zend_Db_Statement_Interface */ protected function _getRuleProductsStmt($fromDate, $toDate, $productId = null, $websiteId = null) { $read = $this->_getReadAdapter(); /** * Sort order is important * It used for check stop price rule condition. * website_id customer_group_id product_id sort_order * 1 1 1 0 * 1 1 1 1 * 1 1 1 2 * if row with sort order 1 will have stop flag we should exclude * all next rows for same product id from price calculation */ $select = $read->select() ->from(array('rp' => $this->getTable('catalogrule/rule_product'))) ->where($read->quoteInto('rp.from_time = 0 or rp.from_time <= ?', $toDate) . ' OR ' . $read->quoteInto('rp.to_time = 0 or rp.to_time >= ?', $fromDate)) ->order(array('rp.website_id', 'rp.customer_group_id', 'rp.product_id', 'rp.sort_order', 'rp.rule_id')); if (!is_null($productId)) { $select->where('rp.product_id=?', $productId); } /** * Join default price and websites prices to result */ $priceAttr = Mage::getSingleton('eav/config')->getAttribute(Mage_Catalog_Model_Product::ENTITY, 'price'); $priceTable = $priceAttr->getBackend()->getTable(); $attributeId= $priceAttr->getId(); $joinCondition = '%1$s.entity_id=rp.product_id AND (%1$s.attribute_id=' . $attributeId . ') and %1$s.store_id=%2$s'; $select->join( array('pp_default'=>$priceTable), sprintf($joinCondition, 'pp_default', Mage_Core_Model_App::ADMIN_STORE_ID), array('default_price'=>'pp_default.value') ); if ($websiteId !== null) { $website = Mage::app()->getWebsite($websiteId); $defaultGroup = $website->getDefaultGroup(); if ($defaultGroup instanceof Mage_Core_Model_Store_Group) { $storeId = $defaultGroup->getDefaultStoreId(); } else { $storeId = Mage_Core_Model_App::ADMIN_STORE_ID; } $select->joinInner( array('product_website' => $this->getTable('catalog/product_website')), 'product_website.product_id=rp.product_id ' . 'AND rp.website_id=product_website.website_id ' . 'AND product_website.website_id='.$websiteId, array() ); $tableAlias = 'pp'.$websiteId; $fieldAlias = 'website_'.$websiteId.'_price'; $select->joinLeft( array($tableAlias=>$priceTable), sprintf($joinCondition, $tableAlias, $storeId), array($fieldAlias=>$tableAlias.'.value') ); } else { foreach (Mage::app()->getWebsites() as $website) { $websiteId = $website->getId(); $defaultGroup = $website->getDefaultGroup(); if ($defaultGroup instanceof Mage_Core_Model_Store_Group) { $storeId = $defaultGroup->getDefaultStoreId(); } else { $storeId = Mage_Core_Model_App::ADMIN_STORE_ID; } $tableAlias = 'pp' . $websiteId; $fieldAlias = 'website_' . $websiteId . '_price'; $select->joinLeft( array($tableAlias => $priceTable), sprintf($joinCondition, $tableAlias, $storeId), array($fieldAlias => $tableAlias.'.value') ); } } return $read->query($select); } /** * Generate catalog price rules prices for specified date range * If from date is not defined - will be used previous day by UTC * If to date is not defined - will be used next day by UTC * * @param int|string|null $fromDate * @param int|string|null $toDate * @param int $productId * * @return Mage_CatalogRule_Model_Resource_Rule */ public function applyAllRulesForDateRange($fromDate = null, $toDate = null, $productId = null) { $write = $this->_getWriteAdapter(); $write->beginTransaction(); Mage::dispatchEvent('catalogrule_before_apply', array('resource' => $this)); $clearOldData = false; if ($fromDate === null) { $fromDate = mktime(0,0,0,date('m'),date('d')-1); /** * If fromDate not specified we can delete all data oldest than 1 day * We have run it for clear table in case when cron was not installed * and old data exist in table */ $clearOldData = true; } if (is_string($fromDate)) { $fromDate = strtotime($fromDate); } if ($toDate === null) { $toDate = mktime(0,0,0,date('m'),date('d')+1); } if (is_string($toDate)) { $toDate = strtotime($toDate); } $product = null; if ($productId instanceof Mage_Catalog_Model_Product) { $product = $productId; $productId = $productId->getId(); } $this->removeCatalogPricesForDateRange($fromDate, $toDate, $productId); if ($clearOldData) { $this->deleteOldData($fromDate, $productId); } $dayPrices = array(); try { /** * Update products rules prices per each website separately * because of max join limit in mysql */ foreach (Mage::app()->getWebsites(false) as $website) { $productsStmt = $this->_getRuleProductsStmt( $fromDate, $toDate, $productId, $website->getId() ); $dayPrices = array(); $stopFlags = array(); $prevKey = null; while ($ruleData = $productsStmt->fetch()) { $ruleProductId = $ruleData['product_id']; $productKey = $ruleProductId . '_' . $ruleData['website_id'] . '_' . $ruleData['customer_group_id']; if ($prevKey && ($prevKey != $productKey)) { $stopFlags = array(); } /** * Build prices for each day */ for ($time=$fromDate; $time<=$toDate; $time+=self::SECONDS_IN_DAY) { if (($ruleData['from_time']==0 || $time >= $ruleData['from_time']) && ($ruleData['to_time']==0 || $time <=$ruleData['to_time']) ) { $priceKey = $time . '_' . $productKey; if (isset($stopFlags[$priceKey])) { continue; } if (!isset($dayPrices[$priceKey])) { $dayPrices[$priceKey] = array( 'rule_date' => $time, 'website_id' => $ruleData['website_id'], 'customer_group_id' => $ruleData['customer_group_id'], 'product_id' => $ruleProductId, 'rule_price' => $this->_calcRuleProductPrice($ruleData), 'latest_start_date' => $ruleData['from_time'], 'earliest_end_date' => $ruleData['to_time'], ); } else { $dayPrices[$priceKey]['rule_price'] = $this->_calcRuleProductPrice( $ruleData, $dayPrices[$priceKey] ); $dayPrices[$priceKey]['latest_start_date'] = max( $dayPrices[$priceKey]['latest_start_date'], $ruleData['from_time'] ); $dayPrices[$priceKey]['earliest_end_date'] = min( $dayPrices[$priceKey]['earliest_end_date'], $ruleData['to_time'] ); } if ($ruleData['action_stop']) { $stopFlags[$priceKey] = true; } } } $prevKey = $productKey; if (count($dayPrices)>1000) { $this->_saveRuleProductPrices($dayPrices); $dayPrices = array(); } } $this->_saveRuleProductPrices($dayPrices); } $this->_saveRuleProductPrices($dayPrices); $write->delete($this->getTable('catalogrule/rule_group_website'), array()); $timestamp = Mage::getModel('core/date')->gmtTimestamp(); $select = $write->select() ->distinct(true) ->from( $this->getTable('catalogrule/rule_product'), array('rule_id', 'customer_group_id', 'website_id') )->where("{$timestamp} >= from_time AND (({$timestamp} <= to_time AND to_time > 0) OR to_time = 0)"); $query = $select->insertFromSelect($this->getTable('catalogrule/rule_group_website')); $write->query($query); $write->commit(); } catch (Exception $e) { $write->rollback(); throw $e; } $productCondition = Mage::getModel('catalog/product_condition') ->setTable($this->getTable('catalogrule/affected_product')) ->setPkFieldName('product_id'); Mage::dispatchEvent('catalogrule_after_apply', array( 'product' => $product, 'product_condition' => $productCondition )); $write->delete($this->getTable('catalogrule/affected_product')); return $this; } /** * Calculate product price based on price rule data and previous information * * @param array $ruleData * @param null|array $productData * * @return float */ protected function _calcRuleProductPrice($ruleData, $productData = null) { if ($productData !== null && isset($productData['rule_price'])) { $productPrice = $productData['rule_price']; } else { $websiteId = $ruleData['website_id']; if (isset($ruleData['website_'.$websiteId.'_price'])) { $productPrice = $ruleData['website_'.$websiteId.'_price']; } else { $productPrice = $ruleData['default_price']; } } $productPrice = Mage::helper('catalogrule')->calcPriceRule( $ruleData['action_operator'], $ruleData['action_amount'], $productPrice); return Mage::app()->getStore()->roundPrice($productPrice); } /** * Save rule prices for products to DB * * @param array $arrData * * @return Mage_CatalogRule_Model_Resource_Rule */ protected function _saveRuleProductPrices($arrData) { if (empty($arrData)) { return $this; } $adapter = $this->_getWriteAdapter(); $productIds = array(); $adapter->beginTransaction(); try { foreach ($arrData as $key => $data) { $productIds['product_id'] = $data['product_id']; $arrData[$key]['rule_date'] = $this->formatDate($data['rule_date'], false); $arrData[$key]['latest_start_date'] = $this->formatDate($data['latest_start_date'], false); $arrData[$key]['earliest_end_date'] = $this->formatDate($data['earliest_end_date'], false); } $adapter->insertOnDuplicate($this->getTable('catalogrule/affected_product'), array_unique($productIds)); $adapter->insertOnDuplicate($this->getTable('catalogrule/rule_product_price'), $arrData); } catch (Exception $e) { $adapter->rollback(); throw $e; } $adapter->commit(); return $this; } /** * Get catalog rules product price for specific date, website and * customer group * * @param int|string $date * @param int $wId * @param int $gId * @param int $pId * * @return float|bool */ public function getRulePrice($date, $wId, $gId, $pId) { $data = $this->getRulePrices($date, $wId, $gId, array($pId)); if (isset($data[$pId])) { return $data[$pId]; } return false; } /** * Retrieve product prices by catalog rule for specific date, website and customer group * Collect data with product Id => price pairs * * @param int|string $date * @param int $websiteId * @param int $customerGroupId * @param array $productIds * * @return array */ public function getRulePrices($date, $websiteId, $customerGroupId, $productIds) { $adapter = $this->_getReadAdapter(); $select = $adapter->select() ->from($this->getTable('catalogrule/rule_product_price'), array('product_id', 'rule_price')) ->where('rule_date = ?', $this->formatDate($date, false)) ->where('website_id = ?', $websiteId) ->where('customer_group_id = ?', $customerGroupId) ->where('product_id IN(?)', $productIds); return $adapter->fetchPairs($select); } /** * Get active rule data based on few filters * * @param int|string $date * @param int $websiteId * @param int $customerGroupId * @param int $productId * @return array */ public function getRulesFromProduct($date, $websiteId, $customerGroupId, $productId) { $adapter = $this->_getReadAdapter(); if (is_string($date)) { $date = strtotime($date); } $select = $adapter->select() ->from($this->getTable('catalogrule/rule_product')) ->where('website_id = ?', $websiteId) ->where('customer_group_id = ?', $customerGroupId) ->where('product_id = ?', $productId) ->where('from_time = 0 or from_time < ?', $date) ->where('to_time = 0 or to_time > ?', $date); return $adapter->fetchAll($select); } /** * Retrieve product price data for all customer groups * * @param int|string $date * @param int $wId * @param int $pId * * @return array */ public function getRulesForProduct($date, $wId, $pId) { $read = $this->_getReadAdapter(); $select = $read->select() ->from($this->getTable('catalogrule/rule_product_price'), '*') ->where('rule_date=?', $this->formatDate($date, false)) ->where('website_id=?', $wId) ->where('product_id=?', $pId); return $read->fetchAll($select); } /** * Apply catalog rule to product * * @param Mage_CatalogRule_Model_Rule $rule * @param Mage_Catalog_Model_Product $product * @param array $websiteIds * * @return Mage_CatalogRule_Model_Resource_Rule */ public function applyToProduct($rule, $product, $websiteIds) { if (!$rule->getIsActive()) { return $this; } $ruleId = $rule->getId(); $productId = $product->getId(); $write = $this->_getWriteAdapter(); $write->beginTransaction(); $write->delete($this->getTable('catalogrule/rule_product'), array( $write->quoteInto('rule_id=?', $ruleId), $write->quoteInto('product_id=?', $productId), )); if (!$rule->getConditions()->validate($product)) { $write->delete($this->getTable('catalogrule/rule_product_price'), array( $write->quoteInto('product_id=?', $productId), )); $write->commit(); return $this; } $customerGroupIds = $rule->getCustomerGroupIds(); $fromTime = strtotime($rule->getFromDate()); $toTime = strtotime($rule->getToDate()); $toTime = $toTime ? $toTime + self::SECONDS_IN_DAY - 1 : 0; $sortOrder = (int)$rule->getSortOrder(); $actionOperator = $rule->getSimpleAction(); $actionAmount = $rule->getDiscountAmount(); $actionStop = $rule->getStopRulesProcessing(); $subActionOperator = $rule->getSubIsEnable() ? $rule->getSubSimpleAction() : ''; $subActionAmount = $rule->getSubDiscountAmount(); $rows = array(); try { foreach ($websiteIds as $websiteId) { foreach ($customerGroupIds as $customerGroupId) { $rows[] = array( 'rule_id' => $ruleId, 'from_time' => $fromTime, 'to_time' => $toTime, 'website_id' => $websiteId, 'customer_group_id' => $customerGroupId, 'product_id' => $productId, 'action_operator' => $actionOperator, 'action_amount' => $actionAmount, 'action_stop' => $actionStop, 'sort_order' => $sortOrder, 'sub_simple_action' => $subActionOperator, 'sub_discount_amount' => $subActionAmount, ); if (count($rows) == 1000) { $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows); $rows = array(); } } } if (!empty($rows)) { $write->insertMultiple($this->getTable('catalogrule/rule_product'), $rows); } } catch (Exception $e) { $write->rollback(); throw $e; } $this->applyAllRulesForDateRange(null, null, $product); $write->commit(); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ interface Mage_Rule_Model_Condition_Interface { } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract Rule condition data model * * @category Mage * @package Mage_Rule * @author Magento Core Team */ abstract class Mage_Rule_Model_Condition_Abstract extends Varien_Object implements Mage_Rule_Model_Condition_Interface { /** * Defines which operators will be available for this condition * * @var string */ protected $_inputType = null; /** * Default values for possible operator options * @var array */ protected $_defaultOperatorOptions = null; /** * Default combinations of operator options, depending on input type * @var array */ protected $_defaultOperatorInputByType = null; /** * List of input types for values which should be array * @var array */ protected $_arrayInputTypes = array(); public function __construct() { parent::__construct(); $this->loadAttributeOptions()->loadOperatorOptions()->loadValueOptions(); if ($options = $this->getAttributeOptions()) { foreach ($options as $attr=>$dummy) { $this->setAttribute($attr); break; } } if ($options = $this->getOperatorOptions()) { foreach ($options as $operator=>$dummy) { $this->setOperator($operator); break; } } } /** * Default operator input by type map getter * * @return array */ public function getDefaultOperatorInputByType() { if (null === $this->_defaultOperatorInputByType) { $this->_defaultOperatorInputByType = array( 'string' => array('==', '!=', '>=', '>', '<=', '<', '{}', '!{}', '()', '!()'), 'numeric' => array('==', '!=', '>=', '>', '<=', '<', '()', '!()'), 'date' => array('==', '>=', '<='), 'select' => array('==', '!='), 'boolean' => array('==', '!='), 'multiselect' => array('{}', '!{}', '()', '!()'), 'grid' => array('()', '!()'), ); $this->_arrayInputTypes = array('multiselect', 'grid'); } return $this->_defaultOperatorInputByType; } /** * Default operator options getter * Provides all possible operator options * * @return array */ public function getDefaultOperatorOptions() { if (null === $this->_defaultOperatorOptions) { $this->_defaultOperatorOptions = array( '==' => Mage::helper('rule')->__('is'), '!=' => Mage::helper('rule')->__('is not'), '>=' => Mage::helper('rule')->__('equals or greater than'), '<=' => Mage::helper('rule')->__('equals or less than'), '>' => Mage::helper('rule')->__('greater than'), '<' => Mage::helper('rule')->__('less than'), '{}' => Mage::helper('rule')->__('contains'), '!{}' => Mage::helper('rule')->__('does not contain'), '()' => Mage::helper('rule')->__('is one of'), '!()' => Mage::helper('rule')->__('is not one of') ); } return $this->_defaultOperatorOptions; } public function getForm() { return $this->getRule()->getForm(); } public function asArray(array $arrAttributes = array()) { $out = array( 'type'=>$this->getType(), 'attribute'=>$this->getAttribute(), 'operator'=>$this->getOperator(), 'value'=>$this->getValue(), 'is_value_processed'=>$this->getIsValueParsed(), ); return $out; } public function asXml() { $xml = "".$this->getType()."" ."".$this->getAttribute()."" ."".$this->getOperator()."" ."".$this->getValue().""; return $xml; } public function loadArray($arr) { $this->setType($arr['type']); $this->setAttribute(isset($arr['attribute']) ? $arr['attribute'] : false); $this->setOperator(isset($arr['operator']) ? $arr['operator'] : false); $this->setValue(isset($arr['value']) ? $arr['value'] : false); $this->setIsValueParsed(isset($arr['is_value_parsed']) ? $arr['is_value_parsed'] : false); // $this->loadAttributeOptions(); // $this->loadOperatorOptions(); // $this->loadValueOptions(); return $this; } public function loadXml($xml) { if (is_string($xml)) { $xml = simplexml_load_string($xml); } $arr = (array)$xml; $this->loadArray($arr); return $this; } public function loadAttributeOptions() { return $this; } public function getAttributeOptions() { return array(); } public function getAttributeSelectOptions() { $opt = array(); foreach ($this->getAttributeOption() as $k=>$v) { $opt[] = array('value'=>$k, 'label'=>$v); } return $opt; } public function getAttributeName() { return $this->getAttributeOption($this->getAttribute()); } public function loadOperatorOptions() { $this->setOperatorOption($this->getDefaultOperatorOptions()); $this->setOperatorByInputType($this->getDefaultOperatorInputByType()); return $this; } /** * This value will define which operators will be available for this condition. * * Possible values are: string, numeric, date, select, multiselect, grid, boolean * * @return string */ public function getInputType() { if (null === $this->_inputType) { return 'string'; } return $this->_inputType; } public function getOperatorSelectOptions() { $type = $this->getInputType(); $opt = array(); $operatorByType = $this->getOperatorByInputType(); foreach ($this->getOperatorOption() as $k => $v) { if (!$operatorByType || in_array($k, $operatorByType[$type])) { $opt[] = array('value' => $k, 'label' => $v); } } return $opt; } public function getOperatorName() { return $this->getOperatorOption($this->getOperator()); } public function loadValueOptions() { // $this->setValueOption(array( // true => Mage::helper('rule')->__('TRUE'), // false => Mage::helper('rule')->__('FALSE'), // )); $this->setValueOption(array()); return $this; } public function getValueSelectOptions() { $valueOption = $opt = array(); if ($this->hasValueOption()) { $valueOption = (array) $this->getValueOption(); } foreach ($valueOption as $k => $v) { $opt[] = array('value' => $k, 'label' => $v); } return $opt; } /** * Retrieve parsed value * * @return array|string|int|float */ public function getValueParsed() { if (!$this->hasValueParsed()) { $value = $this->getData('value'); if ($this->isArrayOperatorType() && is_string($value)) { $value = preg_split('#\s*[,;]\s*#', $value, null, PREG_SPLIT_NO_EMPTY); } $this->setValueParsed($value); } return $this->getData('value_parsed'); } /** * Check if value should be array * * Depends on operator input type * * @return bool */ public function isArrayOperatorType() { $op = $this->getOperator(); return $op === '()' || $op === '!()' || in_array($this->getInputType(), $this->_arrayInputTypes); } public function getValue() { if ($this->getInputType()=='date' && !$this->getIsValueParsed()) { // date format intentionally hard-coded $this->setValue( Mage::app()->getLocale()->date($this->getData('value'), Varien_Date::DATE_INTERNAL_FORMAT, null, false)->toString(Varien_Date::DATE_INTERNAL_FORMAT) ); $this->setIsValueParsed(true); } return $this->getData('value'); } public function getValueName() { $value = $this->getValue(); if (is_null($value) || '' === $value) { return '...'; } $options = $this->getValueSelectOptions(); $valueArr = array(); if (!empty($options)) { foreach ($options as $o) { if (is_array($value)) { if (in_array($o['value'], $value)) { $valueArr[] = $o['label']; } } else { if (is_array($o['value'])) { foreach ($o['value'] as $v) { if ($v['value']==$value) { return $v['label']; } } } if ($o['value'] == $value) { return $o['label']; } } } } if (!empty($valueArr)) { $value = implode(', ', $valueArr); } return $value; } /** * Get inherited conditions selectors * * @return array */ public function getNewChildSelectOptions() { return array( array('value'=>'', 'label'=>Mage::helper('rule')->__('Please choose a condition to add...')), ); } public function getNewChildName() { return $this->getAddLinkHtml(); } public function asHtml() { $html = $this->getTypeElementHtml() .$this->getAttributeElementHtml() .$this->getOperatorElementHtml() .$this->getValueElementHtml() .$this->getRemoveLinkHtml() .$this->getChooserContainerHtml(); return $html; } public function asHtmlRecursive() { $html = $this->asHtml(); return $html; } public function getTypeElement() { return $this->getForm()->addField($this->getPrefix() . '__' . $this->getId() . '__type', 'hidden', array( 'name' => 'rule[' . $this->getPrefix() . '][' . $this->getId() . '][type]', 'value' => $this->getType(), 'no_span' => true, 'class' => 'hidden', )); } public function getTypeElementHtml() { return $this->getTypeElement()->getHtml(); } public function getAttributeElement() { if (is_null($this->getAttribute())) { foreach ($this->getAttributeOption() as $k => $v) { $this->setAttribute($k); break; } } return $this->getForm()->addField($this->getPrefix().'__'.$this->getId().'__attribute', 'select', array( 'name'=>'rule['.$this->getPrefix().']['.$this->getId().'][attribute]', 'values'=>$this->getAttributeSelectOptions(), 'value'=>$this->getAttribute(), 'value_name'=>$this->getAttributeName(), ))->setRenderer(Mage::getBlockSingleton('rule/editable')); } public function getAttributeElementHtml() { return $this->getAttributeElement()->getHtml(); } /** * Retrieve Condition Operator element Instance * If the operator value is empty - define first available operator value as default * * @return Varien_Data_Form_Element_Select */ public function getOperatorElement() { $options = $this->getOperatorSelectOptions(); if (is_null($this->getOperator())) { foreach ($options as $option) { $this->setOperator($option['value']); break; } } $elementId = sprintf('%s__%s__operator', $this->getPrefix(), $this->getId()); $elementName = sprintf('rule[%s][%s][operator]', $this->getPrefix(), $this->getId()); $element = $this->getForm()->addField($elementId, 'select', array( 'name' => $elementName, 'values' => $options, 'value' => $this->getOperator(), 'value_name' => $this->getOperatorName(), )); $element->setRenderer(Mage::getBlockSingleton('rule/editable')); return $element; } public function getOperatorElementHtml() { return $this->getOperatorElement()->getHtml(); } /** * Value element type will define renderer for condition value element * * @see Varien_Data_Form_Element * @return string */ public function getValueElementType() { return 'text'; } public function getValueElementRenderer() { if (strpos($this->getValueElementType(), '/')!==false) { return Mage::getBlockSingleton($this->getValueElementType()); } return Mage::getBlockSingleton('rule/editable'); } public function getValueElement() { $elementParams = array( 'name' => 'rule['.$this->getPrefix().']['.$this->getId().'][value]', 'value' => $this->getValue(), 'values' => $this->getValueSelectOptions(), 'value_name' => $this->getValueName(), 'after_element_html' => $this->getValueAfterElementHtml(), 'explicit_apply' => $this->getExplicitApply(), ); if ($this->getInputType()=='date') { // date format intentionally hard-coded $elementParams['input_format'] = Varien_Date::DATE_INTERNAL_FORMAT; $elementParams['format'] = Varien_Date::DATE_INTERNAL_FORMAT; } return $this->getForm()->addField($this->getPrefix().'__'.$this->getId().'__value', $this->getValueElementType(), $elementParams )->setRenderer($this->getValueElementRenderer()); } public function getValueElementHtml() { return $this->getValueElement()->getHtml(); } public function getAddLinkHtml() { $src = Mage::getDesign()->getSkinUrl('images/rule_component_add.gif'); $html = ''; return $html; } public function getRemoveLinkHtml() { $src = Mage::getDesign()->getSkinUrl('images/rule_component_remove.gif'); $html = ' '; return $html; } public function getChooserContainerHtml() { $url = $this->getValueElementChooserUrl(); $html = ''; if ($url) { $html = '
'; } return $html; } public function asString($format = '') { $str = $this->getAttributeName() . ' ' . $this->getOperatorName() . ' ' . $this->getValueName(); return $str; } public function asStringRecursive($level=0) { $str = str_pad('', $level * 3, ' ', STR_PAD_LEFT) . $this->asString(); return $str; } /** * Validate product attrbute value for condition * * @param mixed $validatedValue product attribute value * @return bool */ public function validateAttribute($validatedValue) { if (is_object($validatedValue)) { return false; } /** * Condition attribute value */ $value = $this->getValueParsed(); /** * Comparison operator */ $op = $this->getOperatorForValidate(); // if operator requires array and it is not, or on opposite, return false if ($this->isArrayOperatorType() xor is_array($value)) { return false; } $result = false; switch ($op) { case '==': case '!=': if (is_array($value)) { if (is_array($validatedValue)) { $result = array_intersect($value, $validatedValue); $result = !empty($result); } else { return false; } } else { if (is_array($validatedValue)) { $result = count($validatedValue) == 1 && array_shift($validatedValue) == $value; } else { $result = $this->_compareValues($validatedValue, $value); } } break; case '<=': case '>': if (!is_scalar($validatedValue)) { return false; } else { $result = $validatedValue <= $value; } break; case '>=': case '<': if (!is_scalar($validatedValue)) { return false; } else { $result = $validatedValue >= $value; } break; case '{}': case '!{}': if (is_scalar($validatedValue) && is_array($value)) { foreach ($value as $item) { if (stripos($validatedValue,$item)!==false) { $result = true; break; } } } elseif (is_array($value)) { if (is_array($validatedValue)) { $result = array_intersect($value, $validatedValue); $result = !empty($result); } else { return false; } } else { if (is_array($validatedValue)) { $result = in_array($value, $validatedValue); } else { $result = $this->_compareValues($value, $validatedValue, false); } } break; case '()': case '!()': if (is_array($validatedValue)) { $result = count(array_intersect($validatedValue, (array)$value))>0; } else { $value = (array)$value; foreach ($value as $item) { if ($this->_compareValues($validatedValue, $item)) { $result = true; break; } } } break; } if ('!=' == $op || '>' == $op || '<' == $op || '!{}' == $op || '!()' == $op) { $result = !$result; } return $result; } /** * Case and type insensitive comparison of values * * @param string|int|float $validatedValue * @param string|int|float $value * @return bool */ protected function _compareValues($validatedValue, $value, $strict = true) { if ($strict && is_numeric($validatedValue) && is_numeric($value)) { return $validatedValue == $value; } else { $validatePattern = preg_quote($validatedValue, '~'); if ($strict) { $validatePattern = '^' . $validatePattern . '$'; } return (bool)preg_match('~' . $validatePattern . '~iu', $value); } } public function validate(Varien_Object $object) { return $this->validateAttribute($object->getData($this->getAttribute())); } /** * Retrieve operator for php validation * * @return string */ public function getOperatorForValidate() { return $this->getOperator(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract Rule product condition data model * * @category Mage * @package Mage_Rule * @author Magento Core Team */ abstract class Mage_Rule_Model_Condition_Product_Abstract extends Mage_Rule_Model_Condition_Abstract { /** * All attribute values as array in form: * array( * [entity_id_1] => array( * [store_id_1] => store_value_1, * [store_id_2] => store_value_2, * ... * [store_id_n] => store_value_n * ), * ... * ) * * Will be set only for not global scope attribute * * @var array */ protected $_entityAttributeValues = null; /** * Attribute data key that indicates whether it should be used for rules * * @var string */ protected $_isUsedForRuleProperty = 'is_used_for_promo_rules'; /** * Customize default operator input by type mapper for some types * * @return array */ public function getDefaultOperatorInputByType() { if (null === $this->_defaultOperatorInputByType) { parent::getDefaultOperatorInputByType(); /* * '{}' and '!{}' are left for back-compatibility and equal to '==' and '!=' */ $this->_defaultOperatorInputByType['category'] = array('==', '!=', '{}', '!{}', '()', '!()'); $this->_arrayInputTypes[] = 'category'; } return $this->_defaultOperatorInputByType; } /** * Retrieve attribute object * * @return Mage_Catalog_Model_Resource_Eav_Attribute */ public function getAttributeObject() { try { $obj = Mage::getSingleton('eav/config') ->getAttribute(Mage_Catalog_Model_Product::ENTITY, $this->getAttribute()); } catch (Exception $e) { $obj = new Varien_Object(); $obj->setEntity(Mage::getResourceSingleton('catalog/product')) ->setFrontendInput('text'); } return $obj; } /** * Add special attributes * * @param array $attributes */ protected function _addSpecialAttributes(array &$attributes) { $attributes['attribute_set_id'] = Mage::helper('catalogrule')->__('Attribute Set'); $attributes['category_ids'] = Mage::helper('catalogrule')->__('Category'); } /** * Load attribute options * * @return Mage_CatalogRule_Model_Rule_Condition_Product */ public function loadAttributeOptions() { $productAttributes = Mage::getResourceSingleton('catalog/product') ->loadAllAttributes() ->getAttributesByCode(); $attributes = array(); foreach ($productAttributes as $attribute) { /* @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ if (!$attribute->isAllowedForRuleCondition() || !$attribute->getDataUsingMethod($this->_isUsedForRuleProperty) ) { continue; } $attributes[$attribute->getAttributeCode()] = $attribute->getFrontendLabel(); } $this->_addSpecialAttributes($attributes); asort($attributes); $this->setAttributeOption($attributes); return $this; } /** * Prepares values options to be used as select options or hashed array * Result is stored in following keys: * 'value_select_options' - normal select array: array(array('value' => $value, 'label' => $label), ...) * 'value_option' - hashed array: array($value => $label, ...), * * @return Mage_CatalogRule_Model_Rule_Condition_Product */ protected function _prepareValueOptions() { // Check that both keys exist. Maybe somehow only one was set not in this routine, but externally. $selectReady = $this->getData('value_select_options'); $hashedReady = $this->getData('value_option'); if ($selectReady && $hashedReady) { return $this; } // Get array of select options. It will be used as source for hashed options $selectOptions = null; if ($this->getAttribute() === 'attribute_set_id') { $entityTypeId = Mage::getSingleton('eav/config') ->getEntityType(Mage_Catalog_Model_Product::ENTITY)->getId(); $selectOptions = Mage::getResourceModel('eav/entity_attribute_set_collection') ->setEntityTypeFilter($entityTypeId) ->load() ->toOptionArray(); } else if (is_object($this->getAttributeObject())) { $attributeObject = $this->getAttributeObject(); if ($attributeObject->usesSource()) { if ($attributeObject->getFrontendInput() == 'multiselect') { $addEmptyOption = false; } else { $addEmptyOption = true; } $selectOptions = $attributeObject->getSource()->getAllOptions($addEmptyOption); } } // Set new values only if we really got them if ($selectOptions !== null) { // Overwrite only not already existing values if (!$selectReady) { $this->setData('value_select_options', $selectOptions); } if (!$hashedReady) { $hashedOptions = array(); foreach ($selectOptions as $o) { if (is_array($o['value'])) { continue; // We cannot use array as index } $hashedOptions[$o['value']] = $o['label']; } $this->setData('value_option', $hashedOptions); } } return $this; } /** * Retrieve value by option * * @param mixed $option * @return string */ public function getValueOption($option=null) { $this->_prepareValueOptions(); return $this->getData('value_option'.(!is_null($option) ? '/'.$option : '')); } /** * Retrieve select option values * * @return array */ public function getValueSelectOptions() { $this->_prepareValueOptions(); return $this->getData('value_select_options'); } /** * Retrieve after element HTML * * @return string */ public function getValueAfterElementHtml() { $html = ''; switch ($this->getAttribute()) { case 'sku': case 'category_ids': $image = Mage::getDesign()->getSkinUrl('images/rule_chooser_trigger.gif'); break; } if (!empty($image)) { $html = ''; } return $html; } /** * Retrieve attribute element * * @return Varien_Form_Element_Abstract */ public function getAttributeElement() { $element = parent::getAttributeElement(); $element->setShowAsText(true); return $element; } /** * Collect validated attributes * * @param Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection $productCollection * @return Mage_CatalogRule_Model_Rule_Condition_Product */ public function collectValidatedAttributes($productCollection) { $attribute = $this->getAttribute(); if ('category_ids' != $attribute) { if ($this->getAttributeObject()->isScopeGlobal()) { $attributes = $this->getRule()->getCollectedAttributes(); $attributes[$attribute] = true; $this->getRule()->setCollectedAttributes($attributes); $productCollection->addAttributeToSelect($attribute, 'left'); } else { $this->_entityAttributeValues = $productCollection->getAllAttributeValues($attribute); } } return $this; } /** * Retrieve input type * * @return string */ public function getInputType() { if ($this->getAttribute()==='attribute_set_id') { return 'select'; } if (!is_object($this->getAttributeObject())) { return 'string'; } if ($this->getAttributeObject()->getAttributeCode() == 'category_ids') { return 'category'; } switch ($this->getAttributeObject()->getFrontendInput()) { case 'select': return 'select'; case 'multiselect': return 'multiselect'; case 'date': return 'date'; case 'boolean': return 'boolean'; default: return 'string'; } } /** * Retrieve value element type * * @return string */ public function getValueElementType() { if ($this->getAttribute()==='attribute_set_id') { return 'select'; } if (!is_object($this->getAttributeObject())) { return 'text'; } switch ($this->getAttributeObject()->getFrontendInput()) { case 'select': case 'boolean': return 'select'; case 'multiselect': return 'multiselect'; case 'date': return 'date'; default: return 'text'; } } /** * Retrieve value element * * @return Varien_Data_Form_Element_Abstract */ public function getValueElement() { $element = parent::getValueElement(); if (is_object($this->getAttributeObject())) { switch ($this->getAttributeObject()->getFrontendInput()) { case 'date': $element->setImage(Mage::getDesign()->getSkinUrl('images/grid-cal.gif')); break; } } return $element; } /** * Retrieve value element chooser URL * * @return string */ public function getValueElementChooserUrl() { $url = false; switch ($this->getAttribute()) { case 'sku': case 'category_ids': $url = 'adminhtml/promo_widget/chooser' .'/attribute/'.$this->getAttribute(); if ($this->getJsFormObject()) { $url .= '/form/'.$this->getJsFormObject(); } break; } return $url!==false ? Mage::helper('adminhtml')->getUrl($url) : ''; } /** * Retrieve Explicit Apply * * @return bool */ public function getExplicitApply() { switch ($this->getAttribute()) { case 'sku': case 'category_ids': return true; } if (is_object($this->getAttributeObject())) { switch ($this->getAttributeObject()->getFrontendInput()) { case 'date': return true; } } return false; } /** * Load array * * @param array $arr * @return Mage_CatalogRule_Model_Rule_Condition_Product */ public function loadArray($arr) { $this->setAttribute(isset($arr['attribute']) ? $arr['attribute'] : false); $attribute = $this->getAttributeObject(); $isContainsOperator = !empty($arr['operator']) && in_array($arr['operator'], array('{}', '!{}')); if ($attribute && $attribute->getBackendType() == 'decimal' && !$isContainsOperator) { if (isset($arr['value'])) { if (!empty($arr['operator']) && in_array($arr['operator'], array('!()', '()')) && false !== strpos($arr['value'], ',')) { $tmp = array(); foreach (explode(',', $arr['value']) as $value) { $tmp[] = Mage::app()->getLocale()->getNumber($value); } $arr['value'] = implode(',', $tmp); } else { $arr['value'] = Mage::app()->getLocale()->getNumber($arr['value']); } } else { $arr['value'] = false; } $arr['is_value_parsed'] = isset($arr['is_value_parsed']) ? Mage::app()->getLocale()->getNumber($arr['is_value_parsed']) : false; } return parent::loadArray($arr); } /** * Validate product attrbute value for condition * * @param Varien_Object $object * @return bool */ public function validate(Varien_Object $object) { $attrCode = $this->getAttribute(); if ('category_ids' == $attrCode) { return $this->validateAttribute($object->getAvailableInCategories()); } elseif (! isset($this->_entityAttributeValues[$object->getId()])) { if (!$object->getResource()) { return false; } $attr = $object->getResource()->getAttribute($attrCode); if ($attr && $attr->getBackendType() == 'datetime' && !is_int($this->getValue())) { $this->setValue(strtotime($this->getValue())); $value = strtotime($object->getData($attrCode)); return $this->validateAttribute($value); } if ($attr && $attr->getFrontendInput() == 'multiselect') { $value = $object->getData($attrCode); $value = strlen($value) ? explode(',', $value) : array(); return $this->validateAttribute($value); } return parent::validate($object); } else { $result = false; // any valid value will set it to TRUE // remember old attribute state $oldAttrValue = $object->hasData($attrCode) ? $object->getData($attrCode) : null; foreach ($this->_entityAttributeValues[$object->getId()] as $storeId => $value) { $attr = $object->getResource()->getAttribute($attrCode); if ($attr && $attr->getBackendType() == 'datetime') { $value = strtotime($value); } else if ($attr && $attr->getFrontendInput() == 'multiselect') { $value = strlen($value) ? explode(',', $value) : array(); } $object->setData($attrCode, $value); $result |= parent::validate($object); if ($result) { break; } } if (is_null($oldAttrValue)) { $object->unsetData($attrCode); } else { $object->setData($attrCode, $oldAttrValue); } return (bool) $result; } } /** * Correct '==' and '!=' operators * Categories can't be equal because product is included categories selected by administrator and in their parents * * @return string */ public function getOperatorForValidate() { $op = $this->getOperator(); if ($this->getInputType() == 'category') { if ($op == '==') { $op = '{}'; } elseif ($op == '!=') { $op = '!{}'; } } return $op; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_CatalogRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog Rule Product Condition data model */ class Mage_CatalogRule_Model_Rule_Condition_Product extends Mage_Rule_Model_Condition_Product_Abstract { } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product price block * * @category Mage * @package Mage_Catalog */ class Mage_Catalog_Block_Product_Price extends Mage_Core_Block_Template { protected $_priceDisplayType = null; protected $_idSuffix = ''; /** * Retrieve product * * @return Mage_Catalog_Model_Product */ public function getProduct() { $product = $this->_getData('product'); if (!$product) { $product = Mage::registry('product'); } return $product; } public function getDisplayMinimalPrice() { return $this->_getData('display_minimal_price'); } public function setIdSuffix($idSuffix) { $this->_idSuffix = $idSuffix; return $this; } public function getIdSuffix() { return $this->_idSuffix; } /** * Get tier prices (formatted) * * @param Mage_Catalog_Model_Product $product * @return array */ public function getTierPrices($product = null) { if (is_null($product)) { $product = $this->getProduct(); } $prices = $product->getFormatedTierPrice(); $res = array(); if (is_array($prices)) { foreach ($prices as $price) { $price['price_qty'] = $price['price_qty'] * 1; $productPrice = $product->getPrice(); if ($product->getPrice() != $product->getFinalPrice()) { $productPrice = $product->getFinalPrice(); } // Group price must be used for percent calculation if it is lower $groupPrice = $product->getGroupPrice(); if ($productPrice > $groupPrice) { $productPrice = $groupPrice; } if ($price['price'] < $productPrice) { $price['savePercent'] = ceil(100 - ((100 / $productPrice) * $price['price'])); $tierPrice = Mage::app()->getStore()->convertPrice( Mage::helper('tax')->getPrice($product, $price['website_price']) ); $price['formated_price'] = Mage::app()->getStore()->formatPrice($tierPrice); $price['formated_price_incl_tax'] = Mage::app()->getStore()->formatPrice( Mage::app()->getStore()->convertPrice( Mage::helper('tax')->getPrice($product, $price['website_price'], true) ) ); if (Mage::helper('catalog')->canApplyMsrp($product)) { $oldPrice = $product->getFinalPrice(); $product->setPriceCalculation(false); $product->setPrice($tierPrice); $product->setFinalPrice($tierPrice); $this->getLayout()->getBlock('product.info')->getPriceHtml($product); $product->setPriceCalculation(true); $price['real_price_html'] = $product->getRealPriceHtml(); $product->setFinalPrice($oldPrice); } $res[] = $price; } } } return $res; } /** * Retrieve url for direct adding product to cart * * @param Mage_Catalog_Model_Product $product * @param array $additional * @return string */ public function getAddToCartUrl($product, $additional = array()) { return $this->helper('checkout/cart')->getAddUrl($product, $additional); } /** * Prevent displaying if the price is not available * * @return string */ protected function _toHtml() { if (!$this->getProduct() || $this->getProduct()->getCanShowPrice() === false) { return ''; } return parent::_toHtml(); } /** * Get Product Price valid JS string * * @param Mage_Catalog_Model_Product $product * @return string */ public function getRealPriceJs($product) { $html = $this->hasRealPriceHtml() ? $this->getRealPriceHtml() : $product->getRealPriceHtml(); return Mage::helper('core')->jsonEncode($html); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog Product Flat Helper * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Helper_Product_Flat extends Mage_Core_Helper_Abstract { const XML_PATH_USE_PRODUCT_FLAT = 'catalog/frontend/flat_catalog_product'; const XML_NODE_ADD_FILTERABLE_ATTRIBUTES = 'global/catalog/product/flat/add_filterable_attributes'; const XML_NODE_ADD_CHILD_DATA = 'global/catalog/product/flat/add_child_data'; /** * Catalog Flat Product index process code * * @var string */ const CATALOG_FLAT_PROCESS_CODE = 'catalog_product_flat'; /** * Catalog Product Flat index process * * @var Mage_Index_Model_Process */ protected $_process; /** * Catalog Product Flat status by store * * @var array */ protected $_isEnabled = array(); /** * Catalog Product Flat Flag object * * @var Mage_Catalog_Model_Product_Flat_Flag */ protected $_flagObject; /** * Retrieve Catalog Product Flat Flag object * * @return Mage_Catalog_Model_Product_Flat_Flag */ public function getFlag() { if (is_null($this->_flagObject)) { $this->_flagObject = Mage::getSingleton('catalog/product_flat_flag') ->loadSelf(); } return $this->_flagObject; } /** * Check is builded Catalog Product Flat Data * * @return bool */ public function isBuilt() { return $this->getFlag()->getIsBuilt(); } /** * Check is enable catalog product for store * * @param mixed $store * @return bool */ public function isEnabled($store = null) { $store = Mage::app()->getStore($store); if ($store->isAdmin()) { return false; } if (!isset($this->_isEnabled[$store->getId()])) { if (Mage::getStoreConfigFlag(self::XML_PATH_USE_PRODUCT_FLAT, $store)) { $this->_isEnabled[$store->getId()] = $this->getProcess()->getStatus() == Mage_Index_Model_Process::STATUS_PENDING; } else { $this->_isEnabled[$store->getId()] = false; } } return $this->_isEnabled[$store->getId()]; } /** * Is add filterable attributes to Flat table * * @return int */ public function isAddFilterableAttributes() { return intval(Mage::getConfig()->getNode(self::XML_NODE_ADD_FILTERABLE_ATTRIBUTES)); } /** * Is add child data to Flat * * @return int */ public function isAddChildData() { return intval(Mage::getConfig()->getNode(self::XML_NODE_ADD_CHILD_DATA)); } /** * Retrive Catalog Product Flat index process * * @return Mage_Index_Model_Process */ public function getProcess() { if (is_null($this->_process)) { $this->_process = Mage::getModel('index/process') ->load(self::CATALOG_FLAT_PROCESS_CODE, 'indexer_code'); } return $this->_process; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Catalog_Model_Config extends Mage_Eav_Model_Config { const XML_PATH_LIST_DEFAULT_SORT_BY = 'catalog/frontend/default_sort_by'; protected $_attributeSetsById; protected $_attributeSetsByName; protected $_attributeGroupsById; protected $_attributeGroupsByName; protected $_productTypesById; /** * Array of attributes codes needed for product load * * @var array */ protected $_productAttributes; /** * Product Attributes used in product listing * * @var array */ protected $_usedInProductListing; /** * Product Attributes For Sort By * * @var array */ protected $_usedForSortBy; protected $_storeId = null; const XML_PATH_PRODUCT_COLLECTION_ATTRIBUTES = 'frontend/product/collection/attributes'; /** * Initialize resource model * */ protected function _construct() { $this->_init('catalog/config'); } /** * Set store id * * @param integer $storeId * @return Mage_Catalog_Model_Config */ public function setStoreId($storeId) { $this->_storeId = $storeId; return $this; } /** * Return store id, if is not set return current app store * * @return integer */ public function getStoreId() { if ($this->_storeId === null) { return Mage::app()->getStore()->getId(); } return $this->_storeId; } public function loadAttributeSets() { if ($this->_attributeSetsById) { return $this; } $attributeSetCollection = Mage::getResourceModel('eav/entity_attribute_set_collection') ->load(); $this->_attributeSetsById = array(); $this->_attributeSetsByName = array(); foreach ($attributeSetCollection as $id=>$attributeSet) { $entityTypeId = $attributeSet->getEntityTypeId(); $name = $attributeSet->getAttributeSetName(); $this->_attributeSetsById[$entityTypeId][$id] = $name; $this->_attributeSetsByName[$entityTypeId][strtolower($name)] = $id; } return $this; } public function getAttributeSetName($entityTypeId, $id) { if (!is_numeric($id)) { return $id; } $this->loadAttributeSets(); if (!is_numeric($entityTypeId)) { $entityTypeId = $this->getEntityType($entityTypeId)->getId(); } return isset($this->_attributeSetsById[$entityTypeId][$id]) ? $this->_attributeSetsById[$entityTypeId][$id] : false; } public function getAttributeSetId($entityTypeId, $name) { if (is_numeric($name)) { return $name; } $this->loadAttributeSets(); if (!is_numeric($entityTypeId)) { $entityTypeId = $this->getEntityType($entityTypeId)->getId(); } $name = strtolower($name); return isset($this->_attributeSetsByName[$entityTypeId][$name]) ? $this->_attributeSetsByName[$entityTypeId][$name] : false; } public function loadAttributeGroups() { if ($this->_attributeGroupsById) { return $this; } $attributeSetCollection = Mage::getResourceModel('eav/entity_attribute_group_collection') ->load(); $this->_attributeGroupsById = array(); $this->_attributeGroupsByName = array(); foreach ($attributeSetCollection as $id=>$attributeGroup) { $attributeSetId = $attributeGroup->getAttributeSetId(); $name = $attributeGroup->getAttributeGroupName(); $this->_attributeGroupsById[$attributeSetId][$id] = $name; $this->_attributeGroupsByName[$attributeSetId][strtolower($name)] = $id; } return $this; } public function getAttributeGroupName($attributeSetId, $id) { if (!is_numeric($id)) { return $id; } $this->loadAttributeGroups(); if (!is_numeric($attributeSetId)) { $attributeSetId = $this->getAttributeSetId($attributeSetId); } return isset($this->_attributeGroupsById[$attributeSetId][$id]) ? $this->_attributeGroupsById[$attributeSetId][$id] : false; } public function getAttributeGroupId($attributeSetId, $name) { if (is_numeric($name)) { return $name; } $this->loadAttributeGroups(); if (!is_numeric($attributeSetId)) { $attributeSetId = $this->getAttributeSetId($attributeSetId); } $name = strtolower($name); return isset($this->_attributeGroupsByName[$attributeSetId][$name]) ? $this->_attributeGroupsByName[$attributeSetId][$name] : false; } public function loadProductTypes() { if ($this->_productTypesById) { return $this; } /* $productTypeCollection = Mage::getResourceModel('catalog/product_type_collection') ->load(); */ $productTypeCollection = Mage::getModel('catalog/product_type') ->getOptionArray(); $this->_productTypesById = array(); $this->_productTypesByName = array(); foreach ($productTypeCollection as $id=>$type) { //$name = $type->getCode(); $name = $type; $this->_productTypesById[$id] = $name; $this->_productTypesByName[strtolower($name)] = $id; } return $this; } public function getProductTypeId($name) { if (is_numeric($name)) { return $name; } $this->loadProductTypes(); $name = strtolower($name); return isset($this->_productTypesByName[$name]) ? $this->_productTypesByName[$name] : false; } public function getProductTypeName($id) { if (!is_numeric($id)) { return $id; } $this->loadProductTypes(); return isset($this->_productTypesById[$id]) ? $this->_productTypesById[$id] : false; } public function getSourceOptionId($source, $value) { foreach ($source->getAllOptions() as $option) { if (strcasecmp($option['label'], $value)==0 || $option['value'] == $value) { return $option['value']; } } return null; } /** * Load Product attributes * * @return array */ public function getProductAttributes() { if (is_null($this->_productAttributes)) { $this->_productAttributes = array_keys($this->getAttributesUsedInProductListing()); } return $this->_productAttributes; } /** * Retrieve Product Collection Attributes from XML config file * Used only for install/upgrade * * @return array */ public function getProductCollectionAttributes() { $attributes = Mage::getConfig() ->getNode(self::XML_PATH_PRODUCT_COLLECTION_ATTRIBUTES) ->asArray(); return array_keys($attributes);; } /** * Retrieve resource model * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Config */ protected function _getResource() { return Mage::getResourceModel('catalog/config'); } /** * Retrieve Attributes used in product listing * * @return array */ public function getAttributesUsedInProductListing() { if (is_null($this->_usedInProductListing)) { $this->_usedInProductListing = array(); $entityType = Mage_Catalog_Model_Product::ENTITY; $attributesData = $this->_getResource() ->setStoreId($this->getStoreId()) ->getAttributesUsedInListing(); Mage::getSingleton('eav/config') ->importAttributesData($entityType, $attributesData); foreach ($attributesData as $attributeData) { $attributeCode = $attributeData['attribute_code']; $this->_usedInProductListing[$attributeCode] = Mage::getSingleton('eav/config') ->getAttribute($entityType, $attributeCode); } } return $this->_usedInProductListing; } /** * Retrieve Attributes array used for sort by * * @return array */ public function getAttributesUsedForSortBy() { if (is_null($this->_usedForSortBy)) { $this->_usedForSortBy = array(); $entityType = Mage_Catalog_Model_Product::ENTITY; $attributesData = $this->_getResource() ->getAttributesUsedForSortBy(); Mage::getSingleton('eav/config') ->importAttributesData($entityType, $attributesData); foreach ($attributesData as $attributeData) { $attributeCode = $attributeData['attribute_code']; $this->_usedForSortBy[$attributeCode] = Mage::getSingleton('eav/config') ->getAttribute($entityType, $attributeCode); } } return $this->_usedForSortBy; } /** * Retrieve Attributes Used for Sort by as array * key = code, value = name * * @return array */ public function getAttributeUsedForSortByArray() { $options = array( 'position' => Mage::helper('catalog')->__('Position') ); foreach ($this->getAttributesUsedForSortBy() as $attribute) { /* @var $attribute Mage_Eav_Model_Entity_Attribute_Abstract */ $options[$attribute->getAttributeCode()] = $attribute->getStoreLabel(); } return $options; } /** * Retrieve Product List Default Sort By * * @param mixed $store * @return string */ public function getProductListDefaultSortBy($store = null) { return Mage::getStoreConfig(self::XML_PATH_LIST_DEFAULT_SORT_BY, $store); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product model * * @method Mage_Catalog_Model_Resource_Product getResource() * @method Mage_Catalog_Model_Resource_Product _getResource() * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product extends Mage_Catalog_Model_Abstract { /** * Entity code. * Can be used as part of method name for entity processing */ const ENTITY = 'catalog_product'; const CACHE_TAG = 'catalog_product'; protected $_cacheTag = 'catalog_product'; protected $_eventPrefix = 'catalog_product'; protected $_eventObject = 'product'; protected $_canAffectOptions = false; /** * Product type instance * * @var Mage_Catalog_Model_Product_Type_Abstract */ protected $_typeInstance = null; /** * Product type instance as singleton */ protected $_typeInstanceSingleton = null; /** * Product link instance * * @var Mage_Catalog_Model_Product_Link */ protected $_linkInstance; /** * Product object customization (not stored in DB) * * @var array */ protected $_customOptions = array(); /** * Product Url Instance * * @var Mage_Catalog_Model_Product_Url */ protected $_urlModel = null; protected static $_url; protected static $_urlRewrite; protected $_errors = array(); protected $_optionInstance; protected $_options = array(); /** * Product reserved attribute codes */ protected $_reservedAttributes; /** * Flag for available duplicate function * * @var boolean */ protected $_isDuplicable = true; /** * Flag for get Price function * * @var boolean */ protected $_calculatePrice = true; /** * Initialize resources */ protected function _construct() { $this->_init('catalog/product'); } /** * Init mapping array of short fields to * its full names * * @return Varien_Object */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = Mage::helper('catalog')->getOldFieldMap(); return $this; } /** * Retrieve Store Id * * @return int */ public function getStoreId() { if ($this->hasData('store_id')) { return $this->getData('store_id'); } return Mage::app()->getStore()->getId(); } /** * Get collection instance * * @return object */ public function getResourceCollection() { if (empty($this->_resourceCollectionName)) { Mage::throwException(Mage::helper('catalog')->__('The model collection resource name is not defined.')); } $collection = Mage::getResourceModel($this->_resourceCollectionName); $collection->setStoreId($this->getStoreId()); return $collection; } /** * Get product url model * * @return Mage_Catalog_Model_Product_Url */ public function getUrlModel() { if ($this->_urlModel === null) { $this->_urlModel = Mage::getSingleton('catalog/product_url'); } return $this->_urlModel; } /** * Validate Product Data * * @todo implement full validation process with errors returning which are ignoring now * * @return Mage_Catalog_Model_Product */ public function validate() { // $this->getAttributes(); // Mage::dispatchEvent($this->_eventPrefix.'_validate_before', array($this->_eventObject=>$this)); // $result = $this->_getResource()->validate($this); // Mage::dispatchEvent($this->_eventPrefix.'_validate_after', array($this->_eventObject=>$this)); // return $result; Mage::dispatchEvent($this->_eventPrefix.'_validate_before', array($this->_eventObject=>$this)); $this->_getResource()->validate($this); Mage::dispatchEvent($this->_eventPrefix.'_validate_after', array($this->_eventObject=>$this)); return $this; } /** * Get product name * * @return string */ public function getName() { return $this->_getData('name'); } /** * Get product price throught type instance * * @return unknown */ public function getPrice() { if ($this->_calculatePrice || !$this->getData('price')) { return $this->getPriceModel()->getPrice($this); } else { return $this->getData('price'); } } /** * Set Price calculation flag * * @param bool $calculate * @return void */ public function setPriceCalculation($calculate = true) { $this->_calculatePrice = $calculate; } /** * Get product type identifier * * @return string */ public function getTypeId() { return $this->_getData('type_id'); } /** * Get product status * * @return int */ public function getStatus() { if (is_null($this->_getData('status'))) { $this->setData('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); } return $this->_getData('status'); } /** * Retrieve type instance * * Type instance implement type depended logic * * @param bool $singleton * @return Mage_Catalog_Model_Product_Type_Abstract */ public function getTypeInstance($singleton = false) { if ($singleton === true) { if (is_null($this->_typeInstanceSingleton)) { $this->_typeInstanceSingleton = Mage::getSingleton('catalog/product_type') ->factory($this, true); } return $this->_typeInstanceSingleton; } if ($this->_typeInstance === null) { $this->_typeInstance = Mage::getSingleton('catalog/product_type') ->factory($this); } return $this->_typeInstance; } /** * Set type instance for external * * @param Mage_Catalog_Model_Product_Type_Abstract $instance Product type instance * @param bool $singleton Whether instance is singleton * @return Mage_Catalog_Model_Product */ public function setTypeInstance($instance, $singleton = false) { if ($singleton === true) { $this->_typeInstanceSingleton = $instance; } else { $this->_typeInstance = $instance; } return $this; } /** * Retrieve link instance * * @return Mage_Catalog_Model_Product_Link */ public function getLinkInstance() { if (!$this->_linkInstance) { $this->_linkInstance = Mage::getSingleton('catalog/product_link'); } return $this->_linkInstance; } /** * Retrive product id by sku * * @param string $sku * @return integer */ public function getIdBySku($sku) { return $this->_getResource()->getIdBySku($sku); } /** * Retrieve product category id * * @return int */ public function getCategoryId() { if ($category = Mage::registry('current_category')) { return $category->getId(); } return false; } /** * Retrieve product category * * @return Mage_Catalog_Model_Category */ public function getCategory() { $category = $this->getData('category'); if (is_null($category) && $this->getCategoryId()) { $category = Mage::getModel('catalog/category')->load($this->getCategoryId()); $this->setCategory($category); } return $category; } /** * Set assigned category IDs array to product * * @param array|string $ids * @return Mage_Catalog_Model_Product */ public function setCategoryIds($ids) { if (is_string($ids)) { $ids = explode(',', $ids); } elseif (!is_array($ids)) { Mage::throwException(Mage::helper('catalog')->__('Invalid category IDs.')); } foreach ($ids as $i => $v) { if (empty($v)) { unset($ids[$i]); } } $this->setData('category_ids', $ids); return $this; } /** * Retrieve assigned category Ids * * @return array */ public function getCategoryIds() { if (! $this->hasData('category_ids')) { $wasLocked = false; if ($this->isLockedAttribute('category_ids')) { $wasLocked = true; $this->unlockAttribute('category_ids'); } $ids = $this->_getResource()->getCategoryIds($this); $this->setData('category_ids', $ids); if ($wasLocked) { $this->lockAttribute('category_ids'); } } return (array) $this->_getData('category_ids'); } /** * Retrieve product categories * * @return Varien_Data_Collection */ public function getCategoryCollection() { return $this->_getResource()->getCategoryCollection($this); } /** * Retrieve product websites identifiers * * @return array */ public function getWebsiteIds() { if (!$this->hasWebsiteIds()) { $ids = $this->_getResource()->getWebsiteIds($this); $this->setWebsiteIds($ids); } return $this->getData('website_ids'); } /** * Get all sore ids where product is presented * * @return array */ public function getStoreIds() { if (!$this->hasStoreIds()) { $storeIds = array(); if ($websiteIds = $this->getWebsiteIds()) { foreach ($websiteIds as $websiteId) { $websiteStores = Mage::app()->getWebsite($websiteId)->getStoreIds(); $storeIds = array_merge($storeIds, $websiteStores); } } $this->setStoreIds($storeIds); } return $this->getData('store_ids'); } /** * Retrieve product attributes * if $groupId is null - retrieve all product attributes * * @param int $groupId Retrieve attributes of the specified group * @param bool $skipSuper Not used * @return array */ public function getAttributes($groupId = null, $skipSuper = false) { $productAttributes = $this->getTypeInstance(true)->getEditableAttributes($this); if ($groupId) { $attributes = array(); foreach ($productAttributes as $attribute) { if ($attribute->isInGroup($this->getAttributeSetId(), $groupId)) { $attributes[] = $attribute; } } } else { $attributes = $productAttributes; } return $attributes; } /** * Check product options and type options and save them, too */ protected function _beforeSave() { $this->cleanCache(); $this->setTypeHasOptions(false); $this->setTypeHasRequiredOptions(false); $this->getTypeInstance(true)->beforeSave($this); $hasOptions = false; $hasRequiredOptions = false; /** * $this->_canAffectOptions - set by type instance only * $this->getCanSaveCustomOptions() - set either in controller when "Custom Options" ajax tab is loaded, * or in type instance as well */ $this->canAffectOptions($this->_canAffectOptions && $this->getCanSaveCustomOptions()); if ($this->getCanSaveCustomOptions()) { $options = $this->getProductOptions(); if (is_array($options)) { $this->setIsCustomOptionChanged(true); foreach ($this->getProductOptions() as $option) { $this->getOptionInstance()->addOption($option); if ((!isset($option['is_delete'])) || $option['is_delete'] != '1') { $hasOptions = true; } } foreach ($this->getOptionInstance()->getOptions() as $option) { if ($option['is_require'] == '1') { $hasRequiredOptions = true; break; } } } } /** * Set true, if any * Set false, ONLY if options have been affected by Options tab and Type instance tab */ if ($hasOptions || (bool)$this->getTypeHasOptions()) { $this->setHasOptions(true); if ($hasRequiredOptions || (bool)$this->getTypeHasRequiredOptions()) { $this->setRequiredOptions(true); } elseif ($this->canAffectOptions()) { $this->setRequiredOptions(false); } } elseif ($this->canAffectOptions()) { $this->setHasOptions(false); $this->setRequiredOptions(false); } parent::_beforeSave(); } /** * Check/set if options can be affected when saving product * If value specified, it will be set. * * @param bool $value * @return bool */ public function canAffectOptions($value = null) { if (null !== $value) { $this->_canAffectOptions = (bool)$value; } return $this->_canAffectOptions; } /** * Saving product type related data and init index * * @return Mage_Catalog_Model_Product */ protected function _afterSave() { $this->getLinkInstance()->saveProductRelations($this); $this->getTypeInstance(true)->save($this); /** * Product Options */ $this->getOptionInstance()->setProduct($this) ->saveOptions(); $result = parent::_afterSave(); Mage::getSingleton('index/indexer')->processEntityAction( $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE ); return $result; } /** * Clear chache related with product and protect delete from not admin * Register indexing event before delete product * * @return Mage_Catalog_Model_Product */ protected function _beforeDelete() { $this->_protectFromNonAdmin(); $this->cleanCache(); Mage::getSingleton('index/indexer')->logEvent( $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE ); return parent::_beforeDelete(); } /** * Init indexing process after product delete commit * * @return Mage_Catalog_Model_Product */ protected function _afterDeleteCommit() { parent::_afterDeleteCommit(); Mage::getSingleton('index/indexer')->indexEvents( self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE ); } /** * Load product options if they exists * * @return Mage_Catalog_Model_Product */ protected function _afterLoad() { parent::_afterLoad(); /** * Load product options */ if ($this->getHasOptions()) { foreach ($this->getProductOptionsCollection() as $option) { $option->setProduct($this); $this->addOption($option); } } return $this; } /** * Retrieve resource instance wrapper * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product */ protected function _getResource() { return parent::_getResource(); } /** * Clear cache related with product id * * @return Mage_Catalog_Model_Product */ public function cleanCache() { Mage::app()->cleanCache('catalog_product_'.$this->getId()); return $this; } /** * Get product price model * * @return Mage_Catalog_Model_Product_Type_Price */ public function getPriceModel() { return Mage::getSingleton('catalog/product_type')->priceFactory($this->getTypeId()); } /** * Get product group price * * @return float */ public function getGroupPrice() { return $this->getPriceModel()->getGroupPrice($this); } /** * Get product tier price by qty * * @param double $qty * @return double */ public function getTierPrice($qty=null) { return $this->getPriceModel()->getTierPrice($qty, $this); } /** * Count how many tier prices we have for the product * * @return int */ public function getTierPriceCount() { return $this->getPriceModel()->getTierPriceCount($this); } /** * Get formated by currency tier price * * @param double $qty * @return array || double */ public function getFormatedTierPrice($qty=null) { return $this->getPriceModel()->getFormatedTierPrice($qty, $this); } /** * Get formated by currency product price * * @return array || double */ public function getFormatedPrice() { return $this->getPriceModel()->getFormatedPrice($this); } /** * Sets final price of product * * This func is equal to magic 'setFinalPrice()', but added as a separate func, because in cart with bundle * products it's called very often in Item->getProduct(). So removing chain of magic with more cpu consuming * algorithms gives nice optimization boost. * * @param float $price Price amount * @return Mage_Catalog_Model_Product */ public function setFinalPrice($price) { $this->_data['final_price'] = $price; return $this; } /** * Get product final price * * @param double $qty * @return double */ public function getFinalPrice($qty=null) { $price = $this->_getData('final_price'); if ($price !== null) { return $price; } return $this->getPriceModel()->getFinalPrice($qty, $this); } /** * Returns calculated final price * * @return float */ public function getCalculatedFinalPrice() { return $this->_getData('calculated_final_price'); } /** * Returns minimal price * * @return float */ public function getMinimalPrice() { return max($this->_getData('minimal_price'), 0); } /** * Returns special price * * @return float */ public function getSpecialPrice() { return $this->_getData('special_price'); } /** * Returns starting date of the special price * * @return mixed */ public function getSpecialFromDate() { return $this->_getData('special_from_date'); } /** * Returns end date of the special price * * @return mixed */ public function getSpecialToDate() { return $this->_getData('special_to_date'); } /******************************************************************************* ** Linked products API */ /** * Retrieve array of related roducts * * @return array */ public function getRelatedProducts() { if (!$this->hasRelatedProducts()) { $products = array(); $collection = $this->getRelatedProductCollection(); foreach ($collection as $product) { $products[] = $product; } $this->setRelatedProducts($products); } return $this->getData('related_products'); } /** * Retrieve related products identifiers * * @return array */ public function getRelatedProductIds() { if (!$this->hasRelatedProductIds()) { $ids = array(); foreach ($this->getRelatedProducts() as $product) { $ids[] = $product->getId(); } $this->setRelatedProductIds($ids); } return $this->getData('related_product_ids'); } /** * Retrieve collection related product * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function getRelatedProductCollection() { $collection = $this->getLinkInstance()->useRelatedLinks() ->getProductCollection() ->setIsStrongMode(); $collection->setProduct($this); return $collection; } /** * Retrieve collection related link * * @return Mage_Catalog_Model_Resource_Product_Link_Collection */ public function getRelatedLinkCollection() { $collection = $this->getLinkInstance()->useRelatedLinks() ->getLinkCollection(); $collection->setProduct($this); $collection->addLinkTypeIdFilter(); $collection->addProductIdFilter(); $collection->joinAttributes(); return $collection; } /** * Retrieve array of up sell products * * @return array */ public function getUpSellProducts() { if (!$this->hasUpSellProducts()) { $products = array(); foreach ($this->getUpSellProductCollection() as $product) { $products[] = $product; } $this->setUpSellProducts($products); } return $this->getData('up_sell_products'); } /** * Retrieve up sell products identifiers * * @return array */ public function getUpSellProductIds() { if (!$this->hasUpSellProductIds()) { $ids = array(); foreach ($this->getUpSellProducts() as $product) { $ids[] = $product->getId(); } $this->setUpSellProductIds($ids); } return $this->getData('up_sell_product_ids'); } /** * Retrieve collection up sell product * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function getUpSellProductCollection() { $collection = $this->getLinkInstance()->useUpSellLinks() ->getProductCollection() ->setIsStrongMode(); $collection->setProduct($this); return $collection; } /** * Retrieve collection up sell link * * @return Mage_Catalog_Model_Resource_Product_Link_Collection */ public function getUpSellLinkCollection() { $collection = $this->getLinkInstance()->useUpSellLinks() ->getLinkCollection(); $collection->setProduct($this); $collection->addLinkTypeIdFilter(); $collection->addProductIdFilter(); $collection->joinAttributes(); return $collection; } /** * Retrieve array of cross sell products * * @return array */ public function getCrossSellProducts() { if (!$this->hasCrossSellProducts()) { $products = array(); foreach ($this->getCrossSellProductCollection() as $product) { $products[] = $product; } $this->setCrossSellProducts($products); } return $this->getData('cross_sell_products'); } /** * Retrieve cross sell products identifiers * * @return array */ public function getCrossSellProductIds() { if (!$this->hasCrossSellProductIds()) { $ids = array(); foreach ($this->getCrossSellProducts() as $product) { $ids[] = $product->getId(); } $this->setCrossSellProductIds($ids); } return $this->getData('cross_sell_product_ids'); } /** * Retrieve collection cross sell product * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection */ public function getCrossSellProductCollection() { $collection = $this->getLinkInstance()->useCrossSellLinks() ->getProductCollection() ->setIsStrongMode(); $collection->setProduct($this); return $collection; } /** * Retrieve collection cross sell link * * @return Mage_Catalog_Model_Resource_Product_Link_Collection */ public function getCrossSellLinkCollection() { $collection = $this->getLinkInstance()->useCrossSellLinks() ->getLinkCollection(); $collection->setProduct($this); $collection->addLinkTypeIdFilter(); $collection->addProductIdFilter(); $collection->joinAttributes(); return $collection; } /** * Retrieve collection grouped link * * @return Mage_Catalog_Model_Resource_Product_Link_Collection */ public function getGroupedLinkCollection() { $collection = $this->getLinkInstance()->useGroupedLinks() ->getLinkCollection(); $collection->setProduct($this); $collection->addLinkTypeIdFilter(); $collection->addProductIdFilter(); $collection->joinAttributes(); return $collection; } /******************************************************************************* ** Media API */ /** * Retrive attributes for media gallery * * @return array */ public function getMediaAttributes() { if (!$this->hasMediaAttributes()) { $mediaAttributes = array(); foreach ($this->getAttributes() as $attribute) { if($attribute->getFrontend()->getInputType() == 'media_image') { $mediaAttributes[$attribute->getAttributeCode()] = $attribute; } } $this->setMediaAttributes($mediaAttributes); } return $this->getData('media_attributes'); } /** * Retrive media gallery images * * @return Varien_Data_Collection */ public function getMediaGalleryImages() { if(!$this->hasData('media_gallery_images') && is_array($this->getMediaGallery('images'))) { $images = new Varien_Data_Collection(); foreach ($this->getMediaGallery('images') as $image) { if ($image['disabled']) { continue; } $image['url'] = $this->getMediaConfig()->getMediaUrl($image['file']); $image['id'] = isset($image['value_id']) ? $image['value_id'] : null; $image['path'] = $this->getMediaConfig()->getMediaPath($image['file']); $images->addItem(new Varien_Object($image)); } $this->setData('media_gallery_images', $images); } return $this->getData('media_gallery_images'); } /** * Add image to media gallery * * @param string $file file path of image in file system * @param string|array $mediaAttribute code of attribute with type 'media_image', * leave blank if image should be only in gallery * @param boolean $move if true, it will move source file * @param boolean $exclude mark image as disabled in product page view * @return Mage_Catalog_Model_Product */ public function addImageToMediaGallery($file, $mediaAttribute=null, $move=false, $exclude=true) { $attributes = $this->getTypeInstance(true)->getSetAttributes($this); if (!isset($attributes['media_gallery'])) { return $this; } $mediaGalleryAttribute = $attributes['media_gallery']; /* @var $mediaGalleryAttribute Mage_Catalog_Model_Resource_Eav_Attribute */ $mediaGalleryAttribute->getBackend()->addImage($this, $file, $mediaAttribute, $move, $exclude); return $this; } /** * Retrive product media config * * @return Mage_Catalog_Model_Product_Media_Config */ public function getMediaConfig() { return Mage::getSingleton('catalog/product_media_config'); } /** * Create duplicate * * @return Mage_Catalog_Model_Product */ public function duplicate() { $this->getWebsiteIds(); $this->getCategoryIds(); /* @var $newProduct Mage_Catalog_Model_Product */ $newProduct = Mage::getModel('catalog/product')->setData($this->getData()) ->setIsDuplicate(true) ->setOriginalId($this->getId()) ->setSku(null) ->setStatus(Mage_Catalog_Model_Product_Status::STATUS_DISABLED) ->setCreatedAt(null) ->setUpdatedAt(null) ->setId(null) ->setStoreId(Mage::app()->getStore()->getId()); Mage::dispatchEvent( 'catalog_model_product_duplicate', array('current_product' => $this, 'new_product' => $newProduct) ); /* Prepare Related*/ $data = array(); $this->getLinkInstance()->useRelatedLinks(); $attributes = array(); foreach ($this->getLinkInstance()->getAttributes() as $_attribute) { if (isset($_attribute['code'])) { $attributes[] = $_attribute['code']; } } foreach ($this->getRelatedLinkCollection() as $_link) { $data[$_link->getLinkedProductId()] = $_link->toArray($attributes); } $newProduct->setRelatedLinkData($data); /* Prepare UpSell*/ $data = array(); $this->getLinkInstance()->useUpSellLinks(); $attributes = array(); foreach ($this->getLinkInstance()->getAttributes() as $_attribute) { if (isset($_attribute['code'])) { $attributes[] = $_attribute['code']; } } foreach ($this->getUpSellLinkCollection() as $_link) { $data[$_link->getLinkedProductId()] = $_link->toArray($attributes); } $newProduct->setUpSellLinkData($data); /* Prepare Cross Sell */ $data = array(); $this->getLinkInstance()->useCrossSellLinks(); $attributes = array(); foreach ($this->getLinkInstance()->getAttributes() as $_attribute) { if (isset($_attribute['code'])) { $attributes[] = $_attribute['code']; } } foreach ($this->getCrossSellLinkCollection() as $_link) { $data[$_link->getLinkedProductId()] = $_link->toArray($attributes); } $newProduct->setCrossSellLinkData($data); /* Prepare Grouped */ $data = array(); $this->getLinkInstance()->useGroupedLinks(); $attributes = array(); foreach ($this->getLinkInstance()->getAttributes() as $_attribute) { if (isset($_attribute['code'])) { $attributes[] = $_attribute['code']; } } foreach ($this->getGroupedLinkCollection() as $_link) { $data[$_link->getLinkedProductId()] = $_link->toArray($attributes); } $newProduct->setGroupedLinkData($data); $newProduct->save(); $this->getOptionInstance()->duplicate($this->getId(), $newProduct->getId()); $this->getResource()->duplicate($this->getId(), $newProduct->getId()); // TODO - duplicate product on all stores of the websites it is associated with /*if ($storeIds = $this->getWebsiteIds()) { foreach ($storeIds as $storeId) { $this->setStoreId($storeId) ->load($this->getId()); $newProduct->setData($this->getData()) ->setSku(null) ->setStatus(Mage_Catalog_Model_Product_Status::STATUS_DISABLED) ->setId($newId) ->save(); } }*/ return $newProduct; } /** * Is product grouped * * @return bool */ public function isSuperGroup() { return $this->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_GROUPED; } /** * Alias for isConfigurable() * * @return bool */ public function isSuperConfig() { return $this->isConfigurable(); } /** * Check is product grouped * * @return bool */ public function isGrouped() { return $this->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_GROUPED; } /** * Check is product configurable * * @return bool */ public function isConfigurable() { return $this->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE; } /** * Whether product configurable or grouped * * @return bool */ public function isSuper() { return $this->isConfigurable() || $this->isGrouped(); } /** * Returns visible status IDs in catalog * * @return array */ public function getVisibleInCatalogStatuses() { return Mage::getSingleton('catalog/product_status')->getVisibleStatusIds(); } /** * Retrieve visible statuses * * @return array */ public function getVisibleStatuses() { return Mage::getSingleton('catalog/product_status')->getVisibleStatusIds(); } /** * Check Product visilbe in catalog * * @return bool */ public function isVisibleInCatalog() { return in_array($this->getStatus(), $this->getVisibleInCatalogStatuses()); } /** * Retrieve visible in site visibilities * * @return array */ public function getVisibleInSiteVisibilities() { return Mage::getSingleton('catalog/product_visibility')->getVisibleInSiteIds(); } /** * Check Product visible in site * * @return bool */ public function isVisibleInSiteVisibility() { return in_array($this->getVisibility(), $this->getVisibleInSiteVisibilities()); } /** * Checks product can be duplicated * * @return boolean */ public function isDuplicable() { return $this->_isDuplicable; } /** * Set is duplicable flag * * @param boolean $value * @return Mage_Catalog_Model_Product */ public function setIsDuplicable($value) { $this->_isDuplicable = (boolean) $value; return $this; } /** * Check is product available for sale * * @return bool */ public function isSalable() { Mage::dispatchEvent('catalog_product_is_salable_before', array( 'product' => $this )); $salable = $this->isAvailable(); if ( $salable < 2 ) { $backorders = $this->getStockItem()->getBackorders(); if ( $backorders ) { $salable = 2; } } $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(); } /** * Check whether the product type or stock allows to purchase the product * * @return bool */ public function isAvailable() { return $this->getTypeInstance(true)->isSalable($this) || Mage::helper('catalog/product')->getSkipSaleableCheck(); } /** * Is product salable detecting by product type * * @return bool */ public function getIsSalable() { $productType = $this->getTypeInstance(true); if (is_callable(array($productType, 'getIsSalable'))) { return $productType->getIsSalable($this); } if ($this->hasData('is_salable')) { return $this->getData('is_salable'); } return $this->isSalable(); } /** * Check is a virtual product * Data helper wrapper * * @return bool */ public function isVirtual() { return $this->getIsVirtual(); } /** * Whether the product is a recurring payment * * @return bool */ public function isRecurring() { return $this->getIsRecurring() == '1'; } /** * Alias for isSalable() * * @return bool */ public function isSaleable() { return $this->isSalable(); } /** * Whether product available in stock * * @return bool */ public function isInStock() { return $this->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_ENABLED; } /** * Get attribute text by its code * * @param $attributeCode Code of the attribute * @return string */ public function getAttributeText($attributeCode) { return $this->getResource() ->getAttribute($attributeCode) ->getSource() ->getOptionText($this->getData($attributeCode)); } /** * Returns array with dates for custom design * * @return array */ public function getCustomDesignDate() { $result = array(); $result['from'] = $this->getData('custom_design_from'); $result['to'] = $this->getData('custom_design_to'); return $result; } /** * Retrieve Product URL * * @param bool $useSid * @return string */ public function getProductUrl($useSid = null) { return $this->getUrlModel()->getProductUrl($this, $useSid); } /** * Retrieve URL in current store * * @param array $params the route params * @return string */ public function getUrlInStore($params = array()) { return $this->getUrlModel()->getUrlInStore($this, $params); } /** * Formats URL key * * @param $str URL * @return string */ public function formatUrlKey($str) { return $this->getUrlModel()->formatUrlKey($str); } /** * Retrieve Product Url Path (include category) * * @param Mage_Catalog_Model_Category $category * @return string */ public function getUrlPath($category=null) { return $this->getUrlModel()->getUrlPath($this, $category); } /** * Save current attribute with code $code and assign new value * * @param string $code Attribute code * @param mixed $value New attribute value * @param int $store Store ID * @return void */ public function addAttributeUpdate($code, $value, $store) { $oldValue = $this->getData($code); $oldStore = $this->getStoreId(); $this->setData($code, $value); $this->setStoreId($store); $this->getResource()->saveAttribute($this, $code); $this->setData($code, $oldValue); $this->setStoreId($oldStore); } /** * Renders the object to array * * @param array $arrAttributes Attribute array * @return array */ public function toArray(array $arrAttributes=array()) { $data = parent::toArray($arrAttributes); if ($stock = $this->getStockItem()) { $data['stock_item'] = $stock->toArray(); } unset($data['stock_item']['product']); return $data; } /** * Same as setData(), but also initiates the stock item (if it is there) * * @param array $data Array to form the object from * @return Mage_Catalog_Model_Product */ public function fromArray($data) { if (isset($data['stock_item'])) { if (Mage::helper('catalog')->isModuleEnabled('Mage_CatalogInventory')) { $stockItem = Mage::getModel('cataloginventory/stock_item') ->setData($data['stock_item']) ->setProduct($this); $this->setStockItem($stockItem); } unset($data['stock_item']); } $this->setData($data); return $this; } /** * @deprecated after 1.4.2.0 * @return Mage_Catalog_Model_Product */ public function loadParentProductIds() { return $this->setParentProductIds(array()); } /** * Delete product * * @return Mage_Catalog_Model_Product */ public function delete() { parent::delete(); Mage::dispatchEvent($this->_eventPrefix.'_delete_after_done', array($this->_eventObject=>$this)); return $this; } /** * Returns request path * * @return string */ public function getRequestPath() { return $this->_getData('request_path'); } /** * Custom function for other modules * @return string */ public function getGiftMessageAvailable() { return $this->_getData('gift_message_available'); } /** * Returns rating summary * * @return mixed */ public function getRatingSummary() { return $this->_getData('rating_summary'); } /** * Check is product composite * * @return bool */ public function isComposite() { return $this->getTypeInstance(true)->isComposite($this); } /** * Check if product can be configured * * @return bool */ public function canConfigure() { $options = $this->getOptions(); return !empty($options) || $this->getTypeInstance(true)->canConfigure($this); } /** * Retrieve sku through type instance * * @return string */ public function getSku() { return $this->getTypeInstance(true)->getSku($this); } /** * Retrieve weight throught type instance * * @return unknown */ public function getWeight() { return $this->getTypeInstance(true)->getWeight($this); } /** * Retrieve option instance * * @return Mage_Catalog_Model_Product_Option */ public function getOptionInstance() { if (!$this->_optionInstance) { $this->_optionInstance = Mage::getSingleton('catalog/product_option'); } return $this->_optionInstance; } /** * Retrieve options collection of product * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Option_Collection */ public function getProductOptionsCollection() { $collection = $this->getOptionInstance() ->getProductOptionCollection($this); return $collection; } /** * Add option to array of product options * * @param Mage_Catalog_Model_Product_Option $option * @return Mage_Catalog_Model_Product */ public function addOption(Mage_Catalog_Model_Product_Option $option) { $this->_options[$option->getId()] = $option; return $this; } /** * Get option from options array of product by given option id * * @param int $optionId * @return Mage_Catalog_Model_Product_Option | null */ public function getOptionById($optionId) { if (isset($this->_options[$optionId])) { return $this->_options[$optionId]; } return null; } /** * Get all options of product * * @return array */ public function getOptions() { return $this->_options; } /** * Retrieve is a virtual product * * @return bool */ public function getIsVirtual() { return $this->getTypeInstance(true)->isVirtual($this); } /** * Add custom option information to product * * @param string $code Option code * @param mixed $value Value of the option * @param int $product Product ID * @return Mage_Catalog_Model_Product */ public function addCustomOption($code, $value, $product=null) { $product = $product ? $product : $this; $option = Mage::getModel('catalog/product_configuration_item_option') ->addData(array( 'product_id'=> $product->getId(), 'product' => $product, 'code' => $code, 'value' => $value, )); $this->_customOptions[$code] = $option; return $this; } /** * Sets custom options for the product * * @param array $options Array of options * @return void */ public function setCustomOptions(array $options) { $this->_customOptions = $options; } /** * Get all custom options of the product * * @return array */ public function getCustomOptions() { return $this->_customOptions; } /** * Get product custom option info * * @param string $code * @return array */ public function getCustomOption($code) { if (isset($this->_customOptions[$code])) { return $this->_customOptions[$code]; } return null; } /** * Checks if there custom option for this product * * @return bool */ public function hasCustomOptions() { if (count($this->_customOptions)) { return true; } else { return false; } } /** * Check availability display product in category * * @param int $categoryId * @return bool */ public function canBeShowInCategory($categoryId) { return $this->_getResource()->canBeShowInCategory($this, $categoryId); } /** * Retrieve category ids where product is available * * @return array */ public function getAvailableInCategories() { return $this->_getResource()->getAvailableInCategories($this); } /** * Retrieve default attribute set id * * @return int */ public function getDefaultAttributeSetId() { return $this->getResource()->getEntityType()->getDefaultAttributeSetId(); } /** * Return Catalog Product Image helper instance * * @return Mage_Catalog_Helper_Image */ protected function _getImageHelper() { return Mage::helper('catalog/image'); } /** * Return re-sized image URL * * @deprecated since 1.1.5 * @return string */ public function getImageUrl() { return (string)$this->_getImageHelper()->init($this, 'image')->resize(265); } /** * Return re-sized small image URL * * @deprecated since 1.1.5 * @param int $width * @param int $height * @return string */ public function getSmallImageUrl($width = 88, $height = 77) { return (string)$this->_getImageHelper()->init($this, 'small_image')->resize($width, $height); } /** * Return re-sized thumbnail image URL * * @deprecated since 1.1.5 * @param int $width * @param int $height * @return string */ public function getThumbnailUrl($width = 75, $height = 75) { return (string)$this->_getImageHelper()->init($this, 'thumbnail')->resize($width, $height); } /** * Returns system reserved attribute codes * * @return array Reserved attribute names */ public function getReservedAttributes() { if ($this->_reservedAttributes === null) { $_reserved = array('position'); $methods = get_class_methods(__CLASS__); foreach ($methods as $method) { if (preg_match('/^get([A-Z]{1}.+)/', $method, $matches)) { $method = $matches[1]; $tmp = strtolower(preg_replace('/(.)([A-Z])/', "$1_$2", $method)); $_reserved[] = $tmp; } } $_allowed = array( 'type_id','calculated_final_price','request_path','rating_summary' ); $this->_reservedAttributes = array_diff($_reserved, $_allowed); } return $this->_reservedAttributes; } /** * Check whether attribute reserved or not * * @param Mage_Catalog_Model_Entity_Attribute $attribute Attribute model object * @return boolean */ public function isReservedAttribute ($attribute) { return $attribute->getIsUserDefined() && in_array($attribute->getAttributeCode(), $this->getReservedAttributes()); } /** * Set original loaded data if needed * * @param string $key * @param mixed $data * @return Varien_Object */ public function setOrigData($key=null, $data=null) { if (Mage::app()->getStore()->isAdmin()) { return parent::setOrigData($key, $data); } return $this; } /** * Reset all model data * * @return Mage_Catalog_Model_Product */ public function reset() { $this->unlockAttributes(); $this->_clearData(); return $this; } /** * Get cahce tags associated with object id * * @return array */ public function getCacheIdTags() { $tags = parent::getCacheIdTags(); $affectedCategoryIds = $this->getAffectedCategoryIds(); if (!$affectedCategoryIds) { $affectedCategoryIds = $this->getCategoryIds(); } foreach ($affectedCategoryIds as $categoryId) { $tags[] = Mage_Catalog_Model_Category::CACHE_TAG.'_'.$categoryId; } return $tags; } /** * Check for empty SKU on each product * * @param array $productIds * @return boolean|null */ public function isProductsHasSku(array $productIds) { $products = $this->_getResource()->getProductsSku($productIds); if (count($products)) { foreach ($products as $product) { if (empty($product['sku'])) { return false; } } return true; } return null; } /** * Parse buyRequest into options values used by product * * @param Varien_Object $buyRequest * @return Varien_Object */ public function processBuyRequest(Varien_Object $buyRequest) { $options = new Varien_Object(); /* add product custom options data */ $customOptions = $buyRequest->getOptions(); if (is_array($customOptions)) { $options->setOptions(array_diff($buyRequest->getOptions(), array(''))); } /* add product type selected options data */ $type = $this->getTypeInstance(true); $typeSpecificOptions = $type->processBuyRequest($this, $buyRequest); $options->addData($typeSpecificOptions); /* check correctness of product's options */ $options->setErrors($type->checkProductConfiguration($this, $buyRequest)); return $options; } /** * Get preconfigured values from product * * @return Varien_Object */ public function getPreconfiguredValues() { $preconfiguredValues = $this->getData('preconfigured_values'); if (!$preconfiguredValues) { $preconfiguredValues = new Varien_Object(); } return $preconfiguredValues; } /** * Prepare product custom options. * To be sure that all product custom options does not has ID and has product instance * * @return Mage_Catalog_Model_Product */ public function prepareCustomOptions() { foreach ($this->getCustomOptions() as $option) { if (!is_object($option->getProduct()) || $option->getId()) { $this->addCustomOption($option->getCode(), $option->getValue()); } } return $this; } /** * Clearing references on product * * @return Mage_Catalog_Model_Product */ protected function _clearReferences() { $this->_clearOptionReferences(); return $this; } /** * Clearing product's data * * @return Mage_Catalog_Model_Product */ protected function _clearData() { foreach ($this->_data as $data){ if (is_object($data) && method_exists($data, 'reset')){ $data->reset(); } } $this->setData(array()); $this->setOrigData(); $this->_customOptions = array(); $this->_optionInstance = null; $this->_options = array(); $this->_canAffectOptions = false; $this->_errors = array(); return $this; } /** * Clearing references to product from product's options * * @return Mage_Catalog_Model_Product */ protected function _clearOptionReferences() { /** * unload product options */ if (!empty($this->_options)) { foreach ($this->_options as $key => $option) { $option->setProduct(); $option->clearInstance(); } } return $this; } /** * Retrieve product entities info as array * * @param string|array $columns One or several columns * @return array */ public function getProductEntitiesInfo($columns = null) { return $this->_getResource()->getProductEntitiesInfo($columns); } /** * Checks whether product has disabled status * * @return bool */ public function isDisabled() { return $this->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_DISABLED; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product media gallery attribute backend model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Attribute_Backend_Media extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { protected $_renamedImages = array(); /** * Load attribute data after product loaded * * @param Mage_Catalog_Model_Product $object */ public function afterLoad($object) { $attrCode = $this->getAttribute()->getAttributeCode(); $value = array(); $value['images'] = array(); $value['values'] = array(); $localAttributes = array('label', 'position', 'disabled'); foreach ($this->_getResource()->loadGallery($object, $this) as $image) { foreach ($localAttributes as $localAttribute) { if (is_null($image[$localAttribute])) { $image[$localAttribute] = $this->_getDefaultValue($localAttribute, $image); } } $value['images'][] = $image; } $object->setData($attrCode, $value); } protected function _getDefaultValue($key, &$image) { if (isset($image[$key . '_default'])) { return $image[$key . '_default']; } return ''; } /** * Validate media_gallery attribute data * * @param Mage_Catalog_Model_Product $object * @throws Mage_Core_Exception * @return bool */ public function validate($object) { if ($this->getAttribute()->getIsRequired()) { $value = $object->getData($this->getAttribute()->getAttributeCode()); if ($this->getAttribute()->isValueEmpty($value)) { if ( !(is_array($value) && count($value)>0) ) { return false; } } } if ($this->getAttribute()->getIsUnique()) { if (!$this->getAttribute()->getEntity()->checkAttributeUniqueValue($this->getAttribute(), $object)) { $label = $this->getAttribute()->getFrontend()->getLabel(); Mage::throwException(Mage::helper('eav')->__('The value of attribute "%s" must be unique.', $label)); } } return true; } public function beforeSave($object) { $attrCode = $this->getAttribute()->getAttributeCode(); $value = $object->getData($attrCode); if (!is_array($value) || !isset($value['images'])) { return; } if(!is_array($value['images']) && strlen($value['images']) > 0) { $value['images'] = Mage::helper('core')->jsonDecode($value['images']); } if (!is_array($value['images'])) { $value['images'] = array(); } $clearImages = array(); $newImages = array(); $existImages = array(); if ($object->getIsDuplicate()!=true) { foreach ($value['images'] as &$image) { if(!empty($image['removed'])) { $clearImages[] = $image['file']; } else if (!isset($image['value_id'])) { $newFile = $this->_moveImageFromTmp($image['file']); $image['new_file'] = $newFile; $newImages[$image['file']] = $image; $this->_renamedImages[$image['file']] = $newFile; $image['file'] = $newFile; } else { $existImages[$image['file']] = $image; } } } else { // For duplicating we need copy original images. $duplicate = array(); foreach ($value['images'] as &$image) { if (!isset($image['value_id'])) { continue; } $duplicate[$image['value_id']] = $this->_copyImage($image['file']); $newImages[$image['file']] = $duplicate[$image['value_id']]; } $value['duplicate'] = $duplicate; } foreach ($object->getMediaAttributes() as $mediaAttribute) { $mediaAttrCode = $mediaAttribute->getAttributeCode(); $attrData = $object->getData($mediaAttrCode); if (in_array($attrData, $clearImages)) { $object->setData($mediaAttrCode, 'no_selection'); } if (in_array($attrData, array_keys($newImages))) { $object->setData($mediaAttrCode, $newImages[$attrData]['new_file']); $object->setData($mediaAttrCode.'_label', $newImages[$attrData]['label']); } if (in_array($attrData, array_keys($existImages))) { $object->setData($mediaAttrCode.'_label', $existImages[$attrData]['label']); } } Mage::dispatchEvent('catalog_product_media_save_before', array('product' => $object, 'images' => $value)); $object->setData($attrCode, $value); return $this; } /** * Retrieve renamed image name * * @param string $file * @return string */ public function getRenamedImage($file) { if (isset($this->_renamedImages[$file])) { return $this->_renamedImages[$file]; } return $file; } public function afterSave($object) { if ($object->getIsDuplicate() == true) { $this->duplicate($object); return; } $attrCode = $this->getAttribute()->getAttributeCode(); $value = $object->getData($attrCode); if (!is_array($value) || !isset($value['images']) || $object->isLockedAttribute($attrCode)) { return; } $storeId = $object->getStoreId(); $storeIds = $object->getStoreIds(); $storeIds[] = Mage_Core_Model_App::ADMIN_STORE_ID; // remove current storeId $storeIds = array_flip($storeIds); unset($storeIds[$storeId]); $storeIds = array_keys($storeIds); $images = Mage::getResourceModel('catalog/product') ->getAssignedImages($object, $storeIds); $picturesInOtherStores = array(); foreach ($images as $image) { $picturesInOtherStores[$image['filepath']] = true; } $toDelete = array(); $filesToValueIds = array(); foreach ($value['images'] as &$image) { if(!empty($image['removed'])) { if(isset($image['value_id']) && !isset($picturesInOtherStores[$image['file']])) { $toDelete[] = $image['value_id']; } continue; } if(!isset($image['value_id'])) { $data = array(); $data['entity_id'] = $object->getId(); $data['attribute_id'] = $this->getAttribute()->getId(); $data['value'] = $image['file']; $image['value_id'] = $this->_getResource()->insertGallery($data); } $this->_getResource()->deleteGalleryValueInStore($image['value_id'], $object->getStoreId()); // Add per store labels, position, disabled $data = array(); $data['value_id'] = $image['value_id']; $data['label'] = $image['label']; $data['position'] = (int) $image['position']; $data['disabled'] = (int) $image['disabled']; $data['store_id'] = (int) $object->getStoreId(); $this->_getResource()->insertGalleryValueInStore($data); } $this->_getResource()->deleteGallery($toDelete); } /** * Add image to media gallery and return new filename * * @param Mage_Catalog_Model_Product $product * @param string $file file path of image in file system * @param string|array $mediaAttribute code of attribute with type 'media_image', * leave blank if image should be only in gallery * @param boolean $move if true, it will move source file * @param boolean $exclude mark image as disabled in product page view * @return string */ public function addImage(Mage_Catalog_Model_Product $product, $file, $mediaAttribute = null, $move = false, $exclude = true) { $file = realpath($file); if (!$file || !file_exists($file)) { Mage::throwException(Mage::helper('catalog')->__('Image does not exist.')); } Mage::dispatchEvent('catalog_product_media_add_image', array('product' => $product, 'image' => $file)); $pathinfo = pathinfo($file); $imgExtensions = array('jpg','jpeg','gif','png'); if (!isset($pathinfo['extension']) || !in_array(strtolower($pathinfo['extension']), $imgExtensions)) { Mage::throwException(Mage::helper('catalog')->__('Invalid image file type.')); } $fileName = Mage_Core_Model_File_Uploader::getCorrectFileName($pathinfo['basename']); $dispretionPath = Mage_Core_Model_File_Uploader::getDispretionPath($fileName); $fileName = $dispretionPath . DS . $fileName; $fileName = $this->_getNotDuplicatedFilename($fileName, $dispretionPath); $ioAdapter = new Varien_Io_File(); $ioAdapter->setAllowCreateFolders(true); $distanationDirectory = dirname($this->_getConfig()->getTmpMediaPath($fileName)); try { $ioAdapter->open(array( 'path'=>$distanationDirectory )); /** @var $storageHelper Mage_Core_Helper_File_Storage_Database */ $storageHelper = Mage::helper('core/file_storage_database'); if ($move) { $ioAdapter->mv($file, $this->_getConfig()->getTmpMediaPath($fileName)); //If this is used, filesystem should be configured properly $storageHelper->saveFile($this->_getConfig()->getTmpMediaShortUrl($fileName)); } else { $ioAdapter->cp($file, $this->_getConfig()->getTmpMediaPath($fileName)); $storageHelper->saveFile($this->_getConfig()->getTmpMediaShortUrl($fileName)); $ioAdapter->chmod($this->_getConfig()->getTmpMediaPath($fileName), 0777); } } catch (Exception $e) { Mage::throwException(Mage::helper('catalog')->__('Failed to move file: %s', $e->getMessage())); } $fileName = str_replace(DS, '/', $fileName); $attrCode = $this->getAttribute()->getAttributeCode(); $mediaGalleryData = $product->getData($attrCode); $position = 0; if (!is_array($mediaGalleryData)) { $mediaGalleryData = array( 'images' => array() ); } foreach ($mediaGalleryData['images'] as &$image) { if (isset($image['position']) && $image['position'] > $position) { $position = $image['position']; } } $position++; $mediaGalleryData['images'][] = array( 'file' => $fileName, 'position' => $position, 'label' => '', 'disabled' => (int) $exclude ); $product->setData($attrCode, $mediaGalleryData); if (!is_null($mediaAttribute)) { $this->setMediaAttribute($product, $mediaAttribute, $fileName); } return $fileName; } /** * Add images with different media attributes. * Image will be added only once if the same image is used with different media attributes * * @param Mage_Catalog_Model_Product $product * @param array $fileAndAttributesArray array of arrays of filename and corresponding media attribute * @param string $filePath path, where image cand be found * @param boolean $move if true, it will move source file * @param boolean $exclude mark image as disabled in product page view * @return array array of parallel arrays with original and renamed files */ public function addImagesWithDifferentMediaAttributes(Mage_Catalog_Model_Product $product, $fileAndAttributesArray, $filePath = '', $move = false, $exclude = true) { $alreadyAddedFiles = array(); $alreadyAddedFilesNames = array(); foreach ($fileAndAttributesArray as $key => $value) { $keyInAddedFiles = array_search($value['file'], $alreadyAddedFiles, true); if ($keyInAddedFiles === false) { $savedFileName = $this->addImage($product, $filePath . $value['file'], null, $move, $exclude); $alreadyAddedFiles[$key] = $value['file']; $alreadyAddedFilesNames[$key] = $savedFileName; } else { $savedFileName = $alreadyAddedFilesNames[$keyInAddedFiles]; } if (!is_null($value['mediaAttribute'])) { $this->setMediaAttribute($product, $value['mediaAttribute'], $savedFileName); } } return array('alreadyAddedFiles' => $alreadyAddedFiles, 'alreadyAddedFilesNames' => $alreadyAddedFilesNames); } /** * Update image in gallery * * @param Mage_Catalog_Model_Product $product * @param sting $file * @param array $data * @return Mage_Catalog_Model_Product_Attribute_Backend_Media */ public function updateImage(Mage_Catalog_Model_Product $product, $file, $data) { $fieldsMap = array( 'label' => 'label', 'position' => 'position', 'disabled' => 'disabled', 'exclude' => 'disabled' ); $attrCode = $this->getAttribute()->getAttributeCode(); $mediaGalleryData = $product->getData($attrCode); if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { return $this; } foreach ($mediaGalleryData['images'] as &$image) { if ($image['file'] == $file) { foreach ($fieldsMap as $mappedField=>$realField) { if (isset($data[$mappedField])) { $image[$realField] = $data[$mappedField]; } } } } $product->setData($attrCode, $mediaGalleryData); return $this; } /** * Remove image from gallery * * @param Mage_Catalog_Model_Product $product * @param string $file * @return Mage_Catalog_Model_Product_Attribute_Backend_Media */ public function removeImage(Mage_Catalog_Model_Product $product, $file) { $attrCode = $this->getAttribute()->getAttributeCode(); $mediaGalleryData = $product->getData($attrCode); if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { return $this; } foreach ($mediaGalleryData['images'] as &$image) { if ($image['file'] == $file) { $image['removed'] = 1; } } $product->setData($attrCode, $mediaGalleryData); return $this; } /** * Retrive image from gallery * * @param Mage_Catalog_Model_Product $product * @param string $file * @return array|boolean */ public function getImage(Mage_Catalog_Model_Product $product, $file) { $attrCode = $this->getAttribute()->getAttributeCode(); $mediaGalleryData = $product->getData($attrCode); if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { return false; } foreach ($mediaGalleryData['images'] as $image) { if ($image['file'] == $file) { return $image; } } return false; } /** * Clear media attribute value * * @param Mage_Catalog_Model_Product $product * @param string|array $mediaAttribute * @return Mage_Catalog_Model_Product_Attribute_Backend_Media */ public function clearMediaAttribute(Mage_Catalog_Model_Product $product, $mediaAttribute) { $mediaAttributeCodes = array_keys($product->getMediaAttributes()); if (is_array($mediaAttribute)) { foreach ($mediaAttribute as $atttribute) { if (in_array($atttribute, $mediaAttributeCodes)) { $product->setData($atttribute, null); } } } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { $product->setData($mediaAttribute, null); } return $this; } /** * Set media attribute value * * @param Mage_Catalog_Model_Product $product * @param string|array $mediaAttribute * @param string $value * @return Mage_Catalog_Model_Product_Attribute_Backend_Media */ public function setMediaAttribute(Mage_Catalog_Model_Product $product, $mediaAttribute, $value) { $mediaAttributeCodes = array_keys($product->getMediaAttributes()); if (is_array($mediaAttribute)) { foreach ($mediaAttribute as $atttribute) { if (in_array($atttribute, $mediaAttributeCodes)) { $product->setData($atttribute, $value); } } } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { $product->setData($mediaAttribute, $value); } return $this; } /** * Retrieve resource model * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Media */ protected function _getResource() { return Mage::getResourceSingleton('catalog/product_attribute_backend_media'); } /** * Retrive media config * * @return Mage_Catalog_Model_Product_Media_Config */ protected function _getConfig() { return Mage::getSingleton('catalog/product_media_config'); } /** * Move image from temporary directory to normal * * @param string $file * @return string */ protected function _moveImageFromTmp($file) { $ioObject = new Varien_Io_File(); $destDirectory = dirname($this->_getConfig()->getMediaPath($file)); try { $ioObject->open(array('path'=>$destDirectory)); } catch (Exception $e) { $ioObject->mkdir($destDirectory, 0777, true); $ioObject->open(array('path'=>$destDirectory)); } if (strrpos($file, '.tmp') == strlen($file)-4) { $file = substr($file, 0, strlen($file)-4); } $destFile = $this->_getUniqueFileName($file, $ioObject->dirsep()); /** @var $storageHelper Mage_Core_Helper_File_Storage_Database */ $storageHelper = Mage::helper('core/file_storage_database'); if ($storageHelper->checkDbUsage()) { $storageHelper->renameFile( $this->_getConfig()->getTmpMediaShortUrl($file), $this->_getConfig()->getMediaShortUrl($destFile)); $ioObject->rm($this->_getConfig()->getTmpMediaPath($file)); $ioObject->rm($this->_getConfig()->getMediaPath($destFile)); } else { $ioObject->mv( $this->_getConfig()->getTmpMediaPath($file), $this->_getConfig()->getMediaPath($destFile) ); } return str_replace($ioObject->dirsep(), '/', $destFile); } /** * Check whether file to move exists. Getting unique name * * @param $file * @param $dirsep * @return string */ protected function _getUniqueFileName($file, $dirsep) { if (Mage::helper('core/file_storage_database')->checkDbUsage()) { $destFile = Mage::helper('core/file_storage_database') ->getUniqueFilename( Mage::getSingleton('catalog/product_media_config')->getBaseMediaUrlAddition(), $file ); } else { $destFile = dirname($file) . $dirsep . Mage_Core_Model_File_Uploader::getNewFileName($this->_getConfig()->getMediaPath($file)); } return $destFile; } /** * Copy image and return new filename. * * @param string $file * @return string */ protected function _copyImage($file) { try { $ioObject = new Varien_Io_File(); $destDirectory = dirname($this->_getConfig()->getMediaPath($file)); $ioObject->open(array('path'=>$destDirectory)); $destFile = $this->_getUniqueFileName($file, $ioObject->dirsep()); if (!$ioObject->fileExists($this->_getConfig()->getMediaPath($file),true)) { throw new Exception(); } if (Mage::helper('core/file_storage_database')->checkDbUsage()) { Mage::helper('core/file_storage_database') ->copyFile($this->_getConfig()->getMediaShortUrl($file), $this->_getConfig()->getMediaShortUrl($destFile)); $ioObject->rm($this->_getConfig()->getMediaPath($destFile)); } else { $ioObject->cp( $this->_getConfig()->getMediaPath($file), $this->_getConfig()->getMediaPath($destFile) ); } } catch (Exception $e) { $file = $this->_getConfig()->getMediaPath($file); Mage::throwException( Mage::helper('catalog')->__('Failed to copy file %s. Please, delete media with non-existing images and try again.', $file) ); } return str_replace($ioObject->dirsep(), '/', $destFile); } public function duplicate($object) { $attrCode = $this->getAttribute()->getAttributeCode(); $mediaGalleryData = $object->getData($attrCode); if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { return $this; } $this->_getResource()->duplicate( $this, (isset($mediaGalleryData['duplicate']) ? $mediaGalleryData['duplicate'] : array()), $object->getOriginalId(), $object->getId() ); return $this; } /** * Get filename which is not duplicated with other files in media temporary and media directories * * @param String $fileName * @param String $dispretionPath * @return String */ protected function _getNotDuplicatedFilename($fileName, $dispretionPath) { $fileMediaName = $dispretionPath . DS . Mage_Core_Model_File_Uploader::getNewFileName($this->_getConfig()->getMediaPath($fileName)); $fileTmpMediaName = $dispretionPath . DS . Mage_Core_Model_File_Uploader::getNewFileName($this->_getConfig()->getTmpMediaPath($fileName)); if ($fileMediaName != $fileTmpMediaName) { if ($fileMediaName != $fileName) { return $this->_getNotDuplicatedFileName($fileMediaName, $dispretionPath); } elseif ($fileTmpMediaName != $fileName) { return $this->_getNotDuplicatedFilename($fileTmpMediaName, $dispretionPath); } } return $fileMediaName; } } // Class Mage_Catalog_Model_Product_Attribute_Backend_Media End /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product price attribute backend model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Attribute_Backend_Price extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { /** * Set Attribute instance * Rewrite for redefine attribute scope * * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return Mage_Catalog_Model_Product_Attribute_Backend_Price */ public function setAttribute($attribute) { parent::setAttribute($attribute); $this->setScope($attribute); return $this; } /** * Redefine Attribute scope * * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return Mage_Catalog_Model_Product_Attribute_Backend_Price */ public function setScope($attribute) { if (Mage::helper('catalog')->isPriceGlobal()) { $attribute->setIsGlobal(Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL); } else { $attribute->setIsGlobal(Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE); } return $this; } /** * After Save Attribute manipulation * * @param Mage_Catalog_Model_Product $object * @return Mage_Catalog_Model_Product_Attribute_Backend_Price */ public function afterSave($object) { $value = $object->getData($this->getAttribute()->getAttributeCode()); /** * Orig value is only for existing objects */ $oridData = $object->getOrigData(); $origValueExist = $oridData && array_key_exists($this->getAttribute()->getAttributeCode(), $oridData); if ($object->getStoreId() != 0 || !$value || $origValueExist) { return $this; } if ($this->getAttribute()->getIsGlobal() == Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE) { $baseCurrency = Mage::app()->getBaseCurrencyCode(); $storeIds = $object->getStoreIds(); if (is_array($storeIds)) { foreach ($storeIds as $storeId) { $storeCurrency = Mage::app()->getStore($storeId)->getBaseCurrencyCode(); if ($storeCurrency == $baseCurrency) { continue; } $rate = Mage::getModel('directory/currency')->load($baseCurrency)->getRate($storeCurrency); if (!$rate) { $rate = 1; } $newValue = $value * $rate; $object->addAttributeUpdate($this->getAttribute()->getAttributeCode(), $newValue, $storeId); } } } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * * Speical Start Date attribute backend * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Attribute_Backend_Startdate extends Mage_Eav_Model_Entity_Attribute_Backend_Datetime { public function beforeSave($object) { $attributeName = $this->getAttribute()->getName(); $startDate = $object->getData($attributeName); if ($startDate === false) { return $this; } if ($startDate == '' && $object->getSpecialPrice()) { $startDate = Mage::app()->getLocale()->date(); } $object->setData($attributeName, $startDate); parent::beforeSave($object); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product abstract group price backend attribute model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ abstract class Mage_Catalog_Model_Product_Attribute_Backend_Groupprice_Abstract extends Mage_Catalog_Model_Product_Attribute_Backend_Price { /** * Website currency codes and rates * * @var array */ protected $_rates; /** * Error message when duplicates * * @abstract * @return string */ abstract protected function _getDuplicateErrorMessage(); /** * Retrieve websites currency rates and base currency codes * * @return array */ protected function _getWebsiteCurrencyRates() { if (is_null($this->_rates)) { $this->_rates = array(); $baseCurrency = Mage::app()->getBaseCurrencyCode(); foreach (Mage::app()->getWebsites() as $website) { /* @var $website Mage_Core_Model_Website */ if ($website->getBaseCurrencyCode() != $baseCurrency) { $rate = Mage::getModel('directory/currency') ->load($baseCurrency) ->getRate($website->getBaseCurrencyCode()); if (!$rate) { $rate = 1; } $this->_rates[$website->getId()] = array( 'code' => $website->getBaseCurrencyCode(), 'rate' => $rate ); } else { $this->_rates[$website->getId()] = array( 'code' => $baseCurrency, 'rate' => 1 ); } } } return $this->_rates; } /** * Get additional unique fields * * @param array $objectArray * @return array */ protected function _getAdditionalUniqueFields($objectArray) { return array(); } /** * Whether group price value fixed or percent of original price * * @param Mage_Catalog_Model_Product_Type_Price $priceObject * @return bool */ protected function _isPriceFixed($priceObject) { return $priceObject->isGroupPriceFixed(); } /** * Validate group price data * * @param Mage_Catalog_Model_Product $object * @throws Mage_Core_Exception * @return bool */ public function validate($object) { $attribute = $this->getAttribute(); $priceRows = $object->getData($attribute->getName()); if (empty($priceRows)) { return true; } // validate per website $duplicates = array(); foreach ($priceRows as $priceRow) { if (!empty($priceRow['delete'])) { continue; } $compare = join('-', array_merge( array($priceRow['website_id'], $priceRow['cust_group']), $this->_getAdditionalUniqueFields($priceRow) )); if (isset($duplicates[$compare])) { Mage::throwException($this->_getDuplicateErrorMessage()); } $duplicates[$compare] = true; } // if attribute scope is website and edit in store view scope // add global group prices for duplicates find if (!$attribute->isScopeGlobal() && $object->getStoreId()) { $origGroupPrices = $object->getOrigData($attribute->getName()); foreach ($origGroupPrices as $price) { if ($price['website_id'] == 0) { $compare = join('-', array_merge( array($price['website_id'], $price['cust_group']), $this->_getAdditionalUniqueFields($price) )); $duplicates[$compare] = true; } } } // validate currency $baseCurrency = Mage::app()->getBaseCurrencyCode(); $rates = $this->_getWebsiteCurrencyRates(); foreach ($priceRows as $priceRow) { if (!empty($priceRow['delete'])) { continue; } if ($priceRow['website_id'] == 0) { continue; } $globalCompare = join('-', array_merge( array(0, $priceRow['cust_group']), $this->_getAdditionalUniqueFields($priceRow) )); $websiteCurrency = $rates[$priceRow['website_id']]['code']; if ($baseCurrency == $websiteCurrency && isset($duplicates[$globalCompare])) { Mage::throwException($this->_getDuplicateErrorMessage()); } } return true; } /** * Prepare group prices data for website * * @param array $priceData * @param string $productTypeId * @param int $websiteId * @return array */ public function preparePriceData(array $priceData, $productTypeId, $websiteId) { $rates = $this->_getWebsiteCurrencyRates(); $data = array(); $price = Mage::getSingleton('catalog/product_type')->priceFactory($productTypeId); foreach ($priceData as $v) { $key = join('-', array_merge(array($v['cust_group']), $this->_getAdditionalUniqueFields($v))); if ($v['website_id'] == $websiteId) { $data[$key] = $v; $data[$key]['website_price'] = $v['price']; } else if ($v['website_id'] == 0 && !isset($data[$key])) { $data[$key] = $v; $data[$key]['website_id'] = $websiteId; if ($this->_isPriceFixed($price)) { $data[$key]['price'] = $v['price'] * $rates[$websiteId]['rate']; $data[$key]['website_price'] = $v['price'] * $rates[$websiteId]['rate']; } } } return $data; } /** * Assign group prices to product data * * @param Mage_Catalog_Model_Product $object * @return Mage_Catalog_Model_Product_Attribute_Backend_Groupprice_Abstract */ public function afterLoad($object) { $storeId = $object->getStoreId(); $websiteId = null; if ($this->getAttribute()->isScopeGlobal()) { $websiteId = 0; } else if ($storeId) { $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); } $data = $this->_getResource()->loadPriceData($object->getId(), $websiteId); foreach ($data as $k => $v) { $data[$k]['website_price'] = $v['price']; if ($v['all_groups']) { $data[$k]['cust_group'] = Mage_Customer_Model_Group::CUST_GROUP_ALL; } } if (!$object->getData('_edit_mode') && $websiteId) { $data = $this->preparePriceData($data, $object->getTypeId(), $websiteId); } $object->setData($this->getAttribute()->getName(), $data); $object->setOrigData($this->getAttribute()->getName(), $data); $valueChangedKey = $this->getAttribute()->getName() . '_changed'; $object->setOrigData($valueChangedKey, 0); $object->setData($valueChangedKey, 0); return $this; } /** * After Save Attribute manipulation * * @param Mage_Catalog_Model_Product $object * @return Mage_Catalog_Model_Product_Attribute_Backend_Groupprice_Abstract */ public function afterSave($object) { $websiteId = Mage::app()->getStore($object->getStoreId())->getWebsiteId(); $isGlobal = $this->getAttribute()->isScopeGlobal() || $websiteId == 0; $priceRows = $object->getData($this->getAttribute()->getName()); if (empty($priceRows)) { if ($isGlobal) { $this->_getResource()->deletePriceData($object->getId()); } else { $this->_getResource()->deletePriceData($object->getId(), $websiteId); } return $this; } $old = array(); $new = array(); // prepare original data for compare $origGroupPrices = $object->getOrigData($this->getAttribute()->getName()); if (!is_array($origGroupPrices)) { $origGroupPrices = array(); } foreach ($origGroupPrices as $data) { if ($data['website_id'] > 0 || ($data['website_id'] == '0' && $isGlobal)) { $key = join('-', array_merge( array($data['website_id'], $data['cust_group']), $this->_getAdditionalUniqueFields($data) )); $old[$key] = $data; } } // prepare data for save foreach ($priceRows as $data) { $hasEmptyData = false; foreach ($this->_getAdditionalUniqueFields($data) as $field) { if (empty($field)) { $hasEmptyData = true; break; } } if ($hasEmptyData || !isset($data['cust_group']) || !empty($data['delete'])) { continue; } if ($this->getAttribute()->isScopeGlobal() && $data['website_id'] > 0) { continue; } if (!$isGlobal && (int)$data['website_id'] == 0) { continue; } $key = join('-', array_merge( array($data['website_id'], $data['cust_group']), $this->_getAdditionalUniqueFields($data) )); $useForAllGroups = $data['cust_group'] == Mage_Customer_Model_Group::CUST_GROUP_ALL; $customerGroupId = !$useForAllGroups ? $data['cust_group'] : 0; $new[$key] = array_merge(array( 'website_id' => $data['website_id'], 'all_groups' => $useForAllGroups ? 1 : 0, 'customer_group_id' => $customerGroupId, 'value' => $data['price'], ), $this->_getAdditionalUniqueFields($data)); } $delete = array_diff_key($old, $new); $insert = array_diff_key($new, $old); $update = array_intersect_key($new, $old); $isChanged = false; $productId = $object->getId(); if (!empty($delete)) { foreach ($delete as $data) { $this->_getResource()->deletePriceData($productId, null, $data['price_id']); $isChanged = true; } } if (!empty($insert)) { foreach ($insert as $data) { $price = new Varien_Object($data); $price->setEntityId($productId); $this->_getResource()->savePriceData($price); $isChanged = true; } } if (!empty($update)) { foreach ($update as $k => $v) { if ($old[$k]['price'] != $v['value']) { $price = new Varien_Object(array( 'value_id' => $old[$k]['price_id'], 'value' => $v['value'] )); $this->_getResource()->savePriceData($price); $isChanged = true; } } } if ($isChanged) { $valueChangedKey = $this->getAttribute()->getName() . '_changed'; $object->setData($valueChangedKey, 1); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product tier price backend attribute model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Attribute_Backend_Tierprice extends Mage_Catalog_Model_Product_Attribute_Backend_Groupprice_Abstract { /** * Retrieve resource instance * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Backend_Tierprice */ protected function _getResource() { return Mage::getResourceSingleton('catalog/product_attribute_backend_tierprice'); } /** * Retrieve websites rates and base currency codes * * @deprecated since 1.12.0.0 * @return array */ public function _getWebsiteRates() { return $this->_getWebsiteCurrencyRates(); } /** * Add price qty to unique fields * * @param array $objectArray * @return array */ protected function _getAdditionalUniqueFields($objectArray) { $uniqueFields = parent::_getAdditionalUniqueFields($objectArray); $uniqueFields['qty'] = $objectArray['price_qty'] * 1; return $uniqueFields; } /** * Error message when duplicates * * @return string */ protected function _getDuplicateErrorMessage() { return Mage::helper('catalog')->__('Duplicate website tier price customer group and quantity.'); } /** * Whether tier price value fixed or percent of original price * * @param Mage_Catalog_Model_Product_Type_Price $priceObject * @return bool */ protected function _isPriceFixed($priceObject) { return $priceObject->isTierPriceFixed(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product url key attribute backend * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Attribute_Backend_Urlkey extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { public function beforeSave($object) { $attributeName = $this->getAttribute()->getName(); $urlKey = $object->getData($attributeName); if ($urlKey === false) { return $this; } if ($urlKey == '') { $urlKey = $object->getName(); } $object->setData($attributeName, $object->formatUrlKey($urlKey)); return $this; } public function afterSave($object) { /* @var $object Mage_Catalog_Model_Product */ /** * Logic moved to Mage_Catalog_Model_Indexer_Url */ /*if (!$object->getExcludeUrlRewrite() && ($object->dataHasChangedFor('url_key') || $object->getIsChangedCategories() || $object->getIsChangedWebsites())) { Mage::getSingleton('catalog/url')->refreshProductRewrite($object->getId()); }*/ return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product link model * * @method Mage_Catalog_Model_Resource_Product_Link _getResource() * @method Mage_Catalog_Model_Resource_Product_Link getResource() * @method int getProductId() * @method Mage_Catalog_Model_Product_Link setProductId(int $value) * @method int getLinkedProductId() * @method Mage_Catalog_Model_Product_Link setLinkedProductId(int $value) * @method int getLinkTypeId() * @method Mage_Catalog_Model_Product_Link setLinkTypeId(int $value) * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Link extends Mage_Core_Model_Abstract { const LINK_TYPE_RELATED = 1; const LINK_TYPE_GROUPED = 3; const LINK_TYPE_UPSELL = 4; const LINK_TYPE_CROSSSELL = 5; protected $_attributeCollection = null; /** * Initialize resource */ protected function _construct() { $this->_init('catalog/product_link'); } public function useRelatedLinks() { $this->setLinkTypeId(self::LINK_TYPE_RELATED); return $this; } public function useGroupedLinks() { $this->setLinkTypeId(self::LINK_TYPE_GROUPED); return $this; } public function useUpSellLinks() { $this->setLinkTypeId(self::LINK_TYPE_UPSELL); return $this; } /** * @return Mage_Catalog_Model_Product_Link */ public function useCrossSellLinks() { $this->setLinkTypeId(self::LINK_TYPE_CROSSSELL); return $this; } /** * Retrieve table name for attribute type * * @param string $type * @return string */ public function getAttributeTypeTable($type) { return $this->_getResource()->getAttributeTypeTable($type); } /** * Retrieve linked product collection */ public function getProductCollection() { $collection = Mage::getResourceModel('catalog/product_link_product_collection') ->setLinkModel($this); return $collection; } /** * Retrieve link collection */ public function getLinkCollection() { $collection = Mage::getResourceModel('catalog/product_link_collection') ->setLinkModel($this); return $collection; } public function getAttributes($type=null) { if (is_null($type)) { $type = $this->getLinkTypeId(); } return $this->_getResource()->getAttributesByType($type); } /** * Save data for product relations * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Link */ public function saveProductRelations($product) { $data = $product->getRelatedLinkData(); if (!is_null($data)) { $this->_getResource()->saveProductLinks($product, $data, self::LINK_TYPE_RELATED); } $data = $product->getUpSellLinkData(); if (!is_null($data)) { $this->_getResource()->saveProductLinks($product, $data, self::LINK_TYPE_UPSELL); } $data = $product->getCrossSellLinkData(); if (!is_null($data)) { $this->_getResource()->saveProductLinks($product, $data, self::LINK_TYPE_CROSSSELL); } return $this; } /** * Save grouped product relation links * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Link */ public function saveGroupedLinks($product) { $data = $product->getGroupedLinkData(); if (!is_null($data)) { $this->_getResource()->saveGroupedLinks($product, $data, self::LINK_TYPE_GROUPED); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Media * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Media library image config interface * * @category Mage * @package Mage_Media * @author Magento Core Team */ interface Mage_Media_Model_Image_Config_Interface { /** * Retrive base url for media files * * @return string */ function getBaseMediaUrl(); /** * Retrive base path for media files * * @return string */ function getBaseMediaPath(); /** * Retrive url for media file * * @param string $file * @return string */ function getMediaUrl($file); /** * Retrive file system path for media file * * @param string $file * @return string */ function getMediaPath($file); } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product media config * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Media_Config implements Mage_Media_Model_Image_Config_Interface { /** * Filesystem directory path of product images * relatively to media folder * * @return string */ public function getBaseMediaPathAddition() { return 'catalog' . DS . 'product'; } /** * Web-based directory path of product images * relatively to media folder * * @return string */ public function getBaseMediaUrlAddition() { return 'catalog/product'; } /** * Filesystem directory path of temporary product images * relatively to media folder * * @return string */ public function getBaseTmpMediaPathAddition() { return 'tmp' . DS . $this->getBaseMediaPathAddition(); } /** * Web-based directory path of temporary product images * relatively to media folder * * @return string */ public function getBaseTmpMediaUrlAddition() { return 'tmp/' . $this->getBaseMediaUrlAddition(); } public function getBaseMediaPath() { return Mage::getBaseDir('media') . DS . 'catalog' . DS . 'product'; } public function getBaseMediaUrl() { return Mage::getBaseUrl('media') . 'catalog/product'; } public function getBaseTmpMediaPath() { return Mage::getBaseDir('media') . DS . $this->getBaseTmpMediaPathAddition(); } public function getBaseTmpMediaUrl() { return Mage::getBaseUrl('media') . $this->getBaseTmpMediaUrlAddition(); } public function getMediaUrl($file) { $file = $this->_prepareFileForUrl($file); if(substr($file, 0, 1) == '/') { return $this->getBaseMediaUrl() . $file; } return $this->getBaseMediaUrl() . '/' . $file; } public function getMediaPath($file) { $file = $this->_prepareFileForPath($file); if(substr($file, 0, 1) == DS) { return $this->getBaseMediaPath() . DS . substr($file, 1); } return $this->getBaseMediaPath() . DS . $file; } public function getTmpMediaUrl($file) { $file = $this->_prepareFileForUrl($file); if(substr($file, 0, 1) == '/') { $file = substr($file, 1); } return $this->getBaseTmpMediaUrl() . '/' . $file; } /** * Part of URL of temporary product images * relatively to media folder * * @return string */ public function getTmpMediaShortUrl($file) { $file = $this->_prepareFileForUrl($file); if(substr($file, 0, 1) == '/') { $file = substr($file, 1); } return $this->getBaseTmpMediaUrlAddition() . '/' . $file; } /** * Part of URL of product images relatively to media folder * * @return string */ public function getMediaShortUrl($file) { $file = $this->_prepareFileForUrl($file); if(substr($file, 0, 1) == '/') { $file = substr($file, 1); } return $this->getBaseMediaUrlAddition() . '/' . $file; } public function getTmpMediaPath($file) { $file = $this->_prepareFileForPath($file); if(substr($file, 0, 1) == DS) { return $this->getBaseTmpMediaPath() . DS . substr($file, 1); } return $this->getBaseTmpMediaPath() . DS . $file; } protected function _prepareFileForUrl($file) { return str_replace(DS, '/', $file); } protected function _prepareFileForPath($file) { return str_replace('/', DS, $file); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product option model * * @method Mage_Catalog_Model_Resource_Product_Option _getResource() * @method Mage_Catalog_Model_Resource_Product_Option getResource() * @method int getProductId() * @method Mage_Catalog_Model_Product_Option setProductId(int $value) * @method string getType() * @method Mage_Catalog_Model_Product_Option setType(string $value) * @method int getIsRequire() * @method Mage_Catalog_Model_Product_Option setIsRequire(int $value) * @method string getSku() * @method Mage_Catalog_Model_Product_Option setSku(string $value) * @method int getMaxCharacters() * @method Mage_Catalog_Model_Product_Option setMaxCharacters(int $value) * @method string getFileExtension() * @method Mage_Catalog_Model_Product_Option setFileExtension(string $value) * @method int getImageSizeX() * @method Mage_Catalog_Model_Product_Option setImageSizeX(int $value) * @method int getImageSizeY() * @method Mage_Catalog_Model_Product_Option setImageSizeY(int $value) * @method int getSortOrder() * @method Mage_Catalog_Model_Product_Option setSortOrder(int $value) * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Option extends Mage_Core_Model_Abstract { const OPTION_GROUP_TEXT = 'text'; const OPTION_GROUP_FILE = 'file'; const OPTION_GROUP_SELECT = 'select'; const OPTION_GROUP_DATE = 'date'; const OPTION_TYPE_FIELD = 'field'; const OPTION_TYPE_AREA = 'area'; const OPTION_TYPE_FILE = 'file'; const OPTION_TYPE_DROP_DOWN = 'drop_down'; const OPTION_TYPE_RADIO = 'radio'; const OPTION_TYPE_CHECKBOX = 'checkbox'; const OPTION_TYPE_MULTIPLE = 'multiple'; const OPTION_TYPE_DATE = 'date'; const OPTION_TYPE_DATE_TIME = 'date_time'; const OPTION_TYPE_TIME = 'time'; protected $_product; protected $_options = array(); protected $_valueInstance; protected $_values = array(); protected function _construct() { $this->_init('catalog/product_option'); } /** * Add value of option to values array * * @param Mage_Catalog_Model_Product_Option_Value $value * @return Mage_Catalog_Model_Product_Option */ public function addValue(Mage_Catalog_Model_Product_Option_Value $value) { $this->_values[$value->getId()] = $value; return $this; } /** * Get value by given id * * @param int $valueId * @return Mage_Catalog_Model_Product_Option_Value */ public function getValueById($valueId) { if (isset($this->_values[$valueId])) { return $this->_values[$valueId]; } return null; } public function getValues() { return $this->_values; } /** * Retrieve value instance * * @return Mage_Catalog_Model_Product_Option_Value */ public function getValueInstance() { if (!$this->_valueInstance) { $this->_valueInstance = Mage::getSingleton('catalog/product_option_value'); } return $this->_valueInstance; } /** * Add option for save it * * @param array $option * @return Mage_Catalog_Model_Product_Option */ public function addOption($option) { $this->_options[] = $option; return $this; } /** * Get all options * * @return array */ public function getOptions() { return $this->_options; } /** * Set options for array * * @param array $options * @return Mage_Catalog_Model_Product_Option */ public function setOptions($options) { $this->_options = $options; return $this; } /** * Set options to empty array * * @return Mage_Catalog_Model_Product_Option */ public function unsetOptions() { $this->_options = array(); return $this; } /** * Retrieve product instance * * @return Mage_Catalog_Model_Product */ public function getProduct() { return $this->_product; } /** * Set product instance * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Option */ public function setProduct(Mage_Catalog_Model_Product $product = null) { $this->_product = $product; return $this; } /** * Get group name of option by given option type * * @param string $type * @return string */ public function getGroupByType($type = null) { if (is_null($type)) { $type = $this->getType(); } $optionGroupsToTypes = array( self::OPTION_TYPE_FIELD => self::OPTION_GROUP_TEXT, self::OPTION_TYPE_AREA => self::OPTION_GROUP_TEXT, self::OPTION_TYPE_FILE => self::OPTION_GROUP_FILE, self::OPTION_TYPE_DROP_DOWN => self::OPTION_GROUP_SELECT, self::OPTION_TYPE_RADIO => self::OPTION_GROUP_SELECT, self::OPTION_TYPE_CHECKBOX => self::OPTION_GROUP_SELECT, self::OPTION_TYPE_MULTIPLE => self::OPTION_GROUP_SELECT, self::OPTION_TYPE_DATE => self::OPTION_GROUP_DATE, self::OPTION_TYPE_DATE_TIME => self::OPTION_GROUP_DATE, self::OPTION_TYPE_TIME => self::OPTION_GROUP_DATE, ); return isset($optionGroupsToTypes[$type])?$optionGroupsToTypes[$type]:''; } /** * Group model factory * * @param string $type Option type * @return Mage_Catalog_Model_Product_Option_Group_Abstract */ public function groupFactory($type) { $group = $this->getGroupByType($type); if (!empty($group)) { return Mage::getModel('catalog/product_option_type_' . $group); } Mage::throwException(Mage::helper('catalog')->__('Wrong option type to get group instance.')); } /** * Save options. * * @return Mage_Catalog_Model_Product_Option */ public function saveOptions() { foreach ($this->getOptions() as $option) { $this->setData($option) ->setData('product_id', $this->getProduct()->getId()) ->setData('store_id', $this->getProduct()->getStoreId()); if ($this->getData('option_id') == '0') { $this->unsetData('option_id'); } else { $this->setId($this->getData('option_id')); } $isEdit = (bool)$this->getId()? true:false; if ($this->getData('is_delete') == '1') { if ($isEdit) { $this->getValueInstance()->deleteValue($this->getId()); $this->deletePrices($this->getId()); $this->deleteTitles($this->getId()); $this->delete(); } } else { if ($this->getData('previous_type') != '') { $previousType = $this->getData('previous_type'); /** * if previous option has different group from one is came now * need to remove all data of previous group */ if ($this->getGroupByType($previousType) != $this->getGroupByType($this->getData('type'))) { switch ($this->getGroupByType($previousType)) { case self::OPTION_GROUP_SELECT: $this->unsetData('values'); if ($isEdit) { $this->getValueInstance()->deleteValue($this->getId()); } break; case self::OPTION_GROUP_FILE: $this->setData('file_extension', ''); $this->setData('image_size_x', '0'); $this->setData('image_size_y', '0'); break; case self::OPTION_GROUP_TEXT: $this->setData('max_characters', '0'); break; case self::OPTION_GROUP_DATE: break; } if ($this->getGroupByType($this->getData('type')) == self::OPTION_GROUP_SELECT) { $this->setData('sku', ''); $this->unsetData('price'); $this->unsetData('price_type'); if ($isEdit) { $this->deletePrices($this->getId()); } } } } $this->save(); } }//eof foreach() return $this; } protected function _afterSave() { $this->getValueInstance()->unsetValues(); if (is_array($this->getData('values'))) { foreach ($this->getData('values') as $value) { $this->getValueInstance()->addValue($value); } $this->getValueInstance()->setOption($this) ->saveValues(); } elseif ($this->getGroupByType($this->getType()) == self::OPTION_GROUP_SELECT) { Mage::throwException(Mage::helper('catalog')->__('Select type options required values rows.')); } return parent::_afterSave(); } /** * Return price. If $flag is true and price is percent * return converted percent to price * * @param bool $flag * @return decimal */ public function getPrice($flag=false) { if ($flag && $this->getPriceType() == 'percent') { $basePrice = $this->getProduct()->getFinalPrice(); $price = $basePrice*($this->_getData('price')/100); return $price; } return $this->_getData('price'); } /** * Delete prices of option * * @param int $option_id * @return Mage_Catalog_Model_Product_Option */ public function deletePrices($option_id) { $this->getResource()->deletePrices($option_id); return $this; } /** * Delete titles of option * * @param int $option_id * @return Mage_Catalog_Model_Product_Option */ public function deleteTitles($option_id) { $this->getResource()->deleteTitles($option_id); return $this; } /** * get Product Option Collection * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function getProductOptionCollection(Mage_Catalog_Model_Product $product) { $collection = $this->getCollection() ->addFieldToFilter('product_id', $product->getId()) ->addTitleToResult($product->getStoreId()) ->addPriceToResult($product->getStoreId()) ->setOrder('sort_order', 'asc') ->setOrder('title', 'asc'); if ($this->getAddRequiredFilter()) { $collection->addRequiredFilter($this->getAddRequiredFilterValue()); } $collection->addValuesToResult($product->getStoreId()); return $collection; } /** * Get collection of values for current option * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Option_Value_Collection */ public function getValuesCollection() { $collection = $this->getValueInstance() ->getValuesCollection($this); return $collection; } /** * Get collection of values by given option ids * * @param array $optionIds * @param int $store_id * @return unknown */ public function getOptionValuesByOptionId($optionIds, $store_id) { $collection = Mage::getModel('catalog/product_option_value') ->getValuesByOption($optionIds, $this->getId(), $store_id); return $collection; } /** * Prepare array of options for duplicate * * @return array */ public function prepareOptionForDuplicate() { $this->setProductId(null); $this->setOptionId(null); $newOption = $this->__toArray(); if ($_values = $this->getValues()) { $newValuesArray = array(); foreach ($_values as $_value) { $newValuesArray[] = $_value->prepareValueForDuplicate(); } $newOption['values'] = $newValuesArray; } return $newOption; } /** * Duplicate options for product * * @param int $oldProductId * @param int $newProductId * @return Mage_Catalog_Model_Product_Option */ public function duplicate($oldProductId, $newProductId) { $this->getResource()->duplicate($this, $oldProductId, $newProductId); return $this; } /** * Retrieve option searchable data * * @param int $productId * @param int $storeId * @return array */ public function getSearchableData($productId, $storeId) { return $this->_getResource()->getSearchableData($productId, $storeId); } /** * Clearing object's data * * @return Mage_Catalog_Model_Product_Option */ protected function _clearData() { $this->_data = array(); $this->_values = array(); return $this; } /** * Clearing cyclic references * * @return Mage_Catalog_Model_Product_Option */ protected function _clearReferences() { if (!empty($this->_values)) { foreach ($this->_values as $value) { $value->unsetOption(); } } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product status functionality model * * @method Mage_Catalog_Model_Resource_Product_Status _getResource() * @method Mage_Catalog_Model_Resource_Product_Status getResource() * @method int getProductId() * @method Mage_Catalog_Model_Product_Status setProductId(int $value) * @method int getStoreId() * @method Mage_Catalog_Model_Product_Status setStoreId(int $value) * @method int getVisibility() * @method Mage_Catalog_Model_Product_Status setVisibility(int $value) * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Status extends Mage_Core_Model_Abstract { const STATUS_ENABLED = 1; const STATUS_DISABLED = 2; /** * Reference to the attribute instance * * @var Mage_Catalog_Model_Resource_Eav_Attribute */ protected $_attribute; /** * Initialize resource model * */ protected function _construct() { $this->_init('catalog/product_status'); } /** * Retrieve resource model wrapper * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Status */ protected function _getResource() { return parent::_getResource(); } /** * Retrieve Product Attribute by code * * @param string $attributeCode * @return Mage_Eav_Model_Entity_Attribute_Abstract */ public function getProductAttribute($attributeCode) { return $this->_getResource()->getProductAttribute($attributeCode); } /** * Add visible filter to Product Collection * * @deprecated remove on new builds * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @return Mage_Catalog_Model_Product_Status */ public function addVisibleFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) { //$collection->addAttributeToFilter('status', array('in'=>$this->getVisibleStatusIds())); return $this; } /** * Add saleable filter to Product Collection * * @deprecated remove on new builds * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @return Mage_Catalog_Model_Product_Status */ public function addSaleableFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) { //$collection->addAttributeToFilter('status', array('in'=>$this->getSaleableStatusIds())); return $this; } /** * Retrieve Visible Status Ids * * @return array */ public function getVisibleStatusIds() { return array(self::STATUS_ENABLED); } /** * Retrieve Saleable Status Ids * Default Product Enable status * * @return array */ public function getSaleableStatusIds() { return array(self::STATUS_ENABLED); } /** * Retrieve option array * * @return array */ static public function getOptionArray() { return array( self::STATUS_ENABLED => Mage::helper('catalog')->__('Enabled'), self::STATUS_DISABLED => Mage::helper('catalog')->__('Disabled') ); } /** * Retrieve option array with empty value * * @return array */ static public function getAllOption() { $options = self::getOptionArray(); array_unshift($options, array('value'=>'', 'label'=>'')); return $options; } /** * Retrieve option array with empty value * * @return array */ static public function getAllOptions() { $res = array( array( 'value' => '', 'label' => Mage::helper('catalog')->__('-- Please Select --') ) ); foreach (self::getOptionArray() as $index => $value) { $res[] = array( 'value' => $index, 'label' => $value ); } return $res; } /** * Retrieve option text by option value * * @param string $optionId * @return string */ static public function getOptionText($optionId) { $options = self::getOptionArray(); return isset($options[$optionId]) ? $options[$optionId] : null; } /** * Update status value for product * * @param int $productId * @param int $storeId * @param int $value * @return Mage_Catalog_Model_Product_Status */ public function updateProductStatus($productId, $storeId, $value) { Mage::getSingleton('catalog/product_action') ->updateAttributes(array($productId), array('status' => $value), $storeId); // add back compatibility event $status = $this->_getResource()->getProductAttribute('status'); if ($status->isScopeWebsite()) { $website = Mage::app()->getStore($storeId)->getWebsite(); $stores = $website->getStoreIds(); } else if ($status->isScopeStore()) { $stores = array($storeId); } else { $stores = array_keys(Mage::app()->getStores()); } foreach ($stores as $storeId) { Mage::dispatchEvent('catalog_product_status_update', array( 'product_id' => $productId, 'store_id' => $storeId, 'status' => $value )); } return $this; } /** * Retrieve Product(s) status for store * Return array where key is product, value - status * * @param int|array $productIds * @param int $storeId * @return array */ public function getProductStatus($productIds, $storeId = null) { return $this->getResource()->getProductStatus($productIds, $storeId); } /** * ---------------- Eav Source methods for Flat data ----------------------- */ /** * Retrieve flat column definition * * @return array */ public function getFlatColums() { return array(); } /** * Retrieve Indexes for Flat * * @return array */ public function getFlatIndexes() { return array(); } /** * Retrieve Select For Flat Attribute update * * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param int $store * @return Varien_Db_Select|null */ public function getFlatUpdateSelect($store) { return null; } /** * Set attribute instance * * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return Mage_Eav_Model_Entity_Attribute_Frontend_Abstract */ public function setAttribute($attribute) { $this->_attribute = $attribute; return $this; } /** * Get attribute instance * * @return Mage_Catalog_Model_Resource_Eav_Attribute */ public function getAttribute() { return $this->_attribute; } /** * Add Value Sort To Collection Select * * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @param string $dir direction * @return Mage_Eav_Model_Entity_Attribute_Source_Abstract */ public function addValueSortToCollection($collection, $dir = 'asc') { $attributeCode = $this->getAttribute()->getAttributeCode(); $attributeId = $this->getAttribute()->getId(); $attributeTable = $this->getAttribute()->getBackend()->getTable(); if ($this->getAttribute()->isScopeGlobal()) { $tableName = $attributeCode . '_t'; $collection->getSelect() ->joinLeft( array($tableName => $attributeTable), "e.entity_id={$tableName}.entity_id" . " AND {$tableName}.attribute_id='{$attributeId}'" . " AND {$tableName}.store_id='0'", array()); $valueExpr = $tableName . '.value'; } else { $valueTable1 = $attributeCode . '_t1'; $valueTable2 = $attributeCode . '_t2'; $collection->getSelect() ->joinLeft( array($valueTable1 => $attributeTable), "e.entity_id={$valueTable1}.entity_id" . " AND {$valueTable1}.attribute_id='{$attributeId}'" . " AND {$valueTable1}.store_id='0'", array()) ->joinLeft( array($valueTable2 => $attributeTable), "e.entity_id={$valueTable2}.entity_id" . " AND {$valueTable2}.attribute_id='{$attributeId}'" . " AND {$valueTable2}.store_id='{$collection->getStoreId()}'", array() ); $valueExpr = $collection->getConnection()->getCheckSql( $valueTable2 . '.value_id > 0', $valueTable2 . '.value', $valueTable1 . '.value' ); } $collection->getSelect()->order($valueExpr . ' ' . $dir); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product type model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Type { /** * Available product types */ const TYPE_SIMPLE = 'simple'; const TYPE_BUNDLE = 'bundle'; const TYPE_CONFIGURABLE = 'configurable'; const TYPE_GROUPED = 'grouped'; const TYPE_VIRTUAL = 'virtual'; const DEFAULT_TYPE = 'simple'; const DEFAULT_TYPE_MODEL = 'catalog/product_type_simple'; const DEFAULT_PRICE_MODEL = 'catalog/product_type_price'; static protected $_types; static protected $_compositeTypes; static protected $_priceModels; static protected $_typesPriority; /** * Product type instance factory * * @param Mage_Catalog_Model_Product $product * @param bool $singleton * @return Mage_Catalog_Model_Product_Type_Abstract */ public static function factory($product, $singleton = false) { $types = self::getTypes(); $typeId = $product->getTypeId(); if (!empty($types[$typeId]['model'])) { $typeModelName = $types[$typeId]['model']; } else { $typeModelName = self::DEFAULT_TYPE_MODEL; $typeId = self::DEFAULT_TYPE; } if ($singleton === true) { $typeModel = Mage::getSingleton($typeModelName); } else { $typeModel = Mage::getModel($typeModelName); $typeModel->setProduct($product); } $typeModel->setConfig($types[$typeId]); return $typeModel; } /** * Product type price model factory * * @param string $productType * @return Mage_Catalog_Model_Product_Type_Price */ public static function priceFactory($productType) { if (isset(self::$_priceModels[$productType])) { return self::$_priceModels[$productType]; } $types = self::getTypes(); if (!empty($types[$productType]['price_model'])) { $priceModelName = $types[$productType]['price_model']; } else { $priceModelName = self::DEFAULT_PRICE_MODEL; } self::$_priceModels[$productType] = Mage::getModel($priceModelName); return self::$_priceModels[$productType]; } static public function getOptionArray() { $options = array(); foreach(self::getTypes() as $typeId=>$type) { $options[$typeId] = Mage::helper('catalog')->__($type['label']); } return $options; } static public function getAllOption() { $options = self::getOptionArray(); array_unshift($options, array('value'=>'', 'label'=>'')); return $options; } static public function getAllOptions() { $res = array(); $res[] = array('value'=>'', 'label'=>''); foreach (self::getOptionArray() as $index => $value) { $res[] = array( 'value' => $index, 'label' => $value ); } return $res; } static public function getOptions() { $res = array(); foreach (self::getOptionArray() as $index => $value) { $res[] = array( 'value' => $index, 'label' => $value ); } return $res; } static public function getOptionText($optionId) { $options = self::getOptionArray(); return isset($options[$optionId]) ? $options[$optionId] : null; } static public function getTypes() { if (is_null(self::$_types)) { $productTypes = Mage::getConfig()->getNode('global/catalog/product/type')->asArray(); foreach ($productTypes as $productKey => $productConfig) { $moduleName = 'catalog'; if (isset($productConfig['@']['module'])) { $moduleName = $productConfig['@']['module']; } $translatedLabel = Mage::helper($moduleName)->__($productConfig['label']); $productTypes[$productKey]['label'] = $translatedLabel; } self::$_types = $productTypes; } return self::$_types; } /** * Return composite product type Ids * * @return array */ static public function getCompositeTypes() { if (is_null(self::$_compositeTypes)) { self::$_compositeTypes = array(); $types = self::getTypes(); foreach ($types as $typeId=>$typeInfo) { if (array_key_exists('composite', $typeInfo) && $typeInfo['composite']) { self::$_compositeTypes[] = $typeId; } } } return self::$_compositeTypes; } /** * Return product types by type indexing priority * * @return array */ public static function getTypesByPriority() { if (is_null(self::$_typesPriority)) { self::$_typesPriority = array(); $a = array(); $b = array(); $types = self::getTypes(); foreach ($types as $typeId => $typeInfo) { $priority = isset($typeInfo['index_priority']) ? abs(intval($typeInfo['index_priority'])) : 0; if (!empty($typeInfo['composite'])) { $b[$typeId] = $priority; } else { $a[$typeId] = $priority; } } asort($a, SORT_NUMERIC); asort($b, SORT_NUMERIC); foreach (array_keys($a) as $typeId) { self::$_typesPriority[$typeId] = $types[$typeId]; } foreach (array_keys($b) as $typeId) { self::$_typesPriority[$typeId] = $types[$typeId]; } } return self::$_typesPriority; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract model for product type implementation * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ abstract class Mage_Catalog_Model_Product_Type_Abstract { /** * Product model instance * * @deprecated if use as singleton * @var Mage_Catalog_Model_Product */ protected $_product; /** * Product type instance id * * @var string */ protected $_typeId; /** * @deprecated * * @var array */ protected $_setAttributes; /** * @deprecated * * @var array */ protected $_editableAttributes; /** * Is a composite product type * * @var bool */ protected $_isComposite = false; /** * Is a configurable product type * * @var bool */ protected $_canConfigure = false; /** * Whether product quantity is fractional number or not * * @var bool */ protected $_canUseQtyDecimals = true; /** * @deprecated * * @var int */ protected $_storeFilter = null; /** * File queue array * * @var array */ protected $_fileQueue = array(); const CALCULATE_CHILD = 0; const CALCULATE_PARENT = 1; /** * values for shipment type (invoice etc) * */ const SHIPMENT_SEPARATELY = 1; const SHIPMENT_TOGETHER = 0; /** * Process modes * * Full validation - all required options must be set, whole configuration * must be valid */ const PROCESS_MODE_FULL = 'full'; /** * Process modes * * Lite validation - only received options are validated */ const PROCESS_MODE_LITE = 'lite'; /** * Item options prefix */ const OPTION_PREFIX = 'option_'; /** * Specify type instance product * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Type_Abstract */ public function setProduct($product) { $this->_product = $product; return $this; } /** * Specify type identifier * * @param string $typeId * @return Mage_Catalog_Model_Product_Type_Abstract */ public function setTypeId($typeId) { $this->_typeId = $typeId; return $this; } /** * Retrieve catalog product object * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product */ public function getProduct($product = null) { if (is_object($product)) { return $product; } return $this->_product; } /** * Return relation info about used products for specific type instance * * @return Varien_Object Object with information data */ public function getRelationInfo() { return new Varien_Object(); } /** * Retrieve Required children ids * Return grouped array, ex array( * group => array(ids) * ) * * @param int $parentId * @param bool $required * @return array */ public function getChildrenIds($parentId, $required = true) { return array(); } /** * Retrieve parent ids array by requered child * * @param int|array $childId * @return array */ public function getParentIdsByChild($childId) { return array(); } /** * Get array of product set attributes * * @param Mage_Catalog_Model_Product $product * @return array */ public function getSetAttributes($product = null) { return $this->getProduct($product)->getResource() ->loadAllAttributes($this->getProduct($product)) ->getSortedAttributes($this->getProduct($product)->getAttributeSetId()); } /** * Compare attribues sorting * * @param Mage_Catalog_Model_Entity_Attribute $attribute1 * @param Mage_Catalog_Model_Entity_Attribute $attribute2 * @return int */ public function attributesCompare($attribute1, $attribute2) { $sort1 = ($attribute1->getGroupSortPath() * 1000) + ($attribute1->getSortPath() * 0.0001); $sort2 = ($attribute2->getGroupSortPath() * 1000) + ($attribute2->getSortPath() * 0.0001); if ($sort1 > $sort2) { return 1; } elseif ($sort1 < $sort2) { return -1; } return 0; } /** * Retrieve product type attributes * * @param Mage_Catalog_Model_Product $product * @return array */ public function getEditableAttributes($product = null) { $cacheKey = '_cache_editable_attributes'; if (!$this->getProduct($product)->hasData($cacheKey)) { $editableAttributes = array(); foreach ($this->getSetAttributes($product) as $attributeCode => $attribute) { if (!is_array($attribute->getApplyTo()) || count($attribute->getApplyTo())==0 || in_array($this->getProduct($product)->getTypeId(), $attribute->getApplyTo())) { $editableAttributes[$attributeCode] = $attribute; } } $this->getProduct($product)->setData($cacheKey, $editableAttributes); } return $this->getProduct($product)->getData($cacheKey); } /** * Retrieve product attribute by identifier * * @param int $attributeId * @return Mage_Eav_Model_Entity_Attribute_Abstract */ public function getAttributeById($attributeId, $product = null) { foreach ($this->getSetAttributes($product) as $attribute) { if ($attribute->getId() == $attributeId) { return $attribute; } } return null; } /** * Check is virtual product * * @param Mage_Catalog_Model_Product $product * @return bool */ public function isVirtual($product = null) { return false; } /** * Check is product available for sale * * @param Mage_Catalog_Model_Product $product * @return bool */ 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; } /** * Prepare product and its configuration to be added to some products list. * Perform standard preparation process and then prepare options belonging to specific product type. * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product * @param string $processMode * @return array|string */ protected function _prepareProduct(Varien_Object $buyRequest, $product, $processMode) { $product = $this->getProduct($product); /* @var Mage_Catalog_Model_Product $product */ // try to add custom options try { $options = $this->_prepareOptions($buyRequest, $product, $processMode); } catch (Mage_Core_Exception $e) { return $e->getMessage(); } if (is_string($options)) { return $options; } // try to found super product configuration // (if product was buying within grouped product) $superProductConfig = $buyRequest->getSuperProductConfig(); if (!empty($superProductConfig['product_id']) && !empty($superProductConfig['product_type']) ) { $superProductId = (int) $superProductConfig['product_id']; if ($superProductId) { if (!$superProduct = Mage::registry('used_super_product_'.$superProductId)) { $superProduct = Mage::getModel('catalog/product')->load($superProductId); Mage::register('used_super_product_'.$superProductId, $superProduct); } if ($superProduct->getId()) { $assocProductIds = $superProduct->getTypeInstance(true)->getAssociatedProductIds($superProduct); if (in_array($product->getId(), $assocProductIds)) { $productType = $superProductConfig['product_type']; $product->addCustomOption('product_type', $productType, $superProduct); $buyRequest->setData('super_product_config', array( 'product_type' => $productType, 'product_id' => $superProduct->getId() )); } } } } $product->prepareCustomOptions(); $buyRequest->unsetData('_processing_params'); // One-time params only $product->addCustomOption('info_buyRequest', serialize($buyRequest->getData())); if ($options) { $optionIds = array_keys($options); $product->addCustomOption('option_ids', implode(',', $optionIds)); foreach ($options as $optionId => $optionValue) { $product->addCustomOption(self::OPTION_PREFIX . $optionId, $optionValue); } } // set quantity in cart if ($this->_isStrictProcessMode($processMode)) { $product->setCartQty($buyRequest->getQty()); } $product->setQty($buyRequest->getQty()); return array($product); } /** * Process product configuaration * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product * @param string $processMode * @return array|string */ public function processConfiguration(Varien_Object $buyRequest, $product = null, $processMode = self::PROCESS_MODE_LITE) { $_products = $this->_prepareProduct($buyRequest, $product, $processMode); $this->processFileQueue(); return $_products; } /** * Initialize product(s) for add to cart process. * Advanced version of func to prepare product for cart - processMode can be specified there. * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product * @param null|string $processMode * @return array|string */ public function prepareForCartAdvanced(Varien_Object $buyRequest, $product = null, $processMode = null) { if (!$processMode) { $processMode = self::PROCESS_MODE_FULL; } $_products = $this->_prepareProduct($buyRequest, $product, $processMode); $this->processFileQueue(); return $_products; } /** * Initialize product(s) for add to cart process * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product * @return array|string */ public function prepareForCart(Varien_Object $buyRequest, $product = null) { return $this->prepareForCartAdvanced($buyRequest, $product, self::PROCESS_MODE_FULL); } /** * Process File Queue * @return Mage_Catalog_Model_Product_Type_Abstract */ public function processFileQueue() { if (empty($this->_fileQueue)) { return $this; } foreach ($this->_fileQueue as &$queueOptions) { if (isset($queueOptions['operation']) && $operation = $queueOptions['operation']) { switch ($operation) { case 'receive_uploaded_file': $src = isset($queueOptions['src_name']) ? $queueOptions['src_name'] : ''; $dst = isset($queueOptions['dst_name']) ? $queueOptions['dst_name'] : ''; /** @var $uploader Zend_File_Transfer_Adapter_Http */ $uploader = isset($queueOptions['uploader']) ? $queueOptions['uploader'] : null; $path = dirname($dst); $io = new Varien_Io_File(); if (!$io->isWriteable($path) && !$io->mkdir($path, 0777, true)) { Mage::throwException(Mage::helper('catalog')->__("Cannot create writeable directory '%s'.", $path)); } $uploader->setDestination($path); if (empty($src) || empty($dst) || !$uploader->receive($src)) { /** * @todo: show invalid option */ if (isset($queueOptions['option'])) { $queueOptions['option']->setIsValid(false); } Mage::throwException(Mage::helper('catalog')->__("File upload failed")); } Mage::helper('core/file_storage_database')->saveFile($dst); break; case 'move_uploaded_file': $src = $queueOptions['src_name']; $dst = $queueOptions['dst_name']; move_uploaded_file($src, $dst); Mage::helper('core/file_storage_database')->saveFile($dst); break; default: break; } } $queueOptions = null; } return $this; } /** * Add file to File Queue * @param array $queueOptions Array of File Queue * (eg. ['operation'=>'move', * 'src_name'=>'filename', * 'dst_name'=>'filename2']) */ public function addFileQueue($queueOptions) { $this->_fileQueue[] = $queueOptions; } /** * Check if current process mode is strict * * @param string $processMode * @return bool */ protected function _isStrictProcessMode($processMode) { return $processMode == self::PROCESS_MODE_FULL; } /** * Retrieve message for specify option(s) * * @return string */ public function getSpecifyOptionMessage() { return Mage::helper('catalog')->__('Please specify the product\'s required option(s).'); } /** * Process custom defined options for product * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product * @param string $processMode * @return array */ protected function _prepareOptions(Varien_Object $buyRequest, $product, $processMode) { $transport = new StdClass; $transport->options = array(); foreach ($this->getProduct($product)->getOptions() as $_option) { /* @var $_option Mage_Catalog_Model_Product_Option */ $group = $_option->groupFactory($_option->getType()) ->setOption($_option) ->setProduct($this->getProduct($product)) ->setRequest($buyRequest) ->setProcessMode($processMode) ->validateUserValue($buyRequest->getOptions()); $preparedValue = $group->prepareForCart(); if ($preparedValue !== null) { $transport->options[$_option->getId()] = $preparedValue; } } $eventName = sprintf('catalog_product_type_prepare_%s_options', $processMode); Mage::dispatchEvent($eventName, array( 'transport' => $transport, 'buy_request' => $buyRequest, 'product' => $product )); return $transport->options; } /** * Process product custom defined options for cart * * @deprecated after 1.4.2.0 * @see _prepareOptions() * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product * @return array */ protected function _prepareOptionsForCart(Varien_Object $buyRequest, $product = null) { return $this->_prepareOptions($buyRequest, $product, self::PROCESS_MODE_FULL); } /** * Check if product can be bought * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Type_Abstract * @throws Mage_Core_Exception */ public function checkProductBuyState($product = null) { if (!$this->getProduct($product)->getSkipCheckRequiredOption()) { foreach ($this->getProduct($product)->getOptions() as $option) { if ($option->getIsRequire()) { $customOption = $this->getProduct($product) ->getCustomOption(self::OPTION_PREFIX . $option->getId()); if (!$customOption || strlen($customOption->getValue()) == 0) { $this->getProduct($product)->setSkipCheckRequiredOption(true); Mage::throwException( Mage::helper('catalog')->__('The product has required options') ); } } } } return $this; } /** * Prepare additional options/information for order item which will be * created from this product * * @param Mage_Catalog_Model_Product $product * @return array */ public function getOrderOptions($product = null) { $optionArr = array(); if ($info = $this->getProduct($product)->getCustomOption('info_buyRequest')) { $optionArr['info_buyRequest'] = unserialize($info->getValue()); } if ($optionIds = $this->getProduct($product)->getCustomOption('option_ids')) { foreach (explode(',', $optionIds->getValue()) as $optionId) { if ($option = $this->getProduct($product)->getOptionById($optionId)) { $confItemOption = $this->getProduct($product) ->getCustomOption(self::OPTION_PREFIX . $option->getId()); $group = $option->groupFactory($option->getType()) ->setOption($option) ->setProduct($this->getProduct()) ->setConfigurationItemOption($confItemOption); $optionArr['options'][] = array( 'label' => $option->getTitle(), 'value' => $group->getFormattedOptionValue($confItemOption->getValue()), 'print_value' => $group->getPrintableOptionValue($confItemOption->getValue()), 'option_id' => $option->getId(), 'option_type' => $option->getType(), 'option_value' => $confItemOption->getValue(), 'custom_view' => $group->isCustomizedView() ); } } } if ($productTypeConfig = $this->getProduct($product)->getCustomOption('product_type')) { $optionArr['super_product_config'] = array( 'product_code' => $productTypeConfig->getCode(), 'product_type' => $productTypeConfig->getValue(), 'product_id' => $productTypeConfig->getProductId() ); } return $optionArr; } /** * Save type related data * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Type_Abstract */ public function save($product = null) { return $this; } /** * Remove don't applicable attributes data * * @param Mage_Catalog_Model_Product $product */ protected function _removeNotApplicableAttributes($product = null) { $product = $this->getProduct($product); $eavConfig = Mage::getSingleton('eav/config'); $entityType = $product->getResource()->getEntityType(); foreach ($eavConfig->getEntityAttributeCodes($entityType, $product) as $attributeCode) { $attribute = $eavConfig->getAttribute($entityType, $attributeCode); $applyTo = $attribute->getApplyTo(); if (is_array($applyTo) && count($applyTo) > 0 && !in_array($product->getTypeId(), $applyTo)) { $product->unsetData($attribute->getAttributeCode()); } } } /** * Before save type related data * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Type_Abstract */ public function beforeSave($product = null) { $this->_removeNotApplicableAttributes($product); $this->getProduct($product)->canAffectOptions(true); return $this; } /** * Check if product is composite (grouped, configurable, etc) * * @param Mage_Catalog_Model_Product $product * @return bool */ public function isComposite($product = null) { return $this->_isComposite; } /** * Check if product is configurable * * @param Mage_Catalog_Model_Product $product * @return bool */ public function canConfigure($product = null) { return $this->_canConfigure; } /** * Check if product qty is fractional number * * @param Mage_Catalog_Model_Product $product * @return bool */ public function canUseQtyDecimals() { return $this->_canUseQtyDecimals; } /** * Default action to get sku of product * * @param Mage_Catalog_Model_Product $product * @return string */ public function getSku($product = null) { $sku = $this->getProduct($product)->getData('sku'); if ($this->getProduct($product)->getCustomOption('option_ids')) { $sku = $this->getOptionSku($product,$sku); } return $sku; } /** * Default action to get sku of product with option * * @param Mage_Catalog_Model_Product $product Product with Custom Options * @param string $sku Product SKU without option * @return string */ public function getOptionSku($product = null, $sku='') { $skuDelimiter = '-'; if(empty($sku)){ $sku = $this->getProduct($product)->getData('sku'); } if ($optionIds = $this->getProduct($product)->getCustomOption('option_ids')) { foreach (explode(',', $optionIds->getValue()) as $optionId) { if ($option = $this->getProduct($product)->getOptionById($optionId)) { $confItemOption = $this->getProduct($product)->getCustomOption(self::OPTION_PREFIX . $optionId); $group = $option->groupFactory($option->getType()) ->setOption($option)->setListener(new Varien_Object()); if ($optionSku = $group->getOptionSku($confItemOption->getValue(), $skuDelimiter)) { $sku .= $skuDelimiter . $optionSku; } if ($group->getListener()->getHasError()) { $this->getProduct($product) ->setHasError(true) ->setMessage( $group->getListener()->getMessage() ); } } } } return $sku; } /** * Default action to get weight of product * * @param Mage_Catalog_Model_Product $product * @return decimal */ public function getWeight($product = null) { return $this->getProduct($product)->getData('weight'); } /** * Return true if product has options * * @param Mage_Catalog_Model_Product $product * @return bool */ public function hasOptions($product = null) { if ($this->getProduct($product)->getHasOptions()) { return true; } if ($this->getProduct($product)->isRecurring()) { return true; } return false; } /** * Method is needed for specific actions to change given configuration options values * according current product type logic * Example: the cataloginventory validation of decimal qty can change qty to int, * so need to change configuration item qty option value too. * * @param array $options * @param Varien_Object $option * @param mixed $value * * @return object Mage_Catalog_Model_Product_Type_Abstract */ public function updateQtyOption($options, Varien_Object $option, $value, $product = null) { return $this; } /** * Check if product has required options * * @param Mage_Catalog_Model_Product $product * @return bool */ public function hasRequiredOptions($product = null) { if ($this->getProduct($product)->getRequiredOptions()) { return true; } return false; } /** * Retrive store filter for associated products * * @return int|Mage_Core_Model_Store */ public function getStoreFilter($product = null) { $cacheKey = '_cache_instance_store_filter'; return $this->getProduct($product)->getData($cacheKey); } /** * Set store filter for associated products * * @param $store int|Mage_Core_Model_Store * @return Mage_Catalog_Model_Product_Type_Configurable */ public function setStoreFilter($store=null, $product = null) { $cacheKey = '_cache_instance_store_filter'; $this->getProduct($product)->setData($cacheKey, $store); return $this; } /** * Allow for updates of chidren qty's * (applicable for complicated product types. As default returns false) * * @return boolean false */ public function getForceChildItemQtyChanges($product = null) { return false; } /** * Prepare Quote Item Quantity * * @param mixed $qty * @return float */ public function prepareQuoteItemQty($qty, $product = null) { return floatval($qty); } /** * Implementation of product specify logic of which product needs to be assigned to option. * For example if product which was added to option already removed from catalog. * * @param Mage_Catalog_Model_Product $optionProduct * @param Mage_Sales_Model_Quote_Item_Option $option * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Product_Type_Abstract */ public function assignProductToOption($optionProduct, $option, $product = null) { if ($optionProduct) { $option->setProduct($optionProduct); } else { $option->setProduct($this->getProduct($product)); } return $this; } /** * Setting specified product type variables * * @param array $config * @return Mage_Catalog_Model_Product_Type_Abstract */ public function setConfig($config) { if (isset($config['composite'])) { $this->_isComposite = (bool) $config['composite']; } if (isset($config['can_use_qty_decimals'])) { $this->_canUseQtyDecimals = (bool) $config['can_use_qty_decimals']; } return $this; } /** * Retrieve additional searchable data from type instance * Using based on product id and store_id data * * @param Mage_Catalog_Model_Product $product * @return array */ public function getSearchableData($product = null) { $product = $this->getProduct($product); $searchData = array(); if ($product->getHasOptions()){ $searchData = Mage::getSingleton('catalog/product_option') ->getSearchableData($product->getId(), $product->getStoreId()); } return $searchData; } /** * Retrieve products divided into groups required to purchase * At least one product in each group has to be purchased * * @param Mage_Catalog_Model_Product $product * @return array */ public function getProductsToPurchaseByReqGroups($product = null) { $product = $this->getProduct($product); if ($this->isComposite($product)) { return array(); } return array(array($product)); } /** * Prepare selected options for product * * @param Mage_Catalog_Model_Product $product * @param Varien_Object $buyRequest * @return array */ public function processBuyRequest($product, $buyRequest) { return array(); } /** * Check product's options configuration * * @param Mage_Catalog_Model_Product $product * @param Varien_Object $buyRequest * @return array */ public function checkProductConfiguration($product, $buyRequest) { $errors = array(); try { /** * cloning product because prepareForCart() method will modify it */ $productForCheck = clone $product; $buyRequestForCheck = clone $buyRequest; $result = $this->prepareForCart($buyRequestForCheck, $productForCheck); if (is_string($result)) { $errors[] = $result; } } catch (Mage_Core_Exception $e) { $errors[] = $e->getMessages(); } catch (Exception $e) { Mage::logException($e); $errors[] = Mage::helper('catalog')->__('There was an error while request processing.'); } return $errors; } /** * Check if Minimum advertise price is enabled at least in one option * * @param Mage_Catalog_Model_Product $product * @param int $visibility * @return bool */ public function isMapEnabledInOptions($product, $visibility = null) { return false; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product type price model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Type_Price { const CACHE_TAG = 'PRODUCT_PRICE'; static $attributeCache = array(); /** * Default action to get price of product * * @return decimal */ public function getPrice($product) { return $product->getData('price'); } /** * Get base price with apply Group, Tier, Special prises * * @param Mage_Catalog_Model_Product $product * @param float|null $qty * * @return float */ public function getBasePrice($product, $qty = null) { $price = (float)$product->getPrice(); return min($this->_applyGroupPrice($product, $price), $this->_applyTierPrice($product, $qty, $price), $this->_applySpecialPrice($product, $price) ); } /** * Retrieve product final price * * @param float|null $qty * @param Mage_Catalog_Model_Product $product * @return float */ public function getFinalPrice($qty = null, $product) { if (is_null($qty) && !is_null($product->getCalculatedFinalPrice())) { return $product->getCalculatedFinalPrice(); } $finalPrice = $this->getBasePrice($product, $qty); $product->setFinalPrice($finalPrice); Mage::dispatchEvent('catalog_product_get_final_price', array('product' => $product, 'qty' => $qty)); $finalPrice = $product->getData('final_price'); $finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice); $finalPrice = max(0, $finalPrice); $product->setFinalPrice($finalPrice); return $finalPrice; } public function getChildFinalPrice($product, $productQty, $childProduct, $childProductQty) { return $this->getFinalPrice($childProductQty, $childProduct); } /** * Apply group price for product * * @param Mage_Catalog_Model_Product $product * @param float $finalPrice * @return float */ protected function _applyGroupPrice($product, $finalPrice) { $groupPrice = $product->getGroupPrice(); if (is_numeric($groupPrice)) { $finalPrice = min($finalPrice, $groupPrice); } return $finalPrice; } /** * Get product group price * * @param Mage_Catalog_Model_Product $product * @return float */ public function getGroupPrice($product) { $groupPrices = $product->getData('group_price'); if (is_null($groupPrices)) { $attribute = $product->getResource()->getAttribute('group_price'); if ($attribute) { $attribute->getBackend()->afterLoad($product); $groupPrices = $product->getData('group_price'); } } if (is_null($groupPrices) || !is_array($groupPrices)) { return $product->getPrice(); } $customerGroup = $this->_getCustomerGroupId($product); $matchedPrice = $product->getPrice(); foreach ($groupPrices as $groupPrice) { if ($groupPrice['cust_group'] == $customerGroup && $groupPrice['website_price'] < $matchedPrice) { $matchedPrice = $groupPrice['website_price']; break; } } return $matchedPrice; } /** * Apply tier price for product if not return price that was before * * @param Mage_Catalog_Model_Product $product * @param float $qty * @param float $finalPrice * @return float */ protected function _applyTierPrice($product, $qty, $finalPrice) { if (is_null($qty)) { return $finalPrice; } $tierPrice = $product->getTierPrice($qty); if (is_numeric($tierPrice)) { $finalPrice = min($finalPrice, $tierPrice); } return $finalPrice; } /** * Get product tier price by qty * * @param float $qty * @param Mage_Catalog_Model_Product $product * @return float */ public function getTierPrice($qty = null, $product) { $allGroups = Mage_Customer_Model_Group::CUST_GROUP_ALL; $prices = $product->getData('tier_price'); if (is_null($prices)) { $attribute = $product->getResource()->getAttribute('tier_price'); if ($attribute) { $attribute->getBackend()->afterLoad($product); $prices = $product->getData('tier_price'); } } if (is_null($prices) || !is_array($prices)) { if (!is_null($qty)) { return $product->getPrice(); } return array(array( 'price' => $product->getPrice(), 'website_price' => $product->getPrice(), 'price_qty' => 1, 'cust_group' => $allGroups, )); } $custGroup = $this->_getCustomerGroupId($product); if ($qty) { $prevQty = 1; $prevPrice = $product->getPrice(); $prevGroup = $allGroups; foreach ($prices as $price) { if ($price['cust_group']!=$custGroup && $price['cust_group']!=$allGroups) { // tier not for current customer group nor is for all groups continue; } if ($qty < $price['price_qty']) { // tier is higher than product qty continue; } if ($price['price_qty'] < $prevQty) { // higher tier qty already found continue; } if ($price['price_qty'] == $prevQty && $prevGroup != $allGroups && $price['cust_group'] == $allGroups) { // found tier qty is same as current tier qty but current tier group is ALL_GROUPS continue; } if ($price['website_price'] < $prevPrice) { $prevPrice = $price['website_price']; $prevQty = $price['price_qty']; $prevGroup = $price['cust_group']; } } return $prevPrice; } else { $qtyCache = array(); foreach ($prices as $i => $price) { if ($price['cust_group'] != $custGroup && $price['cust_group'] != $allGroups) { unset($prices[$i]); } else if (isset($qtyCache[$price['price_qty']])) { $j = $qtyCache[$price['price_qty']]; if ($prices[$j]['website_price'] > $price['website_price']) { unset($prices[$j]); $qtyCache[$price['price_qty']] = $i; } else { unset($prices[$i]); } } else { $qtyCache[$price['price_qty']] = $i; } } } return ($prices) ? $prices : array(); } protected function _getCustomerGroupId($product) { if ($product->getCustomerGroupId()) { return $product->getCustomerGroupId(); } return Mage::getSingleton('customer/session')->getCustomerGroupId(); } /** * Apply special price for product if not return price that was before * * @param Mage_Catalog_Model_Product $product * @param float $finalPrice * @return float */ protected function _applySpecialPrice($product, $finalPrice) { return $this->calculateSpecialPrice($finalPrice, $product->getSpecialPrice(), $product->getSpecialFromDate(), $product->getSpecialToDate(), $product->getStore() ); } /** * Count how many tier prices we have for the product * * @param Mage_Catalog_Model_Product $product * @return int */ public function getTierPriceCount($product) { $price = $product->getTierPrice(); return count($price); } /** * Get formatted by currency tier price * * @param float $qty * @param Mage_Catalog_Model_Product $product * @return array || float */ public function getFormatedTierPrice($qty=null, $product) { $price = $product->getTierPrice($qty); if (is_array($price)) { foreach ($price as $index => $value) { $price[$index]['formated_price'] = Mage::app()->getStore()->convertPrice( $price[$index]['website_price'], true ); } } else { $price = Mage::app()->getStore()->formatPrice($price); } return $price; } /** * Get formatted by currency product price * * @param Mage_Catalog_Model_Product $product * @return array || float */ public function getFormatedPrice($product) { return Mage::app()->getStore()->formatPrice($product->getFinalPrice()); } /** * Apply options price * * @param Mage_Catalog_Model_Product $product * @param int $qty * @param float $finalPrice * @return float */ protected function _applyOptionsPrice($product, $qty, $finalPrice) { if ($optionIds = $product->getCustomOption('option_ids')) { $basePrice = $finalPrice; foreach (explode(',', $optionIds->getValue()) as $optionId) { if ($option = $product->getOptionById($optionId)) { $confItemOption = $product->getCustomOption('option_'.$option->getId()); $group = $option->groupFactory($option->getType()) ->setOption($option) ->setConfigurationItemOption($confItemOption); $finalPrice += $group->getOptionPrice($confItemOption->getValue(), $basePrice); } } } return $finalPrice; } /** * Calculate product price based on special price data and price rules * * @param float $basePrice * @param float $specialPrice * @param string $specialPriceFrom * @param string $specialPriceTo * @param float|null|false $rulePrice * @param mixed $wId * @param mixed $gId * @param null|int $productId * @return float */ public static function calculatePrice($basePrice, $specialPrice, $specialPriceFrom, $specialPriceTo, $rulePrice = false, $wId = null, $gId = null, $productId = null) { Varien_Profiler::start('__PRODUCT_CALCULATE_PRICE__'); if ($wId instanceof Mage_Core_Model_Store) { $sId = $wId->getId(); $wId = $wId->getWebsiteId(); } else { $sId = Mage::app()->getWebsite($wId)->getDefaultGroup()->getDefaultStoreId(); } $finalPrice = $basePrice; if ($gId instanceof Mage_Customer_Model_Group) { $gId = $gId->getId(); } $finalPrice = self::calculateSpecialPrice($finalPrice, $specialPrice, $specialPriceFrom, $specialPriceTo, $sId); if ($rulePrice === false) { $storeTimestamp = Mage::app()->getLocale()->storeTimeStamp($sId); $rulePrice = Mage::getResourceModel('catalogrule/rule') ->getRulePrice($storeTimestamp, $wId, $gId, $productId); } if ($rulePrice !== null && $rulePrice !== false) { $finalPrice = min($finalPrice, $rulePrice); } $finalPrice = max($finalPrice, 0); Varien_Profiler::stop('__PRODUCT_CALCULATE_PRICE__'); return $finalPrice; } /** * Calculate and apply special price * * @param float $finalPrice * @param float $specialPrice * @param string $specialPriceFrom * @param string $specialPriceTo * @param mixed $store * @return float */ public static function calculateSpecialPrice($finalPrice, $specialPrice, $specialPriceFrom, $specialPriceTo, $store = null) { if (!is_null($specialPrice) && $specialPrice != false) { if (Mage::app()->getLocale()->isStoreDateInInterval($store, $specialPriceFrom, $specialPriceTo)) { $finalPrice = min($finalPrice, $specialPrice); } } return $finalPrice; } /** * Check is tier price value fixed or percent of original price * * @return bool */ public function isTierPriceFixed() { return $this->isGroupPriceFixed(); } /** * Check is group price value fixed or percent of original price * * @return bool */ public function isGroupPriceFixed() { return true; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Simple product type implementation * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Type_Simple extends Mage_Catalog_Model_Product_Type_Abstract { } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product Url model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Url extends Varien_Object { const CACHE_TAG = 'url_rewrite'; /** * Static URL instance * * @var Mage_Core_Model_Url */ protected static $_url; /** * Static URL Rewrite Instance * * @var Mage_Core_Model_Url_Rewrite */ protected static $_urlRewrite; /** * Retrieve URL Instance * * @return Mage_Core_Model_Url */ public function getUrlInstance() { if (!self::$_url) { self::$_url = Mage::getModel('core/url'); } return self::$_url; } /** * Retrieve URL Rewrite Instance * * @return Mage_Core_Model_Url_Rewrite */ public function getUrlRewrite() { if (!self::$_urlRewrite) { self::$_urlRewrite = Mage::getModel('core/url_rewrite'); } return self::$_urlRewrite; } /** * 'no_selection' shouldn't be a valid image attribute value * * @param string $image * @return string */ protected function _validImage($image) { if($image == 'no_selection') { $image = null; } return $image; } /** * Retrieve URL in current store * * @param Mage_Catalog_Model_Product $product * @param array $params the URL route params * @return string */ public function getUrlInStore(Mage_Catalog_Model_Product $product, $params = array()) { $params['_store_to_url'] = true; return $this->getUrl($product, $params); } /** * Retrieve Product URL * * @param Mage_Catalog_Model_Product $product * @param bool $useSid forced SID mode * @return string */ public function getProductUrl($product, $useSid = null) { if ($useSid === null) { $useSid = Mage::app()->getUseSessionInUrl(); } $params = array(); if (!$useSid) { $params['_nosid'] = true; } return $this->getUrl($product, $params); } /** * Format Key for URL * * @param string $str * @return string */ public function formatUrlKey($str) { $urlKey = preg_replace('#[^0-9a-z]+#i', '-', Mage::helper('catalog/product_url')->format($str)); $urlKey = strtolower($urlKey); $urlKey = trim($urlKey, '-'); return $urlKey; } /** * Retrieve Product Url path (with category if exists) * * @param Mage_Catalog_Model_Product $product * @param Mage_Catalog_Model_Category $category * * @return string */ public function getUrlPath($product, $category=null) { $path = $product->getData('url_path'); if (is_null($category)) { /** @todo get default category */ return $path; } elseif (!$category instanceof Mage_Catalog_Model_Category) { Mage::throwException('Invalid category object supplied'); } return Mage::helper('catalog/category')->getCategoryUrlPath($category->getUrlPath()) . '/' . $path; } /** * Retrieve Product URL using UrlDataObject * * @param Mage_Catalog_Model_Product $product * @param array $params * @return string */ public function getUrl(Mage_Catalog_Model_Product $product, $params = array()) { $routePath = ''; $routeParams = $params; $storeId = $product->getStoreId(); if (isset($params['_ignore_category'])) { unset($params['_ignore_category']); $categoryId = null; } else { $categoryId = $product->getCategoryId() && !$product->getDoNotUseCategoryId() ? $product->getCategoryId() : null; } if ($product->hasUrlDataObject()) { $requestPath = $product->getUrlDataObject()->getUrlRewrite(); $routeParams['_store'] = $product->getUrlDataObject()->getStoreId(); } else { $requestPath = $product->getRequestPath(); if (empty($requestPath) && $requestPath !== false) { $idPath = sprintf('product/%d', $product->getEntityId()); if ($categoryId) { $idPath = sprintf('%s/%d', $idPath, $categoryId); } $rewrite = $this->getUrlRewrite(); $rewrite->setStoreId($storeId) ->loadByIdPath($idPath); if ($rewrite->getId()) { $requestPath = $rewrite->getRequestPath(); $product->setRequestPath($requestPath); } else { $product->setRequestPath(false); } } } if (isset($routeParams['_store'])) { $storeId = Mage::app()->getStore($routeParams['_store'])->getId(); } if ($storeId != Mage::app()->getStore()->getId()) { $routeParams['_store_to_url'] = true; } if (!empty($requestPath)) { $routeParams['_direct'] = $requestPath; } else { $routePath = 'catalog/product/view'; $routeParams['id'] = $product->getId(); $routeParams['s'] = $product->getUrlKey(); if ($categoryId) { $routeParams['category'] = $categoryId; } } // reset cached URL instance GET query params if (!isset($routeParams['_query'])) { $routeParams['_query'] = array(); } return $this->getUrlInstance()->setStore($storeId) ->getUrl($routePath, $routeParams); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog Product visibilite model and attribute source model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Product_Visibility extends Varien_Object { const VISIBILITY_NOT_VISIBLE = 1; const VISIBILITY_IN_CATALOG = 2; const VISIBILITY_IN_SEARCH = 3; const VISIBILITY_BOTH = 4; /** * Reference to the attribute instance * * @var Mage_Catalog_Model_Resource_Eav_Attribute */ protected $_attribute; /** * Initialize object * */ public function __construct() { parent::__construct(); $this->setIdFieldName('visibility_id'); } /** * Add visible in catalog filter to collection * * @deprecated * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @return Mage_Catalog_Model_Product_Visibility */ public function addVisibleInCatalogFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) { $collection->setVisibility($this->getVisibleInCatalogIds()); // $collection->addAttributeToFilter('visibility', array('in'=>$this->getVisibleInCatalogIds())); return $this; } /** * Add visibility in searchfilter to collection * * @deprecated * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @return Mage_Catalog_Model_Product_Visibility */ public function addVisibleInSearchFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) { $collection->setVisibility($this->getVisibleInSearchIds()); //$collection->addAttributeToFilter('visibility', array('in'=>$this->getVisibleInSearchIds())); return $this; } /** * Add visibility in site filter to collection * * @deprecated * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @return Mage_Catalog_Model_Product_Visibility */ public function addVisibleInSiteFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) { $collection->setVisibility($this->getVisibleInSiteIds()); //$collection->addAttributeToFilter('visibility', array('in'=>$this->getVisibleInSiteIds())); return $this; } /** * Retrieve visible in catalog ids array * * @return array */ public function getVisibleInCatalogIds() { return array(self::VISIBILITY_IN_CATALOG, self::VISIBILITY_BOTH); } /** * Retrieve visible in search ids array * * @return array */ public function getVisibleInSearchIds() { return array(self::VISIBILITY_IN_SEARCH, self::VISIBILITY_BOTH); } /** * Retrieve visible in site ids array * * @return array */ public function getVisibleInSiteIds() { return array(self::VISIBILITY_IN_SEARCH, self::VISIBILITY_IN_CATALOG, self::VISIBILITY_BOTH); } /** * Retrieve option array * * @return array */ static public function getOptionArray() { return array( self::VISIBILITY_NOT_VISIBLE=> Mage::helper('catalog')->__('Not Visible Individually'), self::VISIBILITY_IN_CATALOG => Mage::helper('catalog')->__('Catalog'), self::VISIBILITY_IN_SEARCH => Mage::helper('catalog')->__('Search'), self::VISIBILITY_BOTH => Mage::helper('catalog')->__('Catalog, Search') ); } /** * Retrieve all options * * @return array */ static public function getAllOption() { $options = self::getOptionArray(); array_unshift($options, array('value'=>'', 'label'=>'')); return $options; } /** * Retireve all options * * @return array */ static public function getAllOptions() { $res = array(); $res[] = array('value'=>'', 'label'=> Mage::helper('catalog')->__('-- Please Select --')); foreach (self::getOptionArray() as $index => $value) { $res[] = array( 'value' => $index, 'label' => $value ); } return $res; } /** * Retrieve option text * * @param int $optionId * @return string */ static public function getOptionText($optionId) { $options = self::getOptionArray(); return isset($options[$optionId]) ? $options[$optionId] : null; } /** * Retrieve flat column definition * * @return array */ public function getFlatColums() { $attributeCode = $this->getAttribute()->getAttributeCode(); $column = array( 'unsigned' => true, 'default' => null, 'extra' => null ); if (Mage::helper('core')->useDbCompatibleMode()) { $column['type'] = 'tinyint'; $column['is_null'] = true; } else { $column['type'] = Varien_Db_Ddl_Table::TYPE_SMALLINT; $column['nullable'] = true; $column['comment'] = 'Catalog Product Visibility ' . $attributeCode . ' column'; } return array($attributeCode => $column); } /** * Retrieve Indexes for Flat * * @return array */ public function getFlatIndexes() { return array(); } /** * Retrieve Select For Flat Attribute update * * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @param int $store * @return Varien_Db_Select|null */ public function getFlatUpdateSelect($store) { return Mage::getResourceSingleton('eav/entity_attribute') ->getFlatUpdateSelect($this->getAttribute(), $store); } /** * Set attribute instance * * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return Mage_Eav_Model_Entity_Attribute_Frontend_Abstract */ public function setAttribute($attribute) { $this->_attribute = $attribute; return $this; } /** * Get attribute instance * * @return Mage_Catalog_Model_Resource_Eav_Attribute */ public function getAttribute() { return $this->_attribute; } /** * Add Value Sort To Collection Select * * @param Mage_Eav_Model_Entity_Collection_Abstract $collection * @param string $dir direction * @return Mage_Eav_Model_Entity_Attribute_Source_Abstract */ public function addValueSortToCollection($collection, $dir = 'asc') { $attributeCode = $this->getAttribute()->getAttributeCode(); $attributeId = $this->getAttribute()->getId(); $attributeTable = $this->getAttribute()->getBackend()->getTable(); if ($this->getAttribute()->isScopeGlobal()) { $tableName = $attributeCode . '_t'; $collection->getSelect() ->joinLeft( array($tableName => $attributeTable), "e.entity_id={$tableName}.entity_id" . " AND {$tableName}.attribute_id='{$attributeId}'" . " AND {$tableName}.store_id='0'", array()); $valueExpr = $tableName . '.value'; } else { $valueTable1 = $attributeCode . '_t1'; $valueTable2 = $attributeCode . '_t2'; $collection->getSelect() ->joinLeft( array($valueTable1 => $attributeTable), "e.entity_id={$valueTable1}.entity_id" . " AND {$valueTable1}.attribute_id='{$attributeId}'" . " AND {$valueTable1}.store_id='0'", array()) ->joinLeft( array($valueTable2 => $attributeTable), "e.entity_id={$valueTable2}.entity_id" . " AND {$valueTable2}.attribute_id='{$attributeId}'" . " AND {$valueTable2}.store_id='{$collection->getStoreId()}'", array() ); $valueExpr = $collection->getConnection()->getCheckSql( $valueTable2 . '.value_id > 0', $valueTable2 . '.value', $valueTable1 . '.value' ); } $collection->getSelect()->order($valueExpr . ' ' . $dir); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog entity abstract model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ abstract class Mage_Catalog_Model_Resource_Abstract extends Mage_Eav_Model_Entity_Abstract { /** * Store firstly set attributes to filter selected attributes when used specific store_id * * @var array */ protected $_attributes = array(); /** * Redeclare attribute model * * @return string */ protected function _getDefaultAttributeModel() { return 'catalog/resource_eav_attribute'; } /** * Returns default Store ID * * @return int */ public function getDefaultStoreId() { return Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID; } /** * Check whether the attribute is Applicable to the object * * @param Varien_Object $object * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return boolean */ protected function _isApplicableAttribute($object, $attribute) { $applyTo = $attribute->getApplyTo(); return count($applyTo) == 0 || in_array($object->getTypeId(), $applyTo); } /** * Check whether attribute instance (attribute, backend, frontend or source) has method and applicable * * @param Mage_Eav_Model_Entity_Attribute_Abstract|Mage_Eav_Model_Entity_Attribute_Backend_Abstract|Mage_Eav_Model_Entity_Attribute_Frontend_Abstract|Mage_Eav_Model_Entity_Attribute_Source_Abstract $instance * @param string $method * @param array $args array of arguments * @return boolean */ protected function _isCallableAttributeInstance($instance, $method, $args) { if ($instance instanceof Mage_Eav_Model_Entity_Attribute_Backend_Abstract && ($method == 'beforeSave' || $method = 'afterSave') ) { $attributeCode = $instance->getAttribute()->getAttributeCode(); if (isset($args[0]) && $args[0] instanceof Varien_Object && $args[0]->getData($attributeCode) === false) { return false; } } return parent::_isCallableAttributeInstance($instance, $method, $args); } /** * Retrieve select object for loading entity attributes values * Join attribute store value * * @param Varien_Object $object * @param string $table * @return Varien_Db_Select */ protected function _getLoadAttributesSelect($object, $table) { /** * This condition is applicable for all cases when we was work in not single * store mode, customize some value per specific store view and than back * to single store mode. We should load correct values */ if (Mage::app()->isSingleStoreMode()) { $storeId = (int)Mage::app()->getStore(true)->getId(); } else { $storeId = (int)$object->getStoreId(); } $setId = $object->getAttributeSetId(); $storeIds = array($this->getDefaultStoreId()); if ($storeId != $this->getDefaultStoreId()) { $storeIds[] = $storeId; } $select = $this->_getReadAdapter()->select() ->from(array('attr_table' => $table), array()) ->where("attr_table.{$this->getEntityIdField()} = ?", $object->getId()) ->where('attr_table.store_id IN (?)', $storeIds); if ($setId) { $select->join( array('set_table' => $this->getTable('eav/entity_attribute')), $this->_getReadAdapter()->quoteInto('attr_table.attribute_id = set_table.attribute_id' . ' AND set_table.attribute_set_id = ?', $setId), array() ); } return $select; } /** * Adds Columns prepared for union * * @param Varien_Db_Select $select * @param string $table * @param string $type * @return Varien_Db_Select */ protected function _addLoadAttributesSelectFields($select, $table, $type) { $select->columns( Mage::getResourceHelper('catalog')->attributeSelectFields('attr_table', $type) ); return $select; } /** * Prepare select object for loading entity attributes values * * @param array $selects * @return Varien_Db_Select */ protected function _prepareLoadSelect(array $selects) { $select = parent::_prepareLoadSelect($selects); $select->order('store_id'); return $select; } /** * Initialize attribute value for object * * @param Mage_Catalog_Model_Abstract $object * @param array $valueRow * @return Mage_Catalog_Model_Resource_Abstract */ protected function _setAttributeValue($object, $valueRow) { $attribute = $this->getAttribute($valueRow['attribute_id']); if ($attribute) { $attributeCode = $attribute->getAttributeCode(); $isDefaultStore = $valueRow['store_id'] == $this->getDefaultStoreId(); if (isset($this->_attributes[$valueRow['attribute_id']])) { if ($isDefaultStore) { $object->setAttributeDefaultValue($attributeCode, $valueRow['value']); } else { $object->setAttributeDefaultValue( $attributeCode, $this->_attributes[$valueRow['attribute_id']]['value'] ); } } else { $this->_attributes[$valueRow['attribute_id']] = $valueRow; } $value = $valueRow['value']; $valueId = $valueRow['value_id']; $object->setData($attributeCode, $value); if (!$isDefaultStore) { $object->setExistsStoreValueFlag($attributeCode); } $attribute->getBackend()->setEntityValueId($object, $valueId); } return $this; } /** * Insert or Update attribute data * * @param Mage_Catalog_Model_Abstract $object * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param mixed $value * @return Mage_Catalog_Model_Resource_Abstract */ protected function _saveAttributeValue($object, $attribute, $value) { $write = $this->_getWriteAdapter(); $storeId = (int)Mage::app()->getStore($object->getStoreId())->getId(); $table = $attribute->getBackend()->getTable(); /** * If we work in single store mode all values should be saved just * for default store id * In this case we clear all not default values */ if (Mage::app()->isSingleStoreMode()) { $storeId = $this->getDefaultStoreId(); $write->delete($table, array( 'attribute_id = ?' => $attribute->getAttributeId(), 'entity_id = ?' => $object->getEntityId(), 'store_id <> ?' => $storeId )); } $data = new Varien_Object(array( 'entity_type_id' => $attribute->getEntityTypeId(), 'attribute_id' => $attribute->getAttributeId(), 'store_id' => $storeId, 'entity_id' => $object->getEntityId(), 'value' => $this->_prepareValueForSave($value, $attribute) )); $bind = $this->_prepareDataForTable($data, $table); if ($attribute->isScopeStore()) { /** * Update attribute value for store */ $this->_attributeValuesToSave[$table][] = $bind; } else if ($attribute->isScopeWebsite() && $storeId != $this->getDefaultStoreId()) { /** * Update attribute value for website */ $storeIds = Mage::app()->getStore($storeId)->getWebsite()->getStoreIds(true); foreach ($storeIds as $storeId) { $bind['store_id'] = (int)$storeId; $this->_attributeValuesToSave[$table][] = $bind; } } else { /** * Update global attribute value */ $bind['store_id'] = $this->getDefaultStoreId(); $this->_attributeValuesToSave[$table][] = $bind; } return $this; } /** * Insert entity attribute value * * @param Varien_Object $object * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param mixed $value * @return Mage_Catalog_Model_Resource_Abstract */ protected function _insertAttribute($object, $attribute, $value) { /** * save required attributes in global scope every time if store id different from default */ $storeId = (int)Mage::app()->getStore($object->getStoreId())->getId(); if ($attribute->getIsRequired() && $this->getDefaultStoreId() != $storeId) { $table = $attribute->getBackend()->getTable(); $select = $this->_getReadAdapter()->select() ->from($table) ->where('entity_type_id = ?', $attribute->getEntityTypeId()) ->where('attribute_id = ?', $attribute->getAttributeId()) ->where('store_id = ?', $this->getDefaultStoreId()) ->where('entity_id = ?', $object->getEntityId()); $row = $this->_getReadAdapter()->fetchOne($select); if (!$row) { $data = new Varien_Object(array( 'entity_type_id' => $attribute->getEntityTypeId(), 'attribute_id' => $attribute->getAttributeId(), 'store_id' => $this->getDefaultStoreId(), 'entity_id' => $object->getEntityId(), 'value' => $this->_prepareValueForSave($value, $attribute) )); $bind = $this->_prepareDataForTable($data, $table); $this->_getWriteAdapter()->insertOnDuplicate($table, $bind, array('value')); } } return $this->_saveAttributeValue($object, $attribute, $value); } /** * Update entity attribute value * * @deprecated after 1.5.1.0 * @see Mage_Catalog_Model_Resource_Abstract::_saveAttributeValue() * * @param Varien_Object $object * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param mixed $valueId * @param mixed $value * @return Mage_Catalog_Model_Resource_Abstract */ protected function _updateAttribute($object, $attribute, $valueId, $value) { return $this->_saveAttributeValue($object, $attribute, $value); } /** * Update attribute value for specific store * * @param Mage_Catalog_Model_Abstract $object * @param object $attribute * @param mixed $value * @param int $storeId * @return Mage_Catalog_Model_Resource_Abstract */ protected function _updateAttributeForStore($object, $attribute, $value, $storeId) { $adapter = $this->_getWriteAdapter(); $table = $attribute->getBackend()->getTable(); $entityIdField = $attribute->getBackend()->getEntityIdField(); $select = $adapter->select() ->from($table, 'value_id') ->where('entity_type_id = :entity_type_id') ->where("$entityIdField = :entity_field_id") ->where('store_id = :store_id') ->where('attribute_id = :attribute_id'); $bind = array( 'entity_type_id' => $object->getEntityTypeId(), 'entity_field_id' => $object->getId(), 'store_id' => $storeId, 'attribute_id' => $attribute->getId() ); $valueId = $adapter->fetchOne($select, $bind); /** * When value for store exist */ if ($valueId) { $bind = array('value' => $this->_prepareValueForSave($value, $attribute)); $where = array('value_id = ?' => (int)$valueId); $adapter->update($table, $bind, $where); } else { $bind = array( $idField => (int)$object->getId(), 'entity_type_id' => (int)$object->getEntityTypeId(), 'attribute_id' => (int)$attribute->getId(), 'value' => $this->_prepareValueForSave($value, $attribute), 'store_id' => (int)$storeId ); $adapter->insert($table, $bind); } return $this; } /** * Delete entity attribute values * * @param Varien_Object $object * @param string $table * @param array $info * @return Mage_Catalog_Model_Resource_Abstract */ protected function _deleteAttributes($object, $table, $info) { $adapter = $this->_getWriteAdapter(); $entityIdField = $this->getEntityIdField(); $globalValues = array(); $websiteAttributes = array(); $storeAttributes = array(); /** * Separate attributes by scope */ foreach ($info as $itemData) { $attribute = $this->getAttribute($itemData['attribute_id']); if ($attribute->isScopeStore()) { $storeAttributes[] = (int)$itemData['attribute_id']; } elseif ($attribute->isScopeWebsite()) { $websiteAttributes[] = (int)$itemData['attribute_id']; } else { $globalValues[] = (int)$itemData['value_id']; } } /** * Delete global scope attributes */ if (!empty($globalValues)) { $adapter->delete($table, array('value_id IN (?)' => $globalValues)); } $condition = array( $entityIdField . ' = ?' => $object->getId(), 'entity_type_id = ?' => $object->getEntityTypeId() ); /** * Delete website scope attributes */ if (!empty($websiteAttributes)) { $storeIds = $object->getWebsiteStoreIds(); if (!empty($storeIds)) { $delCondition = $condition; $delCondition['attribute_id IN(?)'] = $websiteAttributes; $delCondition['store_id IN(?)'] = $storeIds; $adapter->delete($table, $delCondition); } } /** * Delete store scope attributes */ if (!empty($storeAttributes)) { $delCondition = $condition; $delCondition['attribute_id IN(?)'] = $storeAttributes; $delCondition['store_id = ?'] = (int)$object->getStoreId(); $adapter->delete($table, $delCondition); } return $this; } /** * Retrieve Object instance with original data * * @param Varien_Object $object * @return Varien_Object */ protected function _getOrigObject($object) { $className = get_class($object); $origObject = new $className(); $origObject->setData(array()); $origObject->setStoreId($object->getStoreId()); $this->load($origObject, $object->getData($this->getEntityIdField())); return $origObject; } /** * Collect original data * * @deprecated after 1.5.1.0 * * @param Varien_Object $object * @return array */ protected function _collectOrigData($object) { $this->loadAllAttributes($object); if ($this->getUseDataSharing()) { $storeId = $object->getStoreId(); } else { $storeId = $this->getStoreId(); } $data = array(); foreach ($this->getAttributesByTable() as $table=>$attributes) { $select = $this->_getReadAdapter()->select() ->from($table) ->where($this->getEntityIdField() . '=?', $object->getId()); $where = $this->_getReadAdapter()->quoteInto('store_id=?', $storeId); $globalAttributeIds = array(); foreach ($attributes as $attr) { if ($attr->getIsGlobal()) { $globalAttributeIds[] = $attr->getId(); } } if (!empty($globalAttributeIds)) { $where .= ' or '.$this->_getReadAdapter()->quoteInto('attribute_id in (?)', $globalAttributeIds); } $select->where($where); $values = $this->_getReadAdapter()->fetchAll($select); if (empty($values)) { continue; } foreach ($values as $row) { $data[$this->getAttribute($row['attribute_id'])->getName()][$row['store_id']] = $row; } } return $data; } /** * Check is attribute value empty * * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param mixed $value * @return bool */ protected function _isAttributeValueEmpty(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $value) { return $value === false; } /** * Return if attribute exists in original data array. * Checks also attribute's store scope: * We should insert on duplicate key update values if we unchecked 'STORE VIEW' checkbox in store view. * * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param mixed $value New value of the attribute. * @param array $origData * @return bool */ protected function _canUpdateAttribute( Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $value, array &$origData) { $result = parent::_canUpdateAttribute($attribute, $value, $origData); if ($result && ($attribute->isScopeStore() || $attribute->isScopeWebsite()) && !$this->_isAttributeValueEmpty($attribute, $value) && $value == $origData[$attribute->getAttributeCode()] && isset($origData['store_id']) && $origData['store_id'] != $this->getDefaultStoreId() ) { return false; } return $result; } /** * Prepare value for save * * @param mixed $value * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @return mixed */ protected function _prepareValueForSave($value, Mage_Eav_Model_Entity_Attribute_Abstract $attribute) { $type = $attribute->getBackendType(); if (($type == 'int' || $type == 'decimal' || $type == 'datetime') && $value === '') { $value = null; } return parent::_prepareValueForSave($value, $attribute); } /** * Retrieve attribute's raw value from DB. * * @param int $entityId * @param int|string|array $attribute atrribute's ids or codes * @param int|Mage_Core_Model_Store $store * @return bool|string|array */ public function getAttributeRawValue($entityId, $attribute, $store) { if (!$entityId || empty($attribute)) { return false; } if (!is_array($attribute)) { $attribute = array($attribute); } $attributesData = array(); $staticAttributes = array(); $typedAttributes = array(); $staticTable = null; $adapter = $this->_getReadAdapter(); foreach ($attribute as $_attribute) { /* @var $attribute Mage_Catalog_Model_Entity_Attribute */ $_attribute = $this->getAttribute($_attribute); if (!$_attribute) { continue; } $attributeCode = $_attribute->getAttributeCode(); $attrTable = $_attribute->getBackend()->getTable(); $isStatic = $_attribute->getBackend()->isStatic(); if ($isStatic) { $staticAttributes[] = $attributeCode; $staticTable = $attrTable; } else { /** * That structure needed to avoid farther sql joins for getting attribute's code by id */ $typedAttributes[$attrTable][$_attribute->getId()] = $attributeCode; } } /** * Collecting static attributes */ if ($staticAttributes) { $select = $adapter->select()->from($staticTable, $staticAttributes) ->where($this->getEntityIdField() . ' = :entity_id'); $attributesData = $adapter->fetchRow($select, array('entity_id' => $entityId)); } /** * Collecting typed attributes, performing separate SQL query for each attribute type table */ if ($store instanceof Mage_Core_Model_Store) { $store = $store->getId(); } $store = (int)$store; if ($typedAttributes) { foreach ($typedAttributes as $table => $_attributes) { $select = $adapter->select() ->from(array('default_value' => $table), array('attribute_id')) ->where('default_value.attribute_id IN (?)', array_keys($_attributes)) ->where('default_value.entity_type_id = :entity_type_id') ->where('default_value.entity_id = :entity_id') ->where('default_value.store_id = ?', 0); $bind = array( 'entity_type_id' => $this->getTypeId(), 'entity_id' => $entityId, ); if ($store != $this->getDefaultStoreId()) { $valueExpr = $adapter->getCheckSql('store_value.value IS NULL', 'default_value.value', 'store_value.value'); $joinCondition = array( $adapter->quoteInto('store_value.attribute_id IN (?)', array_keys($_attributes)), 'store_value.entity_type_id = :entity_type_id', 'store_value.entity_id = :entity_id', 'store_value.store_id = :store_id', ); $select->joinLeft( array('store_value' => $table), implode(' AND ', $joinCondition), array('attr_value' => $valueExpr) ); $bind['store_id'] = $store; } else { $select->columns(array('attr_value' => 'value'), 'default_value'); } $result = $adapter->fetchPairs($select, $bind); foreach ($result as $attrId => $value) { $attrCode = $typedAttributes[$table][$attrId]; $attributesData[$attrCode] = $value; } } } if (sizeof($attributesData) == 1) { $_data = each($attributesData); $attributesData = $_data[1]; } return $attributesData ? $attributesData : false; } /** * Reset firstly loaded attributes * * @param Varien_Object $object * @param integer $entityId * @param array|null $attributes * @return Mage_Catalog_Model_Resource_Abstract */ public function load($object, $entityId, $attributes = array()) { $this->_attributes = array(); return parent::load($object, $entityId, $attributes); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Eav * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Entity/Attribute/Model - collection abstract * * @category Mage * @package Mage_Eav * @author Magento Core Team */ abstract class Mage_Eav_Model_Entity_Collection_Abstract extends Varien_Data_Collection_Db { /** * Array of items with item id key * * @var array */ protected $_itemsById = array(); /** * Entity static fields * * @var array */ protected $_staticFields = array(); /** * Entity object to define collection's attributes * * @var Mage_Eav_Model_Entity_Abstract */ protected $_entity; /** * Entity types to be fetched for objects in collection * * @var array */ protected $_selectEntityTypes = array(); /** * Attributes to be fetched for objects in collection * * @var array */ protected $_selectAttributes = array(); /** * Attributes to be filtered order sorted by * * @var array */ protected $_filterAttributes = array(); /** * Joined entities * * @var array */ protected $_joinEntities = array(); /** * Joined attributes * * @var array */ protected $_joinAttributes = array(); /** * Joined fields data * * @var array */ protected $_joinFields = array(); /** * Use analytic function flag * If true - allows to prepare final select with analytic functions * * @var bool */ protected $_useAnalyticFunction = false; /** * Cast map for attribute order * * @var array */ protected $_castToIntMap = array( 'validate-digits' ); /** * Collection constructor * * @param Mage_Core_Model_Resource_Abstract $resource */ public function __construct($resource = null) { parent::__construct(); $this->_construct(); $this->setConnection($this->getEntity()->getReadConnection()); $this->_prepareStaticFields(); $this->_initSelect(); } /** * Initialize collection */ protected function _construct() { } /** * Retreive table name * * @param string $table * @return string */ public function getTable($table) { return $this->getResource()->getTable($table); } /** * Prepare static entity fields * * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _prepareStaticFields() { foreach ($this->getEntity()->getDefaultAttributes() as $field) { $this->_staticFields[$field] = $field; } return $this; } /** * Init select * * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _initSelect() { $this->getSelect()->from(array('e' => $this->getEntity()->getEntityTable())); if ($this->getEntity()->getTypeId()) { $this->addAttributeToFilter('entity_type_id', $this->getEntity()->getTypeId()); } return $this; } /** * Standard resource collection initalization * * @param string $model * @return Mage_Core_Model_Mysql4_Collection_Abstract */ protected function _init($model, $entityModel = null) { $this->setItemObjectClass(Mage::getConfig()->getModelClassName($model)); if ($entityModel === null) { $entityModel = $model; } $entity = Mage::getResourceSingleton($entityModel); $this->setEntity($entity); return $this; } /** * Set entity to use for attributes * * @param Mage_Eav_Model_Entity_Abstract $entity * @throws Mage_Eav_Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function setEntity($entity) { if ($entity instanceof Mage_Eav_Model_Entity_Abstract) { $this->_entity = $entity; } elseif (is_string($entity) || $entity instanceof Mage_Core_Model_Config_Element) { $this->_entity = Mage::getModel('eav/entity')->setType($entity); } else { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid entity supplied: %s', print_r($entity, 1))); } return $this; } /** * Get collection's entity object * * @return Mage_Eav_Model_Entity_Abstract */ public function getEntity() { if (empty($this->_entity)) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Entity is not initialized')); } return $this->_entity; } /** * Get resource instance * * @return Mage_Core_Model_Mysql4_Abstract */ public function getResource() { return $this->getEntity(); } /** * Set template object for the collection * * @param Varien_Object $object * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function setObject($object=null) { if (is_object($object)) { $this->setItemObjectClass(get_class($object)); } else { $this->setItemObjectClass($object); } return $this; } /** * Add an object to the collection * * @param Varien_Object $object * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function addItem(Varien_Object $object) { if (get_class($object) !== $this->_itemObjectClass) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Attempt to add an invalid object')); } return parent::addItem($object); } /** * Retrieve entity attribute * * @param string $attributeCode * @return Mage_Eav_Model_Entity_Attribute_Abstract */ public function getAttribute($attributeCode) { if (isset($this->_joinAttributes[$attributeCode])) { return $this->_joinAttributes[$attributeCode]['attribute']; } return $this->getEntity()->getAttribute($attributeCode); } /** * Add attribute filter to collection * * If $attribute is an array will add OR condition with following format: * array( * array('attribute'=>'firstname', 'like'=>'test%'), * array('attribute'=>'lastname', 'like'=>'test%'), * ) * * @see self::_getConditionSql for $condition * @param Mage_Eav_Model_Entity_Attribute_Interface|integer|string|array $attribute * @param null|string|array $condition * @param string $operator * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner') { if ($attribute === null) { $this->getSelect(); return $this; } if (is_numeric($attribute)) { $attribute = $this->getEntity()->getAttribute($attribute)->getAttributeCode(); } else if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Interface) { $attribute = $attribute->getAttributeCode(); } if (is_array($attribute)) { $sqlArr = array(); foreach ($attribute as $condition) { $sqlArr[] = $this->_getAttributeConditionSql($condition['attribute'], $condition, $joinType); } $conditionSql = '('.implode(') OR (', $sqlArr).')'; } else if (is_string($attribute)) { if ($condition === null) { $condition = ''; } $conditionSql = $this->_getAttributeConditionSql($attribute, $condition, $joinType); } if (!empty($conditionSql)) { $this->getSelect()->where($conditionSql, null, Varien_Db_Select::TYPE_CONDITION); } else { Mage::throwException('Invalid attribute identifier for filter ('.get_class($attribute).')'); } return $this; } /** * Wrapper for compatibility with Varien_Data_Collection_Db * * @param mixed $attribute * @param mixed $condition */ public function addFieldToFilter($attribute, $condition = null) { return $this->addAttributeToFilter($attribute, $condition); } /** * Add attribute to sort order * * @param string $attribute * @param string $dir * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) { if (isset($this->_joinFields[$attribute])) { $this->getSelect()->order($this->_getAttributeFieldName($attribute).' '.$dir); return $this; } if (isset($this->_staticFields[$attribute])) { $this->getSelect()->order("e.{$attribute} {$dir}"); return $this; } if (isset($this->_joinAttributes[$attribute])) { $attrInstance = $this->_joinAttributes[$attribute]['attribute']; $entityField = $this->_getAttributeTableAlias($attribute) . '.' . $attrInstance->getAttributeCode(); } else { $attrInstance = $this->getEntity()->getAttribute($attribute); $entityField = 'e.' . $attribute; } if ($attrInstance) { if ($attrInstance->getBackend()->isStatic()) { $orderExpr = $entityField; } else { $this->_addAttributeJoin($attribute, 'left'); if (isset($this->_joinAttributes[$attribute])||isset($this->_joinFields[$attribute])) { $orderExpr = $attribute; } else { $orderExpr = $this->_getAttributeTableAlias($attribute).'.value'; } } if (in_array($attrInstance->getFrontendClass(), $this->_castToIntMap)) { $orderExpr = Mage::getResourceHelper('eav')->getCastToIntExpression( $this->_prepareOrderExpression($orderExpr) ); } $orderExpr .= ' ' . $dir; $this->getSelect()->order($orderExpr); } return $this; } /** * Retrieve attribute expression by specified column * * @param string $field * @return string|Zend_Db_Expr */ protected function _prepareOrderExpression($field) { foreach ($this->getSelect()->getPart(Zend_Db_Select::COLUMNS) as $columnEntry) { if ($columnEntry[2] != $field) { continue; } if ($columnEntry[1] instanceof Zend_Db_Expr) { return $columnEntry[1]; } } return $field; } /** * Add attribute to entities in collection * * If $attribute=='*' select all attributes * * @param array|string|integer|Mage_Core_Model_Config_Element $attribute * @param false|string $joinType flag for joining attribute * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function addAttributeToSelect($attribute, $joinType = false) { if (is_array($attribute)) { Mage::getSingleton('eav/config')->loadCollectionAttributes($this->getEntity()->getType(), $attribute); foreach ($attribute as $a) { $this->addAttributeToSelect($a, $joinType); } return $this; } if ($joinType !== false && !$this->getEntity()->getAttribute($attribute)->isStatic()) { $this->_addAttributeJoin($attribute, $joinType); } elseif ('*' === $attribute) { $entity = clone $this->getEntity(); $attributes = $entity ->loadAllAttributes() ->getAttributesByCode(); foreach ($attributes as $attrCode=>$attr) { $this->_selectAttributes[$attrCode] = $attr->getId(); } } else { if (isset($this->_joinAttributes[$attribute])) { $attrInstance = $this->_joinAttributes[$attribute]['attribute']; } else { $attrInstance = Mage::getSingleton('eav/config') ->getCollectionAttribute($this->getEntity()->getType(), $attribute); } if (empty($attrInstance)) { throw Mage::exception( 'Mage_Eav', Mage::helper('eav')->__('Invalid attribute requested: %s', (string)$attribute) ); } $this->_selectAttributes[$attrInstance->getAttributeCode()] = $attrInstance->getId(); } return $this; } public function addEntityTypeToSelect($entityType, $prefix) { $this->_selectEntityTypes[$entityType] = array( 'prefix' => $prefix, ); return $this; } /** * Add field to static * * @param string $field * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function addStaticField($field) { if (!isset($this->_staticFields[$field])) { $this->_staticFields[$field] = $field; } return $this; } /** * Add attribute expression (SUM, COUNT, etc) * * Example: ('sub_total', 'SUM({{attribute}})', 'revenue') * Example: ('sub_total', 'SUM({{revenue}})', 'revenue') * * For some functions like SUM use groupByAttribute. * * @param string $alias * @param string $expression * @param string $attribute * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function addExpressionAttributeToSelect($alias, $expression, $attribute) { // validate alias if (isset($this->_joinFields[$alias])) { throw Mage::exception( 'Mage_Eav', Mage::helper('eav')->__('Joint field or attribute expression with this alias is already declared') ); } if (!is_array($attribute)) { $attribute = array($attribute); } $fullExpression = $expression; // Replacing multiple attributes foreach ($attribute as $attributeItem) { if (isset($this->_staticFields[$attributeItem])) { $attrField = sprintf('e.%s', $attributeItem); } else { $attributeInstance = $this->getAttribute($attributeItem); if ($attributeInstance->getBackend()->isStatic()) { $attrField = 'e.' . $attributeItem; } else { $this->_addAttributeJoin($attributeItem, 'left'); $attrField = $this->_getAttributeFieldName($attributeItem); } } $fullExpression = str_replace('{{attribute}}', $attrField, $fullExpression); $fullExpression = str_replace('{{' . $attributeItem . '}}', $attrField, $fullExpression); } $this->getSelect()->columns(array($alias => $fullExpression)); $this->_joinFields[$alias] = array( 'table' => false, 'field' => $fullExpression ); return $this; } /** * Groups results by specified attribute * * @param string|array $attribute */ public function groupByAttribute($attribute) { if (is_array($attribute)) { foreach ($attribute as $attributeItem) { $this->groupByAttribute($attributeItem); } } else { if (isset($this->_joinFields[$attribute])) { $this->getSelect()->group($this->_getAttributeFieldName($attribute)); return $this; } if (isset($this->_staticFields[$attribute])) { $this->getSelect()->group(sprintf('e.%s', $attribute)); return $this; } if (isset($this->_joinAttributes[$attribute])) { $attrInstance = $this->_joinAttributes[$attribute]['attribute']; $entityField = $this->_getAttributeTableAlias($attribute) . '.' . $attrInstance->getAttributeCode(); } else { $attrInstance = $this->getEntity()->getAttribute($attribute); $entityField = 'e.' . $attribute; } if ($attrInstance->getBackend()->isStatic()) { $this->getSelect()->group($entityField); } else { $this->_addAttributeJoin($attribute); $this->getSelect()->group($this->_getAttributeTableAlias($attribute).'.value'); } } return $this; } /** * Add attribute from joined entity to select * * Examples: * ('billing_firstname', 'customer_address/firstname', 'default_billing') * ('billing_lastname', 'customer_address/lastname', 'default_billing') * ('shipping_lastname', 'customer_address/lastname', 'default_billing') * ('shipping_postalcode', 'customer_address/postalcode', 'default_shipping') * ('shipping_city', $cityAttribute, 'default_shipping') * * Developer is encouraged to use existing instances of attributes and entities * After first use of string entity name it will be cached in the collection * * @todo connect between joined attributes of same entity * @param string $alias alias for the joined attribute * @param string|Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param string $bind attribute of the main entity to link with joined $filter * @param string $filter primary key for the joined entity (entity_id default) * @param string $joinType inner|left * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType='inner', $storeId=null) { // validate alias if (isset($this->_joinAttributes[$alias])) { throw Mage::exception( 'Mage_Eav', Mage::helper('eav')->__('Invalid alias, already exists in joint attributes') ); } // validate bind attribute if (is_string($bind)) { $bindAttribute = $this->getAttribute($bind); } if (!$bindAttribute || (!$bindAttribute->isStatic() && !$bindAttribute->getId())) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid foreign key')); } // try to explode combined entity/attribute if supplied if (is_string($attribute)) { $attrArr = explode('/', $attribute); if (isset($attrArr[1])) { $entity = $attrArr[0]; $attribute = $attrArr[1]; } } // validate entity if (empty($entity) && $attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { $entity = $attribute->getEntity(); } elseif (is_string($entity)) { // retrieve cached entity if possible if (isset($this->_joinEntities[$entity])) { $entity = $this->_joinEntities[$entity]; } else { $entity = Mage::getModel('eav/entity')->setType($attrArr[0]); } } if (!$entity || !$entity->getTypeId()) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid entity type')); } // cache entity if (!isset($this->_joinEntities[$entity->getType()])) { $this->_joinEntities[$entity->getType()] = $entity; } // validate attribute if (is_string($attribute)) { $attribute = $entity->getAttribute($attribute); } if (!$attribute) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute type')); } if (empty($filter)) { $filter = $entity->getEntityIdField(); } // add joined attribute $this->_joinAttributes[$alias] = array( 'bind' => $bind, 'bindAttribute' => $bindAttribute, 'attribute' => $attribute, 'filter' => $filter, 'store_id' => $storeId, ); $this->_addAttributeJoin($alias, $joinType); return $this; } /** * Join regular table field and use an attribute as fk * * Examples: * ('country_name', 'directory/country_name', 'name', 'country_id=shipping_country', * "{{table}}.language_code='en'", 'left') * * @param string $alias 'country_name' * @param string $table 'directory/country_name' * @param string $field 'name' * @param string $bind 'PK(country_id)=FK(shipping_country_id)' * @param string|array $cond "{{table}}.language_code='en'" OR array('language_code'=>'en') * @param string $joinType 'left' * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function joinField($alias, $table, $field, $bind, $cond=null, $joinType='inner') { // validate alias if (isset($this->_joinFields[$alias])) { throw Mage::exception( 'Mage_Eav', Mage::helper('eav')->__('Joined field with this alias is already declared') ); } // validate table if (strpos($table, '/')!==false) { $table = Mage::getSingleton('core/resource')->getTableName($table); } $tableAlias = $this->_getAttributeTableAlias($alias); // validate bind list($pk, $fk) = explode('=', $bind); $pk = $this->getSelect()->getAdapter()->quoteColumnAs(trim($pk), null); $bindCond = $tableAlias . '.' . trim($pk) . '=' . $this->_getAttributeFieldName(trim($fk)); // process join type switch ($joinType) { case 'left': $joinMethod = 'joinLeft'; break; default: $joinMethod = 'join'; } $condArr = array($bindCond); // add where condition if needed if ($cond !== null) { if (is_array($cond)) { foreach ($cond as $k=>$v) { $condArr[] = $this->_getConditionSql($tableAlias.'.'.$k, $v); } } else { $condArr[] = str_replace('{{table}}', $tableAlias, $cond); } } $cond = '(' . implode(') AND (', $condArr) . ')'; // join table $this->getSelect() ->$joinMethod(array($tableAlias => $table), $cond, ($field ? array($alias=>$field) : array())); // save joined attribute $this->_joinFields[$alias] = array( 'table' => $tableAlias, 'field' => $field, ); return $this; } /** * Join a table * * @param string|array $table * @param string $bind * @param string|array $fields * @param null|array $cond * @param string $joinType * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function joinTable($table, $bind, $fields = null, $cond = null, $joinType = 'inner') { $tableAlias = null; if (is_array($table)) { list($tableAlias, $tableName) = each($table); } else { $tableName = $table; } // validate table if (strpos($tableName, '/') !== false) { $tableName = Mage::getSingleton('core/resource')->getTableName($tableName); } if (empty($tableAlias)) { $tableAlias = $tableName; } // validate fields and aliases if (!$fields) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid joint fields')); } foreach ($fields as $alias=>$field) { if (isset($this->_joinFields[$alias])) { throw Mage::exception( 'Mage_Eav', Mage::helper('eav')->__('A joint field with this alias (%s) is already declared', $alias) ); } $this->_joinFields[$alias] = array( 'table' => $tableAlias, 'field' => $field, ); } // validate bind list($pk, $fk) = explode('=', $bind); $bindCond = $tableAlias . '.' . $pk . '=' . $this->_getAttributeFieldName($fk); // process join type switch ($joinType) { case 'left': $joinMethod = 'joinLeft'; break; default: $joinMethod = 'join'; } $condArr = array($bindCond); // add where condition if needed if ($cond !== null) { if (is_array($cond)) { foreach ($cond as $k => $v) { $condArr[] = $this->_getConditionSql($tableAlias.'.'.$k, $v); } } else { $condArr[] = str_replace('{{table}}', $tableAlias, $cond); } } $cond = '('.implode(') AND (', $condArr).')'; // join table $this->getSelect()->$joinMethod(array($tableAlias => $tableName), $cond, $fields); return $this; } /** * Remove an attribute from selection list * * @param string $attribute * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function removeAttributeToSelect($attribute = null) { if ($attribute === null) { $this->_selectAttributes = array(); } else { unset($this->_selectAttributes[$attribute]); } return $this; } /** * Set collection page start and records to show * * @param integer $pageNum * @param integer $pageSize * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function setPage($pageNum, $pageSize) { $this->setCurPage($pageNum) ->setPageSize($pageSize); return $this; } /** * Load collection data into object items * * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function load($printQuery = false, $logQuery = false) { if ($this->isLoaded()) { return $this; } Varien_Profiler::start('__EAV_COLLECTION_BEFORE_LOAD__'); Mage::dispatchEvent('eav_collection_abstract_load_before', array('collection' => $this)); $this->_beforeLoad(); Varien_Profiler::stop('__EAV_COLLECTION_BEFORE_LOAD__'); $this->_renderFilters(); $this->_renderOrders(); Varien_Profiler::start('__EAV_COLLECTION_LOAD_ENT__'); $this->_loadEntities($printQuery, $logQuery); Varien_Profiler::stop('__EAV_COLLECTION_LOAD_ENT__'); Varien_Profiler::start('__EAV_COLLECTION_LOAD_ATTR__'); $this->_loadAttributes($printQuery, $logQuery); Varien_Profiler::stop('__EAV_COLLECTION_LOAD_ATTR__'); Varien_Profiler::start('__EAV_COLLECTION_ORIG_DATA__'); foreach ($this->_items as $item) { $item->setOrigData(); } Varien_Profiler::stop('__EAV_COLLECTION_ORIG_DATA__'); $this->_setIsLoaded(); Varien_Profiler::start('__EAV_COLLECTION_AFTER_LOAD__'); $this->_afterLoad(); Varien_Profiler::stop('__EAV_COLLECTION_AFTER_LOAD__'); return $this; } /** * Clone and reset collection * * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _getAllIdsSelect($limit = null, $offset = null) { $idsSelect = clone $this->getSelect(); $idsSelect->reset(Zend_Db_Select::ORDER); $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT); $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET); $idsSelect->reset(Zend_Db_Select::COLUMNS); $idsSelect->columns('e.' . $this->getEntity()->getIdFieldName()); $idsSelect->limit($limit, $offset); return $idsSelect; } /** * Retrive all ids for collection * * @return array */ public function getAllIds($limit = null, $offset = null) { return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); } /** * Retrive all ids sql * * @return array */ public function getAllIdsSql() { $idsSelect = clone $this->getSelect(); $idsSelect->reset(Zend_Db_Select::ORDER); $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT); $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET); $idsSelect->reset(Zend_Db_Select::COLUMNS); $idsSelect->reset(Zend_Db_Select::GROUP); $idsSelect->columns('e.'.$this->getEntity()->getIdFieldName()); return $idsSelect; } /** * Save all the entities in the collection * * @todo make batch save directly from collection */ public function save() { foreach ($this->getItems() as $item) { $item->save(); } return $this; } /** * Delete all the entities in the collection * * @todo make batch delete directly from collection */ public function delete() { foreach ($this->getItems() as $k=>$item) { $this->getEntity()->delete($item); unset($this->_items[$k]); } return $this; } /** * Import 2D array into collection as objects * * If the imported items already exist, update the data for existing objects * * @param array $arr * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function importFromArray($arr) { $entityIdField = $this->getEntity()->getEntityIdField(); foreach ($arr as $row) { $entityId = $row[$entityIdField]; if (!isset($this->_items[$entityId])) { $this->_items[$entityId] = $this->getNewEmptyItem(); $this->_items[$entityId]->setData($row); } else { $this->_items[$entityId]->addData($row); } } return $this; } /** * Get collection data as a 2D array * * @return array */ public function exportToArray() { $result = array(); $entityIdField = $this->getEntity()->getEntityIdField(); foreach ($this->getItems() as $item) { $result[$item->getData($entityIdField)] = $item->getData(); } return $result; } /** * Retreive row id field name * * @return string */ public function getRowIdFieldName() { if ($this->_idFieldName === null) { $this->_setIdFieldName($this->getEntity()->getIdFieldName()); } return $this->getIdFieldName(); } /** * Set row id field name * @param string $fieldName * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function setRowIdFieldName($fieldName) { return $this->_setIdFieldName($fieldName); } /** * Load entities records into items * * @throws Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function _loadEntities($printQuery = false, $logQuery = false) { $entity = $this->getEntity(); if ($this->_pageSize) { $this->getSelect()->limitPage($this->getCurPage(), $this->_pageSize); } $this->printLogQuery($printQuery, $logQuery); try { /** * Prepare select query * @var string $query */ $query = $this->_prepareSelect($this->getSelect()); $rows = $this->_fetchAll($query); } catch (Exception $e) { Mage::printException($e, $query); $this->printLogQuery(true, true, $query); throw $e; } foreach ($rows as $v) { $object = $this->getNewEmptyItem() ->setData($v); $this->addItem($object); if (isset($this->_itemsById[$object->getId()])) { $this->_itemsById[$object->getId()][] = $object; } else { $this->_itemsById[$object->getId()] = array($object); } } return $this; } /** * Load attributes into loaded entities * * @throws Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function _loadAttributes($printQuery = false, $logQuery = false) { if (empty($this->_items) || empty($this->_itemsById) || empty($this->_selectAttributes)) { return $this; } $entity = $this->getEntity(); $tableAttributes = array(); $attributeTypes = array(); foreach ($this->_selectAttributes as $attributeCode => $attributeId) { if (!$attributeId) { continue; } $attribute = Mage::getSingleton('eav/config')->getCollectionAttribute($entity->getType(), $attributeCode); if ($attribute && !$attribute->isStatic()) { $tableAttributes[$attribute->getBackendTable()][] = $attributeId; if (!isset($attributeTypes[$attribute->getBackendTable()])) { $attributeTypes[$attribute->getBackendTable()] = $attribute->getBackendType(); } } } $selects = array(); foreach ($tableAttributes as $table=>$attributes) { $select = $this->_getLoadAttributesSelect($table, $attributes); $selects[$attributeTypes[$table]][] = $this->_addLoadAttributesSelectValues( $select, $table, $attributeTypes[$table] ); } $selectGroups = Mage::getResourceHelper('eav')->getLoadAttributesSelectGroups($selects); foreach ($selectGroups as $selects) { if (!empty($selects)) { try { $select = implode(' UNION ALL ', $selects); $values = $this->getConnection()->fetchAll($select); } catch (Exception $e) { Mage::printException($e, $select); $this->printLogQuery(true, true, $select); throw $e; } foreach ($values as $value) { $this->_setItemAttributeValue($value); } } } return $this; } /** * Retrieve attributes load select * * @param string $table * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _getLoadAttributesSelect($table, $attributeIds = array()) { if (empty($attributeIds)) { $attributeIds = $this->_selectAttributes; } $helper = Mage::getResourceHelper('eav'); $entityIdField = $this->getEntity()->getEntityIdField(); $select = $this->getConnection()->select() ->from($table, array($entityIdField, 'attribute_id')) ->where('entity_type_id =?', $this->getEntity()->getTypeId()) ->where("$entityIdField IN (?)", array_keys($this->_itemsById)) ->where('attribute_id IN (?)', $attributeIds); return $select; } /** * @param Varien_Db_Select $select * @param string $table * @param string $type * @return Varien_Db_Select */ protected function _addLoadAttributesSelectValues($select, $table, $type) { $helper = Mage::getResourceHelper('eav'); $select->columns(array( 'value' => $helper->prepareEavAttributeValue($table. '.value', $type), )); return $select; } /** * Initialize entity ubject property value * * $valueInfo is _getLoadAttributesSelect fetch result row * * @param array $valueInfo * @throws Mage_Eav_Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _setItemAttributeValue($valueInfo) { $entityIdField = $this->getEntity()->getEntityIdField(); $entityId = $valueInfo[$entityIdField]; if (!isset($this->_itemsById[$entityId])) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Data integrity: No header row found for attribute') ); } $attributeCode = array_search($valueInfo['attribute_id'], $this->_selectAttributes); if (!$attributeCode) { $attribute = Mage::getSingleton('eav/config')->getCollectionAttribute( $this->getEntity()->getType(), $valueInfo['attribute_id'] ); $attributeCode = $attribute->getAttributeCode(); } foreach ($this->_itemsById[$entityId] as $object) { $object->setData($attributeCode, $valueInfo['value']); } return $this; } /** * Get alias for attribute value table * * @param string $attributeCode * @return string */ protected function _getAttributeTableAlias($attributeCode) { return 'at_' . $attributeCode; } /** * Retreive attribute field name by attribute code * * @param string $attributeCode * @return string */ protected function _getAttributeFieldName($attributeCode) { $attributeCode = trim($attributeCode); if (isset($this->_joinAttributes[$attributeCode]['condition_alias'])) { return $this->_joinAttributes[$attributeCode]['condition_alias']; } if (isset($this->_staticFields[$attributeCode])) { return sprintf('e.%s', $attributeCode); } if (isset($this->_joinFields[$attributeCode])) { $attr = $this->_joinFields[$attributeCode]; return $attr['table'] ? $attr['table'] . '.' . $attr['field'] : $attr['field']; } $attribute = $this->getAttribute($attributeCode); if (!$attribute) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s', $attributeCode)); } if ($attribute->isStatic()) { if (isset($this->_joinAttributes[$attributeCode])) { $fieldName = $this->_getAttributeTableAlias($attributeCode) . '.' . $attributeCode; } else { $fieldName = 'e.' . $attributeCode; } } else { $fieldName = $this->_getAttributeTableAlias($attributeCode) . '.value'; } return $fieldName; } /** * Add attribute value table to the join if it wasn't added previously * * @param string $attributeCode * @param string $joinType inner|left * @throws Mage_Eav_Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _addAttributeJoin($attributeCode, $joinType = 'inner') { if (!empty($this->_filterAttributes[$attributeCode])) { return $this; } $adapter = $this->getConnection(); $attrTable = $this->_getAttributeTableAlias($attributeCode); if (isset($this->_joinAttributes[$attributeCode])) { $attribute = $this->_joinAttributes[$attributeCode]['attribute']; $entity = $attribute->getEntity(); $entityIdField = $entity->getEntityIdField(); $fkName = $this->_joinAttributes[$attributeCode]['bind']; $fkAttribute = $this->_joinAttributes[$attributeCode]['bindAttribute']; $fkTable = $this->_getAttributeTableAlias($fkName); if ($fkAttribute->getBackend()->isStatic()) { if (isset($this->_joinAttributes[$fkName])) { $fk = $fkTable . '.' . $fkAttribute->getAttributeCode(); } else { $fk = 'e.' . $fkAttribute->getAttributeCode(); } } else { $this->_addAttributeJoin($fkAttribute->getAttributeCode(), $joinType); $fk = $fkTable . '.value'; } $pk = $attrTable . '.' . $this->_joinAttributes[$attributeCode]['filter']; } else { $entity = $this->getEntity(); $entityIdField = $entity->getEntityIdField(); $attribute = $entity->getAttribute($attributeCode); $fk = 'e.' . $entityIdField; $pk = $attrTable . '.' . $entityIdField; } if (!$attribute) { throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s', $attributeCode)); } if ($attribute->getBackend()->isStatic()) { $attrFieldName = $attrTable . '.' . $attribute->getAttributeCode(); } else { $attrFieldName = $attrTable . '.value'; } $fk = $adapter->quoteColumnAs($fk, null); $pk = $adapter->quoteColumnAs($pk, null); $condArr = array("$pk = $fk"); if (!$attribute->getBackend()->isStatic()) { $condArr[] = $this->getConnection()->quoteInto( $adapter->quoteColumnAs("$attrTable.attribute_id", null) . ' = ?', $attribute->getId()); } /** * process join type */ $joinMethod = ($joinType == 'left') ? 'joinLeft' : 'join'; $this->_joinAttributeToSelect($joinMethod, $attribute, $attrTable, $condArr, $attributeCode, $attrFieldName); $this->removeAttributeToSelect($attributeCode); $this->_filterAttributes[$attributeCode] = $attribute->getId(); /** * Fix double join for using same as filter */ $this->_joinFields[$attributeCode] = array( 'table' => '', 'field' => $attrFieldName, ); return $this; } /** * Adding join statement to collection select instance * * @param string $method * @param object $attribute * @param string $tableAlias * @param array $condition * @param string $fieldCode * @param string $fieldAlias * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias) { $this->getSelect()->$method( array($tableAlias => $attribute->getBackend()->getTable()), '('.implode(') AND (', $condition).')', array($fieldCode => $fieldAlias) ); return $this; } /** * Get condition sql for the attribute * * @see self::_getConditionSql * @param string $attribute * @param mixed $condition * @param string $joinType * @return string */ protected function _getAttributeConditionSql($attribute, $condition, $joinType = 'inner') { if (isset($this->_joinFields[$attribute])) { return $this->_getConditionSql($this->_getAttributeFieldName($attribute), $condition); } if (isset($this->_staticFields[$attribute])) { return $this->_getConditionSql($this->getConnection()->quoteIdentifier('e.' . $attribute), $condition); } // process linked attribute if (isset($this->_joinAttributes[$attribute])) { $entity = $this->getAttribute($attribute)->getEntity(); $entityTable = $entity->getEntityTable(); } else { $entity = $this->getEntity(); $entityTable = 'e'; } if ($entity->isAttributeStatic($attribute)) { $conditionSql = $this->_getConditionSql( $this->getConnection()->quoteIdentifier('e.' . $attribute), $condition ); } else { $this->_addAttributeJoin($attribute, $joinType); if (isset($this->_joinAttributes[$attribute]['condition_alias'])) { $field = $this->_joinAttributes[$attribute]['condition_alias']; } else { $field = $this->_getAttributeTableAlias($attribute) . '.value'; } $conditionSql = $this->_getConditionSql($field, $condition); } return $conditionSql; } /** * Set sorting order * * $attribute can also be an array of attributes * * @param string|array $attribute * @param string $dir * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function setOrder($attribute, $dir = self::SORT_ORDER_ASC) { if (is_array($attribute)) { foreach ($attribute as $attr) { parent::setOrder($attr, $dir); } } return parent::setOrder($attribute, $dir); } /** * Retreive array of attributes * * @param array $arrAttributes * @return array */ public function toArray($arrAttributes = array()) { $arr = array(); foreach ($this->_items as $k => $item) { $arr[$k] = $item->toArray($arrAttributes); } return $arr; } /** * Treat "order by" items as attributes to sort * * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _renderOrders() { if (!$this->_isOrdersRendered) { foreach ($this->_orders as $attribute => $direction) { $this->addAttributeToSort($attribute, $direction); } $this->_isOrdersRendered = true; } return $this; } /** * After load method * * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _afterLoad() { return $this; } /** * Reset collection * * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _reset() { parent::_reset(); $this->_selectEntityTypes = array(); $this->_selectAttributes = array(); $this->_filterAttributes = array(); $this->_joinEntities = array(); $this->_joinAttributes = array(); $this->_joinFields = array(); return $this; } /** * Returns already loaded element ids * * return array */ public function getLoadedIds() { return array_keys($this->_items); } /** * Prepare select for load * * @param Varien_Db_Select $select OPTIONAL * @return string */ public function _prepareSelect(Varien_Db_Select $select) { if ($this->_useAnalyticFunction) { $helper = Mage::getResourceHelper('core'); return $helper->getQueryUsingAnalyticFunction($select); } return (string)$select; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog EAV collection resource abstract model * Implement using diferent stores for retrieve attribute values * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Collection_Abstract extends Mage_Eav_Model_Entity_Collection_Abstract { /** * Current scope (store Id) * * @var int */ protected $_storeId; /** * Set store scope * * @param int|string|Mage_Core_Model_Store $store * @return Mage_Catalog_Model_Resource_Collection_Abstract */ public function setStore($store) { $this->setStoreId(Mage::app()->getStore($store)->getId()); return $this; } /** * Set store scope * * @param int|string|Mage_Core_Model_Store $storeId * @return Mage_Catalog_Model_Resource_Collection_Abstract */ public function setStoreId($storeId) { if ($storeId instanceof Mage_Core_Model_Store) { $storeId = $storeId->getId(); } $this->_storeId = (int)$storeId; return $this; } /** * Return current store id * * @return int */ public function getStoreId() { if (is_null($this->_storeId)) { $this->setStoreId(Mage::app()->getStore()->getId()); } return $this->_storeId; } /** * Retrieve default store id * * @return int */ public function getDefaultStoreId() { return Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID; } /** * Retrieve attributes load select * * @param string $table * @param array|int $attributeIds * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _getLoadAttributesSelect($table, $attributeIds = array()) { if (empty($attributeIds)) { $attributeIds = $this->_selectAttributes; } $storeId = $this->getStoreId(); if ($storeId) { $adapter = $this->getConnection(); $entityIdField = $this->getEntity()->getEntityIdField(); $joinCondition = array( 't_s.attribute_id = t_d.attribute_id', 't_s.entity_id = t_d.entity_id', $adapter->quoteInto('t_s.store_id = ?', $storeId) ); $select = $adapter->select() ->from(array('t_d' => $table), array($entityIdField, 'attribute_id')) ->joinLeft( array('t_s' => $table), implode(' AND ', $joinCondition), array()) ->where('t_d.entity_type_id = ?', $this->getEntity()->getTypeId()) ->where("t_d.{$entityIdField} IN (?)", array_keys($this->_itemsById)) ->where('t_d.attribute_id IN (?)', $attributeIds) ->where('t_d.store_id = ?', 0); } else { $select = parent::_getLoadAttributesSelect($table) ->where('store_id = ?', $this->getDefaultStoreId()); } return $select; } /** * @param Varien_Db_Select $select * @param string $table * @param string $type * @return Varien_Db_Select */ protected function _addLoadAttributesSelectValues($select, $table, $type) { $storeId = $this->getStoreId(); if ($storeId) { $helper = Mage::getResourceHelper('eav'); $adapter = $this->getConnection(); $valueExpr = $adapter->getCheckSql( 't_s.value_id IS NULL', $helper->prepareEavAttributeValue('t_d.value', $type), $helper->prepareEavAttributeValue('t_s.value', $type) ); $select->columns(array( 'default_value' => $helper->prepareEavAttributeValue('t_d.value', $type), 'store_value' => $helper->prepareEavAttributeValue('t_s.value', $type), 'value' => $valueExpr )); } else { $select = parent::_addLoadAttributesSelectValues($select, $table, $type); } return $select; } /** * Adding join statement to collection select instance * * @param string $method * @param object $attribute * @param string $tableAlias * @param array $condition * @param string $fieldCode * @param string $fieldAlias * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias) { if (isset($this->_joinAttributes[$fieldCode]['store_id'])) { $store_id = $this->_joinAttributes[$fieldCode]['store_id']; } else { $store_id = $this->getStoreId(); } $adapter = $this->getConnection(); if ($store_id != $this->getDefaultStoreId() && !$attribute->isScopeGlobal()) { /** * Add joining default value for not default store * if value for store is null - we use default value */ $defCondition = '('.implode(') AND (', $condition).')'; $defAlias = $tableAlias . '_default'; $defAlias = $this->getConnection()->getTableName($defAlias); $defFieldAlias= str_replace($tableAlias, $defAlias, $fieldAlias); $tableAlias = $this->getConnection()->getTableName($tableAlias); $defCondition = str_replace($tableAlias, $defAlias, $defCondition); $defCondition.= $adapter->quoteInto( " AND " . $adapter->quoteColumnAs("$defAlias.store_id", null) . " = ?", $this->getDefaultStoreId()); $this->getSelect()->$method( array($defAlias => $attribute->getBackend()->getTable()), $defCondition, array() ); $method = 'joinLeft'; $fieldAlias = $this->getConnection()->getCheckSql("{$tableAlias}.value_id > 0", $fieldAlias, $defFieldAlias); $this->_joinAttributes[$fieldCode]['condition_alias'] = $fieldAlias; $this->_joinAttributes[$fieldCode]['attribute'] = $attribute; } else { $store_id = $this->getDefaultStoreId(); } $condition[] = $adapter->quoteInto( $adapter->quoteColumnAs("$tableAlias.store_id", null) . ' = ?', $store_id); return parent::_joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog Config Resource Model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Config extends Mage_Core_Model_Resource_Db_Abstract { /** * catalog_product entity type id * * @var int */ protected $_entityTypeId; /** * Store id * * @var int */ protected $_storeId = null; /** * Initialize connection * */ protected function _construct() { $this->_init('eav/attribute', 'attribute_id'); } /** * Set store id * * @param integer $storeId * @return Mage_Catalog_Model_Resource_Config */ public function setStoreId($storeId) { $this->_storeId = (int)$storeId; return $this; } /** * Return store id. * If is not set return current app store * * @return integer */ public function getStoreId() { if ($this->_storeId === null) { return Mage::app()->getStore()->getId(); } return $this->_storeId; } /** * Retrieve catalog_product entity type id * * @return int */ public function getEntityTypeId() { if ($this->_entityTypeId === null) { $this->_entityTypeId = Mage::getSingleton('eav/config')->getEntityType(Mage_Catalog_Model_Product::ENTITY)->getId(); } return $this->_entityTypeId; } /** * Retrieve Product Attributes Used in Catalog Product listing * * @return array */ public function getAttributesUsedInListing() { $adapter = $this->_getReadAdapter(); $storeLabelExpr = $adapter->getCheckSql('al.value IS NOT NULL', 'al.value', 'main_table.frontend_label'); $select = $adapter->select() ->from(array('main_table' => $this->getTable('eav/attribute'))) ->join( array('additional_table' => $this->getTable('catalog/eav_attribute')), 'main_table.attribute_id = additional_table.attribute_id' ) ->joinLeft( array('al' => $this->getTable('eav/attribute_label')), 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . (int)$this->getStoreId(), array('store_label' => $storeLabelExpr) ) ->where('main_table.entity_type_id = ?', (int)$this->getEntityTypeId()) ->where('additional_table.used_in_product_listing = ?', 1); return $adapter->fetchAll($select); } /** * Retrieve Used Product Attributes for Catalog Product Listing Sort By * * @return array */ public function getAttributesUsedForSortBy() { $adapter = $this->_getReadAdapter(); $storeLabelExpr = $adapter->getCheckSql('al.value IS NULL', 'main_table.frontend_label','al.value'); $select = $adapter->select() ->from(array('main_table' => $this->getTable('eav/attribute'))) ->join( array('additional_table' => $this->getTable('catalog/eav_attribute')), 'main_table.attribute_id = additional_table.attribute_id', array() ) ->joinLeft( array('al' => $this->getTable('eav/attribute_label')), 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . (int)$this->getStoreId(), array('store_label' => $storeLabelExpr) ) ->where('main_table.entity_type_id = ?', (int)$this->getEntityTypeId()) ->where('additional_table.used_for_sort_by = ?', 1); return $adapter->fetchAll($select); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog attribute model * * @method Mage_Catalog_Model_Resource_Attribute _getResource() * @method Mage_Catalog_Model_Resource_Attribute getResource() * @method Mage_Catalog_Model_Resource_Eav_Attribute getFrontendInputRenderer() * @method string setFrontendInputRenderer(string $value) * @method int setIsGlobal(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsVisible() * @method int setIsVisible(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsSearchable() * @method int setIsSearchable(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getSearchWeight() * @method int setSearchWeight(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsFilterable() * @method int setIsFilterable(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsComparable() * @method int setIsComparable(int $value) * @method int setIsVisibleOnFront(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsHtmlAllowedOnFront() * @method int setIsHtmlAllowedOnFront(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsUsedForPriceRules() * @method int setIsUsedForPriceRules(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsFilterableInSearch() * @method int setIsFilterableInSearch(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getUsedInProductListing() * @method int setUsedInProductListing(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getUsedForSortBy() * @method int setUsedForSortBy(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsConfigurable() * @method int setIsConfigurable(int $value) * @method string setApplyTo(string $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsVisibleInAdvancedSearch() * @method int setIsVisibleInAdvancedSearch(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getPosition() * @method int setPosition(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsWysiwygEnabled() * @method int setIsWysiwygEnabled(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsUsedForPromoRules() * @method int setIsUsedForPromoRules(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsUsedForCustomerSegment() * @method int setIsUsedForCustomerSegment(int $value) * @method Mage_Catalog_Model_Resource_Eav_Attribute getIsUsedForTargetRules() * @method int setIsUsedForTargetRules(int $value) * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Eav_Attribute extends Mage_Eav_Model_Entity_Attribute { const SCOPE_STORE = 0; const SCOPE_GLOBAL = 1; const SCOPE_WEBSITE = 2; const MODULE_NAME = 'Mage_Catalog'; const ENTITY = 'catalog_eav_attribute'; /** * Event prefix * * @var string */ protected $_eventPrefix = 'catalog_entity_attribute'; /** * Event object name * * @var string */ protected $_eventObject = 'attribute'; /** * Array with labels * * @var array */ static protected $_labels = null; protected function _construct() { $this->_init('catalog/attribute'); } /** * Processing object before save data * * @throws Mage_Core_Exception * @return Mage_Core_Model_Abstract */ protected function _beforeSave() { $this->setData('modulePrefix', self::MODULE_NAME); if (isset($this->_origData['is_global'])) { if (!isset($this->_data['is_global'])) { $this->_data['is_global'] = self::SCOPE_GLOBAL; } if (($this->_data['is_global'] != $this->_origData['is_global']) && $this->_getResource()->isUsedBySuperProducts($this)) { Mage::throwException(Mage::helper('catalog')->__('Scope must not be changed, because the attribute is used in configurable products.')); } } if ($this->getFrontendInput() == 'price') { if (!$this->getBackendModel()) { $this->setBackendModel('catalog/product_attribute_backend_price'); } } if ($this->getFrontendInput() == 'textarea') { if ($this->getIsWysiwygEnabled()) { $this->setIsHtmlAllowedOnFront(1); } } return parent::_beforeSave(); } /** * Processing object after save data * * @return Mage_Core_Model_Abstract */ protected function _afterSave() { /** * Fix saving attribute in admin */ Mage::getSingleton('eav/config')->clear(); Mage::getSingleton('index/indexer')->processEntityAction( $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE ); return parent::_afterSave(); } /** * Register indexing event before delete catalog eav attribute * * @return Mage_Catalog_Model_Resource_Eav_Attribute */ protected function _beforeDelete() { if ($this->_getResource()->isUsedBySuperProducts($this)) { Mage::throwException(Mage::helper('catalog')->__('This attribute is used in configurable products.')); } Mage::getSingleton('index/indexer')->logEvent( $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE ); return parent::_beforeDelete(); } /** * Init indexing process after catalog eav attribute delete commit * * @return Mage_Catalog_Model_Resource_Eav_Attribute */ protected function _afterDeleteCommit() { parent::_afterDeleteCommit(); Mage::getSingleton('index/indexer')->indexEvents( self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE ); return $this; } /** * Return is attribute global * * @return integer */ public function getIsGlobal() { return $this->_getData('is_global'); } /** * Retrieve attribute is global scope flag * * @return bool */ public function isScopeGlobal() { return $this->getIsGlobal() == self::SCOPE_GLOBAL; } /** * Retrieve attribute is website scope website * * @return bool */ public function isScopeWebsite() { return $this->getIsGlobal() == self::SCOPE_WEBSITE; } /** * Retrieve attribute is store scope flag * * @return bool */ public function isScopeStore() { return !$this->isScopeGlobal() && !$this->isScopeWebsite(); } /** * Retrieve store id * * @return int */ public function getStoreId() { $dataObject = $this->getDataObject(); if ($dataObject) { return $dataObject->getStoreId(); } return $this->getData('store_id'); } /** * Retrieve apply to products array * Return empty array if applied to all products * * @return array */ public function getApplyTo() { if ($this->getData('apply_to')) { if (is_array($this->getData('apply_to'))) { return $this->getData('apply_to'); } return explode(',', $this->getData('apply_to')); } else { return array(); } } /** * Retrieve source model * * @return Mage_Eav_Model_Entity_Attribute_Source_Abstract */ public function getSourceModel() { $model = $this->getData('source_model'); if (empty($model)) { if ($this->getBackendType() == 'int' && $this->getFrontendInput() == 'select') { return $this->_getDefaultSourceModel(); } } return $model; } /** * Check is allow for rule condition * * @return bool */ public function isAllowedForRuleCondition() { $allowedInputTypes = array('text', 'multiselect', 'textarea', 'date', 'datetime', 'select', 'boolean', 'price'); return $this->getIsVisible() && in_array($this->getFrontendInput(), $allowedInputTypes); } /** * Retrieve don't translated frontend label * * @return string */ public function getFrontendLabel() { return $this->_getData('frontend_label'); } /** * Get Attribute translated label for store * * @deprecated * @return string */ protected function _getLabelForStore() { return $this->getFrontendLabel(); } /** * Initialize store Labels for attributes * * @deprecated * @param int $storeId */ public static function initLabels($storeId = null) { if (is_null(self::$_labels)) { if (is_null($storeId)) { $storeId = Mage::app()->getStore()->getId(); } $attributeLabels = array(); $attributes = Mage::getResourceSingleton('catalog/product')->getAttributesByCode(); foreach ($attributes as $attribute) { if (strlen($attribute->getData('frontend_label')) > 0) { $attributeLabels[] = $attribute->getData('frontend_label'); } } self::$_labels = Mage::app()->getTranslator()->getResource() ->getTranslationArrayByStrings($attributeLabels, $storeId); } } /** * Get default attribute source model * * @return string */ public function _getDefaultSourceModel() { return 'eav/entity_attribute_source_table'; } /** * Check is an attribute used in EAV index * * @return bool */ public function isIndexable() { // exclude price attribute if ($this->getAttributeCode() == 'price') { return false; } if (!$this->getIsFilterableInSearch() && !$this->getIsVisibleInAdvancedSearch() && !$this->getIsFilterable()) { return false; } $backendType = $this->getBackendType(); $frontendInput = $this->getFrontendInput(); if ($backendType == 'int' && $frontendInput == 'select') { return true; } else if ($backendType == 'varchar' && $frontendInput == 'multiselect') { return true; } else if ($backendType == 'decimal') { return true; } return false; } /** * Retrieve index type for indexable attribute * * @return string|false */ public function getIndexType() { if (!$this->isIndexable()) { return false; } if ($this->getBackendType() == 'decimal') { return 'decimal'; } return 'source'; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product entity resource model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product extends Mage_Catalog_Model_Resource_Abstract { /** * Product to website linkage table * * @var string */ protected $_productWebsiteTable; /** * Product to category linkage table * * @var string */ protected $_productCategoryTable; /** * Initialize resource */ public function __construct() { parent::__construct(); $this->setType(Mage_Catalog_Model_Product::ENTITY) ->setConnection('catalog_read', 'catalog_write'); $this->_productWebsiteTable = $this->getTable('catalog/product_website'); $this->_productCategoryTable = $this->getTable('catalog/category_product'); } /** * Default product attributes * * @return array */ protected function _getDefaultAttributes() { return array('entity_id', 'entity_type_id', 'attribute_set_id', 'type_id', 'created_at', 'updated_at'); } /** * Retrieve product website identifiers * * @param Mage_Catalog_Model_Product|int $product * @return array */ public function getWebsiteIds($product) { $adapter = $this->_getReadAdapter(); if ($product instanceof Mage_Catalog_Model_Product) { $productId = $product->getId(); } else { $productId = $product; } $select = $adapter->select() ->from($this->_productWebsiteTable, 'website_id') ->where('product_id = ?', (int)$productId); return $adapter->fetchCol($select); } /** * Retrieve product website identifiers by product identifiers * * @param array $productIds * @return array */ public function getWebsiteIdsByProductIds($productIds) { $select = $this->_getWriteAdapter()->select() ->from($this->_productWebsiteTable, array('product_id', 'website_id')) ->where('product_id IN (?)', $productIds); $productsWebsites = array(); foreach ($this->_getWriteAdapter()->fetchAll($select) as $productInfo) { $productId = $productInfo['product_id']; if (!isset($productsWebsites[$productId])) { $productsWebsites[$productId] = array(); } $productsWebsites[$productId][] = $productInfo['website_id']; } return $productsWebsites; } /** * Retrieve product category identifiers * * @param Mage_Catalog_Model_Product $product * @return array */ public function getCategoryIds($product) { $adapter = $this->_getReadAdapter(); $select = $adapter->select() ->from($this->_productCategoryTable, 'category_id') ->where('product_id = ?', (int)$product->getId()); return $adapter->fetchCol($select); } /** * Get product identifier by sku * * @param string $sku * @return int|false */ public function getIdBySku($sku) { $adapter = $this->_getReadAdapter(); $select = $adapter->select() ->from($this->getEntityTable(), 'entity_id') ->where('sku = :sku'); $bind = array(':sku' => (string)$sku); return $adapter->fetchOne($select, $bind); } /** * Process product data before save * * @param Varien_Object $object * @return Mage_Catalog_Model_Resource_Product */ protected function _beforeSave(Varien_Object $object) { /** * Try detect product id by sku if id is not declared */ if (!$object->getId() && $object->getSku()) { $object->setId($this->getIdBySku($object->getSku())); } /** * Check if declared category ids in object data. */ if ($object->hasCategoryIds()) { $categoryIds = Mage::getResourceSingleton('catalog/category')->verifyIds( $object->getCategoryIds() ); $object->setCategoryIds($categoryIds); } return parent::_beforeSave($object); } /** * Save data related with product * * @param Varien_Object $product * @return Mage_Catalog_Model_Resource_Product */ protected function _afterSave(Varien_Object $product) { $this->_saveWebsiteIds($product) ->_saveCategories($product); return parent::_afterSave($product); } /** * Save product website relations * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Resource_Product */ protected function _saveWebsiteIds($product) { $websiteIds = $product->getWebsiteIds(); $oldWebsiteIds = array(); $product->setIsChangedWebsites(false); $adapter = $this->_getWriteAdapter(); $oldWebsiteIds = $this->getWebsiteIds($product); $insert = array_diff($websiteIds, $oldWebsiteIds); $delete = array_diff($oldWebsiteIds, $websiteIds); if (!empty($insert)) { $data = array(); foreach ($insert as $websiteId) { $data[] = array( 'product_id' => (int)$product->getId(), 'website_id' => (int)$websiteId ); } $adapter->insertMultiple($this->_productWebsiteTable, $data); } if (!empty($delete)) { foreach ($delete as $websiteId) { $condition = array( 'product_id = ?' => (int)$product->getId(), 'website_id = ?' => (int)$websiteId, ); $adapter->delete($this->_productWebsiteTable, $condition); } } if (!empty($insert) || !empty($delete)) { $product->setIsChangedWebsites(true); } return $this; } /** * Save product category relations * * @param Varien_Object $object * @return Mage_Catalog_Model_Resource_Product */ protected function _saveCategories(Varien_Object $object) { /** * If category ids data is not declared we haven't do manipulations */ if (!$object->hasCategoryIds()) { return $this; } $categoryIds = $object->getCategoryIds(); $oldCategoryIds = $this->getCategoryIds($object); $object->setIsChangedCategories(false); $insert = array_diff($categoryIds, $oldCategoryIds); $delete = array_diff($oldCategoryIds, $categoryIds); $write = $this->_getWriteAdapter(); if (!empty($insert)) { $data = array(); foreach ($insert as $categoryId) { if (empty($categoryId)) { continue; } $data[] = array( 'category_id' => (int)$categoryId, 'product_id' => (int)$object->getId(), 'position' => 1 ); } if ($data) { $write->insertMultiple($this->_productCategoryTable, $data); } } if (!empty($delete)) { foreach ($delete as $categoryId) { $where = array( 'product_id = ?' => (int)$object->getId(), 'category_id = ?' => (int)$categoryId, ); $write->delete($this->_productCategoryTable, $where); } } if (!empty($insert) || !empty($delete)) { $object->setAffectedCategoryIds(array_merge($insert, $delete)); $object->setIsChangedCategories(true); } return $this; } /** * Refresh Product Enabled Index * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Resource_Product */ public function refreshIndex($product) { $writeAdapter = $this->_getWriteAdapter(); /** * Ids of all categories where product is assigned (not related with store) */ $categoryIds = $product->getCategoryIds(); /** * Clear previous index data related with product */ $condition = array('product_id = ?' => (int)$product->getId()); $writeAdapter->delete($this->getTable('catalog/category_product_index'), $condition); /** @var $categoryObject Mage_Catalog_Model_Resource_Category */ $categoryObject = Mage::getResourceSingleton('catalog/category'); if (!empty($categoryIds)) { $categoriesSelect = $writeAdapter->select() ->from($this->getTable('catalog/category')) ->where('entity_id IN (?)', $categoryIds); $categoriesInfo = $writeAdapter->fetchAll($categoriesSelect); $indexCategoryIds = array(); foreach ($categoriesInfo as $categoryInfo) { $ids = explode('/', $categoryInfo['path']); $ids[] = $categoryInfo['entity_id']; $indexCategoryIds = array_merge($indexCategoryIds, $ids); } $indexCategoryIds = array_unique($indexCategoryIds); $indexProductIds = array($product->getId()); $categoryObject->refreshProductIndex($indexCategoryIds, $indexProductIds); } else { $websites = $product->getWebsiteIds(); if ($websites) { $storeIds = array(); foreach ($websites as $websiteId) { $website = Mage::app()->getWebsite($websiteId); $storeIds = array_merge($storeIds, $website->getStoreIds()); } $categoryObject->refreshProductIndex(array(), array($product->getId()), $storeIds); } } /** * Refresh enabled products index (visibility state) */ $this->refreshEnabledIndex(null, $product); return $this; } /** * Refresh index for visibility of enabled product in store * if store parameter is null - index will refreshed for all stores * if product parameter is null - idex will be refreshed for all products * * @param Mage_Core_Model_Store $store * @param Mage_Catalog_Model_Product $product * @throws Mage_Core_Exception * @return Mage_Catalog_Model_Resource_Product */ public function refreshEnabledIndex($store = null, $product = null) { $statusAttribute = $this->getAttribute('status'); $visibilityAttribute = $this->getAttribute('visibility'); $statusAttributeId = $statusAttribute->getId(); $visibilityAttributeId = $visibilityAttribute->getId(); $statusTable = $statusAttribute->getBackend()->getTable(); $visibilityTable = $visibilityAttribute->getBackend()->getTable(); $adapter = $this->_getWriteAdapter(); $select = $adapter->select(); $condition = array(); $indexTable = $this->getTable('catalog/product_enabled_index'); if (is_null($store) && is_null($product)) { Mage::throwException( Mage::helper('catalog')->__('To reindex the enabled product(s), the store or product must be specified') ); } elseif (is_null($product) || is_array($product)) { $storeId = $store->getId(); $websiteId = $store->getWebsiteId(); if (is_array($product) && !empty($product)) { $condition[] = $adapter->quoteInto('product_id IN (?)', $product); } $condition[] = $adapter->quoteInto('store_id = ?', $storeId); $selectFields = array( 't_v_default.entity_id', new Zend_Db_Expr($storeId), $adapter->getCheckSql('t_v.value_id > 0', 't_v.value', 't_v_default.value'), ); $select->joinInner( array('w' => $this->getTable('catalog/product_website')), $adapter->quoteInto( 'w.product_id = t_v_default.entity_id AND w.website_id = ?', $websiteId ), array() ); } elseif ($store === null) { foreach ($product->getStoreIds() as $storeId) { $store = Mage::app()->getStore($storeId); $this->refreshEnabledIndex($store, $product); } return $this; } else { $productId = is_numeric($product) ? $product : $product->getId(); $storeId = is_numeric($store) ? $store : $store->getId(); $condition = array( 'product_id = ?' => (int)$productId, 'store_id = ?' => (int)$storeId, ); $selectFields = array( new Zend_Db_Expr($productId), new Zend_Db_Expr($storeId), $adapter->getCheckSql('t_v.value_id > 0', 't_v.value', 't_v_default.value') ); $select->where('t_v_default.entity_id = ?', $productId); } $adapter->delete($indexTable, $condition); $select->from(array('t_v_default' => $visibilityTable), $selectFields); $visibilityTableJoinCond = array( 't_v.entity_id = t_v_default.entity_id', $adapter->quoteInto('t_v.attribute_id = ?', $visibilityAttributeId), $adapter->quoteInto('t_v.store_id = ?', $storeId), ); $select->joinLeft( array('t_v' => $visibilityTable), implode(' AND ', $visibilityTableJoinCond), array() ); $defaultStatusJoinCond = array( 't_s_default.entity_id = t_v_default.entity_id', 't_s_default.store_id = 0', $adapter->quoteInto('t_s_default.attribute_id = ?', $statusAttributeId), ); $select->joinInner( array('t_s_default' => $statusTable), implode(' AND ', $defaultStatusJoinCond), array() ); $statusJoinCond = array( 't_s.entity_id = t_v_default.entity_id', $adapter->quoteInto('t_s.store_id = ?', $storeId), $adapter->quoteInto('t_s.attribute_id = ?', $statusAttributeId), ); $select->joinLeft( array('t_s' => $statusTable), implode(' AND ', $statusJoinCond), array() ); $valueCondition = $adapter->getCheckSql('t_s.value_id > 0', 't_s.value', 't_s_default.value'); $select->where('t_v_default.attribute_id = ?', $visibilityAttributeId) ->where('t_v_default.store_id = ?', 0) ->where(sprintf('%s = ?', $valueCondition), Mage_Catalog_Model_Product_Status::STATUS_ENABLED); if (is_array($product) && !empty($product)) { $select->where('t_v_default.entity_id IN (?)', $product); } $adapter->query($adapter->insertFromSelect($select, $indexTable)); return $this; } /** * Get collection of product categories * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Resource_Category_Collection */ public function getCategoryCollection($product) { $collection = Mage::getResourceModel('catalog/category_collection') ->joinField('product_id', 'catalog/category_product', 'product_id', 'category_id = entity_id', null) ->addFieldToFilter('product_id', (int)$product->getId()); return $collection; } /** * Retrieve category ids where product is available * * @param Mage_Catalog_Model_Product $object * @return array */ public function getAvailableInCategories($object) { // is_parent=1 ensures that we'll get only category IDs those are direct parents of the product, instead of // fetching all parent IDs, including those are higher on the tree $select = $this->_getReadAdapter()->select()->distinct() ->from($this->getTable('catalog/category_product_index'), array('category_id')) ->where('product_id = ? AND is_parent = 1', (int)$object->getEntityId()); return $this->_getReadAdapter()->fetchCol($select); } /** * Get default attribute source model * * @return string */ public function getDefaultAttributeSourceModel() { return 'eav/entity_attribute_source_table'; } /** * Check availability display product in category * * @param Mage_Catalog_Model_Product $product * @param int $categoryId * @return string */ public function canBeShowInCategory($product, $categoryId) { $select = $this->_getReadAdapter()->select() ->from($this->getTable('catalog/category_product_index'), 'product_id') ->where('product_id = ?', (int)$product->getId()) ->where('category_id = ?', (int)$categoryId); return $this->_getReadAdapter()->fetchOne($select); } /** * Duplicate product store values * * @param int $oldId * @param int $newId * @return Mage_Catalog_Model_Resource_Product */ public function duplicate($oldId, $newId) { $adapter = $this->_getWriteAdapter(); $eavTables = array('datetime', 'decimal', 'int', 'text', 'varchar'); $adapter = $this->_getWriteAdapter(); // duplicate EAV store values foreach ($eavTables as $suffix) { $tableName = $this->getTable(array('catalog/product', $suffix)); $select = $adapter->select() ->from($tableName, array( 'entity_type_id', 'attribute_id', 'store_id', 'entity_id' => new Zend_Db_Expr($adapter->quote($newId)), 'value' )) ->where('entity_id = ?', $oldId) ->where('store_id > ?', 0); $adapter->query($adapter->insertFromSelect( $select, $tableName, array( 'entity_type_id', 'attribute_id', 'store_id', 'entity_id', 'value' ), Varien_Db_Adapter_Interface::INSERT_ON_DUPLICATE )); } // set status as disabled $statusAttribute = $this->getAttribute('status'); $statusAttributeId = $statusAttribute->getAttributeId(); $statusAttributeTable = $statusAttribute->getBackend()->getTable(); $updateCond[] = 'store_id > 0'; $updateCond[] = $adapter->quoteInto('entity_id = ?', $newId); $updateCond[] = $adapter->quoteInto('attribute_id = ?', $statusAttributeId); $adapter->update( $statusAttributeTable, array('value' => Mage_Catalog_Model_Product_Status::STATUS_DISABLED), $updateCond ); return $this; } /** * Get SKU through product identifiers * * @param array $productIds * @return array */ public function getProductsSku(array $productIds) { $select = $this->_getReadAdapter()->select() ->from($this->getTable('catalog/product'), array('entity_id', 'sku')) ->where('entity_id IN (?)', $productIds); return $this->_getReadAdapter()->fetchAll($select); } /** * @deprecated after 1.4.2.0 * @param $object Mage_Catalog_Model_Product * @return array */ public function getParentProductIds($object) { return array(); } /** * Retrieve product entities info * * @param array|string|null $columns * @return array */ public function getProductEntitiesInfo($columns = null) { if (!empty($columns) && is_string($columns)) { $columns = array($columns); } if (empty($columns) || !is_array($columns)) { $columns = $this->_getDefaultAttributes(); } $adapter = $this->_getReadAdapter(); $select = $adapter->select() ->from($this->getTable('catalog/product'), $columns); return $adapter->fetchAll($select); } /** * Return assigned images for specific stores * * @param Mage_Catalog_Model_Product $product * @param int|array $storeIds * @return array * */ public function getAssignedImages($product, $storeIds) { if (!is_array($storeIds)) { $storeIds = array($storeIds); } $mainTable = $product->getResource()->getAttribute('image') ->getBackend() ->getTable(); $read = $this->_getReadAdapter(); $select = $read->select() ->from( array('images' => $mainTable), array('value as filepath', 'store_id') ) ->joinLeft( array('attr' => $this->getTable('eav/attribute')), 'images.attribute_id = attr.attribute_id', array('attribute_code') ) ->where('entity_id = ?', $product->getId()) ->where('store_id IN (?)', $storeIds) ->where('attribute_code IN (?)', array('small_image', 'thumbnail', 'image')); $images = $read->fetchAll($select); return $images; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product media gallery attribute backend resource * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product_Attribute_Backend_Media extends Mage_Core_Model_Resource_Db_Abstract { const GALLERY_TABLE = 'catalog/product_attribute_media_gallery'; const GALLERY_VALUE_TABLE = 'catalog/product_attribute_media_gallery_value'; const GALLERY_IMAGE_TABLE = 'catalog/product_attribute_media_gallery_image'; /** * Resource initialization */ protected function _construct() { $this->_init(self::GALLERY_TABLE, 'value_id'); } /** * Load gallery images for product * * @param Mage_Catalog_Model_Product $product * @param Mage_Catalog_Model_Product_Attribute_Backend_Media $object * @return array */ public function loadGallery($product, $object) { $adapter = $this->_getReadAdapter(); $positionCheckSql = $adapter->getCheckSql('value.position IS NULL', 'default_value.position', 'value.position'); // Select gallery images for product $select = $adapter->select() ->from( array('main'=>$this->getMainTable()), array('value_id', 'value AS file') ) ->joinLeft( array('value' => $this->getTable(self::GALLERY_VALUE_TABLE)), $adapter->quoteInto('main.value_id = value.value_id AND value.store_id = ?', (int)$product->getStoreId()), array('label','position','disabled') ) ->joinLeft( // Joining default values array('default_value' => $this->getTable(self::GALLERY_VALUE_TABLE)), 'main.value_id = default_value.value_id AND default_value.store_id = 0', array( 'label_default' => 'label', 'position_default' => 'position', 'disabled_default' => 'disabled' ) ) ->where('main.attribute_id = ?', $object->getAttribute()->getId()) ->where('main.entity_id = ?', $product->getId()) ->order($positionCheckSql . ' ' . Varien_Db_Select::SQL_ASC); $result = $adapter->fetchAll($select); $this->_removeDuplicates($result); return $result; } /** * Remove duplicates * * @param array $result * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Media */ protected function _removeDuplicates(&$result) { $fileToId = array(); foreach (array_keys($result) as $index) { if (!isset($fileToId[$result[$index]['file']])) { $fileToId[$result[$index]['file']] = $result[$index]['value_id']; } elseif ($fileToId[$result[$index]['file']] != $result[$index]['value_id']) { $this->deleteGallery($result[$index]['value_id']); unset($result[$index]); } } $result = array_values($result); return $this; } /** * Insert gallery value to db and retrive last id * * @param array $data * @return interger */ public function insertGallery($data) { $adapter = $this->_getWriteAdapter(); $data = $this->_prepareDataForTable(new Varien_Object($data), $this->getMainTable()); $adapter->insert($this->getMainTable(), $data); return $adapter->lastInsertId($this->getMainTable()); } /** * Delete gallery value in db * * @param array|integer $valueId * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Media */ public function deleteGallery($valueId) { if (is_array($valueId) && count($valueId)>0) { $condition = $this->_getWriteAdapter()->quoteInto('value_id IN(?) ', $valueId); } elseif (!is_array($valueId)) { $condition = $this->_getWriteAdapter()->quoteInto('value_id = ? ', $valueId); } else { return $this; } $this->_getWriteAdapter()->delete($this->getMainTable(), $condition); return $this; } /** * Insert gallery value for store to db * * @param array $data * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Media */ public function insertGalleryValueInStore($data) { $data = $this->_prepareDataForTable(new Varien_Object($data), $this->getTable(self::GALLERY_VALUE_TABLE)); $this->_getWriteAdapter()->insert($this->getTable(self::GALLERY_VALUE_TABLE), $data); return $this; } /** * Delete gallery value for store in db * * @param integer $valueId * @param integer $storeId * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Media */ public function deleteGalleryValueInStore($valueId, $storeId) { $adapter = $this->_getWriteAdapter(); $conditions = implode(' AND ', array( $adapter->quoteInto('value_id = ?', (int) $valueId), $adapter->quoteInto('store_id = ?', (int) $storeId), )); $adapter->delete($this->getTable(self::GALLERY_VALUE_TABLE), $conditions); return $this; } /** * Duplicates gallery db values * * @param Mage_Catalog_Model_Product_Attribute_Backend_Media $object * @param array $newFiles * @param int $originalProductId * @param int $newProductId * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Media */ public function duplicate($object, $newFiles, $originalProductId, $newProductId) { $select = $this->_getReadAdapter()->select() ->from($this->getMainTable(), array('value_id', 'value')) ->where('attribute_id = ?', $object->getAttribute()->getId()) ->where('entity_id = ?', $originalProductId); $valueIdMap = array(); // Duplicate main entries of gallery foreach ($this->_getReadAdapter()->fetchAll($select) as $row) { $data = array( 'attribute_id' => $object->getAttribute()->getId(), 'entity_id' => $newProductId, 'value' => (isset($newFiles[$row['value_id']]) ? $newFiles[$row['value_id']] : $row['value']) ); $valueIdMap[$row['value_id']] = $this->insertGallery($data); } if (count($valueIdMap) == 0) { return $this; } // Duplicate per store gallery values $select = $this->_getReadAdapter()->select() ->from($this->getTable(self::GALLERY_VALUE_TABLE)) ->where('value_id IN(?)', array_keys($valueIdMap)); foreach ($this->_getReadAdapter()->fetchAll($select) as $row) { $row['value_id'] = $valueIdMap[$row['value_id']]; $this->insertGalleryValueInStore($row); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product abstract price backend attribute model with customer group specific * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ abstract class Mage_Catalog_Model_Resource_Product_Attribute_Backend_Groupprice_Abstract extends Mage_Core_Model_Resource_Db_Abstract { /** * Load Tier Prices for product * * @param int $productId * @param int $websiteId * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Tierprice */ public function loadPriceData($productId, $websiteId = null) { $adapter = $this->_getReadAdapter(); $columns = array( 'price_id' => $this->getIdFieldName(), 'website_id' => 'website_id', 'all_groups' => 'all_groups', 'cust_group' => 'customer_group_id', 'price' => 'value', ); $columns = $this->_loadPriceDataColumns($columns); $select = $adapter->select() ->from($this->getMainTable(), $columns) ->where('entity_id=?', $productId); $this->_loadPriceDataSelect($select); if (!is_null($websiteId)) { if ($websiteId == '0') { $select->where('website_id = ?', $websiteId); } else { $select->where('website_id IN(?)', array(0, $websiteId)); } } return $adapter->fetchAll($select); } /** * Load specific sql columns * * @param array $columns * @return array */ protected function _loadPriceDataColumns($columns) { return $columns; } /** * Load specific db-select data * * @param Varien_Db_Select $select * @return Varien_Db_Select */ protected function _loadPriceDataSelect($select) { return $select; } /** * Delete Tier Prices for product * * @param int $productId * @param int $websiteId * @param int $priceId * @return int The number of affected rows */ public function deletePriceData($productId, $websiteId = null, $priceId = null) { $adapter = $this->_getWriteAdapter(); $conds = array( $adapter->quoteInto('entity_id = ?', $productId) ); if (!is_null($websiteId)) { $conds[] = $adapter->quoteInto('website_id = ?', $websiteId); } if (!is_null($priceId)) { $conds[] = $adapter->quoteInto($this->getIdFieldName() . ' = ?', $priceId); } $where = implode(' AND ', $conds); return $adapter->delete($this->getMainTable(), $where); } /** * Save tier price object * * @param Varien_Object $priceObject * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Tierprice */ public function savePriceData(Varien_Object $priceObject) { $adapter = $this->_getWriteAdapter(); $data = $this->_prepareDataForTable($priceObject, $this->getMainTable()); if (!empty($data[$this->getIdFieldName()])) { $where = $adapter->quoteInto($this->getIdFieldName() . ' = ?', $data[$this->getIdFieldName()]); unset($data[$this->getIdFieldName()]); $adapter->update($this->getMainTable(), $data, $where); } else { $adapter->insert($this->getMainTable(), $data); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product tier price backend attribute model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product_Attribute_Backend_Tierprice extends Mage_Catalog_Model_Resource_Product_Attribute_Backend_Groupprice_Abstract { /** * Initialize connection and define main table * */ protected function _construct() { $this->_init('catalog/product_attribute_tier_price', 'value_id'); } /** * Add qty column * * @param array $columns * @return array */ protected function _loadPriceDataColumns($columns) { $columns = parent::_loadPriceDataColumns($columns); $columns['price_qty'] = 'qty'; return $columns; } /** * Order by qty * * @param Varien_Db_Select $select * @return Varien_Db_Select */ protected function _loadPriceDataSelect($select) { $select->order('qty'); return $select; } /** * Load product tier prices * * @deprecated since 1.3.2.3 * * @param Mage_Catalog_Model_Product $product * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return array */ public function loadProductPrices($product, $attribute) { $websiteId = null; if ($attribute->isScopeGlobal()) { $websiteId = 0; } else if ($product->getStoreId()) { $websiteId = Mage::app()->getStore($product->getStoreId())->getWebsiteId(); } return $this->loadPriceData($product->getId(), $websiteId); } /** * Delete product tier price data from storage * * @deprecated since 1.3.2.3 * * @param Mage_Catalog_Model_Product $product * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Tierprice */ public function deleteProductPrices($product, $attribute) { $websiteId = null; if (!$attribute->isScopeGlobal()) { $storeId = $product->getProductId(); if ($storeId) { $websiteId = Mage::app()->getStore($storeId)->getWebsiteId(); } } $this->deletePriceData($product->getId(), $websiteId); return $this; } /** * Insert product Tier Price to storage * * @deprecated since 1.3.2.3 * * @param Mage_Catalog_Model_Product $product * @param array $data * @return Mage_Catalog_Model_Resource_Product_Attribute_Backend_Tierprice */ public function insertProductPrice($product, $data) { $priceObject = new Varien_Object($data); $priceObject->setEntityId($product->getId()); return $this->savePriceData($priceObject); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product collection * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product_Collection extends Mage_Catalog_Model_Resource_Collection_Abstract { /** * Alias for index table */ const INDEX_TABLE_ALIAS = 'price_index'; /** * Alias for main table */ const MAIN_TABLE_ALIAS = 'e'; /** * Catalog Product Flat is enabled cache per store * * @var array */ protected $_flatEnabled = array(); /** * Product websites table name * * @var string */ protected $_productWebsiteTable; /** * Product categories table name * * @var string */ protected $_productCategoryTable; /** * Is add URL rewrites to collection flag * * @var bool */ protected $_addUrlRewrite = false; /** * Add URL rewrite for category * * @var int */ protected $_urlRewriteCategory = ''; /** * Is add minimal price to product collection flag * * @var bool */ protected $_addMinimalPrice = false; /** * Is add final price to product collection flag * * @var unknown_type */ protected $_addFinalPrice = false; /** * Cache for all ids * * @var array */ protected $_allIdsCache = null; /** * Is add tax percents to product collection flag * * @var bool */ protected $_addTaxPercents = false; /** * Product limitation filters * Allowed filters * store_id int; * category_id int; * category_is_anchor int; * visibility array|int; * website_ids array|int; * store_table string; * use_price_index bool; join price index table flag * customer_group_id int; required for price; customer group limitation for price * website_id int; required for price; website limitation for price * * @var array */ protected $_productLimitationFilters = array(); /** * Category product count select * * @var Zend_Db_Select */ protected $_productCountSelect = null; /** * Enter description here ... * * @var bool */ protected $_isWebsiteFilter = false; /** * Additional field filters, applied in _productLimitationJoinPrice() * * @var array */ protected $_priceDataFieldFilters = array(); /** * Map of price fields * * @var array */ protected $_map = array('fields' => array( 'price' => 'price_index.price', 'final_price' => 'price_index.final_price', 'min_price' => 'price_index.min_price', 'max_price' => 'price_index.max_price', 'tier_price' => 'price_index.tier_price', 'special_price' => 'price_index.special_price', )); /** * Price expression sql * * @var string|null */ protected $_priceExpression; /** * Additional price expression sql part * * @var string|null */ protected $_additionalPriceExpression; /** * Max prise (statistics data) * * @var float */ protected $_maxPrice; /** * Min prise (statistics data) * * @var float */ protected $_minPrice; /** * Prise standard deviation (statistics data) * * @var float */ protected $_priceStandardDeviation; /** * Prises count (statistics data) * * @var int */ protected $_pricesCount = null; /** * Cloned Select after dispatching 'catalog_prepare_price_select' event * * @var Varien_Db_Select */ protected $_catalogPreparePriceSelect = null; /** * Get cloned Select after dispatching 'catalog_prepare_price_select' event * * @return Varien_Db_Select */ public function getCatalogPreparedSelect() { return $this->_catalogPreparePriceSelect; } /** * Prepare additional price expression sql part * * @param Varien_Db_Select $select * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _preparePriceExpressionParameters($select) { // prepare response object for event $response = new Varien_Object(); $response->setAdditionalCalculations(array()); $tableAliases = array_keys($select->getPart(Zend_Db_Select::FROM)); if (in_array(self::INDEX_TABLE_ALIAS, $tableAliases)) { $table = self::INDEX_TABLE_ALIAS; } else { $table = reset($tableAliases); } // prepare event arguments $eventArgs = array( 'select' => $select, 'table' => $table, 'store_id' => $this->getStoreId(), 'response_object' => $response ); Mage::dispatchEvent('catalog_prepare_price_select', $eventArgs); $additional = join('', $response->getAdditionalCalculations()); $this->_priceExpression = $table . '.min_price'; $this->_additionalPriceExpression = $additional; $this->_catalogPreparePriceSelect = clone $select; return $this; } /** * Get price expression sql part * * @param Varien_Db_Select $select * @return string */ public function getPriceExpression($select) { if (is_null($this->_priceExpression)) { $this->_preparePriceExpressionParameters($select); } return $this->_priceExpression; } /** * Get additional price expression sql part * * @param Varien_Db_Select $select * @return string */ public function getAdditionalPriceExpression($select) { if (is_null($this->_additionalPriceExpression)) { $this->_preparePriceExpressionParameters($select); } return $this->_additionalPriceExpression; } /** * Get currency rate * * @return float */ public function getCurrencyRate() { return Mage::app()->getStore($this->getStoreId())->getCurrentCurrencyRate(); } /** * Retrieve Catalog Product Flat Helper object * * @return Mage_Catalog_Helper_Product_Flat */ public function getFlatHelper() { return Mage::helper('catalog/product_flat'); } /** * Retrieve is flat enabled flag * Return alvays false if magento run admin * * @return bool */ public function isEnabledFlat() { if (Mage::app()->getStore()->isAdmin()) { return false; } if (!isset($this->_flatEnabled[$this->getStoreId()])) { $this->_flatEnabled[$this->getStoreId()] = $this->getFlatHelper() ->isEnabled($this->getStoreId()); } return $this->_flatEnabled[$this->getStoreId()]; } /** * Initialize resources * */ protected function _construct() { if ($this->isEnabledFlat()) { $this->_init('catalog/product', 'catalog/product_flat'); } else { $this->_init('catalog/product'); } $this->_initTables(); } /** * Define product website and category product tables * */ protected function _initTables() { $this->_productWebsiteTable = $this->getResource()->getTable('catalog/product_website'); $this->_productCategoryTable= $this->getResource()->getTable('catalog/category_product'); } /** * Standard resource collection initalization * * @param string $model * @param unknown_type $entityModel * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _init($model, $entityModel = null) { if ($this->isEnabledFlat()) { $entityModel = 'catalog/product_flat'; } return parent::_init($model, $entityModel); } /** * Prepare static entity fields * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _prepareStaticFields() { if ($this->isEnabledFlat()) { return $this; } return parent::_prepareStaticFields(); } /** * Retrieve collection empty item * Redeclared for specifying id field name without getting resource model inside model * * @return Varien_Object */ public function getNewEmptyItem() { $object = parent::getNewEmptyItem(); if ($this->isEnabledFlat()) { $object->setIdFieldName($this->getEntity()->getIdFieldName()); } return $object; } /** * Set entity to use for attributes * * @param Mage_Eav_Model_Entity_Abstract $entity * @return Mage_Catalog_Model_Resource_Product_Collection */ public function setEntity($entity) { if ($this->isEnabledFlat() && ($entity instanceof Mage_Core_Model_Resource_Db_Abstract)) { $this->_entity = $entity; return $this; } return parent::setEntity($entity); } /** * Set Store scope for collection * * @param mixed $store * @return Mage_Catalog_Model_Resource_Product_Collection */ public function setStore($store) { parent::setStore($store); if ($this->isEnabledFlat()) { $this->getEntity()->setStoreId($this->getStoreId()); } return $this; } /** * Initialize collection select * Redeclared for remove entity_type_id condition * in catalog_product_entity we store just products * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _initSelect() { if ($this->isEnabledFlat()) { $this->getSelect() ->from(array(self::MAIN_TABLE_ALIAS => $this->getEntity()->getFlatTableName()), null) ->columns(array('status' => new Zend_Db_Expr(Mage_Catalog_Model_Product_Status::STATUS_ENABLED))); $this->addAttributeToSelect(array('entity_id', 'type_id', 'attribute_set_id')); if ($this->getFlatHelper()->isAddChildData()) { $this->getSelect() ->where('e.is_child=?', 0); $this->addAttributeToSelect(array('child_id', 'is_child')); } } else { $this->getSelect()->from(array(self::MAIN_TABLE_ALIAS => $this->getEntity()->getEntityTable())); } return $this; } /** * Load attributes into loaded entities * * @param bool $printQuery * @param bool $logQuery * @return Mage_Catalog_Model_Resource_Product_Collection */ public function _loadAttributes($printQuery = false, $logQuery = false) { if ($this->isEnabledFlat()) { return $this; } return parent::_loadAttributes($printQuery, $logQuery); } /** * Add attribute to entities in collection * If $attribute=='*' select all attributes * * @param array|string|integer|Mage_Core_Model_Config_Element $attribute * @param false|string $joinType * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addAttributeToSelect($attribute, $joinType = false) { if ($this->isEnabledFlat()) { if (!is_array($attribute)) { $attribute = array($attribute); } foreach ($attribute as $attributeCode) { if ($attributeCode == '*') { foreach ($this->getEntity()->getAllTableColumns() as $column) { $this->getSelect()->columns('e.' . $column); $this->_selectAttributes[$column] = $column; $this->_staticFields[$column] = $column; } } else { $columns = $this->getEntity()->getAttributeForSelect($attributeCode); if ($columns) { foreach ($columns as $alias => $column) { $this->getSelect()->columns(array($alias => 'e.' . $column)); $this->_selectAttributes[$column] = $column; $this->_staticFields[$column] = $column; } } } } return $this; } return parent::addAttributeToSelect($attribute, $joinType); } /** * Add tax class id attribute to select and join price rules data if needed * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _beforeLoad() { Mage::dispatchEvent('catalog_product_collection_load_before', array('collection' => $this)); return parent::_beforeLoad(); } /** * Processing collection items after loading * Adding url rewrites, minimal prices, final prices, tax percents * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _afterLoad() { if ($this->_addUrlRewrite) { $this->_addUrlRewrite($this->_urlRewriteCategory); } $this->_prepareUrlDataObject(); if (count($this) > 0) { Mage::dispatchEvent('catalog_product_collection_load_after', array('collection' => $this)); } foreach ($this as $product) { if ($product->isRecurring() && $profile = $product->getRecurringProfile()) { $product->setRecurringProfile(unserialize($profile)); } } return $this; } /** * Prepare Url Data object * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _prepareUrlDataObject() { $objects = array(); /** @var $item Mage_Catalog_Model_Product */ foreach ($this->_items as $item) { if ($this->getFlag('do_not_use_category_id')) { $item->setDoNotUseCategoryId(true); } if (!$item->isVisibleInSiteVisibility() && $item->getItemStoreId()) { $objects[$item->getEntityId()] = $item->getItemStoreId(); } } if ($objects && $this->hasFlag('url_data_object')) { $objects = Mage::getResourceSingleton('catalog/url') ->getRewriteByProductStore($objects); foreach ($this->_items as $item) { if (isset($objects[$item->getEntityId()])) { $object = new Varien_Object($objects[$item->getEntityId()]); $item->setUrlDataObject($object); } } } return $this; } /** * Add collection filters by identifiers * * @param mixed $productId * @param boolean $exclude * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addIdFilter($productId, $exclude = false) { if (empty($productId)) { $this->_setIsLoaded(true); return $this; } if (is_array($productId)) { if (!empty($productId)) { if ($exclude) { $condition = array('nin' => $productId); } else { $condition = array('in' => $productId); } } else { $condition = ''; } } else { if ($exclude) { $condition = array('neq' => $productId); } else { $condition = $productId; } } $this->addFieldToFilter('entity_id', $condition); return $this; } /** * Adding product website names to result collection * Add for each product websites information * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addWebsiteNamesToResult() { $productWebsites = array(); foreach ($this as $product) { $productWebsites[$product->getId()] = array(); } if (!empty($productWebsites)) { $select = $this->getConnection()->select() ->from(array('product_website' => $this->_productWebsiteTable)) ->join( array('website' => $this->getResource()->getTable('core/website')), 'website.website_id = product_website.website_id', array('name')) ->where('product_website.product_id IN (?)', array_keys($productWebsites)) ->where('website.website_id > ?', 0); $data = $this->getConnection()->fetchAll($select); foreach ($data as $row) { $productWebsites[$row['product_id']][] = $row['website_id']; } } foreach ($this as $product) { if (isset($productWebsites[$product->getId()])) { $product->setData('websites', $productWebsites[$product->getId()]); } } return $this; } /** * Add store availability filter. Include availability product * for store website * * @param mixed $store * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addStoreFilter($store = null) { if ($store === null) { $store = $this->getStoreId(); } $store = Mage::app()->getStore($store); if (!$store->isAdmin()) { $this->setStoreId($store); $this->_productLimitationFilters['store_id'] = $store->getId(); $this->_applyProductLimitations(); } return $this; } /** * Add website filter to collection * * @param unknown_type $websites * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addWebsiteFilter($websites = null) { if (!is_array($websites)) { $websites = array(Mage::app()->getWebsite($websites)->getId()); } $this->_productLimitationFilters['website_ids'] = $websites; $this->_applyProductLimitations(); return $this; } /** * Get filters applied to collection * * @return array */ public function getLimitationFilters() { return $this->_productLimitationFilters; } /** * Specify category filter for product collection * * @param Mage_Catalog_Model_Category $category * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addCategoryFilter(Mage_Catalog_Model_Category $category) { $this->_productLimitationFilters['category_id'] = $category->getId(); if ($category->getIsAnchor()) { unset($this->_productLimitationFilters['category_is_anchor']); } else { $this->_productLimitationFilters['category_is_anchor'] = 1; } if ($this->getStoreId() == Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID) { $this->_applyZeroStoreProductLimitations(); } else { $this->_applyProductLimitations(); } return $this; } /** * Join minimal price attribute to result * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function joinMinimalPrice() { $this->addAttributeToSelect('price') ->addAttributeToSelect('minimal_price'); return $this; } /** * Retrieve max value by attribute * * @param string $attribute * @return mixed */ public function getMaxAttributeValue($attribute) { $select = clone $this->getSelect(); $attribute = $this->getEntity()->getAttribute($attribute); $attributeCode = $attribute->getAttributeCode(); $tableAlias = $attributeCode . '_max_value'; $fieldAlias = 'max_' . $attributeCode; $condition = 'e.entity_id = ' . $tableAlias . '.entity_id AND '.$this->_getConditionSql($tableAlias . '.attribute_id', $attribute->getId()); $select->join( array($tableAlias => $attribute->getBackend()->getTable()), $condition, array($fieldAlias => new Zend_Db_Expr('MAX('.$tableAlias.'.value)')) ) ->group('e.entity_type_id'); $data = $this->getConnection()->fetchRow($select); if (isset($data[$fieldAlias])) { return $data[$fieldAlias]; } return null; } /** * Retrieve ranging product count for arrtibute range * * @param string $attribute * @param int $range * @return array */ public function getAttributeValueCountByRange($attribute, $range) { $select = clone $this->getSelect(); $attribute = $this->getEntity()->getAttribute($attribute); $attributeCode = $attribute->getAttributeCode(); $tableAlias = $attributeCode . '_range_count_value'; $condition = 'e.entity_id = ' . $tableAlias . '.entity_id AND ' . $this->_getConditionSql($tableAlias . '.attribute_id', $attribute->getId()); $select->reset(Zend_Db_Select::GROUP); $select->join( array($tableAlias => $attribute->getBackend()->getTable()), $condition, array( 'count_' . $attributeCode => new Zend_Db_Expr('COUNT(DISTINCT e.entity_id)'), 'range_' . $attributeCode => new Zend_Db_Expr( 'CEIL((' . $tableAlias . '.value+0.01)/' . $range . ')') ) ) ->group('range_' . $attributeCode); $data = $this->getConnection()->fetchAll($select); $res = array(); foreach ($data as $row) { $res[$row['range_' . $attributeCode]] = $row['count_' . $attributeCode]; } return $res; } /** * Retrieve product count by some value of attribute * * @param string $attribute * @return array($value=>$count) */ public function getAttributeValueCount($attribute) { $select = clone $this->getSelect(); $attribute = $this->getEntity()->getAttribute($attribute); $attributeCode = $attribute->getAttributeCode(); $tableAlias = $attributeCode . '_value_count'; $select->reset(Zend_Db_Select::GROUP); $condition = 'e.entity_id=' . $tableAlias . '.entity_id AND '.$this->_getConditionSql($tableAlias . '.attribute_id', $attribute->getId()); $select->join( array($tableAlias => $attribute->getBackend()->getTable()), $condition, array( 'count_' . $attributeCode => new Zend_Db_Expr('COUNT(DISTINCT e.entity_id)'), 'value_' . $attributeCode => new Zend_Db_Expr($tableAlias . '.value') ) ) ->group('value_' . $attributeCode); $data = $this->getConnection()->fetchAll($select); $res = array(); foreach ($data as $row) { $res[$row['value_' . $attributeCode]] = $row['count_' . $attributeCode]; } return $res; } /** * Return all attribute values as array in form: * array( * [entity_id_1] => array( * [store_id_1] => store_value_1, * [store_id_2] => store_value_2, * ... * [store_id_n] => store_value_n * ), * ... * ) * * @param string $attribute attribute code * @return array */ public function getAllAttributeValues($attribute) { /** @var $select Varien_Db_Select */ $select = clone $this->getSelect(); $attribute = $this->getEntity()->getAttribute($attribute); $select->reset() ->from($attribute->getBackend()->getTable(), array('entity_id', 'store_id', 'value')) ->where('attribute_id = ?', (int)$attribute->getId()); $data = $this->getConnection()->fetchAll($select); $res = array(); foreach ($data as $row) { $res[$row['entity_id']][$row['store_id']] = $row['value']; } return $res; } /** * Get SQL for get record count without left JOINs * * @return Varien_Db_Select */ public function getSelectCountSql() { return $this->_getSelectCountSql(); } /** * Get SQL for get record count * * @param bool $resetLeftJoins * @return Varien_Db_Select */ protected function _getSelectCountSql($select = null, $resetLeftJoins = true) { $this->_renderFilters(); $countSelect = (is_null($select)) ? $this->_getClearSelect() : $this->_buildClearSelect($select); $countSelect->columns('COUNT(DISTINCT e.entity_id)'); if ($resetLeftJoins) { $countSelect->resetJoinLeft(); } return $countSelect; } /** * Prepare statistics data * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _prepareStatisticsData() { $select = clone $this->getSelect(); $priceExpression = $this->getPriceExpression($select) . ' ' . $this->getAdditionalPriceExpression($select); $sqlEndPart = ') * ' . $this->getCurrencyRate() . ', 2)'; $select = $this->_getSelectCountSql($select, false); $select->columns('ROUND(MAX(' . $priceExpression . $sqlEndPart); $select->columns('ROUND(MIN(' . $priceExpression . $sqlEndPart); $select->columns($this->getConnection()->getStandardDeviationSql('ROUND((' . $priceExpression . $sqlEndPart)); $select->where($this->getPriceExpression($select) . ' IS NOT NULL'); $row = $this->getConnection()->fetchRow($select, $this->_bindParams, Zend_Db::FETCH_NUM); $this->_pricesCount = (int)$row[0]; $this->_maxPrice = (float)$row[1]; $this->_minPrice = (float)$row[2]; $this->_priceStandardDeviation = (float)$row[3]; return $this; } /** * Retreive clear select * * @return Varien_Db_Select */ protected function _getClearSelect() { return $this->_buildClearSelect(); } /** * Build clear select * * @param Varien_Db_Select $select * @return Varien_Db_Select */ protected function _buildClearSelect($select = null) { if (is_null($select)) { $select = clone $this->getSelect(); } $select->reset(Zend_Db_Select::ORDER); $select->reset(Zend_Db_Select::LIMIT_COUNT); $select->reset(Zend_Db_Select::LIMIT_OFFSET); $select->reset(Zend_Db_Select::COLUMNS); return $select; } /** * Retrive all ids for collection * * @param unknown_type $limit * @param unknown_type $offset * @return array */ public function getAllIds($limit = null, $offset = null) { $idsSelect = $this->_getClearSelect(); $idsSelect->columns('e.' . $this->getEntity()->getIdFieldName()); $idsSelect->limit($limit, $offset); $idsSelect->resetJoinLeft(); return $this->getConnection()->fetchCol($idsSelect, $this->_bindParams); } /** * Retreive product count select for categories * * @return Varien_Db_Select */ public function getProductCountSelect() { if ($this->_productCountSelect === null) { $this->_productCountSelect = clone $this->getSelect(); $this->_productCountSelect->reset(Zend_Db_Select::COLUMNS) ->reset(Zend_Db_Select::GROUP) ->reset(Zend_Db_Select::ORDER) ->distinct(false) ->join(array('count_table' => $this->getTable('catalog/category_product_index')), 'count_table.product_id = e.entity_id', array( 'count_table.category_id', 'product_count' => new Zend_Db_Expr('COUNT(DISTINCT count_table.product_id)') ) ) ->where('count_table.store_id = ?', $this->getStoreId()) ->group('count_table.category_id'); } return $this->_productCountSelect; } /** * Destruct product count select * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function unsProductCountSelect() { $this->_productCountSelect = null; return $this; } /** * Adding product count to categories collection * * @param Mage_Eav_Model_Entity_Collection_Abstract $categoryCollection * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addCountToCategories($categoryCollection) { $isAnchor = array(); $isNotAnchor = array(); foreach ($categoryCollection as $category) { if ($category->getIsAnchor()) { $isAnchor[] = $category->getId(); } else { $isNotAnchor[] = $category->getId(); } } $productCounts = array(); if ($isAnchor || $isNotAnchor) { $select = $this->getProductCountSelect(); Mage::dispatchEvent( 'catalog_product_collection_before_add_count_to_categories', array('collection' => $this) ); if ($isAnchor) { $anchorStmt = clone $select; $anchorStmt->limit(); //reset limits $anchorStmt->where('count_table.category_id IN (?)', $isAnchor); $productCounts += $this->getConnection()->fetchPairs($anchorStmt); $anchorStmt = null; } if ($isNotAnchor) { $notAnchorStmt = clone $select; $notAnchorStmt->limit(); //reset limits $notAnchorStmt->where('count_table.category_id IN (?)', $isNotAnchor); $notAnchorStmt->where('count_table.is_parent = 1'); $productCounts += $this->getConnection()->fetchPairs($notAnchorStmt); $notAnchorStmt = null; } $select = null; $this->unsProductCountSelect(); } foreach ($categoryCollection as $category) { $_count = 0; if (isset($productCounts[$category->getId()])) { $_count = $productCounts[$category->getId()]; } $category->setProductCount($_count); } return $this; } /** * Retrieve unique attribute set ids in collection * * @return array */ public function getSetIds() { $select = clone $this->getSelect(); /** @var $select Varien_Db_Select */ $select->reset(Zend_Db_Select::COLUMNS); $select->distinct(true); $select->columns('attribute_set_id'); return $this->getConnection()->fetchCol($select); } /** * Return array of unique product type ids in collection * * @return array */ public function getProductTypeIds() { $select = clone $this->getSelect(); /** @var $select Varien_Db_Select */ $select->reset(Zend_Db_Select::COLUMNS); $select->distinct(true); $select->columns('type_id'); return $this->getConnection()->fetchCol($select); } /** * Joins url rewrite rules to collection * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function joinUrlRewrite() { $this->joinTable( 'core/url_rewrite', 'entity_id=entity_id', array('request_path'), '{{table}}.type = ' . Mage_Core_Model_Url_Rewrite::TYPE_PRODUCT, 'left' ); return $this; } /** * Add URL rewrites data to product * If collection loadded - run processing else set flag * * @param int $categoryId * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addUrlRewrite($categoryId = '') { $this->_addUrlRewrite = true; if (Mage::getStoreConfig(Mage_Catalog_Helper_Product::XML_PATH_PRODUCT_URL_USE_CATEGORY, $this->getStoreId())) { $this->_urlRewriteCategory = $categoryId; } else { $this->_urlRewriteCategory = 0; } if ($this->isLoaded()) { $this->_addUrlRewrite(); } return $this; } /** * Add URL rewrites to collection * */ protected function _addUrlRewrite() { $urlRewrites = null; if ($this->_cacheConf) { if (!($urlRewrites = Mage::app()->loadCache($this->_cacheConf['prefix'] . 'urlrewrite'))) { $urlRewrites = null; } else { $urlRewrites = unserialize($urlRewrites); } } if (!$urlRewrites) { $productIds = array(); foreach($this->getItems() as $item) { $productIds[] = $item->getEntityId(); } if (!count($productIds)) { return; } $select = $this->getConnection()->select() ->from($this->getTable('core/url_rewrite'), array('product_id', 'request_path')) ->where('store_id = ?', Mage::app()->getStore()->getId()) ->where('is_system = ?', 1) ->where('category_id = ? OR category_id IS NULL', $this->_urlRewriteCategory) ->where('product_id IN(?)', $productIds) ->order('category_id ' . self::SORT_ORDER_DESC); // more priority is data with category id $urlRewrites = array(); foreach ($this->getConnection()->fetchAll($select) as $row) { if (!isset($urlRewrites[$row['product_id']])) { $urlRewrites[$row['product_id']] = $row['request_path']; } } if ($this->_cacheConf) { Mage::app()->saveCache( serialize($urlRewrites), $this->_cacheConf['prefix'] . 'urlrewrite', array_merge($this->_cacheConf['tags'], array(Mage_Catalog_Model_Product_Url::CACHE_TAG)), $this->_cacheLifetime ); } } foreach($this->getItems() as $item) { if (isset($urlRewrites[$item->getEntityId()])) { $item->setData('request_path', $urlRewrites[$item->getEntityId()]); } else { $item->setData('request_path', false); } } } /** * Add minimal price data to result * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addMinimalPrice() { return $this->addPriceData(); } /** * Add minimal price to product collection * * @deprecated sinse 1.3.2.2 * @see Mage_Catalog_Model_Resource_Product_Collection::addPriceData * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _addMinimalPrice() { return $this; } /** * Add price data for calculate final price * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addFinalPrice() { return $this->addPriceData(); } /** * Join prices from price rules to products collection * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _joinPriceRules() { if ($this->isEnabledFlat()) { $customerGroup = Mage::getSingleton('customer/session')->getCustomerGroupId(); $priceColumn = 'e.display_price_group_' . $customerGroup; $this->getSelect()->columns(array('_rule_price' => $priceColumn)); return $this; } if (!Mage::helper('catalog')->isModuleEnabled('Mage_CatalogRule')) { return $this; } $wId = Mage::app()->getWebsite()->getId(); $gId = Mage::getSingleton('customer/session')->getCustomerGroupId(); $storeDate = Mage::app()->getLocale()->storeTimeStamp($this->getStoreId()); $conditions = 'price_rule.product_id = e.entity_id AND '; $conditions .= "price_rule.rule_date = '".$this->getResource()->formatDate($storeDate, false)."' AND "; $conditions .= $this->getConnection()->quoteInto('price_rule.website_id = ? AND', $wId); $conditions .= $this->getConnection()->quoteInto('price_rule.customer_group_id = ?', $gId); $this->getSelect()->joinLeft( array('price_rule' => $this->getTable('catalogrule/rule_product_price')), $conditions, array('rule_price' => 'rule_price') ); return $this; } /** * Add final price to the product * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _addFinalPrice() { foreach ($this->_items as $product) { $basePrice = $product->getPrice(); $specialPrice = $product->getSpecialPrice(); $specialPriceFrom = $product->getSpecialFromDate(); $specialPriceTo = $product->getSpecialToDate(); if ($this->isEnabledFlat()) { $rulePrice = null; if ($product->getData('_rule_price') != $basePrice) { $rulePrice = $product->getData('_rule_price'); } } else { $rulePrice = $product->getData('_rule_price'); } $finalPrice = $product->getPriceModel()->calculatePrice( $basePrice, $specialPrice, $specialPriceFrom, $specialPriceTo, $rulePrice, null, null, $product->getId() ); $product->setCalculatedFinalPrice($finalPrice); } return $this; } /** * Retreive all ids * * @param boolean $resetCache * @return array */ public function getAllIdsCache($resetCache = false) { $ids = null; if (!$resetCache) { $ids = $this->_allIdsCache; } if (is_null($ids)) { $ids = $this->getAllIds(); $this->setAllIdsCache($ids); } return $ids; } /** * Set all ids * * @param array $value * @return Mage_Catalog_Model_Resource_Product_Collection */ public function setAllIdsCache($value) { $this->_allIdsCache = $value; return $this; } /** * Add Price Data to result * * @param int $customerGroupId * @param int $websiteId * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addPriceData($customerGroupId = null, $websiteId = null) { $this->_productLimitationFilters['use_price_index'] = true; if (!isset($this->_productLimitationFilters['customer_group_id']) && is_null($customerGroupId)) { $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId(); } if (!isset($this->_productLimitationFilters['website_id']) && is_null($websiteId)) { $websiteId = Mage::app()->getStore($this->getStoreId())->getWebsiteId(); } if (!is_null($customerGroupId)) { $this->_productLimitationFilters['customer_group_id'] = $customerGroupId; } if (!is_null($websiteId)) { $this->_productLimitationFilters['website_id'] = $websiteId; } $this->_applyProductLimitations(); return $this; } /** * Add attribute to filter * * @param Mage_Eav_Model_Entity_Attribute_Abstract|string $attribute * @param array $condition * @param string $joinType * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner') { if ($this->isEnabledFlat()) { if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { $attribute = $attribute->getAttributeCode(); } if (is_array($attribute)) { $sqlArr = array(); foreach ($attribute as $condition) { $sqlArr[] = $this->_getAttributeConditionSql($condition['attribute'], $condition, $joinType); } $conditionSql = '('.join(') OR (', $sqlArr).')'; $this->getSelect()->where($conditionSql); return $this; } if (!isset($this->_selectAttributes[$attribute])) { $this->addAttributeToSelect($attribute); } if (isset($this->_selectAttributes[$attribute])) { $this->getSelect()->where($this->_getConditionSql('e.' . $attribute, $condition)); } return $this; } $this->_allIdsCache = null; if (is_string($attribute) && $attribute == 'is_saleable') { $columns = $this->getSelect()->getPart(Zend_Db_Select::COLUMNS); foreach ($columns as $columnEntry) { list($correlationName, $column, $alias) = $columnEntry; if ($alias == 'is_saleable') { if ($column instanceof Zend_Db_Expr) { $field = $column; } else { $adapter = $this->getSelect()->getAdapter(); if (empty($correlationName)) { $field = $adapter->quoteColumnAs($column, $alias, true); } else { $field = $adapter->quoteColumnAs(array($correlationName, $column), $alias, true); } } $this->getSelect()->where("{$field} = ?", $condition); break; } } return $this; } else { return parent::addAttributeToFilter($attribute, $condition, $joinType); } } /** * Add requere tax percent flag for product collection * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addTaxPercents() { $this->_addTaxPercents = true; return $this; } /** * Get require tax percent flag value * * @return bool */ public function requireTaxPercent() { return $this->_addTaxPercents; } /** * Enter description here ... * * @deprecated from 1.3.0 * */ protected function _addTaxPercents() { $classToRate = array(); $request = Mage::getSingleton('tax/calculation')->getRateRequest(); foreach ($this as &$item) { if (null === $item->getTaxClassId()) { $item->setTaxClassId($item->getMinimalTaxClassId()); } if (!isset($classToRate[$item->getTaxClassId()])) { $request->setProductClassId($item->getTaxClassId()); $classToRate[$item->getTaxClassId()] = Mage::getSingleton('tax/calculation')->getRate($request); } $item->setTaxPercent($classToRate[$item->getTaxClassId()]); } } /** * Adding product custom options to result collection * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addOptionsToResult() { $productIds = array(); foreach ($this as $product) { $productIds[] = $product->getId(); } if (!empty($productIds)) { $options = Mage::getModel('catalog/product_option') ->getCollection() ->addTitleToResult(Mage::app()->getStore()->getId()) ->addPriceToResult(Mage::app()->getStore()->getId()) ->addProductToFilter($productIds) ->addValuesToResult(); foreach ($options as $option) { if($this->getItemById($option->getProductId())) { $this->getItemById($option->getProductId())->addOption($option); } } } return $this; } /** * Filter products with required options * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addFilterByRequiredOptions() { $this->addAttributeToFilter('required_options', array(array('neq' => '1'), array('null' => true)), 'left'); return $this; } /** * Set product visibility filter for enabled products * * @param array $visibility * @return Mage_Catalog_Model_Resource_Product_Collection */ public function setVisibility($visibility) { $this->_productLimitationFilters['visibility'] = $visibility; $this->_applyProductLimitations(); return $this; } /** * Add attribute to sort order * * @param string $attribute * @param string $dir * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) { if ($attribute == 'position') { if (isset($this->_joinFields[$attribute])) { $this->getSelect()->order($this->_getAttributeFieldName($attribute) . ' ' . $dir); return $this; } if ($this->isEnabledFlat()) { $this->getSelect()->order("cat_index_position {$dir}"); } // optimize if using cat index $filters = $this->_productLimitationFilters; if (isset($filters['category_id']) || isset($filters['visibility'])) { $this->getSelect()->order('cat_index.position ' . $dir); } else { $this->getSelect()->order('e.entity_id ' . $dir); } return $this; } elseif($attribute == 'is_saleable'){ $this->getSelect()->order("is_saleable " . $dir); return $this; } $storeId = $this->getStoreId(); if ($attribute == 'price' && $storeId != 0) { $this->addPriceData(); $this->getSelect()->order("price_index.min_price {$dir}"); return $this; } if ($this->isEnabledFlat()) { $column = $this->getEntity()->getAttributeSortColumn($attribute); if ($column) { $this->getSelect()->order("e.{$column} {$dir}"); } else if (isset($this->_joinFields[$attribute])) { $this->getSelect()->order($this->_getAttributeFieldName($attribute) . ' ' . $dir); } return $this; } else { $attrInstance = $this->getEntity()->getAttribute($attribute); if ($attrInstance && $attrInstance->usesSource()) { $attrInstance->getSource() ->addValueSortToCollection($this, $dir); return $this; } } return parent::addAttributeToSort($attribute, $dir); } /** * Prepare limitation filters * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _prepareProductLimitationFilters() { if (isset($this->_productLimitationFilters['visibility']) && !isset($this->_productLimitationFilters['store_id']) ) { $this->_productLimitationFilters['store_id'] = $this->getStoreId(); } if (isset($this->_productLimitationFilters['category_id']) && !isset($this->_productLimitationFilters['store_id']) ) { $this->_productLimitationFilters['store_id'] = $this->getStoreId(); } if (isset($this->_productLimitationFilters['store_id']) && isset($this->_productLimitationFilters['visibility']) && !isset($this->_productLimitationFilters['category_id']) ) { $this->_productLimitationFilters['category_id'] = Mage::app() ->getStore($this->_productLimitationFilters['store_id']) ->getRootCategoryId(); } return $this; } /** * Join website product limitation * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _productLimitationJoinWebsite() { $joinWebsite = false; $filters = $this->_productLimitationFilters; $conditions = array('product_website.product_id = e.entity_id'); if (isset($filters['website_ids'])) { $joinWebsite = true; if (count($filters['website_ids']) > 1) { $this->getSelect()->distinct(true); } $conditions[] = $this->getConnection() ->quoteInto('product_website.website_id IN(?)', $filters['website_ids']); } elseif (isset($filters['store_id']) && (!isset($filters['visibility']) && !isset($filters['category_id'])) && !$this->isEnabledFlat() ) { $joinWebsite = true; $websiteId = Mage::app()->getStore($filters['store_id'])->getWebsiteId(); $conditions[] = $this->getConnection() ->quoteInto('product_website.website_id = ?', $websiteId); } $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM); if (isset($fromPart['product_website'])) { if (!$joinWebsite) { unset($fromPart['product_website']); } else { $fromPart['product_website']['joinCondition'] = join(' AND ', $conditions); } $this->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart); } elseif ($joinWebsite) { $this->getSelect()->join( array('product_website' => $this->getTable('catalog/product_website')), join(' AND ', $conditions), array() ); } return $this; } /** * Join additional (alternative) store visibility filter * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _productLimitationJoinStore() { $filters = $this->_productLimitationFilters; if (!isset($filters['store_table'])) { return $this; } $hasColumn = false; foreach ($this->getSelect()->getPart(Zend_Db_Select::COLUMNS) as $columnEntry) { list(,,$alias) = $columnEntry; if ($alias == 'visibility') { $hasColumn = true; } } if (!$hasColumn) { $this->getSelect()->columns('visibility', 'cat_index'); } $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM); if (!isset($fromPart['store_index'])) { $this->getSelect()->joinLeft( array('store_index' => $this->getTable('core/store')), 'store_index.store_id = ' . $filters['store_table'] . '.store_id', array() ); } if (!isset($fromPart['store_group_index'])) { $this->getSelect()->joinLeft( array('store_group_index' => $this->getTable('core/store_group')), 'store_index.group_id = store_group_index.group_id', array() ); } if (!isset($fromPart['store_cat_index'])) { $this->getSelect()->joinLeft( array('store_cat_index' => $this->getTable('catalog/category_product_index')), join(' AND ', array( 'store_cat_index.product_id = e.entity_id', 'store_cat_index.store_id = ' . $filters['store_table'] . '.store_id', 'store_cat_index.category_id=store_group_index.root_category_id' )), array('store_visibility' => 'visibility') ); } // Avoid column duplication problems Mage::getResourceHelper('core')->prepareColumnsList($this->getSelect()); $whereCond = join(' OR ', array( $this->getConnection()->quoteInto('cat_index.visibility IN(?)', $filters['visibility']), $this->getConnection()->quoteInto('store_cat_index.visibility IN(?)', $filters['visibility']) )); $wherePart = $this->getSelect()->getPart(Zend_Db_Select::WHERE); $hasCond = false; foreach ($wherePart as $cond) { if ($cond == '(' . $whereCond . ')') { $hasCond = true; } } if (!$hasCond) { $this->getSelect()->where($whereCond); } return $this; } /** * Join Product Price Table * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _productLimitationJoinPrice() { return $this->_productLimitationPrice(); } /** * Join Product Price Table with left-join possibility * * @see Mage_Catalog_Model_Resource_Product_Collection::_productLimitationJoinPrice() * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _productLimitationPrice($joinLeft = false) { $filters = $this->_productLimitationFilters; if (empty($filters['use_price_index'])) { return $this; } $helper = Mage::getResourceHelper('core'); $connection = $this->getConnection(); $select = $this->getSelect(); $joinCond = join(' AND ', array( 'price_index.entity_id = e.entity_id', $connection->quoteInto('price_index.website_id = ?', $filters['website_id']), $connection->quoteInto('price_index.customer_group_id = ?', $filters['customer_group_id']) )); $fromPart = $select->getPart(Zend_Db_Select::FROM); if (!isset($fromPart['price_index'])) { $least = $connection->getLeastSql(array('price_index.min_price', 'price_index.tier_price')); $minimalExpr = $connection->getCheckSql('price_index.tier_price IS NOT NULL', $least, 'price_index.min_price'); $colls = array('price', 'tax_class_id', 'final_price', 'minimal_price' => $minimalExpr , 'min_price', 'max_price', 'tier_price'); $tableName = array('price_index' => $this->getTable('catalog/product_index_price')); if ($joinLeft) { $select->joinLeft($tableName, $joinCond, $colls); } else { $select->join($tableName, $joinCond, $colls); } // Set additional field filters foreach ($this->_priceDataFieldFilters as $filterData) { $select->where(call_user_func_array('sprintf', $filterData)); } } else { $fromPart['price_index']['joinCondition'] = $joinCond; $select->setPart(Zend_Db_Select::FROM, $fromPart); } //Clean duplicated fields $helper->prepareColumnsList($select); return $this; } /** * Apply front-end price limitation filters to the collection * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function applyFrontendPriceLimitations() { $this->_productLimitationFilters['use_price_index'] = true; if (!isset($this->_productLimitationFilters['customer_group_id'])) { $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId(); $this->_productLimitationFilters['customer_group_id'] = $customerGroupId; } if (!isset($this->_productLimitationFilters['website_id'])) { $websiteId = Mage::app()->getStore($this->getStoreId())->getWebsiteId(); $this->_productLimitationFilters['website_id'] = $websiteId; } $this->_applyProductLimitations(); return $this; } /** * Apply limitation filters to collection * Method allows using one time category product index table (or product website table) * for different combinations of store_id/category_id/visibility filter states * Method supports multiple changes in one collection object for this parameters * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _applyProductLimitations() { $this->_prepareProductLimitationFilters(); $this->_productLimitationJoinWebsite(); $this->_productLimitationJoinPrice(); $filters = $this->_productLimitationFilters; if (!isset($filters['category_id']) && !isset($filters['visibility'])) { return $this; } $conditions = array( 'cat_index.product_id=e.entity_id', $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id']) ); if (isset($filters['visibility']) && !isset($filters['store_table'])) { $conditions[] = $this->getConnection() ->quoteInto('cat_index.visibility IN(?)', $filters['visibility']); } $conditions[] = $this->getConnection() ->quoteInto('cat_index.category_id=?', $filters['category_id']); if (isset($filters['category_is_anchor'])) { $conditions[] = $this->getConnection() ->quoteInto('cat_index.is_parent=?', $filters['category_is_anchor']); } $joinCond = join(' AND ', $conditions); $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM); if (isset($fromPart['cat_index'])) { $fromPart['cat_index']['joinCondition'] = $joinCond; $this->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart); } else { $this->getSelect()->join( array('cat_index' => $this->getTable('catalog/category_product_index')), $joinCond, array('cat_index_position' => 'position') ); } $this->_productLimitationJoinStore(); Mage::dispatchEvent('catalog_product_collection_apply_limitations_after', array( 'collection' => $this )); return $this; } /** * Apply limitation filters to collection base on API * Method allows using one time category product table * for combinations of category_id filter states * * @return Mage_Catalog_Model_Resource_Product_Collection */ protected function _applyZeroStoreProductLimitations() { $filters = $this->_productLimitationFilters; $conditions = array( 'cat_pro.product_id=e.entity_id', $this->getConnection()->quoteInto('cat_pro.category_id=?', $filters['category_id']) ); $joinCond = join(' AND ', $conditions); $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM); if (isset($fromPart['cat_pro'])) { $fromPart['cat_pro']['joinCondition'] = $joinCond; $this->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart); } else { $this->getSelect()->join( array('cat_pro' => $this->getTable('catalog/category_product')), $joinCond, array('cat_index_position' => 'position') ); } $this->_joinFields['position'] = array( 'table' => 'cat_pro', 'field' => 'position', ); return $this; } /** * Add category ids to loaded items * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addCategoryIds() { if ($this->getFlag('category_ids_added')) { return $this; } $ids = array_keys($this->_items); if (empty($ids)) { return $this; } $select = $this->getConnection()->select(); $select->from($this->_productCategoryTable, array('product_id', 'category_id')); $select->where('product_id IN (?)', $ids); $data = $this->getConnection()->fetchAll($select); $categoryIds = array(); foreach ($data as $info) { if (isset($categoryIds[$info['product_id']])) { $categoryIds[$info['product_id']][] = $info['category_id']; } else { $categoryIds[$info['product_id']] = array($info['category_id']); } } foreach ($this->getItems() as $item) { $productId = $item->getId(); if (isset($categoryIds[$productId])) { $item->setCategoryIds($categoryIds[$productId]); } else { $item->setCategoryIds(array()); } } $this->setFlag('category_ids_added', true); return $this; } /** * Add tier price data to loaded items * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function addTierPriceData() { if ($this->getFlag('tier_price_added')) { return $this; } $tierPrices = array(); $productIds = array(); foreach ($this->getItems() as $item) { $productIds[] = $item->getId(); $tierPrices[$item->getId()] = array(); } if (!$productIds) { return $this; } /** @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */ $attribute = $this->getAttribute('tier_price'); if ($attribute->isScopeGlobal()) { $websiteId = 0; } else if ($this->getStoreId()) { $websiteId = Mage::app()->getStore($this->getStoreId())->getWebsiteId(); } $adapter = $this->getConnection(); $columns = array( 'price_id' => 'value_id', 'website_id' => 'website_id', 'all_groups' => 'all_groups', 'cust_group' => 'customer_group_id', 'price_qty' => 'qty', 'price' => 'value', 'product_id' => 'entity_id' ); $select = $adapter->select() ->from($this->getTable('catalog/product_attribute_tier_price'), $columns) ->where('entity_id IN(?)', $productIds) ->order(array('entity_id','qty')); if ($websiteId == '0') { $select->where('website_id = ?', $websiteId); } else { $select->where('website_id IN(?)', array('0', $websiteId)); } foreach ($adapter->fetchAll($select) as $row) { $tierPrices[$row['product_id']][] = array( 'website_id' => $row['website_id'], 'cust_group' => $row['all_groups'] ? Mage_Customer_Model_Group::CUST_GROUP_ALL : $row['cust_group'], 'price_qty' => $row['price_qty'], 'price' => $row['price'], 'website_price' => $row['price'], ); } /* @var $backend Mage_Catalog_Model_Product_Attribute_Backend_Tierprice */ $backend = $attribute->getBackend(); foreach ($this->getItems() as $item) { $data = $tierPrices[$item->getId()]; if (!empty($data) && $websiteId) { $data = $backend->preparePriceData($data, $item->getTypeId(), $websiteId); } $item->setData('tier_price', $data); } $this->setFlag('tier_price_added', true); return $this; } /** * Add field comparison expression * * @param string $comparisonFormat - expression for sprintf() * @param array $fields - list of fields * @return Mage_Catalog_Model_Resource_Product_Collection * @throws Exception */ public function addPriceDataFieldFilter($comparisonFormat, $fields) { if (!preg_match('/^%s( (<|>|=|<=|>=|<>) %s)*$/', $comparisonFormat)) { throw new Exception('Invalid comparison format.'); } if (!is_array($fields)) { $fields = array($fields); } foreach ($fields as $key => $field) { $fields[$key] = $this->_getMappedField($field); } $this->_priceDataFieldFilters[] = array_merge(array($comparisonFormat), $fields); return $this; } /** * Clear collection * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function clear() { foreach ($this->_items as $i => $item) { if ($item->hasStockItem()) { $item->unsStockItem(); } $item = $this->_items[$i] = null; } foreach ($this->_itemsById as $i => $item) { $item = $this->_itemsById[$i] = null; } unset($this->_items, $this->_data, $this->_itemsById); $this->_data = array(); $this->_itemsById = array(); return parent::clear(); } /** * Set Order field * * @param string $attribute * @param string $dir * @return Mage_Catalog_Model_Resource_Product_Collection */ public function setOrder($attribute, $dir = 'desc') { if ($attribute == 'price') { $this->addAttributeToSort($attribute, $dir); } else { parent::setOrder($attribute, $dir); } return $this; } /** * Get products max price * * @return float */ public function getMaxPrice() { if (is_null($this->_maxPrice)) { $this->_prepareStatisticsData(); } return $this->_maxPrice; } /** * Get products min price * * @return float */ public function getMinPrice() { if (is_null($this->_minPrice)) { $this->_prepareStatisticsData(); } return $this->_minPrice; } /** * Get standard deviation of products price * * @return float */ public function getPriceStandardDeviation() { if (is_null($this->_priceStandardDeviation)) { $this->_prepareStatisticsData(); } return $this->_priceStandardDeviation; } /** * Get count of product prices * * @return int */ public function getPricesCount() { if (is_null($this->_pricesCount)) { $this->_prepareStatisticsData(); } return $this->_pricesCount; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product linked products collection * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product_Link_Product_Collection extends Mage_Catalog_Model_Resource_Product_Collection { /** * Store product model * * @var Mage_Catalog_Model_Product */ protected $_product; /** * Store product link model * * @var Mage_Catalog_Model_Product_Link */ protected $_linkModel; /** * Store link type id * * @var int */ protected $_linkTypeId; /** * Store strong mode flag that determine if needed for inner join or left join of linked products * * @var bool */ protected $_isStrongMode; /** * Store flag that determine if product filter was enabled * * @var bool */ protected $_hasLinkFilter = false; /** * Declare link model and initialize type attributes join * * @param Mage_Catalog_Model_Product_Link $linkModel * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setLinkModel(Mage_Catalog_Model_Product_Link $linkModel) { $this->_linkModel = $linkModel; if ($linkModel->getLinkTypeId()) { $this->_linkTypeId = $linkModel->getLinkTypeId(); } return $this; } /** * Enable strong mode for inner join of linked products * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setIsStrongMode() { $this->_isStrongMode = true; return $this; } /** * Retrieve collection link model * * @return Mage_Catalog_Model_Product_Link */ public function getLinkModel() { return $this->_linkModel; } /** * Initialize collection parent product and add limitation join * * @param Mage_Catalog_Model_Product $product * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setProduct(Mage_Catalog_Model_Product $product) { $this->_product = $product; if ($product && $product->getId()) { $this->_hasLinkFilter = true; } return $this; } /** * Retrieve collection base product object * * @return Mage_Catalog_Model_Product */ public function getProduct() { return $this->_product; } /** * Exclude products from filter * * @param array $products * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function addExcludeProductFilter($products) { if (!empty($products)) { if (!is_array($products)) { $products = array($products); } $this->_hasLinkFilter = true; $this->getSelect()->where('links.linked_product_id NOT IN (?)', $products); } return $this; } /** * Add products to filter * * @param array|int|string $products * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function addProductFilter($products) { if (!empty($products)) { if (!is_array($products)) { $products = array($products); } $this->getSelect()->where('links.product_id IN (?)', $products); $this->_hasLinkFilter = true; } return $this; } /** * Add random sorting order * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setRandomOrder() { $this->getSelect()->orderRand('main_table.entity_id'); return $this; } /** * Setting group by to exclude duplications in collection * * @param string $groupBy * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setGroupBy($groupBy = 'e.entity_id') { $this->getSelect()->group($groupBy); /* * Allow Analytic functions usage */ $this->_useAnalyticFunction = true; return $this; } /** * Join linked products when specified link model * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ protected function _beforeLoad() { if ($this->getLinkModel()) { $this->_joinLinks(); } return parent::_beforeLoad(); } /** * Join linked products and their attributes * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ protected function _joinLinks() { $select = $this->getSelect(); $adapter = $select->getAdapter(); $joinCondition = array( 'links.linked_product_id = e.entity_id', $adapter->quoteInto('links.link_type_id = ?', $this->_linkTypeId) ); $joinType = 'join'; if ($this->getProduct() && $this->getProduct()->getId()) { $productId = $this->getProduct()->getId(); if ($this->_isStrongMode) { $this->getSelect()->where('links.product_id = ?', (int)$productId); } else { $joinType = 'joinLeft'; $joinCondition[] = $adapter->quoteInto('links.product_id = ?', $productId); } $this->addFieldToFilter('entity_id', array('neq' => $productId)); } else if ($this->_isStrongMode) { $this->addFieldToFilter('entity_id', array('eq' => -1)); } if($this->_hasLinkFilter) { $select->$joinType( array('links' => $this->getTable('catalog/product_link')), implode(' AND ', $joinCondition), array('link_id') ); $this->joinAttributes(); } return $this; } /** * Enable sorting products by its position * * @param string $dir sort type asc|desc * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setPositionOrder($dir = self::SORT_ORDER_ASC) { if ($this->_hasLinkFilter) { $this->getSelect()->order('position ' . $dir); } return $this; } /** * Enable sorting products by its attribute set name * * @param string $dir sort type asc|desc * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setAttributeSetIdOrder($dir = self::SORT_ORDER_ASC) { $this->getSelect() ->joinLeft( array('set' => $this->getTable('eav/attribute_set')), 'e.attribute_set_id = set.attribute_set_id', array('attribute_set_name') ) ->order('set.attribute_set_name ' . $dir); return $this; } /** * Join attributes * * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function joinAttributes() { if (!$this->getLinkModel()) { return $this; } $attributes = $this->getLinkModel()->getAttributes(); $attributesByType = array(); foreach ($attributes as $attribute) { $table = $this->getLinkModel()->getAttributeTypeTable($attribute['type']); $alias = sprintf('link_attribute_%s_%s', $attribute['code'], $attribute['type']); $joinCondiotion = array( "{$alias}.link_id = links.link_id", $this->getSelect()->getAdapter()->quoteInto("{$alias}.product_link_attribute_id = ?", $attribute['id']) ); $this->getSelect()->joinLeft( array($alias => $table), implode(' AND ', $joinCondiotion), array($attribute['code'] => 'value') ); } return $this; } /** * Set sorting order * * $attribute can also be an array of attributes * * @param string|array $attribute * @param string $dir * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection */ public function setOrder($attribute, $dir = self::SORT_ORDER_ASC) { if ($attribute == 'position') { return $this->setPositionOrder($dir); } elseif ($attribute == 'attribute_set_id') { return $this->setAttributeSetIdOrder($dir); } return parent::setOrder($attribute, $dir); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product custom option resource model * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product_Option extends Mage_Core_Model_Resource_Db_Abstract { /** * Define main table and initialize connection * */ protected function _construct() { $this->_init('catalog/product_option', 'option_id'); } /** * Save options store data * * @param Mage_Core_Model_Abstract $object * @return Mage_Core_Model_Resource_Db_Abstract */ protected function _afterSave(Mage_Core_Model_Abstract $object) { $this->_saveValuePrices($object); $this->_saveValueTitles($object); return parent::_afterSave($object); } /** * Save value prices * * @param Mage_Core_Model_Abstract $object * @return Mage_Catalog_Model_Resource_Product_Option */ protected function _saveValuePrices(Mage_Core_Model_Abstract $object) { $priceTable = $this->getTable('catalog/product_option_price'); $readAdapter = $this->_getReadAdapter(); $writeAdapter = $this->_getWriteAdapter(); /* * Better to check param 'price' and 'price_type' for saving. * If there is not price skip saving price */ if ($object->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_FIELD || $object->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_AREA || $object->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_FILE || $object->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE || $object->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE_TIME || $object->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_TIME ) { //save for store_id = 0 if (!$object->getData('scope', 'price')) { $statement = $readAdapter->select() ->from($priceTable, 'option_id') ->where('option_id = ?', $object->getId()) ->where('store_id = ?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID); $optionId = $readAdapter->fetchOne($statement); if ($optionId) { if ($object->getStoreId() == '0') { $data = $this->_prepareDataForTable( new Varien_Object( array( 'price' => $object->getPrice(), 'price_type' => $object->getPriceType()) ), $priceTable ); $writeAdapter->update( $priceTable, $data, array( 'option_id = ?' => $object->getId(), 'store_id = ?' => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID, ) ); } } else { $data = $this->_prepareDataForTable( new Varien_Object( array( 'option_id' => $object->getId(), 'store_id' => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID, 'price' => $object->getPrice(), 'price_type' => $object->getPriceType() ) ), $priceTable ); $writeAdapter->insert($priceTable, $data); } } $scope = (int) Mage::app()->getStore()->getConfig(Mage_Core_Model_Store::XML_PATH_PRICE_SCOPE); if ($object->getStoreId() != '0' && $scope == Mage_Core_Model_Store::PRICE_SCOPE_WEBSITE && !$object->getData('scope', 'price')) { $baseCurrency = Mage::app()->getBaseCurrencyCode(); $storeIds = Mage::app()->getStore($object->getStoreId())->getWebsite()->getStoreIds(); if (is_array($storeIds)) { foreach ($storeIds as $storeId) { if ($object->getPriceType() == 'fixed') { $storeCurrency = Mage::app()->getStore($storeId)->getBaseCurrencyCode(); $rate = Mage::getModel('directory/currency')->load($baseCurrency)->getRate($storeCurrency); if (!$rate) { $rate=1; } $newPrice = $object->getPrice() * $rate; } else { $newPrice = $object->getPrice(); } $statement = $readAdapter->select() ->from($priceTable) ->where('option_id = ?', $object->getId()) ->where('store_id = ?', $storeId); if ($readAdapter->fetchOne($statement)) { $data = $this->_prepareDataForTable( new Varien_Object( array( 'price' => $newPrice, 'price_type' => $object->getPriceType() ) ), $priceTable ); $writeAdapter->update( $priceTable, $data, array( 'option_id = ?' => $object->getId(), 'store_id = ?' => $storeId ) ); } else { $data = $this->_prepareDataForTable( new Varien_Object( array( 'option_id' => $object->getId(), 'store_id' => $storeId, 'price' => $newPrice, 'price_type' => $object->getPriceType() ) ), $priceTable ); $writeAdapter->insert($priceTable, $data); } }// end foreach() } } elseif ($scope == Mage_Core_Model_Store::PRICE_SCOPE_WEBSITE && $object->getData('scope', 'price')) { $writeAdapter->delete( $priceTable, array( 'option_id = ?' => $object->getId(), 'store_id = ?' => $object->getStoreId() ) ); } } return $this; } /** * Save titles * * @param Mage_Core_Model_Abstract $object * @return Mage_Catalog_Model_Resource_Product_Option */ protected function _saveValueTitles(Mage_Core_Model_Abstract $object) { $readAdapter = $this->_getReadAdapter(); $writeAdapter = $this->_getWriteAdapter(); $titleTable = $this->getTable('catalog/product_option_title'); //title if (!$object->getData('scope', 'title')) { $statement = $readAdapter->select() ->from($titleTable) ->where('option_id = ?', $object->getId()) ->where('store_id = ?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID); if ($readAdapter->fetchOne($statement)) { if ($object->getStoreId() == '0') { $data = $this->_prepareDataForTable( new Varien_Object( array( 'title' => $object->getTitle() ) ), $titleTable ); $writeAdapter->update( $titleTable, $data, array( 'option_id = ?' => $object->getId(), 'store_id = ?' => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID ) ); } } else { $data = $this->_prepareDataForTable( new Varien_Object( array( 'option_id' => $object->getId(), 'store_id' => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID, 'title' => $object->getTitle() ) ), $titleTable ); $writeAdapter->insert($titleTable, $data); } } if ($object->getStoreId() != '0' && !$object->getData('scope', 'title')) { $statement = $readAdapter->select() ->from($titleTable) ->where('option_id = ?', $object->getId()) ->where('store_id = ?', $object->getStoreId()); if ($readAdapter->fetchOne($statement)) { $data = $this->_prepareDataForTable( new Varien_Object( array( 'title' => $object->getTitle() ) ), $titleTable ); $writeAdapter->update( $titleTable, $data, array( 'option_id = ?' => $object->getId(), 'store_id = ?' => $object->getStoreId() ) ); } else { $data = $this->_prepareDataForTable( new Varien_Object( array( 'option_id' => $object->getId(), 'store_id' => $object->getStoreId(), 'title' => $object->getTitle() ) ), $titleTable ); $writeAdapter->insert($titleTable, $data); } } elseif ($object->getData('scope', 'title')) { $writeAdapter->delete( $titleTable, array( 'option_id = ?' => $object->getId(), 'store_id = ?' => $object->getStoreId() ) ); } } /** * Delete prices * * @param int $optionId * @return Mage_Catalog_Model_Resource_Product_Option */ public function deletePrices($optionId) { $this->_getWriteAdapter()->delete( $this->getTable('catalog/product_option_price'), array( 'option_id = ?' => $optionId ) ); return $this; } /** * Delete titles * * @param int $optionId * @return Mage_Catalog_Model_Resource_Product_Option */ public function deleteTitles($optionId) { $this->_getWriteAdapter()->delete( $this->getTable('catalog/product_option_title'), array( 'option_id = ?' => $optionId ) ); return $this; } /** * Duplicate custom options for product * * @param Mage_Catalog_Model_Product_Option $object * @param int $oldProductId * @param int $newProductId * @return Mage_Catalog_Model_Product_Option */ public function duplicate(Mage_Catalog_Model_Product_Option $object, $oldProductId, $newProductId) { $write = $this->_getWriteAdapter(); $read = $this->_getReadAdapter(); $optionsCond = array(); $optionsData = array(); // read and prepare original product options $select = $read->select() ->from($this->getTable('catalog/product_option')) ->where('product_id = ?', $oldProductId); $query = $read->query($select); while ($row = $query->fetch()) { $optionsData[$row['option_id']] = $row; $optionsData[$row['option_id']]['product_id'] = $newProductId; unset($optionsData[$row['option_id']]['option_id']); } // insert options to duplicated product foreach ($optionsData as $oId => $data) { $write->insert($this->getMainTable(), $data); $optionsCond[$oId] = $write->lastInsertId($this->getMainTable()); } // copy options prefs foreach ($optionsCond as $oldOptionId => $newOptionId) { // title $table = $this->getTable('catalog/product_option_title'); $select = $this->_getReadAdapter()->select() ->from($table, array(new Zend_Db_Expr($newOptionId), 'store_id', 'title')) ->where('option_id = ?', $oldOptionId); $insertSelect = $write->insertFromSelect( $select, $table, array('option_id', 'store_id', 'title'), Varien_Db_Adapter_Interface::INSERT_ON_DUPLICATE ); $write->query($insertSelect); // price $table = $this->getTable('catalog/product_option_price'); $select = $read->select() ->from($table, array(new Zend_Db_Expr($newOptionId), 'store_id', 'price', 'price_type')) ->where('option_id = ?', $oldOptionId); $insertSelect = $write->insertFromSelect( $select, $table, array( 'option_id', 'store_id', 'price', 'price_type' ), Varien_Db_Adapter_Interface::INSERT_ON_DUPLICATE ); $write->query($insertSelect); $object->getValueInstance()->duplicate($oldOptionId, $newOptionId); } return $object; } /** * Retrieve option searchable data * * @param int $productId * @param int $storeId * @return array */ public function getSearchableData($productId, $storeId) { $searchData = array(); $adapter = $this->_getReadAdapter(); $titleCheckSql = $adapter->getCheckSql( 'option_title_store.title IS NULL', 'option_title_default.title', 'option_title_store.title' ); // retrieve options title $defaultOptionJoin = implode( ' AND ', array('option_title_default.option_id=product_option.option_id', $adapter->quoteInto('option_title_default.store_id = ?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID)) ); $storeOptionJoin = implode( ' AND ', array( 'option_title_store.option_id=product_option.option_id', $adapter->quoteInto('option_title_store.store_id = ?', (int) $storeId)) ); $select = $adapter->select() ->from(array('product_option' => $this->getMainTable()), null) ->join( array('option_title_default' => $this->getTable('catalog/product_option_title')), $defaultOptionJoin, array() ) ->joinLeft( array('option_title_store' => $this->getTable('catalog/product_option_title')), $storeOptionJoin, array('title' => $titleCheckSql) ) ->where('product_option.product_id = ?', $productId); if ($titles = $adapter->fetchCol($select)) { $searchData = array_merge($searchData, $titles); } //select option type titles $defaultOptionJoin = implode( ' AND ', array( 'option_title_default.option_type_id=option_type.option_type_id', $adapter->quoteInto('option_title_default.store_id = ?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID)) ); $storeOptionJoin = implode( ' AND ', array( 'option_title_store.option_type_id = option_type.option_type_id', $adapter->quoteInto('option_title_store.store_id = ?', (int) $storeId)) ); $select = $adapter->select() ->from(array('product_option' => $this->getMainTable()), null) ->join( array('option_type' => $this->getTable('catalog/product_option_type_value')), 'option_type.option_id=product_option.option_id', array() ) ->join( array('option_title_default' => $this->getTable('catalog/product_option_type_title')), $defaultOptionJoin, array() ) ->joinLeft( array('option_title_store' => $this->getTable('catalog/product_option_type_title')), $storeOptionJoin, array('title' => $titleCheckSql) ) ->where('product_option.product_id = ?', $productId); if ($titles = $adapter->fetchCol($select)) { $searchData = array_merge($searchData, $titles); } return $searchData; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog product options collection * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ class Mage_Catalog_Model_Resource_Product_Option_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Resource initialization */ protected function _construct() { $this->_init('catalog/product_option'); } /** * Adds title, price & price_type attributes to result * * @param int $storeId * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function getOptions($storeId) { $this->addPriceToResult($storeId) ->addTitleToResult($storeId); return $this; } /** * Add title to result * * @param int $storeId * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function addTitleToResult($storeId) { $productOptionTitleTable = $this->getTable('catalog/product_option_title'); $adapter = $this->getConnection(); $titleExpr = $adapter->getCheckSql( 'store_option_title.title IS NULL', 'default_option_title.title', 'store_option_title.title' ); $this->getSelect() ->join(array('default_option_title' => $productOptionTitleTable), 'default_option_title.option_id = main_table.option_id', array('default_title' => 'title')) ->joinLeft( array('store_option_title' => $productOptionTitleTable), 'store_option_title.option_id = main_table.option_id AND ' . $adapter->quoteInto('store_option_title.store_id = ?', $storeId), array( 'store_title' => 'title', 'title' => $titleExpr )) ->where('default_option_title.store_id = ?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID); return $this; } /** * Add price to result * * @param int $storeId * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function addPriceToResult($storeId) { $productOptionPriceTable = $this->getTable('catalog/product_option_price'); $adapter = $this->getConnection(); $priceExpr = $adapter->getCheckSql( 'store_option_price.price IS NULL', 'default_option_price.price', 'store_option_price.price' ); $priceTypeExpr = $adapter->getCheckSql( 'store_option_price.price_type IS NULL', 'default_option_price.price_type', 'store_option_price.price_type' ); $this->getSelect() ->joinLeft( array('default_option_price' => $productOptionPriceTable), 'default_option_price.option_id = main_table.option_id AND ' . $adapter->quoteInto( 'default_option_price.store_id = ?', Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID ), array( 'default_price' => 'price', 'default_price_type' => 'price_type' )) ->joinLeft( array('store_option_price' => $productOptionPriceTable), 'store_option_price.option_id = main_table.option_id AND ' . $adapter->quoteInto('store_option_price.store_id = ?', $storeId), array( 'store_price' => 'price', 'store_price_type' => 'price_type', 'price' => $priceExpr, 'price_type' => $priceTypeExpr )); return $this; } /** * Add value to result * * @param int $storeId * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function addValuesToResult($storeId = null) { if ($storeId === null) { $storeId = Mage::app()->getStore()->getId(); } $optionIds = array(); foreach ($this as $option) { $optionIds[] = $option->getId(); } if (!empty($optionIds)) { /** @var $values Mage_Catalog_Model_Option_Value_Collection */ $values = Mage::getModel('catalog/product_option_value') ->getCollection() ->addTitleToResult($storeId) ->addPriceToResult($storeId) ->addOptionToFilter($optionIds) ->setOrder('sort_order', self::SORT_ORDER_ASC) ->setOrder('title', self::SORT_ORDER_ASC); foreach ($values as $value) { $optionId = $value->getOptionId(); if($this->getItemById($optionId)) { $this->getItemById($optionId)->addValue($value); $value->setOption($this->getItemById($optionId)); } } } return $this; } /** * Add product_id filter to select * * @param array|Mage_Catalog_Model_Product|int $product * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function addProductToFilter($product) { if (empty($product)) { $this->addFieldToFilter('product_id', ''); } elseif (is_array($product)) { $this->addFieldToFilter('product_id', array('in' => $product)); } elseif ($product instanceof Mage_Catalog_Model_Product) { $this->addFieldToFilter('product_id', $product->getId()); } else { $this->addFieldToFilter('product_id', $product); } return $this; } /** * Add is_required filter to select * * @param bool $required * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function addRequiredFilter($required = true) { $this->addFieldToFilter('main_table.is_require', (string)$required); return $this; } /** * Add filtering by option ids * * @param mixed $optionIds * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Option_Collection */ public function addIdsToFilter($optionIds) { $this->addFieldToFilter('main_table.option_id', $optionIds); return $this; } /** * Call of protected method reset * * @return Mage_Catalog_Model_Resource_Product_Option_Collection */ public function reset() { return $this->_reset(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Shopping cart block * * @category Mage * @package Mage_Checkout * @author Magento Core Team */ class Mage_Checkout_Block_Cart extends Mage_Checkout_Block_Cart_Abstract { /** * Prepare Quote Item Product URLs * */ public function __construct() { parent::__construct(); $this->prepareItemUrls(); } /** * prepare cart items URLs */ public function prepareItemUrls() { $products = array(); /* @var $item Mage_Sales_Model_Quote_Item */ foreach ($this->getItems() as $item) { $product = $item->getProduct(); $option = $item->getOptionByCode('product_type'); if ($option) { $product = $option->getProduct(); } if ($item->getStoreId() != Mage::app()->getStore()->getId() && !$item->getRedirectUrl() && !$product->isVisibleInSiteVisibility()) { $products[$product->getId()] = $item->getStoreId(); } } if ($products) { $products = Mage::getResourceSingleton('catalog/url') ->getRewriteByProductStore($products); foreach ($this->getItems() as $item) { $product = $item->getProduct(); $option = $item->getOptionByCode('product_type'); if ($option) { $product = $option->getProduct(); } if (isset($products[$product->getId()])) { $object = new Varien_Object($products[$product->getId()]); $item->getProduct()->setUrlDataObject($object); } } } } public function chooseTemplate() { $itemsCount = $this->getItemsCount() ? $this->getItemsCount() : $this->getQuote()->getItemsCount(); if ($itemsCount) { $this->setTemplate($this->getCartTemplate()); } else { $this->setTemplate($this->getEmptyTemplate()); } } public function hasError() { return $this->getQuote()->getHasError(); } public function getItemsSummaryQty() { return $this->getQuote()->getItemsSummaryQty(); } public function isWishlistActive() { $isActive = $this->_getData('is_wishlist_active'); if ($isActive === null) { $isActive = Mage::getStoreConfig('wishlist/general/active') && Mage::getSingleton('customer/session')->isLoggedIn(); $this->setIsWishlistActive($isActive); } return $isActive; } public function getCheckoutUrl() { return $this->getUrl('checkout/onepage', array('_secure'=>true)); } public function getContinueShoppingUrl() { $url = $this->getData('continue_shopping_url'); if (is_null($url)) { $url = Mage::getSingleton('checkout/session')->getContinueShoppingUrl(true); if (!$url) { $url = Mage::getUrl('full-range.html'); $url = rtrim($url, '/'); } $this->setData('continue_shopping_url', $url); } return $url; } public function getIsVirtual() { return $this->helper('checkout/cart')->getIsVirtualQuote(); } /** * Return list of available checkout methods * * @param string $nameInLayout Container block alias in layout * @return array */ public function getMethods($nameInLayout) { if ($this->getChild($nameInLayout) instanceof Mage_Core_Block_Abstract) { return $this->getChild($nameInLayout)->getSortedChildren(); } return array(); } /** * Return HTML of checkout method (link, button etc.) * * @param string $name Block name in layout * @return string */ public function getMethodHtml($name) { $block = $this->getLayout()->getBlock($name); if (!$block) { Mage::throwException(Mage::helper('checkout')->__('Invalid method: %s', $name)); } return $block->toHtml(); } /** * Return customer quote items * * @return array */ public function getItems() { if ($this->getCustomItems()) { return $this->getCustomItems(); } return parent::getItems(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Checkout_Block_Cart_Coupon extends Mage_Checkout_Block_Cart_Abstract { public function getCouponCode() { return $this->getQuote()->getCouponCode(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Cart crosssell list * * @category Mage * @package Mage_Checkout * @author Magento Core Team */ class Mage_Checkout_Block_Cart_Crosssell extends Mage_Catalog_Block_Product_Abstract { /** * Items quantity will be capped to this value * * @var int */ protected $_maxItemCount = 4; /** * Get crosssell items * * @return array */ public function getItems() { $items = $this->getData('items'); if (is_null($items)) { $items = array(); $ninProductIds = $this->_getCartProductIds(); if ($ninProductIds) { $lastAdded = (int) $this->_getLastAddedProductId(); if ($lastAdded) { $collection = $this->_getCollection() ->addProductFilter($lastAdded); if (!empty($ninProductIds)) { $collection->addExcludeProductFilter($ninProductIds); } $collection->setPositionOrder()->load(); foreach ($collection as $item) { $ninProductIds[] = $item->getId(); $items[] = $item; } } if (count($items) < $this->_maxItemCount) { $filterProductIds = array_merge($this->_getCartProductIds(), $this->_getCartProductIdsRel()); $collection = $this->_getCollection() ->addProductFilter($filterProductIds) ->addExcludeProductFilter($ninProductIds) ->setPageSize($this->_maxItemCount-count($items)) ->setGroupBy() ->setPositionOrder() ->load(); foreach ($collection as $item) { $items[] = $item; } } } $this->setData('items', $items); } return $items; } /** * Count items * * @return int */ public function getItemCount() { return count($this->getItems()); } /** * Get ids of products that are in cart * * @return array */ protected function _getCartProductIds() { $ids = $this->getData('_cart_product_ids'); if (is_null($ids)) { $ids = array(); foreach ($this->getQuote()->getAllItems() as $item) { if ($product = $item->getProduct()) { $ids[] = $product->getId(); } } $this->setData('_cart_product_ids', $ids); } return $ids; } /** * Retrieve Array of product ids which have special relation with products in Cart * For example simple product as part of Grouped product * * @return array */ protected function _getCartProductIdsRel() { $productIds = array(); foreach ($this->getQuote()->getAllItems() as $quoteItem) { $productTypeOpt = $quoteItem->getOptionByCode('product_type'); if ($productTypeOpt instanceof Mage_Sales_Model_Quote_Item_Option && $productTypeOpt->getValue() == Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE && $productTypeOpt->getProductId() ) { $productIds[] = $productTypeOpt->getProductId(); } } return $productIds; } /** * Get last product ID that was added to cart and remove this information from session * * @return int */ protected function _getLastAddedProductId() { return Mage::getSingleton('checkout/session')->getLastAddedProductId(true); } /** * Get quote instance * * @return Mage_Sales_Model_Quote */ public function getQuote() { return Mage::getSingleton('checkout/session')->getQuote(); } /** * Get crosssell products collection * * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection */ protected function _getCollection() { $collection = Mage::getModel('catalog/product_link')->useCrossSellLinks() ->getProductCollection() ->setStoreId(Mage::app()->getStore()->getId()) ->addStoreFilter() ->setPageSize($this->_maxItemCount); $this->_addProductAttributesAndPrices($collection); Mage::getSingleton('catalog/product_status')->addSaleableFilterToCollection($collection); Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection); Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($collection); return $collection; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Shopping cart item render block * * @category Mage * @package Mage_Checkout * @author Magento Core Team * * @method Mage_Checkout_Block_Cart_Item_Renderer setProductName(string) * @method Mage_Checkout_Block_Cart_Item_Renderer setDeleteUrl(string) */ class Mage_Checkout_Block_Cart_Item_Renderer extends Mage_Core_Block_Template { /** @var Mage_Checkout_Model_Session */ protected $_checkoutSession; protected $_item; protected $_productUrl = null; protected $_productThumbnail = null; /** * Whether qty will be converted to number * * @var bool */ protected $_strictQtyMode = true; /** * Check, whether product URL rendering should be ignored * * @var bool */ protected $_ignoreProductUrl = false; /** * Set item for render * * @param Mage_Sales_Model_Quote_Item $item * @return Mage_Checkout_Block_Cart_Item_Renderer */ public function setItem(Mage_Sales_Model_Quote_Item_Abstract $item) { $this->_item = $item; return $this; } /** * Get quote item * * @return Mage_Sales_Model_Quote_Item */ public function getItem() { return $this->_item; } /** * Get item product * * @return Mage_Catalog_Model_Product */ public function getProduct() { return $this->getItem()->getProduct(); } public function overrideProductThumbnail($productThumbnail) { $this->_productThumbnail = $productThumbnail; return $this; } /** * Get product thumbnail image * * @return Mage_Catalog_Model_Product_Image */ public function getProductThumbnail() { if (!is_null($this->_productThumbnail)) { return $this->_productThumbnail; } return $this->helper('catalog/image')->init($this->getProduct(), 'thumbnail'); } public function overrideProductUrl($productUrl) { $this->_productUrl = $productUrl; return $this; } /** * Check Product has URL * * @return bool */ public function hasProductUrl() { if ($this->_ignoreProductUrl) { return false; } if ($this->_productUrl || $this->getItem()->getRedirectUrl()) { return true; } $product = $this->getProduct(); $option = $this->getItem()->getOptionByCode('product_type'); if ($option) { $product = $option->getProduct(); } if ($product->isVisibleInSiteVisibility()) { return true; } else { if ($product->hasUrlDataObject()) { $data = $product->getUrlDataObject(); if (in_array($data->getVisibility(), $product->getVisibleInSiteVisibilities())) { return true; } } } return false; } /** * Retrieve URL to item Product * * @return string */ public function getProductUrl() { if (!is_null($this->_productUrl)) { return $this->_productUrl; } if ($this->getItem()->getRedirectUrl()) { return $this->getItem()->getRedirectUrl(); } $product = $this->getProduct(); $option = $this->getItem()->getOptionByCode('product_type'); if ($option) { $product = $option->getProduct(); } return $product->getUrlModel()->getUrl($product); } /** * Get item product name * * @return string */ public function getProductName() { if ($this->hasProductName()) { return $this->getData('product_name'); } return $this->getProduct()->getName(); } /** * Get product customize options * * @return array || false */ public function getProductOptions() { /* @var $helper Mage_Catalog_Helper_Product_Configuration */ $helper = Mage::helper('catalog/product_configuration'); return $helper->getCustomOptions($this->getItem()); } /** * Get list of all otions for product * * @return array */ public function getOptionList() { return $this->getProductOptions(); } /** * Get item configure url * * @return string */ public function getConfigureUrl() { return $this->getUrl( 'checkout/cart/configure', array('id' => $this->getItem()->getId()) ); } /** * Get item delete url * * @return string */ public function getDeleteUrl() { if ($this->hasDeleteUrl()) { return $this->getData('delete_url'); } return $this->getUrl( 'checkout/cart/delete', array( 'id'=>$this->getItem()->getId(), Mage_Core_Controller_Front_Action::PARAM_NAME_URL_ENCODED => $this->helper('core/url')->getEncodedUrl() ) ); } /** * Get quote item qty * * @return float|int|string */ public function getQty() { if (!$this->_strictQtyMode && (string)$this->getItem()->getQty() == '') { return ''; } return $this->getItem()->getQty() * 1; } /** * Check item is in stock * * @deprecated after 1.4.2.0-beta1 * @return bool */ public function getIsInStock() { if ($this->getItem()->getProduct()->isSaleable()) { if ($this->getItem()->getProduct()->getStockItem()->getQty() >= $this->getItem()->getQty()) { return true; } } return false; } /** * Get checkout session * * @return Mage_Checkout_Model_Session */ public function getCheckoutSession() { if (null === $this->_checkoutSession) { $this->_checkoutSession = Mage::getSingleton('checkout/session'); } return $this->_checkoutSession; } /** * Retrieve item messages * Return array with keys * * text => the message text * type => type of a message * * @return array */ public function getMessages() { $messages = array(); $quoteItem = $this->getItem(); // Add basic messages occuring during this page load $baseMessages = $quoteItem->getMessage(false); if ($baseMessages) { foreach ($baseMessages as $message) { $messages[] = array( 'text' => $message, 'type' => $quoteItem->getHasError() ? 'error' : 'notice' ); } } // Add messages saved previously in checkout session $checkoutSession = $this->getCheckoutSession(); if ($checkoutSession) { /* @var $collection Mage_Core_Model_Message_Collection */ $collection = $checkoutSession->getQuoteItemMessages($quoteItem->getId(), true); if ($collection) { $additionalMessages = $collection->getItems(); foreach ($additionalMessages as $message) { /* @var $message Mage_Core_Model_Message_Abstract */ $messages[] = array( 'text' => $message->getCode(), 'type' => ($message->getType() == Mage_Core_Model_Message::ERROR) ? 'error' : 'notice' ); } } } return $messages; } /** * Accept option value and return its formatted view * * @param mixed $optionValue * Method works well with these $optionValue format: * 1. String * 2. Indexed array e.g. array(val1, val2, ...) * 3. Associative array, containing additional option info, including option value, e.g. * array * ( * [label] => ..., * [value] => ..., * [print_value] => ..., * [option_id] => ..., * [option_type] => ..., * [custom_view] =>..., * ) * * @return array */ public function getFormatedOptionValue($optionValue) { /* @var $helper Mage_Catalog_Helper_Product_Configuration */ $helper = Mage::helper('catalog/product_configuration'); $params = array( 'max_length' => 55, 'cut_replacer' => ' ...' ); return $helper->getFormattedOptionValue($optionValue, $params); } /** * Check whether Product is visible in site * * @return bool */ public function isProductVisible() { return $this->getProduct()->isVisibleInSiteVisibility(); } /** * Return product additional information block * * @return Mage_Core_Block_Abstract */ public function getProductAdditionalInformationBlock() { return $this->getLayout()->getBlock('additional.product.info'); } /** * Get html for MAP product enabled * * @param Mage_Sales_Model_Quote_Item $item * @return string */ public function getMsrpHtml($item) { return $this->getLayout()->createBlock('catalog/product_price') ->setTemplate('catalog/product/price_msrp_item.phtml') ->setProduct($item->getProduct()) ->toHtml(); } /** * Set qty mode to be strict or not * * @param bool $strict * @return Mage_Checkout_Block_Cart_Item_Renderer */ public function setQtyMode($strict) { $this->_strictQtyMode = $strict; return $this; } /** * Set ignore product URL rendering * * @param bool $ignore * @return Mage_Checkout_Block_Cart_Item_Renderer */ public function setIgnoreProductUrl($ignore = true) { $this->_ignoreProductUrl = $ignore; return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Checkout_Block_Cart_Shipping extends Mage_Checkout_Block_Cart_Abstract { /** * Available Carriers Instances * @var null|array */ protected $_carriers = null; /** * Estimate Rates * @var array */ protected $_rates = array(); /** * Address Model * * @var array */ protected $_address = array(); /** * Get Estimate Rates * * @return array */ public function getEstimateRates() { if (empty($this->_rates)) { $groups = $this->getAddress()->getGroupedAllShippingRates(); $this->_rates = $groups; } return $this->_rates; } /** * Get Address Model * * @return Mage_Sales_Model_Quote_Address */ public function getAddress() { if (empty($this->_address)) { $this->_address = $this->getQuote()->getShippingAddress(); } return $this->_address; } /** * Get Carrier Name * * @param string $carrierCode * @return mixed */ public function getCarrierName($carrierCode) { if ($name = Mage::getStoreConfig('carriers/'.$carrierCode.'/title')) { return $name; } return $carrierCode; } /** * Get Shipping Method * * @return string */ public function getAddressShippingMethod() { return $this->getAddress()->getShippingMethod(); } /** * Get Estimate Country Id * * @return string */ public function getEstimateCountryId() { return $this->getAddress()->getCountryId(); } /** * Get Estimate Postcode * * @return string */ public function getEstimatePostcode() { return $this->getAddress()->getPostcode(); } /** * Get Estimate City * * @return string */ public function getEstimateCity() { return $this->getAddress()->getCity(); } /** * Get Estimate Region Id * * @return mixed */ public function getEstimateRegionId() { return $this->getAddress()->getRegionId(); } /** * Get Estimate Region * * @return string */ public function getEstimateRegion() { return $this->getAddress()->getRegion(); } /** * Show City in Shipping Estimation * * @return bool */ public function getCityActive() { return (bool)Mage::getStoreConfig('carriers/dhl/active') || (bool)Mage::getStoreConfig('carriers/dhlint/active'); } /** * Show State in Shipping Estimation * * @return bool */ public function getStateActive() { return (bool)Mage::getStoreConfig('carriers/dhl/active') || (bool)Mage::getStoreConfig('carriers/tablerate/active') || (bool)Mage::getStoreConfig('carriers/dhlint/active'); } /** * Convert price from default currency to current currency * * @param float $price * @return float */ public function formatPrice($price) { return $this->getQuote()->getStore()->convertPrice($price, true); } /** * Get Shipping Price * * @param float $price * @param bool $flag * @return float */ public function getShippingPrice($price, $flag) { return $this->formatPrice($this->helper('tax')->getShippingPrice( $price, $flag, $this->getAddress(), $this->getQuote()->getCustomerTaxClassId() )); } /** * Obtain available carriers instances * * @return array */ public function getCarriers() { if (null === $this->_carriers) { $this->_carriers = array(); $this->getEstimateRates(); foreach ($this->_rates as $rateGroup) { if (!empty($rateGroup)) { foreach ($rateGroup as $rate) { $this->_carriers[] = $rate->getCarrierInstance(); } } } } return $this->_carriers; } /** * Check if one of carriers require state/province * * @return bool */ public function isStateProvinceRequired() { foreach ($this->getCarriers() as $carrier) { if ($carrier->isStateProvinceRequired()) { return true; } } return false; } /** * Check if one of carriers require city * * @return bool */ public function isCityRequired() { foreach ($this->getCarriers() as $carrier) { if ($carrier->isCityRequired()) { return true; } } return false; } /** * Check if one of carriers require zip code * * @return bool */ public function isZipCodeRequired() { foreach ($this->getCarriers() as $carrier) { if ($carrier->isZipCodeRequired($this->getEstimateCountryId())) { return true; } } return false; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Checkout_Block_Cart_Totals extends Mage_Checkout_Block_Cart_Abstract { protected $_totalRenderers; protected $_defaultRenderer = 'checkout/total_default'; protected $_totals = null; public function getTotals() { if (is_null($this->_totals)) { return parent::getTotals(); } return $this->_totals; } public function setTotals($value) { $this->_totals = $value; return $this; } protected function _getTotalRenderer($code) { $blockName = $code.'_total_renderer'; $block = $this->getLayout()->getBlock($blockName); if (!$block) { $block = $this->_defaultRenderer; $config = Mage::getConfig()->getNode("global/sales/quote/totals/{$code}/renderer"); if ($config) { $block = (string) $config; } $block = $this->getLayout()->createBlock($block, $blockName); } /** * Transfer totals to renderer */ $block->setTotals($this->getTotals()); return $block; } public function renderTotal($total, $area = null, $colspan = 1) { $code = $total->getCode(); if ($total->getAs()) { $code = $total->getAs(); } return $this->_getTotalRenderer($code) ->setTotal($total) ->setColspan($colspan) ->setRenderingArea(is_null($area) ? -1 : $area) ->toHtml(); } /** * Render totals html for specific totals area (footer, body) * * @param null|string $area * @param int $colspan * @return string */ public function renderTotals($area = null, $colspan = 1) { $html = ''; foreach($this->getTotals() as $total) { if ($total->getArea() != $area && $area != -1) { continue; } $html .= $this->renderTotal($total, $area, $colspan); } return $html; } /** * Check if we have display grand total in base currency * * @return bool */ public function needDisplayBaseGrandtotal() { $quote = $this->getQuote(); if ($quote->getBaseCurrencyCode() != $quote->getQuoteCurrencyCode()) { return true; } return false; } /** * Get formated in base currency base grand total value * * @return string */ public function displayBaseGrandtotal() { $firstTotal = reset($this->_totals); if ($firstTotal) { $total = $firstTotal->getAddress()->getBaseGrandTotal(); return Mage::app()->getStore()->getBaseCurrency()->format($total, array(), true); } return '-'; } /** * Get active or custom quote * * @return Mage_Sales_Model_Quote */ public function getQuote() { if ($this->getCustomQuote()) { return $this->getCustomQuote(); } if (null === $this->_quote) { $this->_quote = $this->getCheckout()->getQuote(); } return $this->_quote; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Multishipping cart link * * @category Mage * @package Mage_Checkout * @author Magento Core Team */ class Mage_Checkout_Block_Multishipping_Link extends Mage_Core_Block_Template { public function getCheckoutUrl() { return $this->getUrl('checkout/multishipping', array('_secure'=>true)); } public function getQuote() { return Mage::getSingleton('checkout/session')->getQuote(); } public function _toHtml() { if (!Mage::helper('checkout')->isMultishippingCheckoutAvailable()){ return ''; } return parent::_toHtml(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * One page checkout cart link * * @category Mage * @package Mage_Checkout * @author Magento Core Team */ class Mage_Checkout_Block_Onepage_Link extends Mage_Core_Block_Template { public function getCheckoutUrl() { return $this->getUrl('checkout/onepage', array('_secure'=>true)); } public function isDisabled() { return !Mage::getSingleton('checkout/session')->getQuote()->validateMinimumAmount(); } public function isPossibleOnepageCheckout() { return $this->helper('checkout')->canOnepageCheckout(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Default Total Row Renderer * * @author Magento Core Team */ class Mage_Checkout_Block_Total_Default extends Mage_Checkout_Block_Cart_Totals { protected $_template = 'checkout/total/default.phtml'; protected $_store; protected function _construct() { $this->setTemplate($this->_template); $this->_store = Mage::app()->getStore(); } /** * Get style assigned to total object * * @return string */ public function getStyle() { return $this->getTotal()->getStyle(); } public function setTotal($total) { $this->setData('total', $total); if ($total->getAddress()) { $this->_store = $total->getAddress()->getQuote()->getStore(); } return $this; } public function getStore() { return $this->_store; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Tax Total Row Renderer * * @author Magento Core Team */ class Mage_Checkout_Block_Total_Tax extends Mage_Checkout_Block_Total_Default { protected $_template = 'checkout/total/tax.phtml'; } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Checkout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Checkout observer model * * @category Mage * @package Mage_Checkout * @author Magento Core Team */ class Mage_Checkout_Model_Observer { public function unsetAll() { Mage::getSingleton('checkout/session')->unsetAll(); } public function loadCustomerQuote() { try { Mage::getSingleton('checkout/session')->loadCustomerQuote(); } catch (Mage_Core_Exception $e) { Mage::getSingleton('checkout/session')->addError($e->getMessage()); } catch (Exception $e) { Mage::getSingleton('checkout/session')->addException( $e, Mage::helper('checkout')->__('Load customer quote error') ); } } public function salesQuoteSaveAfter($observer) { $quote = $observer->getEvent()->getQuote(); /* @var $quote Mage_Sales_Model_Quote */ if ($quote->getIsCheckoutCart()) { Mage::getSingleton('checkout/session')->getQuoteId($quote->getId()); } } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Core * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * HTML select element block * * @category Mage * @package Mage_Core * @author Magento Core Team */ class Mage_Core_Block_Html_Select extends Mage_Core_Block_Abstract { protected $_options = array(); /** * Get options of the element * * @return array */ public function getOptions() { return $this->_options; } /** * Set options for the HTML select * * @param array $options * @return Mage_Core_Block_Html_Select */ public function setOptions($options) { $this->_options = $options; return $this; } /** * Add an option to HTML select * * @param string $value HTML value * @param string $label HTML label * @param array $params HTML attributes * @return Mage_Core_Block_Html_Select */ public function addOption($value, $label, $params=array()) { $this->_options[] = array('value' => $value, 'label' => $label, 'params' => $params); return $this; } /** * Set element's HTML ID * * @param string $id ID * @return Mage_Core_Block_Html_Select */ public function setId($id) { $this->setData('id', $id); return $this; } /** * Set element's CSS class * * @param string $class Class * @return Mage_Core_Block_Html_Select */ public function setClass($class) { $this->setData('class', $class); return $this; } /** * Set element's HTML title * * @param string $title Title * @return Mage_Core_Block_Html_Select */ public function setTitle($title) { $this->setData('title', $title); return $this; } /** * HTML ID of the element * * @return string */ public function getId() { return $this->getData('id'); } /** * CSS class of the element * * @return string */ public function getClass() { return $this->getData('class'); } /** * Returns HTML title of the element * * @return string */ public function getTitle() { return $this->getData('title'); } /** * Render HTML * * @return string */ protected function _toHtml() { if (!$this->_beforeToHtml()) { return ''; } $html = ''; return $html; } /** * Return option HTML node * * @param array $option * @param boolean $selected * @return string */ protected function _optionToHtml($option, $selected = false) { $selectedHtml = $selected ? ' selected="selected"' : ''; if ($this->getIsRenderToJsTemplate() === true) { $selectedHtml .= ' #{option_extra_attr_' . self::calcOptionHash($option['value']) . '}'; } $params = ''; if (!empty($option['params']) && is_array($option['params'])) { foreach ($option['params'] as $key => $value) { if (is_array($value)) { foreach ($value as $keyMulti => $valueMulti) { $params .= sprintf(' %s="%s" ', $keyMulti, $valueMulti); } } else { $params .= sprintf(' %s="%s" ', $key, $value); } } } return sprintf('', $this->escapeHtml($option['value']), $selectedHtml, $params, $this->escapeHtml($option['label'])); } /** * Alias for toHtml() * * @return string */ public function getHtml() { return $this->toHtml(); } /** * Calculate CRC32 hash for option value * * @param string $optionValue Value of the option * @return string */ public function calcOptionHash($optionValue) { return sprintf('%u', crc32($this->getName() . $this->getId() . $optionValue)); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Core * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Date conversion model * * @author Magento Core Team */ class Mage_Core_Model_Date { /** * Current config offset in seconds * * @var int */ private $_offset = 0; /** * Current system offset in seconds * * @var int */ private $_systemOffset = 0; /** * Init offset * */ public function __construct() { $this->_offset = $this->calculateOffset($this->_getConfigTimezone()); $this->_systemOffset = $this->calculateOffset(); } /** * Gets the store config timezone * * @return string */ protected function _getConfigTimezone() { return Mage::app()->getStore()->getConfig('general/locale/timezone'); } /** * Calculates timezone offset * * @param string $timezone * @return int offset between timezone and gmt */ public function calculateOffset($timezone = null) { $result = true; $offset = 0; if (!is_null($timezone)){ $oldzone = @date_default_timezone_get(); $result = date_default_timezone_set($timezone); } if ($result === true) { $offset = gmmktime(0, 0, 0, 1, 2, 1970) - mktime(0, 0, 0, 1, 2, 1970); } if (!is_null($timezone)){ date_default_timezone_set($oldzone); } return $offset; } /** * Forms GMT date * * @param string $format * @param int|string $input date in current timezone * @return string */ public function gmtDate($format = null, $input = null) { if (is_null($format)) { $format = 'Y-m-d H:i:s'; } $date = $this->gmtTimestamp($input); if ($date === false) { return false; } $result = date($format, $date); return $result; } /** * Converts input date into date with timezone offset * Input date must be in GMT timezone * * @param string $format * @param int|string $input date in GMT timezone * @return string */ public function date($format = null, $input = null) { if (is_null($format)) { $format = 'Y-m-d H:i:s'; } $result = date($format, $this->timestamp($input)); return $result; } /** * Forms GMT timestamp * * @param int|string $input date in current timezone * @return int */ public function gmtTimestamp($input = null) { if (is_null($input)) { return gmdate('U'); } else if (is_numeric($input)) { $result = $input; } else { $result = strtotime($input); } if ($result === false) { // strtotime() unable to parse string (it's not a date or has incorrect format) return false; } $date = Mage::app()->getLocale()->date($result); $timestamp = $date->get(Zend_Date::TIMESTAMP) - $date->get(Zend_Date::TIMEZONE_SECS); unset($date); return $timestamp; } /** * Converts input date into timestamp with timezone offset * Input date must be in GMT timezone * * @param int|string $input date in GMT timezone * @return int */ public function timestamp($input = null) { if (is_null($input)) { $result = $this->gmtTimestamp(); } else if (is_numeric($input)) { $result = $input; } else { $result = strtotime($input); } $date = Mage::app()->getLocale()->date($result); $timestamp = $date->get(Zend_Date::TIMESTAMP) + $date->get(Zend_Date::TIMEZONE_SECS); unset($date); return $timestamp; } /** * Get current timezone offset in seconds/minutes/hours * * @param string $type * @return int */ public function getGmtOffset($type = 'seconds') { $result = $this->_offset; switch ($type) { case 'seconds': default: break; case 'minutes': $result = $result / 60; break; case 'hours': $result = $result / 60 / 60; break; } return $result; } /** * Deprecated since 1.1.7 */ public function checkDateTime($year, $month, $day, $hour = 0, $minute = 0, $second = 0) { if (!checkdate($month, $day, $year)) { return false; } foreach (array('hour' => 23, 'minute' => 59, 'second' => 59) as $var => $maxValue) { $value = (int)$$var; if (($value < 0) || ($value > $maxValue)) { return false; } } return true; } /** * Deprecated since 1.1.7 */ public function parseDateTime($dateTimeString, $dateTimeFormat) { // look for supported format $isSupportedFormatFound = false; $formats = array( // priority is important! '%m/%d/%y %I:%M' => array( '/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2, 'h' => 4, 'i' => 5) ), 'm/d/y h:i' => array( '/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2, 'h' => 4, 'i' => 5) ), '%m/%d/%y' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2)), 'm/d/y' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2)), ); foreach ($formats as $supportedFormat => $regRule) { if (false !== strpos($dateTimeFormat, $supportedFormat, 0)) { $isSupportedFormatFound = true; break; } } if (!$isSupportedFormatFound) { Mage::throwException(Mage::helper('core')->__('Date/time format "%s" is not supported.', $dateTimeFormat)); } // apply reg rule to found format $regex = array_shift($regRule); $mask = array_shift($regRule); if (!preg_match($regex, $dateTimeString, $matches)) { Mage::throwException(Mage::helper('core')->__('Specified date/time "%1$s" do not match format "%2$s".', $dateTimeString, $dateTimeFormat)); } // make result $result = array(); foreach (array('y', 'm', 'd', 'h', 'i', 's') as $key) { $value = 0; if (isset($mask[$key]) && isset($matches[$mask[$key]])) { $value = (int)$matches[$mask[$key]]; } $result[] = $value; } // make sure to return full year if ($result[0] < 100) { $result[0] = 2000 + $result[0]; } return $result; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Core * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Provides basic logic for hashing passwords and encrypting/decrypting misc data * * @category Mage * @package Mage_Core * @author Magento Core Team */ class Mage_Core_Model_Encryption { /** * @var Varien_Crypt_Mcrypt */ protected $_crypt; /** * @var Mage_Core_Helper_Data */ protected $_helper; /** * Set helper instance * * @param Mage_Core_Helper_Data $helper * @return Mage_Core_Model_Encryption */ public function setHelper($helper) { $this->_helper = $helper; return $this; } /** * Generate a [salted] hash. * * $salt can be: * false - a random will be generated * integer - a random with specified length will be generated * string * * @param string $password * @param mixed $salt * @return string */ public function getHash($password, $salt = false) { if (is_integer($salt)) { $salt = $this->_helper->getRandomString($salt); } return $salt === false ? $this->hash($password) : $this->hash($salt . $password) . ':' . $salt; } /** * Hash a string * * @param string $data * @return string */ public function hash($data) { return md5($data); } /** * Validate hash against hashing method (with or without salt) * * @param string $password * @param string $hash * @return bool * @throws Exception */ public function validateHash($password, $hash) { $hashArr = explode(':', $hash); switch (count($hashArr)) { case 1: return $this->hash($password) === $hash; case 2: return $this->hash($hashArr[1] . $password) === $hashArr[0]; } Mage::throwException('Invalid hash.'); } /** * Instantiate crypt model * * @param string $key * @return Varien_Crypt_Mcrypt */ protected function _getCrypt($key = null) { if (!$this->_crypt) { if (null === $key) { $key = (string)Mage::getConfig()->getNode('global/crypt/key'); } $this->_crypt = Varien_Crypt::factory()->init($key); } return $this->_crypt; } /** * Encrypt a string * * @param string $data * @return string */ public function encrypt($data) { return base64_encode($this->_getCrypt()->encrypt((string)$data)); } /** * Decrypt a string * * @param string $data * @return string */ public function decrypt($data) { return str_replace("\x0", '', trim($this->_getCrypt()->decrypt(base64_decode((string)$data)))); } /** * Return crypt model, instantiate if it is empty * * @param string $key * @return Varien_Crypt_Mcrypt */ public function validateKey($key) { return $this->_getCrypt($key); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Customer * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Address abstract model * * @category Mage * @package Mage_Customer * @author Magento Core Team */ class Mage_Customer_Model_Address_Abstract extends Mage_Core_Model_Abstract { /** * Possible customer address types */ const TYPE_BILLING = 'billing'; const TYPE_SHIPPING = 'shipping'; /** * Prefix of model events * * @var string */ protected $_eventPrefix = 'customer_address'; /** * Name of event object * * @var string */ protected $_eventObject = 'customer_address'; /** * Directory country models * * @var array */ static protected $_countryModels = array(); /** * Directory region models * * @var array */ static protected $_regionModels = array(); /** * Get full customer name * * @return string */ public function getName() { $name = ''; $config = Mage::getSingleton('eav/config'); if ($config->getAttribute('customer_address', 'prefix')->getIsVisible() && $this->getPrefix()) { $name .= $this->getPrefix() . ' '; } $name .= $this->getFirstname(); if ($config->getAttribute('customer_address', 'middlename')->getIsVisible() && $this->getMiddlename()) { $name .= ' ' . $this->getMiddlename(); } $name .= ' ' . $this->getLastname(); if ($config->getAttribute('customer_address', 'suffix')->getIsVisible() && $this->getSuffix()) { $name .= ' ' . $this->getSuffix(); } return $name; } /** * get address street * * @param int $line address line index * @return string */ public function getStreet($line=0) { $street = parent::getData('street'); if (-1 === $line) { return $street; } else { $arr = is_array($street) ? $street : explode("\n", $street); if (0 === $line || $line === null) { return $arr; } elseif (isset($arr[$line-1])) { return $arr[$line-1]; } else { return ''; } } } public function getStreet1() { return $this->getStreet(1); } public function getStreet2() { return $this->getStreet(2); } public function getStreet3() { return $this->getStreet(3); } public function getStreet4() { return $this->getStreet(4); } public function getStreetFull() { return $this->getData('street'); } public function setStreetFull($street) { return $this->setStreet($street); } /** * set address street informa * * @param unknown_type $street * @return unknown */ public function setStreet($street) { if (is_array($street)) { $street = trim(implode("\n", $street)); } $this->setData('street', $street); return $this; } /** * Create fields street1, street2, etc. * * To be used in controllers for views data * */ public function explodeStreetAddress() { $streetLines = $this->getStreet(); foreach ($streetLines as $i=>$line) { $this->setData('street'.($i+1), $line); } return $this; } /** * To be used when processing _POST */ public function implodeStreetAddress() { $this->setStreet($this->getData('street')); return $this; } /** * Retrieve region name * * @return string */ public function getRegion() { $regionId = $this->getData('region_id'); $region = $this->getData('region'); if ($regionId) { if ($this->getRegionModel($regionId)->getCountryId() == $this->getCountryId()) { $region = $this->getRegionModel($regionId)->getName(); $this->setData('region', $region); } } if (!empty($region) && is_string($region)) { $this->setData('region', $region); } elseif (!$regionId && is_numeric($region)) { if ($this->getRegionModel($region)->getCountryId() == $this->getCountryId()) { $this->setData('region', $this->getRegionModel($region)->getName()); $this->setData('region_id', $region); } } elseif ($regionId && !$region) { if ($this->getRegionModel($regionId)->getCountryId() == $this->getCountryId()) { $this->setData('region', $this->getRegionModel($regionId)->getName()); } } return $this->getData('region'); } /** * Return 2 letter state code if available, otherwise full region name * */ public function getRegionCode() { $regionId = $this->getData('region_id'); $region = $this->getData('region'); if (!$regionId && is_numeric($region)) { if ($this->getRegionModel($region)->getCountryId() == $this->getCountryId()) { $this->setData('region_code', $this->getRegionModel($region)->getCode()); } } elseif ($regionId) { if ($this->getRegionModel($regionId)->getCountryId() == $this->getCountryId()) { $this->setData('region_code', $this->getRegionModel($regionId)->getCode()); } } elseif (is_string($region)) { $this->setData('region_code', $region); } return $this->getData('region_code'); } public function getRegionId() { $regionId = $this->getData('region_id'); $region = $this->getData('region'); if (!$regionId) { if (is_numeric($region)) { $this->setData('region_id', $region); $this->unsRegion(); } else { $regionModel = Mage::getModel('directory/region') ->loadByCode($this->getRegionCode(), $this->getCountryId()); $this->setData('region_id', $regionModel->getId()); } } return $this->getData('region_id'); } public function getCountry() { /*if ($this->getData('country_id') && !$this->getData('country')) { $this->setData('country', Mage::getModel('directory/country') ->load($this->getData('country_id'))->getIso2Code()); } return $this->getData('country');*/ $country = $this->getCountryId(); return $country ? $country : $this->getData('country'); } /** * Retrive country model * * @return Mage_Directory_Model_Country */ public function getCountryModel() { if(!isset(self::$_countryModels[$this->getCountryId()])) { self::$_countryModels[$this->getCountryId()] = Mage::getModel('directory/country') ->load($this->getCountryId()); } return self::$_countryModels[$this->getCountryId()]; } /** * Retrive country model * * @return Mage_Directory_Model_Country */ public function getRegionModel($region=null) { if(is_null($region)) { $region = $this->getRegionId(); } if(!isset(self::$_regionModels[$region])) { self::$_regionModels[$region] = Mage::getModel('directory/region')->load($region); } return self::$_regionModels[$region]; } /** * @deprecated for public function format */ public function getHtmlFormat() { return $this->getConfig()->getFormatByCode('html'); } /** * @deprecated for public function format */ public function getFormated($html=false) { return $this->format($html ? 'html' : 'text'); //Mage::getModel('directory/country')->load($this->getCountryId())->formatAddress($this, $html); } public function format($type) { if(!($formatType = $this->getConfig()->getFormatByCode($type)) || !$formatType->getRenderer()) { return null; } Mage::dispatchEvent('customer_address_format', array('type' => $formatType, 'address' => $this)); return $formatType->getRenderer()->render($this); } /** * Retrive address config object * * @return Mage_Customer_Model_Address_Config */ public function getConfig() { return Mage::getSingleton('customer/address_config'); } protected function _beforeSave() { parent::_beforeSave(); $this->getRegion(); return $this; } /** * Validate address attribute values * * @return bool */ public function validate() { $errors = array(); $this->implodeStreetAddress(); if (!Zend_Validate::is($this->getFirstname(), 'NotEmpty')) { $errors[] = Mage::helper('customer')->__('Please enter the first name.'); } if (!Zend_Validate::is($this->getLastname(), 'NotEmpty')) { $errors[] = Mage::helper('customer')->__('Please enter the last name.'); } if (!Zend_Validate::is($this->getStreet(1), 'NotEmpty')) { $errors[] = Mage::helper('customer')->__('Please enter the street.'); } if (!Zend_Validate::is($this->getCity(), 'NotEmpty')) { $errors[] = Mage::helper('customer')->__('Please enter the city.'); } if (!Zend_Validate::is($this->getTelephone(), 'NotEmpty')) { $errors[] = Mage::helper('customer')->__('Please enter the telephone number.'); } $_havingOptionalZip = Mage::helper('directory')->getCountriesWithOptionalZip(); if (!in_array($this->getCountryId(), $_havingOptionalZip) && !Zend_Validate::is($this->getPostcode(), 'NotEmpty') ) { $errors[] = Mage::helper('customer')->__('Please enter the zip/postal code.'); } if (!Zend_Validate::is($this->getCountryId(), 'NotEmpty')) { $errors[] = Mage::helper('customer')->__('Please enter the country.'); } if ($this->getCountryModel()->getRegionCollection()->getSize() && !Zend_Validate::is($this->getRegionId(), 'NotEmpty') && Mage::helper('directory')->isRegionRequired($this->getCountryId()) ) { $errors[] = Mage::helper('customer')->__('Please enter the state/province.'); } if (empty($errors) || $this->getShouldIgnoreValidation()) { return true; } return $errors; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Customer * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Customer group resource model * * @category Mage * @package Mage_Customer * @author Magento Core Team */ class Mage_Customer_Model_Resource_Group extends Mage_Core_Model_Resource_Db_Abstract { /** * Resource initialization */ protected function _construct() { $this->_init('customer/customer_group', 'customer_group_id'); } /** * Initialize unique fields * * @return Mage_Customer_Model_Resource_Group */ protected function _initUniqueFields() { $this->_uniqueFields = array( array( 'field' => 'customer_group_code', 'title' => Mage::helper('customer')->__('Customer Group') )); return $this; } /** * Check if group uses as default * * @param Mage_Core_Model_Abstract $group * @throws Mage_Core_Exception * @return Mage_Core_Model_Resource_Db_Abstract */ protected function _beforeDelete(Mage_Core_Model_Abstract $group) { if ($group->usesAsDefault()) { Mage::throwException(Mage::helper('customer')->__('The group "%s" cannot be deleted', $group->getCode())); } return parent::_beforeDelete($group); } /** * Method set default group id to the customers collection * * @param Mage_Core_Model_Abstract $group * @return Mage_Core_Model_Resource_Db_Abstract */ protected function _afterDelete(Mage_Core_Model_Abstract $group) { $customerCollection = Mage::getResourceModel('customer/customer_collection') ->addAttributeToFilter('group_id', $group->getId()) ->load(); foreach ($customerCollection as $customer) { $defaultGroupId = Mage::helper('customer')->getDefaultCustomerGroupId($customer->getStoreId()); $customer->setGroupId($defaultGroupId); $customer->save(); } return parent::_afterDelete($group); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Customer * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Customer group resource model * * @category Mage * @package Mage_Customer * @author Magento Core Team */ class Mage_Customer_Model_Entity_Group extends Mage_Customer_Model_Resource_Group { } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Directory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Directory data block * * @category Mage * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Block_Data extends Mage_Core_Block_Template { public function getLoadrRegionUrl() { return $this->getUrl('directory/json/childRegion'); } public function getCountryCollection() { $collection = $this->getData('country_collection'); if (is_null($collection)) { $collection = Mage::getModel('directory/country')->getResourceCollection() ->loadByStore(); $this->setData('country_collection', $collection); } return $collection; } public function getCountryHtmlSelect($defValue=null, $name='country_id', $id='country', $title='Country') { Varien_Profiler::start('TEST: '.__METHOD__); if (is_null($defValue)) { $defValue = $this->getCountryId(); } $cacheKey = 'DIRECTORY_COUNTRY_SELECT_STORE_'.Mage::app()->getStore()->getCode(); if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { $options = unserialize($cache); } else { $options = $this->getCountryCollection()->toOptionArray(); if (Mage::app()->useCache('config')) { Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); } } $html = $this->getLayout()->createBlock('core/html_select') ->setName($name) ->setId($id) ->setTitle(Mage::helper('directory')->__($title)) ->setClass('validate-select') ->setValue($defValue) ->setOptions($options) ->getHtml(); Varien_Profiler::stop('TEST: '.__METHOD__); return $html; } public function getRegionCollection() { $collection = $this->getData('region_collection'); if (is_null($collection)) { $collection = Mage::getModel('directory/region')->getResourceCollection() ->addCountryFilter($this->getCountryId()) ->load(); $this->setData('region_collection', $collection); } return $collection; } public function getRegionHtmlSelect() { Varien_Profiler::start('TEST: '.__METHOD__); $cacheKey = 'DIRECTORY_REGION_SELECT_STORE'.Mage::app()->getStore()->getId(); if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { $options = unserialize($cache); } else { $options = $this->getRegionCollection()->toOptionArray(); if (Mage::app()->useCache('config')) { Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); } } $html = $this->getLayout()->createBlock('core/html_select') ->setName('region') ->setTitle(Mage::helper('directory')->__('State/Province')) ->setId('state') ->setClass('required-entry validate-state') ->setValue(intval($this->getRegionId())) ->setOptions($options) ->getHtml(); Varien_Profiler::start('TEST: '.__METHOD__); return $html; } public function getCountryId() { $countryId = $this->getData('country_id'); if (is_null($countryId)) { $countryId = Mage::helper('core')->getDefaultCountry(); } return $countryId; } public function getRegionsJs() { Varien_Profiler::start('TEST: '.__METHOD__); $regionsJs = $this->getData('regions_js'); if (!$regionsJs) { $countryIds = array(); foreach ($this->getCountryCollection() as $country) { $countryIds[] = $country->getCountryId(); } $collection = Mage::getModel('directory/region')->getResourceCollection() ->addCountryFilter($countryIds) ->load(); $regions = array(); foreach ($collection as $region) { if (!$region->getRegionId()) { continue; } $regions[$region->getCountryId()][$region->getRegionId()] = array( 'code'=>$region->getCode(), 'name'=>$region->getName() ); } $regionsJs = Mage::helper('core')->jsonEncode($regions); } Varien_Profiler::stop('TEST: '.__METHOD__); return $regionsJs; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Directory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Directory data helper * * @author Magento Core Team */ class Mage_Directory_Helper_Data extends Mage_Core_Helper_Abstract { /** * Config value that lists ISO2 country codes which have optional Zip/Postal pre-configured */ const OPTIONAL_ZIP_COUNTRIES_CONFIG_PATH = 'general/country/optional_zip_countries'; /* * Path to config value, which lists countries, for which state is required. */ const XML_PATH_STATES_REQUIRED = 'general/region/state_required'; /* * Path to config value, which detects whether or not display the state for the country, if it is not required */ const XML_PATH_DISPLAY_ALL_STATES = 'general/region/display_all'; /** * Country collection * * @var Mage_Directory_Model_Resource_Country_Collection */ protected $_countryCollection; /** * Region collection * * @var Mage_Directory_Model_Resource_Region_Collection */ protected $_regionCollection; /** * Json representation of regions data * * @var string */ protected $_regionJson; /** * Currency cache * * @var array */ protected $_currencyCache = array(); /** * ISO2 country codes which have optional Zip/Postal pre-configured * * @var array */ protected $_optionalZipCountries = null; /** * Retrieve region collection * * @return Mage_Directory_Model_Resource_Region_Collection */ public function getRegionCollection() { if (!$this->_regionCollection) { $this->_regionCollection = Mage::getModel('directory/region')->getResourceCollection() ->addCountryFilter($this->getAddress()->getCountryId()) ->load(); } return $this->_regionCollection; } /** * Retrieve country collection * * @return Mage_Directory_Model_Resource_Country_Collection */ public function getCountryCollection() { if (!$this->_countryCollection) { $this->_countryCollection = Mage::getModel('directory/country')->getResourceCollection() ->loadByStore(); } return $this->_countryCollection; } /** * Retrieve regions data json * * @return string */ public function getRegionJson() { Varien_Profiler::start('TEST: '.__METHOD__); if (!$this->_regionJson) { $cacheKey = 'DIRECTORY_REGIONS_JSON_STORE'.Mage::app()->getStore()->getId(); if (Mage::app()->useCache('config')) { $json = Mage::app()->loadCache($cacheKey); } if (empty($json)) { $countryIds = array(); foreach ($this->getCountryCollection() as $country) { $countryIds[] = $country->getCountryId(); } $collection = Mage::getModel('directory/region')->getResourceCollection() ->addCountryFilter($countryIds) ->load(); $regions = array( 'config' => array( 'show_all_regions' => $this->getShowNonRequiredState(), 'regions_required' => $this->getCountriesWithStatesRequired() ) ); foreach ($collection as $region) { if (!$region->getRegionId()) { continue; } $regions[$region->getCountryId()][$region->getRegionId()] = array( 'code' => $region->getCode(), 'name' => $this->__($region->getName()) ); } $json = Mage::helper('core')->jsonEncode($regions); if (Mage::app()->useCache('config')) { Mage::app()->saveCache($json, $cacheKey, array('config')); } } $this->_regionJson = $json; } Varien_Profiler::stop('TEST: '.__METHOD__); return $this->_regionJson; } /** * Convert currency * * @param float $amount * @param string $from * @param string $to * @return float */ public function currencyConvert($amount, $from, $to = null) { if (empty($this->_currencyCache[$from])) { $this->_currencyCache[$from] = Mage::getModel('directory/currency')->load($from); } if (is_null($to)) { $to = Mage::app()->getStore()->getCurrentCurrencyCode(); } $converted = $this->_currencyCache[$from]->convert($amount, $to); return $converted; } /** * Return ISO2 country codes, which have optional Zip/Postal pre-configured * * @param bool $asJson * @return array|string */ public function getCountriesWithOptionalZip($asJson = false) { if (null === $this->_optionalZipCountries) { $this->_optionalZipCountries = preg_split('/\,/', Mage::getStoreConfig(self::OPTIONAL_ZIP_COUNTRIES_CONFIG_PATH), 0, PREG_SPLIT_NO_EMPTY); } if ($asJson) { return Mage::helper('core')->jsonEncode($this->_optionalZipCountries); } return $this->_optionalZipCountries; } /** * Check whether zip code is optional for specified country code * * @param string $countryCode * @return boolean */ public function isZipCodeOptional($countryCode) { $this->getCountriesWithOptionalZip(); return in_array($countryCode, $this->_optionalZipCountries); } /** * Returns the list of countries, for which region is required * * @param boolean $asJson * @return array */ public function getCountriesWithStatesRequired($asJson = false) { $countryList = explode(',', Mage::getStoreConfig(self::XML_PATH_STATES_REQUIRED)); if ($asJson) { return Mage::helper('core')->jsonEncode($countryList); } return $countryList; } /** * Return flag, which indicates whether or not non required state should be shown * * @return bool */ public function getShowNonRequiredState() { return (boolean)Mage::getStoreConfig(self::XML_PATH_DISPLAY_ALL_STATES); } /** * Returns flag, which indicates whether region is required for specified country * * @param string $countryId * @return bool */ public function isRegionRequired($countryId) { $countyList = $this->getCountriesWithStatesRequired(); if(!is_array($countyList)) { return false; } return in_array($countryId, $countyList); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Directory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Currency model * * @category Mage * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Currency extends Mage_Core_Model_Abstract { /** * CONFIG path constants */ const XML_PATH_CURRENCY_ALLOW = 'currency/options/allow'; const XML_PATH_CURRENCY_DEFAULT = 'currency/options/default'; const XML_PATH_CURRENCY_BASE = 'currency/options/base'; protected $_filter; /** * Currency Rates * * @var array */ protected $_rates; protected function _construct() { $this->_init('directory/currency'); } /** * Get currency code * * @return string */ public function getCode() { return $this->_getData('currency_code'); } public function getCurrencyCode() { return $this->_getData('currency_code'); } /** * Currency Rates getter * * @return array */ public function getRates() { return $this->_rates; } /** * Currency Rates setter * * @param array Currency Rates * @return Mage_Directory_Model_Currency */ public function setRates(array $rates) { $this->_rates = $rates; return $this; } /** * Loading currency data * * @param string $id * @param string $field * @return Mage_Directory_Model_Currency */ public function load($id, $field=null) { $this->unsRate(); $this->setData('currency_code', $id); return $this; } /** * Get currency rate (only base=>allowed) * * @param string $toCurrency * @return double */ public function getRate($toCurrency) { if (is_string($toCurrency)) { $code = $toCurrency; } elseif ($toCurrency instanceof Mage_Directory_Model_Currency) { $code = $toCurrency->getCurrencyCode(); } else { throw Mage::exception('Mage_Directory', Mage::helper('directory')->__('Invalid target currency.')); } $rates = $this->getRates(); if (!isset($rates[$code])) { $rates[$code] = $this->_getResource()->getRate($this->getCode(), $toCurrency); $this->setRates($rates); } return $rates[$code]; } /** * Get currency rate (base=>allowed or allowed=>base) * * @param string $toCurrency * @return double */ public function getAnyRate($toCurrency) { if (is_string($toCurrency)) { $code = $toCurrency; } elseif ($toCurrency instanceof Mage_Directory_Model_Currency) { $code = $toCurrency->getCurrencyCode(); } else { throw Mage::exception('Mage_Directory', Mage::helper('directory')->__('Invalid target currency.')); } $rates = $this->getRates(); if (!isset($rates[$code])) { $rates[$code] = $this->_getResource()->getAnyRate($this->getCode(), $toCurrency); $this->setRates($rates); } return $rates[$code]; } /** * Convert price to currency format * * @param double $price * @param string $toCurrency * @return double */ public function convert($price, $toCurrency=null) { if (is_null($toCurrency)) { return $price; } elseif ($rate = $this->getRate($toCurrency)) { return $price*$rate; } throw new Exception(Mage::helper('directory')->__('Undefined rate from "%s-%s".', $this->getCode(), $toCurrency->getCode())); } /** * Get currency filter * * @return Mage_Directory_Model_Currency_Filter */ public function getFilter() { if (!$this->_filter) { $this->_filter = new Mage_Directory_Model_Currency_Filter($this->getCode()); } return $this->_filter; } /** * Format price to currency format * * @param double $price * @param bool $includeContainer * @return string */ public function format($price, $options=array(), $includeContainer = true, $addBrackets = false) { return $this->formatPrecision($price, 2, $options, $includeContainer, $addBrackets); } /** * Apply currency format to number with specific rounding precision * * @param float $price * @param int $precision * @param array $options * @param bool $includeContainer * @param bool $addBrackets * @return string */ public function formatPrecision($price, $precision, $options=array(), $includeContainer = true, $addBrackets = false) { if (!isset($options['precision'])) { $options['precision'] = $precision; } if ($includeContainer) { return '' . ($addBrackets ? '[' : '') . $this->formatTxt($price, $options) . ($addBrackets ? ']' : '') . ''; } return $this->formatTxt($price, $options); } public function formatTxt($price, $options=array()) { if (!is_numeric($price)) { $price = Mage::app()->getLocale()->getNumber($price); } /** * Fix problem with 12 000 000, 1 200 000 * * %f - the argument is treated as a float, and presented as a floating-point number (locale aware). * %F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). */ $price = sprintf("%F", $price); return Mage::app()->getLocale()->currency($this->getCode())->toCurrency($price, $options); } public function getOutputFormat() { $formated = $this->formatTxt(0); $number = $this->formatTxt(0, array('display'=>Zend_Currency::NO_SYMBOL)); return str_replace($number, '%s', $formated); } /** * Retrieve allowed currencies according to config * */ public function getConfigAllowCurrencies() { $allowedCurrencies = $this->_getResource()->getConfigCurrencies($this, self::XML_PATH_CURRENCY_ALLOW); $appBaseCurrencyCode = Mage::app()->getBaseCurrencyCode(); if (!in_array($appBaseCurrencyCode, $allowedCurrencies)) { $allowedCurrencies[] = $appBaseCurrencyCode; } foreach (Mage::app()->getStores() as $store) { $code = $store->getBaseCurrencyCode(); if (!in_array($code, $allowedCurrencies)) { $allowedCurrencies[] = $code; } } return $allowedCurrencies; } /** * Retrieve default currencies according to config * */ public function getConfigDefaultCurrencies() { $defaultCurrencies = $this->_getResource()->getConfigCurrencies($this, self::XML_PATH_CURRENCY_DEFAULT); return $defaultCurrencies; } public function getConfigBaseCurrencies() { $defaultCurrencies = $this->_getResource()->getConfigCurrencies($this, self::XML_PATH_CURRENCY_BASE); return $defaultCurrencies; } /** * Retrieve currency rates to other currencies * * @param string $currency * @param array $toCurrencies * @return array */ public function getCurrencyRates($currency, $toCurrencies=null) { if ($currency instanceof Mage_Directory_Model_Currency) { $currency = $currency->getCode(); } $data = $this->_getResource()->getCurrencyRates($currency, $toCurrencies); return $data; } /** * Save currency rates * * @param array $rates * @return object */ public function saveRates($rates) { $this->_getResource()->saveRates($rates); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Directory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Region * * @method Mage_Directory_Model_Resource_Region _getResource() * @method Mage_Directory_Model_Resource_Region getResource() * @method string getCountryId() * @method Mage_Directory_Model_Region setCountryId(string $value) * @method string getCode() * @method Mage_Directory_Model_Region setCode(string $value) * @method string getDefaultName() * @method Mage_Directory_Model_Region setDefaultName(string $value) * * @category Mage * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Region extends Mage_Core_Model_Abstract { protected function _construct() { $this->_init('directory/region'); } /** * Retrieve region name * * If name is no declared, then default_name is used * * @return string */ public function getName() { $name = $this->getData('name'); if (is_null($name)) { $name = $this->getData('default_name'); } return $name; } public function loadByCode($code, $countryId) { if ($code) { $this->_getResource()->loadByCode($this, $code, $countryId); } return $this; } public function loadByName($name, $countryId) { $this->_getResource()->loadByName($this, $name, $countryId); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Directory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Directory Currency Resource Model * * @category Mage * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Resource_Currency extends Mage_Core_Model_Resource_Db_Abstract { /** * Currency rate table * * @var string */ protected $_currencyRateTable; /** * Currency rate cache array * * @var array */ protected static $_rateCache; /** * Define main and currency rate tables * */ protected function _construct() { $this->_init('directory/currency', 'currency_code'); $this->_currencyRateTable = $this->getTable('directory/currency_rate'); } /** * Retrieve currency rate (only base=>allowed) * * @param Mage_Directory_Model_Currency|string $currencyFrom * @param Mage_Directory_Model_Currency|string $currencyTo * @return float */ public function getRate($currencyFrom, $currencyTo) { if ($currencyFrom instanceof Mage_Directory_Model_Currency) { $currencyFrom = $currencyFrom->getCode(); } if ($currencyTo instanceof Mage_Directory_Model_Currency) { $currencyTo = $currencyTo->getCode(); } if ($currencyFrom == $currencyTo) { return 1; } if (!isset(self::$_rateCache[$currencyFrom][$currencyTo])) { $read = $this->_getReadAdapter(); $bind = array( ':currency_from' => strtoupper($currencyFrom), ':currency_to' => strtoupper($currencyTo) ); $select = $read->select() ->from($this->_currencyRateTable, 'rate') ->where('currency_from = :currency_from') ->where('currency_to = :currency_to'); self::$_rateCache[$currencyFrom][$currencyTo] = $read->fetchOne($select, $bind); } return self::$_rateCache[$currencyFrom][$currencyTo]; } /** * Retrieve currency rate (base=>allowed or allowed=>base) * * @param Mage_Directory_Model_Currency|string $currencyFrom * @param Mage_Directory_Model_Currency|string $currencyTo * @return float */ public function getAnyRate($currencyFrom, $currencyTo) { if ($currencyFrom instanceof Mage_Directory_Model_Currency) { $currencyFrom = $currencyFrom->getCode(); } if ($currencyTo instanceof Mage_Directory_Model_Currency) { $currencyTo = $currencyTo->getCode(); } if ($currencyFrom == $currencyTo) { return 1; } if (!isset(self::$_rateCache[$currencyFrom][$currencyTo])) { $adapter = $this->_getReadAdapter(); $bind = array( ':currency_from' => strtoupper($currencyFrom), ':currency_to' => strtoupper($currencyTo) ); $select = $adapter->select() ->from($this->_currencyRateTable, 'rate') ->where('currency_from = :currency_from') ->where('currency_to = :currency_to'); $rate = $adapter->fetchOne($select, $bind); if ($rate === false) { $select = $adapter->select() ->from($this->_currencyRateTable, new Zend_Db_Expr('1/rate')) ->where('currency_to = :currency_from') ->where('currency_from = :currency_to'); $rate = $adapter->fetchOne($select, $bind); } self::$_rateCache[$currencyFrom][$currencyTo] = $rate; } return self::$_rateCache[$currencyFrom][$currencyTo]; } /** * Saving currency rates * * @param array $rates */ public function saveRates($rates) { if (is_array($rates) && sizeof($rates) > 0) { $adapter = $this->_getWriteAdapter(); $data = array(); foreach ($rates as $currencyCode => $rate) { foreach ($rate as $currencyTo => $value) { $value = abs($value); if ($value == 0) { continue; } $data[] = array( 'currency_from' => $currencyCode, 'currency_to' => $currencyTo, 'rate' => $value, ); } } if ($data) { $adapter->insertOnDuplicate($this->_currencyRateTable, $data, array('rate')); } } else { Mage::throwException(Mage::helper('directory')->__('Invalid rates received')); } } /** * Retrieve config currency data by config path * * @param Mage_Directory_Model_Currency $model * @param string $path * * @return array */ public function getConfigCurrencies($model, $path) { $adapter = $this->_getReadAdapter(); $bind = array(':config_path' => $path); $select = $adapter->select() ->from($this->getTable('core/config_data')) ->where('path = :config_path'); $result = array(); $rowSet = $adapter->fetchAll($select, $bind); foreach ($rowSet as $row) { $result = array_merge($result, explode(',', $row['value'])); } sort($result); return array_unique($result); } /** * Return currency rates * * @param string|array $currency * @param array $toCurrencies * * @return array */ public function getCurrencyRates($currency, $toCurrencies = null) { $rates = array(); if (is_array($currency)) { foreach ($currency as $code) { $rates[$code] = $this->_getRatesByCode($code, $toCurrencies); } } else { $rates = $this->_getRatesByCode($currency, $toCurrencies); } return $rates; } /** * Protected method used by getCurrencyRates() method * * @param string $code * @param array $toCurrencies * @return array */ protected function _getRatesByCode($code, $toCurrencies = null) { $adapter = $this->_getReadAdapter(); $bind = array( ':currency_from' => $code ); $select = $adapter->select() ->from($this->getTable('directory/currency_rate'), array('currency_to', 'rate')) ->where('currency_from = :currency_from') ->where('currency_to IN(?)', $toCurrencies); $rowSet = $adapter->fetchAll($select, $bind); $result = array(); foreach ($rowSet as $row) { $result[$row['currency_to']] = $row['rate']; } return $result; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Directory * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Directory Region Resource Model * * @category Mage * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Resource_Region extends Mage_Core_Model_Resource_Db_Abstract { /** * Table with localized region names * * @var string */ protected $_regionNameTable; /** * Define main and locale region name tables * */ protected function _construct() { $this->_init('directory/country_region', 'region_id'); $this->_regionNameTable = $this->getTable('directory/country_region_name'); } /** * Retrieve select object for load object data * * @param string $field * @param mixed $value * @param Mage_Core_Model_Abstract $object * * @return Varien_Db_Select */ protected function _getLoadSelect($field, $value, $object) { $select = parent::_getLoadSelect($field, $value, $object); $adapter = $this->_getReadAdapter(); $locale = Mage::app()->getLocale()->getLocaleCode(); $systemLocale = Mage::app()->getDistroLocaleCode(); $regionField = $adapter->quoteIdentifier($this->getMainTable() . '.' . $this->getIdFieldName()); $condition = $adapter->quoteInto('lrn.locale = ?', $locale); $select->joinLeft( array('lrn' => $this->_regionNameTable), "{$regionField} = lrn.region_id AND {$condition}", array()); if ($locale != $systemLocale) { $nameExpr = $adapter->getCheckSql('lrn.region_id is null', 'srn.name', 'lrn.name'); $condition = $adapter->quoteInto('srn.locale = ?', $systemLocale); $select->joinLeft( array('srn' => $this->_regionNameTable), "{$regionField} = srn.region_id AND {$condition}", array('name' => $nameExpr)); } else { $select->columns(array('name'), 'lrn'); } return $select; } /** * Load object by country id and code or default name * * @param Mage_Core_Model_Abstract $object * @param int $countryId * @param string $value * @param string $field * * @return Mage_Directory_Model_Resource_Region */ protected function _loadByCountry($object, $countryId, $value, $field) { $adapter = $this->_getReadAdapter(); $locale = Mage::app()->getLocale()->getLocaleCode(); $joinCondition = $adapter->quoteInto('rname.region_id = region.region_id AND rname.locale = ?', $locale); $select = $adapter->select() ->from(array('region' => $this->getMainTable())) ->joinLeft( array('rname' => $this->_regionNameTable), $joinCondition, array('name')) ->where('region.country_id = ?', $countryId) ->where("region.{$field} = ?", $value); $data = $adapter->fetchRow($select); if ($data) { $object->setData($data); } $this->_afterLoad($object); return $this; } /** * Loads region by region code and country id * * @param Mage_Directory_Model_Region $region * @param string $regionCode * @param string $countryId * * @return Mage_Directory_Model_Resource_Region */ public function loadByCode(Mage_Directory_Model_Region $region, $regionCode, $countryId) { return $this->_loadByCountry($region, $countryId, (string)$regionCode, 'code'); } /** * Load data by country id and default region name * * @param Mage_Directory_Model_Region $region * @param string $regionName * @param string $countryId * * @return Mage_Directory_Model_Resource_Region */ public function loadByName(Mage_Directory_Model_Region $region, $regionName, $countryId) { return $this->_loadByCountry($region, $countryId, (string)$regionName, 'default_name'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Eav * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Backend model for attribute with multiple values * * @category Mage * @package Mage_Eav * @author Magento Core Team */ class Mage_Eav_Model_Entity_Attribute_Backend_Array extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { /** * Prepare data for save * * @param Varien_Object $object * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract */ public function beforeSave($object) { $attributeCode = $this->getAttribute()->getAttributeCode(); $data = $object->getData($attributeCode); if (is_array($data)) { $data = array_filter($data); $object->setData($attributeCode, implode(',', $data)); } return parent::beforeSave($object); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_GiftMessage * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product attribute for allowing of gift messages per item * * @deprecated after 1.4.2.0 * * @category Mage * @package Mage_GiftMessage * @author Magento Core Team */ class Mage_GiftMessage_Model_Entity_Attribute_Backend_Boolean_Config extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { /** * Set attribute default value if value empty * * @param Varien_Object $object */ public function afterLoad($object) { if(!$object->hasData($this->getAttribute()->getAttributeCode())) { $object->setData($this->getAttribute()->getAttributeCode(), $this->getDefaultValue()); } } /** * Set attribute default value if value empty * * @param Varien_Object $object */ public function beforeSave($object) { if($object->hasData($this->getAttribute()->getAttributeCode()) && $object->getData($this->getAttribute()->getAttributeCode()) == $this->getDefaultValue()) { $object->unsData($this->getAttribute()->getAttributeCode()); } } /** * Validate attribute data * * @param Varien_Object $object * @return boolean */ public function validate($object) { // all attribute's options $optionsAllowed = array('0', '1', '2'); $value = $object->getData($this->getAttribute()->getAttributeCode()); return in_array($value, $optionsAllowed)? true : false; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_GiftMessage * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Gift Message Observer Model * * @category Mage * @package Mage_GiftMessage * @author Magento Core Team */ class Mage_GiftMessage_Model_Observer extends Varien_Object { /** * Set gift messages to order item on import item * * @param Varien_Object $observer * @return Mage_GiftMessage_Model_Observer */ public function salesEventConvertQuoteItemToOrderItem($observer) { $orderItem = $observer->getEvent()->getOrderItem(); $quoteItem = $observer->getEvent()->getItem(); $isAvailable = Mage::helper('giftmessage/message')->getIsMessagesAvailable( 'item', $quoteItem, $quoteItem->getStoreId() ); $orderItem->setGiftMessageId($quoteItem->getGiftMessageId()) ->setGiftMessageAvailable($isAvailable); return $this; } /** * Set gift messages to order from quote address * * @param Varien_Object $observer * @return Mage_GiftMessage_Model_Observer */ public function salesEventConvertQuoteAddressToOrder($observer) { if($observer->getEvent()->getAddress()->getGiftMessageId()) { $observer->getEvent()->getOrder() ->setGiftMessageId($observer->getEvent()->getAddress()->getGiftMessageId()); } return $this; } /** * Set gift messages to order from quote address * * @param Varien_Object $observer * @return Mage_GiftMessage_Model_Observer */ public function salesEventConvertQuoteToOrder($observer) { $observer->getEvent()->getOrder() ->setGiftMessageId($observer->getEvent()->getQuote()->getGiftMessageId()); return $this; } /** * Geter for available gift messages value from product * * @deprecated after 1.5.0.0 * @param Mage_Catalog_Model_Product|integer $product * @return integer|null */ protected function _getAvailable($product) { if(is_object($product)) { return $product->getGiftMessageAvailable(); } return Mage::getModel('catalog/product')->load($product)->getGiftMessageAvailable(); } /** * Operate with gift messages on checkout proccess * * @param Varieb_Object $observer * @return Mage_GiftMessage_Model_Observer */ public function checkoutEventCreateGiftMessage($observer) { $giftMessages = $observer->getEvent()->getRequest()->getParam('giftmessage'); $quote = $observer->getEvent()->getQuote(); /* @var $quote Mage_Sales_Model_Quote */ if(is_array($giftMessages)) { foreach ($giftMessages as $entityId=>$message) { $giftMessage = Mage::getModel('giftmessage/message'); switch ($message['type']) { case 'quote': $entity = $quote; break; case 'quote_item': $entity = $quote->getItemById($entityId); break; case 'quote_address': $entity = $quote->getAddressById($entityId); break; case 'quote_address_item': $entity = $quote->getAddressById($message['address'])->getItemById($entityId); break; default: $entity = $quote; break; } if($entity->getGiftMessageId()) { $giftMessage->load($entity->getGiftMessageId()); } if(trim($message['message'])=='') { if($giftMessage->getId()) { try{ $giftMessage->delete(); $entity->setGiftMessageId(0) ->save(); } catch (Exception $e) { } } continue; } try { $giftMessage->setSender($message['from']) ->setRecipient($message['to']) ->setMessage($message['message']) ->save(); $entity->setGiftMessageId($giftMessage->getId()) ->save(); } catch (Exception $e) { } } } return $this; } /** * Set giftmessage available default value to product * on catalog products collection load * * @deprecated after 1.4.2.0-beta1 * @param Varien_Object $observer * @return Mage_GiftMessage_Model_Observer */ public function catalogEventProductCollectionAfterLoad($observer) { return $this; } /** * Duplicates giftmessage from order to quote on import or reorder * * @param Varien_Event_Observer $observer * @return Mage_GiftMessage_Model_Observer */ public function salesEventOrderToQuote($observer) { $order = $observer->getEvent()->getOrder(); // Do not import giftmessage data if order is reordered if ($order->getReordered()) { return $this; } if (!Mage::helper('giftmessage/message')->isMessagesAvailable('order', $order, $order->getStore())){ return $this; } $giftMessageId = $order->getGiftMessageId(); if($giftMessageId) { $giftMessage = Mage::getModel('giftmessage/message')->load($giftMessageId) ->setId(null) ->save(); $observer->getEvent()->getQuote()->setGiftMessageId($giftMessage->getId()); } return $this; } /** * Duplicates giftmessage from order item to quote item on import or reorder * * @param Varien_Event_Observer $observer * @return Mage_GiftMessage_Model_Observer */ public function salesEventOrderItemToQuoteItem($observer) { /** @var $orderItem Mage_Sales_Model_Order_Item */ $orderItem = $observer->getEvent()->getOrderItem(); // Do not import giftmessage data if order is reordered $order = $orderItem->getOrder(); if ($order && $order->getReordered()) { return $this; } $isAvailable = Mage::helper('giftmessage/message')->isMessagesAvailable( 'order_item', $orderItem, $orderItem->getStoreId() ); if (!$isAvailable) { return $this; } /** @var $quoteItem Mage_Sales_Model_Quote_Item */ $quoteItem = $observer->getEvent()->getQuoteItem(); if ($giftMessageId = $orderItem->getGiftMessageId()) { $giftMessage = Mage::getModel('giftmessage/message')->load($giftMessageId) ->setId(null) ->save(); $quoteItem->setGiftMessageId($giftMessage->getId()); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_GoogleCheckout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Google Checkout shortcut link * * @category Mage * @package Mage_GoogleCheckout * @author Magento Core Team */ class Mage_GoogleCheckout_Block_Link extends Mage_Core_Block_Template { public function getImageStyle() { $s = Mage::getStoreConfig('google/checkout/checkout_image'); if (!$s) { $s = '180/46/trans'; } return explode('/', $s); } public function getImageUrl() { $url = 'https://checkout.google.com/buttons/checkout.gif'; $url .= '?merchant_id='.Mage::getStoreConfig('google/checkout/merchant_id'); $v = $this->getImageStyle(); $url .= '&w='.$v[0].'&h='.$v[1].'&style='.$v[2]; $url .= '&variant='.($this->getIsDisabled() ? 'disabled' : 'text'); $url .= '&loc='.Mage::getStoreConfig('google/checkout/locale'); return $url; } public function getCheckoutUrl() { return $this->getUrl('googlecheckout/redirect/checkout'); } /** * @deprecated after 1.4.1.1 * @return bool */ public function getIsActiveAanalytics() { return false; } public function getImageWidth() { $v = $this->getImageStyle(); return $v[0]; } public function getImageHeight() { $v = $this->getImageStyle(); return $v[1]; } /** * Check whether method is available and render HTML * @return string */ public function _toHtml() { $quote = Mage::getSingleton('checkout/session')->getQuote(); if (Mage::getModel('googlecheckout/payment')->isAvailable($quote) && $quote->validateMinimumAmount()) { Mage::dispatchEvent('googlecheckout_block_link_html_before', array('block' => $this)); return parent::_toHtml(); } return ''; } public function getIsDisabled() { $quote = Mage::getSingleton('checkout/session')->getQuote(); /* @var $quote Mage_Sales_Model_Quote */ foreach ($quote->getAllVisibleItems() as $item) { /* @var $item Mage_Sales_Model_Quote_Item */ if (!$item->getProduct()->getEnableGooglecheckout()) { return true; } } return false; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_GoogleCheckout * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * GoogleCheckout data helper */ class Mage_GoogleCheckout_Helper_Data extends Mage_Core_Helper_Abstract { /** * Google Checkout settings */ const XML_PATH_REQUEST_PHONE = 'google/checkout/request_phone'; const XML_PATH_DISABLE_DEFAULT_TAX_TABLES = 'google/checkout/disable_default_tax_tables'; /** * Google Checkout Shipping - Digital Delivery settings */ const XML_PATH_SHIPPING_VIRTUAL_ACTIVE = 'google/checkout_shipping_virtual/active'; const XML_PATH_SHIPPING_VIRTUAL_SCHEDULE = 'google/checkout_shipping_virtual/schedule'; const XML_PATH_SHIPPING_VIRTUAL_METHOD = 'google/checkout_shipping_virtual/method'; /** * Google Checkout Shipping - Carrier Calculated settings */ const XML_PATH_SHIPPING_CARRIER_ACTIVE = 'google/checkout_shipping_carrier/active'; const XML_PATH_SHIPPING_CARRIER_METHODS = 'google/checkout_shipping_carrier/methods'; const XML_PATH_SHIPPING_CARRIER_DEFAULT_PRICE = 'google/checkout_shipping_carrier/default_price'; const XML_PATH_SHIPPING_CARRIER_DEFAULT_WIDTH = 'google/checkout_shipping_carrier/default_width'; const XML_PATH_SHIPPING_CARRIER_DEFAULT_HEIGHT = 'google/checkout_shipping_carrier/default_height'; const XML_PATH_SHIPPING_CARRIER_DEFAULT_LENGTH = 'google/checkout_shipping_carrier/default_length'; const XML_PATH_SHIPPING_CARRIER_ADDRESS_CATEGORY = 'google/checkout_shipping_carrier/address_category'; /** * Google Checkout Shipping - Flat Rate settings */ const XML_PATH_SHIPPING_FLATRATE_ACTIVE = 'google/checkout_shipping_flatrate/active'; /** * Google Checkout Shipping - Merchant Calculated settings */ const XML_PATH_SHIPPING_MERCHANT_ACTIVE = 'google/checkout_shipping_merchant/active'; const XML_PATH_SHIPPING_MERCHANT_ALLOWED_METHODS = 'google/checkout_shipping_merchant/allowed_methods'; /** * Google Checkout Shipping - Pickup settings */ const XML_PATH_SHIPPING_PICKUP_ACTIVE = 'google/checkout_shipping_pickup/active'; const XML_PATH_SHIPPING_PICKUP_TITLE = 'google/checkout_shipping_pickup/title'; const XML_PATH_SHIPPING_PICKUP_PRICE = 'google/checkout_shipping_pickup/price'; /** * Check if option googlecheckout shipping carrier is enabled * * @param $storeId * @return bool */ public function isShippingCarrierActive($storeId) { return (true == Mage::getStoreConfig(self::XML_PATH_SHIPPING_CARRIER_ACTIVE, $storeId)); } /** * Convert Magento zip range to array of Google Checkout zip-patterns * (e.g., 12000-13999 -> [12*, 13*]) * * @param string $zipRange * @return array */ public function zipRangeToZipPattern($zipRange) { $zipLength = 5; $zipPattern = array(); if (!preg_match("/^(.+)-(.+)$/", $zipRange, $zipParts)) { return array($zipRange); } if ($zipParts[1] == $zipParts[2]) { return array($zipParts[1]); } if ($zipParts[1] > $zipParts[2]) { list($zipParts[2], $zipParts[1]) = array($zipParts[1], $zipParts[2]); } $from = str_split($zipParts[1]); $to = str_split($zipParts[2]); $startZip = ''; $diffPosition = null; for ($pos = 0; $pos < $zipLength; $pos++) { if ($from[$pos] == $to[$pos]) { $startZip .= $from[$pos]; } else { $diffPosition = $pos; break; } } /* * calculate zip-patterns */ if (min(array_slice($to, $diffPosition)) == 9 && max(array_slice($from, $diffPosition)) == 0) { // particular case like 11000-11999 -> 11* return array($startZip . '*'); } else { // calculate approximate zip-patterns $start = $from[$diffPosition]; $finish = $to[$diffPosition]; if ($diffPosition < $zipLength - 1) { $start++; $finish--; } $end = $diffPosition < $zipLength - 1 ? '*' : ''; for ($digit = $start; $digit <= $finish; $digit++) { $zipPattern[] = $startZip . $digit . $end; } } if ($diffPosition == $zipLength - 1) { return $zipPattern; } $nextAsteriskFrom = true; $nextAsteriskTo = true; for ($pos = $zipLength - 1; $pos > $diffPosition; $pos--) { // calculate zip-patterns based on $from value if ($from[$pos] == 0 && $nextAsteriskFrom) { $nextAsteriskFrom = true; } else { $subZip = ''; for ($k = $diffPosition; $k < $pos; $k++) { $subZip .= $from[$k]; } $delta = $nextAsteriskFrom ? 0 : 1; $end = $pos < $zipLength - 1 ? '*' : ''; for ($i = $from[$pos] + $delta; $i <= 9; $i++) { $zipPattern[] = $startZip . $subZip . $i . $end; } $nextAsteriskFrom = false; } // calculate zip-patterns based on $to value if ($to[$pos] == 9 && $nextAsteriskTo) { $nextAsteriskTo = true; } else { $subZip = ''; for ($k = $diffPosition; $k < $pos; $k++) { $subZip .= $to[$k]; } $delta = $nextAsteriskTo ? 0 : 1; $end = $pos < $zipLength - 1 ? '*' : ''; for ($i = 0; $i <= $to[$pos] - $delta; $i++) { $zipPattern[] = $startZip . $subZip . $i . $end; } $nextAsteriskTo = false; } } if ($nextAsteriskFrom) { $zipPattern[] = $startZip . $from[$diffPosition] . '*'; } if ($nextAsteriskTo) { $zipPattern[] = $startZip . $to[$diffPosition] . '*'; } return $zipPattern; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Reports * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Reports Event observer model * * @category Mage * @package Mage_Reports * @author Magento Core Team */ class Mage_Reports_Model_Event_Observer { /** * Abstract Event obeserver logic * * Save event * * @param int $eventTypeId * @param int $objectId * @param int $subjectId * @param int $subtype * @return Mage_Reports_Model_Event_Observer */ protected function _event($eventTypeId, $objectId, $subjectId = null, $subtype = 0) { if (is_null($subjectId)) { if (Mage::getSingleton('customer/session')->isLoggedIn()) { $customer = Mage::getSingleton('customer/session')->getCustomer(); $subjectId = $customer->getId(); } else { $subjectId = Mage::getSingleton('log/visitor')->getId(); $subtype = 1; } } $eventModel = Mage::getModel('reports/event'); $storeId = Mage::app()->getStore()->getId(); $eventModel ->setEventTypeId($eventTypeId) ->setObjectId($objectId) ->setSubjectId($subjectId) ->setSubtype($subtype) ->setStoreId($storeId); $eventModel->save(); return $this; } /** * Customer login action * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function customerLogin(Varien_Event_Observer $observer) { if (!Mage::getSingleton('customer/session')->isLoggedIn()) { return $this; } $visitorId = Mage::getSingleton('log/visitor')->getId(); $customerId = Mage::getSingleton('customer/session')->getCustomerId(); $eventModel = Mage::getModel('reports/event'); $eventModel->updateCustomerType($visitorId, $customerId); Mage::getModel('reports/product_index_compared') ->updateCustomerFromVisitor() ->calculate(); Mage::getModel('reports/product_index_viewed') ->updateCustomerFromVisitor() ->calculate(); return $this; } /** * Customer logout processing * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function customerLogout(Varien_Event_Observer $observer) { Mage::getModel('reports/product_index_compared') ->purgeVisitorByCustomer() ->calculate(); Mage::getModel('reports/product_index_viewed') ->purgeVisitorByCustomer() ->calculate(); return $this; } /** * View Catalog Product action * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function catalogProductView(Varien_Event_Observer $observer) { $productId = $observer->getEvent()->getProduct()->getId(); Mage::getModel('reports/product_index_viewed') ->setProductId($productId) ->save() ->calculate(); return $this->_event(Mage_Reports_Model_Event::EVENT_PRODUCT_VIEW, $productId); } /** * Send Product link to friends action * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function sendfriendProduct(Varien_Event_Observer $observer) { return $this->_event(Mage_Reports_Model_Event::EVENT_PRODUCT_SEND, $observer->getEvent()->getProduct()->getId() ); } /** * Remove Product from Compare Products action * * Reset count of compared products cache * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function catalogProductCompareRemoveProduct(Varien_Event_Observer $observer) { Mage::getModel('reports/product_index_compared')->calculate(); return $this; } /** * Remove All Products from Compare Products * * Reset count of compared products cache * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function catalogProductCompareClear(Varien_Event_Observer $observer) { Mage::getModel('reports/product_index_compared')->calculate(); return $this; } /** * Add Product to Compare Products List action * * Reset count of compared products cache * * @param Varien_Event_Observer $observer * @return unknown */ public function catalogProductCompareAddProduct(Varien_Event_Observer $observer) { $productId = $observer->getEvent()->getProduct()->getId(); Mage::getModel('reports/product_index_compared') ->setProductId($productId) ->save() ->calculate(); return $this->_event(Mage_Reports_Model_Event::EVENT_PRODUCT_COMPARE, $productId); } /** * Add product to shopping cart action * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function checkoutCartAddProduct(Varien_Event_Observer $observer) { $quoteItem = $observer->getEvent()->getItem(); if (!$quoteItem->getId() && !$quoteItem->getParentItem()) { $productId = $quoteItem->getProductId(); $this->_event(Mage_Reports_Model_Event::EVENT_PRODUCT_TO_CART, $productId); } return $this; } /** * Add product to wishlist action * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function wishlistAddProduct(Varien_Event_Observer $observer) { return $this->_event(Mage_Reports_Model_Event::EVENT_PRODUCT_TO_WISHLIST, $observer->getEvent()->getProduct()->getId() ); } /** * Share customer wishlist action * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function wishlistShare(Varien_Event_Observer $observer) { return $this->_event(Mage_Reports_Model_Event::EVENT_WISHLIST_SHARE, $observer->getEvent()->getWishlist()->getId() ); } /** * Clean events by old visitors * * @see Global Log Clean Settings * * @param Varien_Event_Observer $observer * @return Mage_Reports_Model_Event_Observer */ public function eventClean(Varien_Event_Observer $observer) { /* @var $event Mage_Reports_Model_Event */ $event = Mage::getModel('reports/event'); $event->clean(); Mage::getModel('reports/product_index_compared')->clean(); Mage::getModel('reports/product_index_viewed')->clean(); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Rule data helper */ class Mage_Rule_Helper_Data extends Mage_Core_Helper_Abstract { } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract Rule entity data model * * @category Mage * @package Mage_Rule * @author Magento Core Team */ abstract class Mage_Rule_Model_Abstract extends Mage_Core_Model_Abstract { /** * Store rule combine conditions model * * @var Mage_Rule_Model_Condition_Combine */ protected $_conditions; /** * Store rule actions model * * @var Mage_Rule_Model_Action_Collection */ protected $_actions; /** * Store rule form instance * * @var Varien_Data_Form */ protected $_form; /** * Is model can be deleted flag * * @var bool */ protected $_isDeleteable = true; /** * Is model readonly * * @var bool */ protected $_isReadonly = false; /** * Getter for rule combine conditions instance * * @return Mage_Rule_Model_Condition_Combine */ abstract public function getConditionsInstance(); /** * Getter for rule actions collection instance * * @return Mage_Rule_Model_Action_Collection */ abstract public function getActionsInstance(); /** * Prepare data before saving * * @return Mage_Rule_Model_Abstract */ protected function _beforeSave() { // Check if discount amount not negative if ($this->hasDiscountAmount()) { if ((int)$this->getDiscountAmount() < 0) { Mage::throwException(Mage::helper('rule')->__('Invalid discount amount.')); } } // Serialize conditions if ($this->getConditions()) { $this->setConditionsSerialized(serialize($this->getConditions()->asArray())); $this->unsConditions(); } // Serialize actions if ($this->getActions()) { $this->setActionsSerialized(serialize($this->getActions()->asArray())); $this->unsActions(); } /** * Prepare website Ids if applicable and if they were set as string in comma separated format. * Backwards compatibility. */ if ($this->hasWebsiteIds()) { $websiteIds = $this->getWebsiteIds(); if (is_string($websiteIds) && !empty($websiteIds)) { $this->setWebsiteIds(explode(',', $websiteIds)); } } /** * Prepare customer group Ids if applicable and if they were set as string in comma separated format. * Backwards compatibility. */ if ($this->hasCustomerGroupIds()) { $groupIds = $this->getCustomerGroupIds(); if (is_string($groupIds) && !empty($groupIds)) { $this->setCustomerGroupIds(explode(',', $groupIds)); } } parent::_beforeSave(); return $this; } /** * Set rule combine conditions model * * @param Mage_Rule_Model_Condition_Combine $conditions * * @return Mage_Rule_Model_Abstract */ public function setConditions($conditions) { $this->_conditions = $conditions; return $this; } /** * Retrieve rule combine conditions model * * @return Mage_Rule_Model_Condition_Combine */ public function getConditions() { if (empty($this->_conditions)) { $this->_resetConditions(); } // Load rule conditions if it is applicable if ($this->hasConditionsSerialized()) { $conditions = $this->getConditionsSerialized(); if (!empty($conditions)) { $conditions = unserialize($conditions); if (is_array($conditions) && !empty($conditions)) { $this->_conditions->loadArray($conditions); } } $this->unsConditionsSerialized(); } return $this->_conditions; } /** * Set rule actions model * * @param Mage_Rule_Model_Action_Collection $actions * * @return Mage_Rule_Model_Abstract */ public function setActions($actions) { $this->_actions = $actions; return $this; } /** * Retrieve rule actions model * * @return Mage_Rule_Model_Action_Collection */ public function getActions() { if (!$this->_actions) { $this->_resetActions(); } // Load rule actions if it is applicable if ($this->hasActionsSerialized()) { $actions = $this->getActionsSerialized(); if (!empty($actions)) { $actions = unserialize($actions); if (is_array($actions) && !empty($actions)) { $this->_actions->loadArray($actions); } } $this->unsActionsSerialized(); } return $this->_actions; } /** * Reset rule combine conditions * * @param null|Mage_Rule_Model_Condition_Combine $conditions * * @return Mage_Rule_Model_Abstract */ protected function _resetConditions($conditions = null) { if (is_null($conditions)) { $conditions = $this->getConditionsInstance(); } $conditions->setRule($this)->setId('1')->setPrefix('conditions'); $this->setConditions($conditions); return $this; } /** * Reset rule actions * * @param null|Mage_Rule_Model_Action_Collection $actions * * @return Mage_Rule_Model_Abstract */ protected function _resetActions($actions = null) { if (is_null($actions)) { $actions = $this->getActionsInstance(); } $actions->setRule($this)->setId('1')->setPrefix('actions'); $this->setActions($actions); return $this; } /** * Rule form getter * * @return Varien_Data_Form */ public function getForm() { if (!$this->_form) { $this->_form = new Varien_Data_Form(); } return $this->_form; } /** * Initialize rule model data from array * * @param array $data * * @return Mage_Rule_Model_Abstract */ public function loadPost(array $data) { $arr = $this->_convertFlatToRecursive($data); if (isset($arr['conditions'])) { $this->getConditions()->setConditions(array())->loadArray($arr['conditions'][1]); } if (isset($arr['actions'])) { $this->getActions()->setActions(array())->loadArray($arr['actions'][1], 'actions'); } return $this; } /** * Set specified data to current rule. * Set conditions and actions recursively. * Convert dates into Zend_Date. * * @param array $data * * @return array */ protected function _convertFlatToRecursive(array $data) { $arr = array(); foreach ($data as $key => $value) { if (($key === 'conditions' || $key === 'actions') && is_array($value)) { foreach ($value as $id=>$data) { $path = explode('--', $id); $node =& $arr; for ($i=0, $l=sizeof($path); $i<$l; $i++) { if (!isset($node[$key][$path[$i]])) { $node[$key][$path[$i]] = array(); } $node =& $node[$key][$path[$i]]; } foreach ($data as $k => $v) { $node[$k] = $v; } } } else { /** * Convert dates into Zend_Date */ if (in_array($key, array('from_date', 'to_date')) && $value) { $value = Mage::app()->getLocale()->date( $value, Varien_Date::DATE_INTERNAL_FORMAT, null, false ); } $this->setData($key, $value); } } return $arr; } /** * Validate rule conditions to determine if rule can run * * @param Varien_Object $object * * @return bool */ public function validate(Varien_Object $object) { return $this->getConditions()->validate($object); } /** * Validate rule data * * @param Varien_Object $object * * @return bool|array - return true if validation passed successfully. Array with errors description otherwise */ public function validateData(Varien_Object $object) { $result = array(); $fromDate = $toDate = null; if ($object->hasFromDate() && $object->hasToDate()) { $fromDate = $object->getFromDate(); $toDate = $object->getToDate(); } if ($fromDate && $toDate) { $fromDate = new Zend_Date($fromDate, Varien_Date::DATE_INTERNAL_FORMAT); $toDate = new Zend_Date($toDate, Varien_Date::DATE_INTERNAL_FORMAT); if ($fromDate->compare($toDate) === 1) { $result[] = Mage::helper('rule')->__('End Date must be greater than Start Date.'); } } if ($object->hasWebsiteIds()) { $websiteIds = $object->getWebsiteIds(); if (empty($websiteIds)) { $result[] = Mage::helper('rule')->__('Websites must be specified.'); } } if ($object->hasCustomerGroupIds()) { $customerGroupIds = $object->getCustomerGroupIds(); if (empty($customerGroupIds)) { $result[] = Mage::helper('rule')->__('Customer Groups must be specified.'); } } return !empty($result) ? $result : true; } /** * Check availability to delete rule * * @return bool */ public function isDeleteable() { return $this->_isDeleteable; } /** * Set is rule can be deleted flag * * @param bool $value * * @return Mage_Rule_Model_Abstract */ public function setIsDeleteable($value) { $this->_isDeleteable = (bool) $value; return $this; } /** * Check if rule is readonly * * @return bool */ public function isReadonly() { return $this->_isReadonly; } /** * Set is readonly flag to rule * * @param bool $value * * @return Mage_Rule_Model_Abstract */ public function setIsReadonly($value) { $this->_isReadonly = (bool) $value; return $this; } /** * Get rule associated website Ids * * @return array */ public function getWebsiteIds() { if (!$this->hasWebsiteIds()) { $websiteIds = $this->_getResource()->getWebsiteIds($this->getId()); $this->setData('website_ids', (array)$websiteIds); } return $this->_getData('website_ids'); } /** * @deprecated since 1.7.0.0 * * @param string $format * * @return string */ public function asString($format='') { return ''; } /** * @deprecated since 1.7.0.0 * * @return string */ public function asHtml() { return ''; } /** * Returns rule as an array for admin interface * * @deprecated since 1.7.0.0 * * @param array $arrAttributes * * @return array */ public function asArray(array $arrAttributes = array()) { return array(); } /** * Combine website ids to string * * @deprecated since 1.7.0.0 * * @return Mage_Rule_Model_Abstract */ protected function _prepareWebsiteIds() { return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Rule_Model_Condition_Combine extends Mage_Rule_Model_Condition_Abstract { /** * Store all used condition models * * @var array */ static protected $_conditionModels = array(); /** * Retrieve new object for each requested model. * If model is requested first time, store it at static array. * * It's made by performance reasons to avoid initialization of same models each time when rules are being processed. * * @param string $modelClass * @return Mage_Rule_Model_Condition_Abstract|bool */ protected function _getNewConditionModelInstance($modelClass) { if (empty($modelClass)) { return false; } if (!array_key_exists($modelClass, self::$_conditionModels)) { $model = Mage::getModel($modelClass); self::$_conditionModels[$modelClass] = $model; } else { $model = self::$_conditionModels[$modelClass]; } if (!$model) { return false; } $newModel = clone $model; return $newModel; } public function __construct() { parent::__construct(); $this->setType('rule/condition_combine') ->setAggregator('all') ->setValue(true) ->setConditions(array()) ->setActions(array()); $this->loadAggregatorOptions(); if ($options = $this->getAggregatorOptions()) { foreach ($options as $aggregator=>$dummy) { $this->setAggregator($aggregator); break; } } } /* start aggregator methods */ public function loadAggregatorOptions() { $this->setAggregatorOption(array( 'all' => Mage::helper('rule')->__('ALL'), 'any' => Mage::helper('rule')->__('ANY'), )); return $this; } public function getAggregatorSelectOptions() { $opt = array(); foreach ($this->getAggregatorOption() as $k=>$v) { $opt[] = array('value'=>$k, 'label'=>$v); } return $opt; } public function getAggregatorName() { return $this->getAggregatorOption($this->getAggregator()); } public function getAggregatorElement() { if (is_null($this->getAggregator())) { foreach ($this->getAggregatorOption() as $k=>$v) { $this->setAggregator($k); break; } } return $this->getForm()->addField($this->getPrefix().'__'.$this->getId().'__aggregator', 'select', array( 'name'=>'rule['.$this->getPrefix().']['.$this->getId().'][aggregator]', 'values'=>$this->getAggregatorSelectOptions(), 'value'=>$this->getAggregator(), 'value_name'=>$this->getAggregatorName(), ))->setRenderer(Mage::getBlockSingleton('rule/editable')); } /* end aggregator methods */ public function loadValueOptions() { $this->setValueOption(array( 1 => Mage::helper('rule')->__('TRUE'), 0 => Mage::helper('rule')->__('FALSE'), )); return $this; } public function addCondition($condition) { $condition->setRule($this->getRule()); $condition->setObject($this->getObject()); $condition->setPrefix($this->getPrefix()); $conditions = $this->getConditions(); $conditions[] = $condition; if (!$condition->getId()) { $condition->setId($this->getId().'--'.sizeof($conditions)); } $this->setData($this->getPrefix(), $conditions); return $this; } public function getValueElementType() { return 'select'; } /** * Returns array containing conditions in the collection * * Output example: * array( * 'type'=>'combine', * 'operator'=>'ALL', * 'value'=>'TRUE', * 'conditions'=>array( * {condition::asArray}, * {combine::asArray}, * {quote_item_combine::asArray} * ) * ) * * @return array */ public function asArray(array $arrAttributes = array()) { $out = parent::asArray(); $out['aggregator'] = $this->getAggregator(); foreach ($this->getConditions() as $condition) { $out['conditions'][] = $condition->asArray(); } return $out; } public function asXml($containerKey='conditions', $itemKey='condition') { $xml = "".$this->getAggregator()."" ."".$this->getValue()."" ."<$containerKey>"; foreach ($this->getConditions() as $condition) { $xml .= "<$itemKey>".$condition->asXml().""; } $xml .= ""; return $xml; } public function loadArray($arr, $key='conditions') { $this->setAggregator(isset($arr['aggregator']) ? $arr['aggregator'] : (isset($arr['attribute']) ? $arr['attribute'] : null)) ->setValue(isset($arr['value']) ? $arr['value'] : (isset($arr['operator']) ? $arr['operator'] : null)); if (!empty($arr[$key]) && is_array($arr[$key])) { foreach ($arr[$key] as $condArr) { try { $cond = $this->_getNewConditionModelInstance($condArr['type']); if ($cond) { $this->addCondition($cond); $cond->loadArray($condArr, $key); } } catch (Exception $e) { Mage::logException($e); } } } return $this; } public function loadXml($xml) { if (is_string($xml)) { $xml = simplexml_load_string($xml); } $arr = parent::loadXml($xml); foreach ($xml->conditions->children() as $condition) { $arr['conditions'] = parent::loadXml($condition); } $this->loadArray($arr); return $this; } public function asHtml() { $html = $this->getTypeElement()->getHtml(). Mage::helper('rule')->__('If %s of these conditions are %s:', $this->getAggregatorElement()->getHtml(), $this->getValueElement()->getHtml()); if ($this->getId() != '1') { $html.= $this->getRemoveLinkHtml(); } return $html; } public function getNewChildElement() { return $this->getForm()->addField($this->getPrefix().'__'.$this->getId().'__new_child', 'select', array( 'name'=>'rule['.$this->getPrefix().']['.$this->getId().'][new_child]', 'values'=>$this->getNewChildSelectOptions(), 'value_name'=>$this->getNewChildName(), ))->setRenderer(Mage::getBlockSingleton('rule/newchild')); } public function asHtmlRecursive() { $html = $this->asHtml().'
    '; foreach ($this->getConditions() as $cond) { $html .= '
  • '.$cond->asHtmlRecursive().'
  • '; } $html .= '
  • '.$this->getNewChildElement()->getHtml().'
'; return $html; } public function asString($format='') { $str = Mage::helper('rule')->__("If %s of these conditions are %s:", $this->getAggregatorName(), $this->getValueName()); return $str; } public function asStringRecursive($level=0) { $str = parent::asStringRecursive($level); foreach ($this->getConditions() as $cond) { $str .= "\n".$cond->asStringRecursive($level+1); } return $str; } public function validate(Varien_Object $object) { if (!$this->getConditions()) { return true; } $all = $this->getAggregator() === 'all'; $true = (bool)$this->getValue(); foreach ($this->getConditions() as $cond) { $validated = $cond->validate($object); if ($all && $validated !== $true) { return false; } elseif (!$all && $validated === $true) { return true; } } return $all ? true : false; } public function setJsFormObject($form) { $this->setData('js_form_object', $form); foreach ($this->getConditions() as $condition) { $condition->setJsFormObject($form); } return $this; } /** * Get conditions, if current prefix is undefined use 'conditions' key * * @return array */ public function getConditions() { $key = $this->getPrefix() ? $this->getPrefix() : 'conditions'; return $this->getData($key); } /** * Set conditions, if current prefix is undefined use 'conditions' key * * @param array $conditions * @return Mage_Rule_Model_Condition_Combine */ public function setConditions($conditions) { $key = $this->getPrefix() ? $this->getPrefix() : 'conditions'; return $this->setData($key, $conditions); } /** * Getter for "Conditions Combination" select option for recursive combines */ protected function _getRecursiveChildSelectOption() { return array('value' => $this->getType(), 'label' => Mage::helper('rule')->__('Conditions Combination')); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract Rule entity resource collection model * * @category Mage * @package Mage_Rule * @author Magento Core Team */ abstract class Mage_Rule_Model_Resource_Rule_Collection_Abstract extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Store associated with rule entities information map * * Example: * array( * 'entity_type1' => array( * 'associations_table' => 'table_name', * 'rule_id_field' => 'rule_id', * 'entity_id_field' => 'entity_id' * ), * 'entity_type2' => array( * 'associations_table' => 'table_name', * 'rule_id_field' => 'rule_id', * 'entity_id_field' => 'entity_id' * ) * .... * ) * * @var array */ protected $_associatedEntitiesMap = array(); /** * Quote rule environment * * @deprecated after 1.6.1.0 * * @var Mage_Rule_Model_Environment */ protected $_env; /** * Add website ids to rules data * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ protected function _afterLoad() { parent::_afterLoad(); if ($this->getFlag('add_websites_to_result') && $this->_items) { /** @var Mage_Rule_Model_Abstract $item */ foreach ($this->_items as $item) { $item->afterLoad(); } } return $this; } /** * Init flag for adding rule website ids to collection result * * @param bool|null $flag * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function addWebsitesToResult($flag = null) { $flag = ($flag === null) ? true : $flag; $this->setFlag('add_websites_to_result', $flag); return $this; } /** * Limit rules collection by specific websites * * @param int|array|Mage_Core_Model_Website $websiteId * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function addWebsiteFilter($websiteId) { $entityInfo = $this->_getAssociatedEntityInfo('website'); if (!$this->getFlag('is_website_table_joined')) { $this->setFlag('is_website_table_joined', true); if ($websiteId instanceof Mage_Core_Model_Website) { $websiteId = $websiteId->getId(); } $subSelect = $this->getConnection()->select() ->from(array('website' => $this->getTable($entityInfo['associations_table'])), '') ->where('website.' . $entityInfo['entity_id_field'] . ' IN (?)', $websiteId); $this->getSelect()->exists( $subSelect, 'main_table.' . $entityInfo['rule_id_field'] . ' = website.' . $entityInfo['rule_id_field'] ); } return $this; } /** * Provide support for website id filter * * @param string $field * @param mixed $condition * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function addFieldToFilter($field, $condition = null) { if ($field == 'website_ids') { return $this->addWebsiteFilter($condition); } parent::addFieldToFilter($field, $condition); return $this; } /** * Filter collection to only active or inactive rules * * @param int $isActive * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function addIsActiveFilter($isActive = 1) { if (!$this->getFlag('is_active_filter')) { $this->addFieldToFilter('is_active', (int)$isActive ? 1 : 0); $this->setFlag('is_active_filter', true); } return $this; } /** * Retrieve correspondent entity information (associations table name, columns names) * of rule's associated entity by specified entity type * * @param string $entityType * * @return array */ protected function _getAssociatedEntityInfo($entityType) { if (isset($this->_associatedEntitiesMap[$entityType])) { return $this->_associatedEntitiesMap[$entityType]; } $e = Mage::exception( 'Mage_Core', Mage::helper('rule')->__( 'There is no information about associated entity type "%s".', $entityType ) ); throw $e; } /** * Set environment for all rules in collection * * @deprecated after 1.6.2.0 * * @param Mage_Rule_Model_Environment $env * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function setEnv(Mage_Rule_Model_Environment $env = null) { $this->_env = $env; return $this; } /** * Retrieve environment for the rules in collection * * @deprecated after 1.6.2.0 * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function getEnv() { return $this->_env; } /** * Set filter for the collection based on the environment * * @deprecated after 1.6.2.0 * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function setActiveFilter() { return $this; } /** * Process the quote with all the rules in collection * * @deprecated after 1.6.2.0 * * @return Mage_Rule_Model_Resource_Rule_Collection_Abstract */ public function process() { return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Rule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract Rule entity data model * * @deprecated since 1.7.0.0 use Mage_Rule_Model_Abstract instead * * @category Mage * @package Mage_Rule * @author Magento Core Team */ class Mage_Rule_Model_Rule extends Mage_Rule_Model_Abstract { /** * Getter for rule combine conditions instance * * @return Mage_Rule_Model_Condition_Combine */ public function getConditionsInstance() { return Mage::getModel('rule/condition_combine'); } /** * Getter for rule actions collection instance * * @return Mage_Rule_Model_Action_Collection */ public function getActionsInstance() { return Mage::getModel('rule/action_collection'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * SalesRule data helper */ class Mage_SalesRule_Helper_Data extends Mage_Core_Helper_Abstract { /** * Set store and base price which will be used during discount calculation to item object * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @param float $basePrice * @param float $price * @return Mage_SalesRule_Helper_Data */ public function setItemDiscountPrices(Mage_Sales_Model_Quote_Item_Abstract $item, $basePrice, $price) { $item->setDiscountCalculationPrice($price); $item->setBaseDiscountCalculationPrice($basePrice); return $this; } /** * Add additional amounts to discount calculation prices * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @param float $basePrice * @param float $price * @return Mage_SalesRule_Helper_Data */ public function addItemDiscountPrices(Mage_Sales_Model_Quote_Item_Abstract $item, $basePrice, $price) { $discountPrice = $item->getDiscountCalculationPrice(); $baseDiscountPrice = $item->getBaseDiscountCalculationPrice(); if ($discountPrice || $baseDiscountPrice || $basePrice || $price) { $discountPrice = $discountPrice ? $discountPrice : $item->getCalculationPrice(); $baseDiscountPrice = $baseDiscountPrice ? $baseDiscountPrice : $item->getBaseCalculationPrice(); $this->setItemDiscountPrices($item, $baseDiscountPrice+$basePrice, $discountPrice+$price); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_SalesRule_Model_Observer { protected $_validator; /** * Get quote item validator/processor object * * @deprecated * @param Varien_Event $event * @return Mage_SalesRule_Model_Validator */ public function getValidator($event) { if (!$this->_validator) { $this->_validator = Mage::getModel('salesrule/validator') ->init($event->getWebsiteId(), $event->getCustomerGroupId(), $event->getCouponCode()); } return $this->_validator; } /** * Process quote item (apply discount to item) * * @deprecated process call movet to total model * @param Varien_Event_Observer $observer */ public function sales_quote_address_discount_item($observer) { $this->getValidator($observer->getEvent()) ->process($observer->getEvent()->getItem()); } public function sales_order_afterPlace($observer) { $order = $observer->getEvent()->getOrder(); if (!$order) { return $this; } // lookup rule ids $ruleIds = explode(',', $order->getAppliedRuleIds()); $ruleIds = array_unique($ruleIds); $ruleCustomer = null; $customerId = $order->getCustomerId(); // use each rule (and apply to customer, if applicable) foreach ($ruleIds as $ruleId) { if (!$ruleId) { continue; } $rule = Mage::getModel('salesrule/rule'); $rule->load($ruleId); if ($rule->getId()) { $rule->setTimesUsed($rule->getTimesUsed() + 1); $rule->save(); if ($customerId) { $ruleCustomer = Mage::getModel('salesrule/rule_customer'); $ruleCustomer->loadByCustomerRule($customerId, $ruleId); if ($ruleCustomer->getId()) { $ruleCustomer->setTimesUsed($ruleCustomer->getTimesUsed()+1); } else { $ruleCustomer ->setCustomerId($customerId) ->setRuleId($ruleId) ->setTimesUsed(1); } $ruleCustomer->save(); } } } $coupon = Mage::getModel('salesrule/coupon'); /** @var Mage_SalesRule_Model_Coupon */ $coupon->load($order->getCouponCode(), 'code'); if ($coupon->getId()) { $coupon->setTimesUsed($coupon->getTimesUsed() + 1); $coupon->save(); if ($customerId) { $couponUsage = Mage::getResourceModel('salesrule/coupon_usage'); $couponUsage->updateCustomerCouponTimesUsed($customerId, $coupon->getId()); } } } /** * Refresh sales coupons report statistics for last day * * @param Mage_Cron_Model_Schedule $schedule * @return Mage_SalesRule_Model_Observer */ public function aggregateSalesReportCouponsData($schedule) { Mage::app()->getLocale()->emulate(0); $currentDate = Mage::app()->getLocale()->date(); $date = $currentDate->subHour(25); Mage::getResourceModel('salesrule/report_rule')->aggregate($date); Mage::app()->getLocale()->revert(); return $this; } /** * Check rules that contains affected attribute * If rules were found they will be set to inactive and notice will be add to admin session * * @param string $attributeCode * @return Mage_SalesRule_Model_Observer */ protected function _checkSalesRulesAvailability($attributeCode) { /* @var $collection Mage_SalesRule_Model_Mysql4_Rule_Collection */ $collection = Mage::getResourceModel('salesrule/rule_collection') ->addAttributeInConditionFilter($attributeCode); $disabledRulesCount = 0; foreach ($collection as $rule) { /* @var $rule Mage_SalesRule_Model_Rule */ $rule->setIsActive(0); /* @var $rule->getConditions() Mage_SalesRule_Model_Rule_Condition_Combine */ $this->_removeAttributeFromConditions($rule->getConditions(), $attributeCode); $this->_removeAttributeFromConditions($rule->getActions(), $attributeCode); $rule->save(); $disabledRulesCount++; } if ($disabledRulesCount) { Mage::getSingleton('adminhtml/session')->addWarning( Mage::helper('salesrule')->__('%d Shopping Cart Price Rules based on "%s" attribute have been disabled.', $disabledRulesCount, $attributeCode)); } return $this; } /** * Remove catalog attribute condition by attribute code from rule conditions * * @param Mage_Rule_Model_Condition_Combine $combine * @param string $attributeCode */ protected function _removeAttributeFromConditions($combine, $attributeCode) { $conditions = $combine->getConditions(); foreach ($conditions as $conditionId => $condition) { if ($condition instanceof Mage_Rule_Model_Condition_Combine) { $this->_removeAttributeFromConditions($condition, $attributeCode); } if ($condition instanceof Mage_SalesRule_Model_Rule_Condition_Product) { if ($condition->getAttribute() == $attributeCode) { unset($conditions[$conditionId]); } } } $combine->setConditions($conditions); } /** * After save attribute if it is not used for promo rules already check rules for containing this attribute * * @param Varien_Event_Observer $observer * @return Mage_SalesRule_Model_Observer */ public function catalogAttributeSaveAfter(Varien_Event_Observer $observer) { $attribute = $observer->getEvent()->getAttribute(); if ($attribute->dataHasChangedFor('is_used_for_promo_rules') && !$attribute->getIsUsedForPromoRules()) { $this->_checkSalesRulesAvailability($attribute->getAttributeCode()); } return $this; } /** * After delete attribute check rules that contains deleted attribute * If rules was found they will seted to inactive and added notice to admin session * * @param Varien_Event_Observer $observer * @return Mage_SalesRule_Model_Observer */ public function catalogAttributeDeleteAfter(Varien_Event_Observer $observer) { $attribute = $observer->getEvent()->getAttribute(); if ($attribute->getIsUsedForPromoRules()) { $this->_checkSalesRulesAvailability($attribute->getAttributeCode()); } return $this; } /** * Append sales rule product attributes to select by quote item collection * * @param Varien_Event_Observer $observer * @return Mage_SalesRule_Model_Observer */ public function addProductAttributes(Varien_Event_Observer $observer) { // @var Varien_Object $attributesTransfer = $observer->getEvent()->getAttributes(); $attributes = Mage::getResourceModel('salesrule/rule') ->getActiveAttributes( Mage::app()->getWebsite()->getId(), Mage::getSingleton('customer/session')->getCustomer()->getGroupId() ); $result = array(); foreach ($attributes as $attribute) { $result[$attribute['attribute_code']] = true; } $attributesTransfer->addData($result); return $this; } /** * Add coupon's rule name to order data * * @param Varien_Event_Observer $observer * @return Mage_SalesRule_Model_Observer */ public function addSalesRuleNameToOrder($observer) { $order = $observer->getOrder(); $couponCode = $order->getCouponCode(); if (empty($couponCode)) { return $this; } /** * @var Mage_SalesRule_Model_Coupon $couponModel */ $couponModel = Mage::getModel('salesrule/coupon'); $couponModel->loadByCode($couponCode); $ruleId = $couponModel->getRuleId(); if (empty($ruleId)) { return $this; } /** * @var Mage_SalesRule_Model_Rule $ruleModel */ $ruleModel = Mage::getModel('salesrule/rule'); $ruleModel->load($ruleId); $order->setCouponRuleName($ruleModel->getName()); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales Rule resource model * * @category Mage * @package Mage_SalesRule * @author Magento Core Team */ class Mage_SalesRule_Model_Resource_Rule extends Mage_Rule_Model_Resource_Abstract { /** * Store associated with rule entities information map * * @var array */ protected $_associatedEntitiesMap = array( 'website' => array( 'associations_table' => 'salesrule/website', 'rule_id_field' => 'rule_id', 'entity_id_field' => 'website_id' ), 'customer_group' => array( 'associations_table' => 'salesrule/customer_group', 'rule_id_field' => 'rule_id', 'entity_id_field' => 'customer_group_id' ) ); /** * Initialize main table and table id field */ protected function _construct() { $this->_init('salesrule/rule', 'rule_id'); } /** * Add customer group ids and website ids to rule data after load * * @param Mage_Core_Model_Abstract $object * * @return Mage_SalesRule_Model_Resource_Rule */ protected function _afterLoad(Mage_Core_Model_Abstract $object) { $object->setData('customer_group_ids', (array)$this->getCustomerGroupIds($object->getId())); $object->setData('website_ids', (array)$this->getWebsiteIds($object->getId())); parent::_afterLoad($object); return $this; } /** * Prepare sales rule's discount quantity * * @param Mage_Core_Model_Abstract $object * * @return Mage_SalesRule_Model_Resource_Rule */ public function _beforeSave(Mage_Core_Model_Abstract $object) { if (!$object->getDiscountQty()) { $object->setDiscountQty(new Zend_Db_Expr('NULL')); } parent::_beforeSave($object); return $this; } /** * Bind sales rule to customer group(s) and website(s). * Save rule's associated store labels. * Save product attributes used in rule. * * @param Mage_Core_Model_Abstract $object * * @return Mage_SalesRule_Model_Resource_Rule */ protected function _afterSave(Mage_Core_Model_Abstract $object) { if ($object->hasStoreLabels()) { $this->saveStoreLabels($object->getId(), $object->getStoreLabels()); } if ($object->hasWebsiteIds()) { $websiteIds = $object->getWebsiteIds(); if (!is_array($websiteIds)) { $websiteIds = explode(',', (string)$websiteIds); } $this->bindRuleToEntity($object->getId(), $websiteIds, 'website'); } if ($object->hasCustomerGroupIds()) { $customerGroupIds = $object->getCustomerGroupIds(); if (!is_array($customerGroupIds)) { $customerGroupIds = explode(',', (string)$customerGroupIds); } $this->bindRuleToEntity($object->getId(), $customerGroupIds, 'customer_group'); } // Save product attributes used in rule $ruleProductAttributes = array_merge( $this->getProductAttributes(serialize($object->getConditions()->asArray())), $this->getProductAttributes(serialize($object->getActions()->asArray())) ); if (count($ruleProductAttributes)) { $this->setActualProductAttributes($object, $ruleProductAttributes); } // Update auto geterated specific coupons if exists if ($object->getUseAutoGeneration() && $object->hasDataChanges()) { Mage::getResourceModel('salesrule/coupon')->updateSpecificCoupons($object); } return parent::_afterSave($object); } /** * Retrieve coupon/rule uses for specified customer * * @param Mage_SalesRule_Model_Rule $rule * @param int $customerId * * @return string */ public function getCustomerUses($rule, $customerId) { $read = $this->_getReadAdapter(); $select = $read->select()->from($this->getTable('rule_customer'), array('cnt'=>'count(*)')) ->where('rule_id = :rule_id') ->where('customer_id = :customer_id'); return $read->fetchOne($select, array(':rule_id' => $rule->getRuleId(), ':customer_id' => $customerId)); } /** * Save rule labels for different store views * * @param int $ruleId * @param array $labels * * @return Mage_SalesRule_Model_Resource_Rule */ public function saveStoreLabels($ruleId, $labels) { $deleteByStoreIds = array(); $table = $this->getTable('salesrule/label'); $adapter = $this->_getWriteAdapter(); $data = array(); foreach ($labels as $storeId => $label) { if (Mage::helper('core/string')->strlen($label)) { $data[] = array('rule_id' => $ruleId, 'store_id' => $storeId, 'label' => $label); } else { $deleteByStoreIds[] = $storeId; } } $adapter->beginTransaction(); try { if (!empty($data)) { $adapter->insertOnDuplicate( $table, $data, array('label') ); } if (!empty($deleteByStoreIds)) { $adapter->delete($table, array( 'rule_id=?' => $ruleId, 'store_id IN (?)' => $deleteByStoreIds )); } } catch (Exception $e) { $adapter->rollback(); throw $e; } $adapter->commit(); return $this; } /** * Get all existing rule labels * * @param int $ruleId * @return array */ public function getStoreLabels($ruleId) { $select = $this->_getReadAdapter()->select() ->from($this->getTable('salesrule/label'), array('store_id', 'label')) ->where('rule_id = :rule_id'); return $this->_getReadAdapter()->fetchPairs($select, array(':rule_id' => $ruleId)); } /** * Get rule label by specific store id * * @param int $ruleId * @param int $storeId * @return string */ public function getStoreLabel($ruleId, $storeId) { $select = $this->_getReadAdapter()->select() ->from($this->getTable('salesrule/label'), 'label') ->where('rule_id = :rule_id') ->where('store_id IN(0, :store_id)') ->order('store_id DESC'); return $this->_getReadAdapter()->fetchOne($select, array(':rule_id' => $ruleId, ':store_id' => $storeId)); } /** * Return codes of all product attributes currently used in promo rules for specified customer group and website * * @param unknown_type $websiteId * @param unknown_type $customerGroupId * @return mixed */ public function getActiveAttributes($websiteId, $customerGroupId) { $read = $this->_getReadAdapter(); $select = $read->select() ->from(array('a' => $this->getTable('salesrule/product_attribute')), new Zend_Db_Expr('DISTINCT ea.attribute_code')) ->joinInner(array('ea' => $this->getTable('eav/attribute')), 'ea.attribute_id = a.attribute_id', array()); return $read->fetchAll($select); } /** * Save product attributes currently used in conditions and actions of rule * * @param Mage_SalesRule_Model_Rule $rule * @param mixed $attributes * @return Mage_SalesRule_Model_Resource_Rule */ public function setActualProductAttributes($rule, $attributes) { $write = $this->_getWriteAdapter(); $write->delete($this->getTable('salesrule/product_attribute'), array('rule_id=?' => $rule->getId())); //Getting attribute IDs for attribute codes $attributeIds = array(); $select = $this->_getReadAdapter()->select() ->from(array('a' => $this->getTable('eav/attribute')), array('a.attribute_id')) ->where('a.attribute_code IN (?)', array($attributes)); $attributesFound = $this->_getReadAdapter()->fetchAll($select); if ($attributesFound) { foreach ($attributesFound as $attribute) { $attributeIds[] = $attribute['attribute_id']; } $data = array(); foreach ($rule->getCustomerGroupIds() as $customerGroupId) { foreach ($rule->getWebsiteIds() as $websiteId) { foreach ($attributeIds as $attribute) { $data[] = array ( 'rule_id' => $rule->getId(), 'website_id' => $websiteId, 'customer_group_id' => $customerGroupId, 'attribute_id' => $attribute ); } } } $write->insertMultiple($this->getTable('salesrule/product_attribute'), $data); } return $this; } /** * Collect all product attributes used in serialized rule's action or condition * * @param string $serializedString * * @return array */ public function getProductAttributes($serializedString) { $result = array(); if (preg_match_all('~s:32:"salesrule/rule_condition_product";s:9:"attribute";s:\d+:"(.*?)"~s', $serializedString, $matches)){ foreach ($matches[1] as $offset => $attributeCode) { $result[] = $attributeCode; } } return $result; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales Rules resource collection model * * @category Mage * @package Mage_SalesRule * @author Magento Core Team */ class Mage_SalesRule_Model_Resource_Rule_Collection extends Mage_Rule_Model_Resource_Rule_Collection_Abstract { /** * Store associated with rule entities information map * * @var array */ protected $_associatedEntitiesMap = array( 'website' => array( 'associations_table' => 'salesrule/website', 'rule_id_field' => 'rule_id', 'entity_id_field' => 'website_id' ), 'customer_group' => array( 'associations_table' => 'salesrule/customer_group', 'rule_id_field' => 'rule_id', 'entity_id_field' => 'customer_group_id' ) ); /** * Set resource model and determine field mapping */ protected function _construct() { $this->_init('salesrule/rule'); $this->_map['fields']['rule_id'] = 'main_table.rule_id'; } /** * Filter collection by specified website, customer group, coupon code, date. * Filter collection to use only active rules. * Involved sorting by sort_order column. * * @param int $websiteId * @param int $customerGroupId * @param string $couponCode * @param string|null $now * @use $this->addWebsiteGroupDateFilter() * * @return Mage_SalesRule_Model_Resource_Rule_Collection */ public function setValidationFilter($websiteId, $customerGroupId, $couponCode = '', $now = null) { if (!$this->getFlag('validation_filter')) { /* We need to overwrite joinLeft if coupon is applied */ $this->getSelect()->reset(); parent::_initSelect(); $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now); $select = $this->getSelect(); if (strlen($couponCode)) { $select->joinLeft( array('rule_coupons' => $this->getTable('salesrule/coupon')), 'main_table.rule_id = rule_coupons.rule_id ', array('code') ); $select->where('(main_table.coupon_type = ? ', Mage_SalesRule_Model_Rule::COUPON_TYPE_NO_COUPON) ->orWhere('(main_table.coupon_type = ? AND rule_coupons.type = 0', Mage_SalesRule_Model_Rule::COUPON_TYPE_AUTO) ->orWhere('main_table.coupon_type = ? AND main_table.use_auto_generation = 1 ' . 'AND rule_coupons.type = 1', Mage_SalesRule_Model_Rule::COUPON_TYPE_SPECIFIC) ->orWhere('main_table.coupon_type = ? AND main_table.use_auto_generation = 0 ' . 'AND rule_coupons.type = 0)', Mage_SalesRule_Model_Rule::COUPON_TYPE_SPECIFIC) ->where('rule_coupons.code = ?)', $couponCode); } else { $this->addFieldToFilter('main_table.coupon_type', Mage_SalesRule_Model_Rule::COUPON_TYPE_NO_COUPON); } $this->setOrder('sort_order', self::SORT_ORDER_ASC); $this->setFlag('validation_filter', true); } return $this; } /** * Filter collection by website(s), customer group(s) and date. * Filter collection to only active rules. * Sorting is not involved * * @param int $websiteId * @param int $customerGroupId * @param string|null $now * @use $this->addWebsiteFilter() * * @return Mage_SalesRule_Model_Mysql4_Rule_Collection */ public function addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now = null) { if (!$this->getFlag('website_group_date_filter')) { if (is_null($now)) { $now = Mage::getModel('core/date')->date('Y-m-d'); } $this->addWebsiteFilter($websiteId); $entityInfo = $this->_getAssociatedEntityInfo('customer_group'); $connection = $this->getConnection(); $this->getSelect() ->joinInner( array('customer_group_ids' => $this->getTable($entityInfo['associations_table'])), $connection->quoteInto( 'main_table.' . $entityInfo['rule_id_field'] . ' = customer_group_ids.' . $entityInfo['rule_id_field'] . ' AND customer_group_ids.' . $entityInfo['entity_id_field'] . ' = ?', (int)$customerGroupId ), array() ) ->where('from_date is null or from_date <= ?', $now) ->where('to_date is null or to_date >= ?', $now); $this->addIsActiveFilter(); $this->setFlag('website_group_date_filter', true); } return $this; } /** * Add primary coupon to collection * * @return Mage_SalesRule_Model_Resource_Rule_Collection */ public function _initSelect() { parent::_initSelect(); $this->getSelect() ->joinLeft( array('rule_coupons' => $this->getTable('salesrule/coupon')), 'main_table.rule_id = rule_coupons.rule_id AND rule_coupons.is_primary = 1', array('code') ); return $this; } /** * Find product attribute in conditions or actions * * @param string $attributeCode * * @return Mage_SalesRule_Model_Resource_Rule_Collection */ public function addAttributeInConditionFilter($attributeCode) { $match = sprintf('%%%s%%', substr(serialize(array('attribute' => $attributeCode)), 5, -1)); $field = $this->_getMappedField('conditions_serialized'); $cCond = $this->_getConditionSql($field, array('like' => $match)); $field = $this->_getMappedField('actions_serialized'); $aCond = $this->_getConditionSql($field, array('like' => $match)); $this->getSelect()->where(sprintf('(%s OR %s)', $cCond, $aCond), null, Varien_Db_Select::TYPE_CONDITION); return $this; } /** * Excludes price rules with generated specific coupon codes from collection * * @return Mage_SalesRule_Model_Resource_Rule_Collection */ public function addAllowedSalesRulesFilter() { $this->addFieldToFilter( 'main_table.use_auto_generation', array('neq' => 1) ); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Shopping Cart Rule data model * * @method Mage_SalesRule_Model_Resource_Rule _getResource() * @method Mage_SalesRule_Model_Resource_Rule getResource() * @method string getName() * @method Mage_SalesRule_Model_Rule setName(string $value) * @method string getDescription() * @method Mage_SalesRule_Model_Rule setDescription(string $value) * @method string getFromDate() * @method Mage_SalesRule_Model_Rule setFromDate(string $value) * @method string getToDate() * @method Mage_SalesRule_Model_Rule setToDate(string $value) * @method int getUsesPerCustomer() * @method Mage_SalesRule_Model_Rule setUsesPerCustomer(int $value) * @method int getUsesPerCoupon() * @method Mage_SalesRule_Model_Rule setUsesPerCoupon(int $value) * @method string getCustomerGroupIds() * @method Mage_SalesRule_Model_Rule setCustomerGroupIds(string $value) * @method int getIsActive() * @method Mage_SalesRule_Model_Rule setIsActive(int $value) * @method string getConditionsSerialized() * @method Mage_SalesRule_Model_Rule setConditionsSerialized(string $value) * @method string getActionsSerialized() * @method Mage_SalesRule_Model_Rule setActionsSerialized(string $value) * @method int getStopRulesProcessing() * @method Mage_SalesRule_Model_Rule setStopRulesProcessing(int $value) * @method int getIsAdvanced() * @method Mage_SalesRule_Model_Rule setIsAdvanced(int $value) * @method string getProductIds() * @method Mage_SalesRule_Model_Rule setProductIds(string $value) * @method int getSortOrder() * @method Mage_SalesRule_Model_Rule setSortOrder(int $value) * @method string getSimpleAction() * @method Mage_SalesRule_Model_Rule setSimpleAction(string $value) * @method float getDiscountAmount() * @method Mage_SalesRule_Model_Rule setDiscountAmount(float $value) * @method float getDiscountQty() * @method Mage_SalesRule_Model_Rule setDiscountQty(float $value) * @method int getDiscountStep() * @method Mage_SalesRule_Model_Rule setDiscountStep(int $value) * @method int getSimpleFreeShipping() * @method Mage_SalesRule_Model_Rule setSimpleFreeShipping(int $value) * @method int getApplyToShipping() * @method Mage_SalesRule_Model_Rule setApplyToShipping(int $value) * @method int getTimesUsed() * @method Mage_SalesRule_Model_Rule setTimesUsed(int $value) * @method int getIsRss() * @method Mage_SalesRule_Model_Rule setIsRss(int $value) * @method string getWebsiteIds() * @method Mage_SalesRule_Model_Rule setWebsiteIds(string $value) * @method int getCouponType() * @method Mage_SalesRule_Model_Rule setCouponType(int $value) * @method int getUseAutoGeneration() * @method Mage_SalesRule_Model_Rule setUseAutoGeneration(int $value) * @method string getCouponCode() * @method Mage_SalesRule_Model_Rule setCouponCode(string $value) * * @category Mage * @package Mage_SalesRule * @author Magento Core Team */ class Mage_SalesRule_Model_Rule extends Mage_Rule_Model_Abstract { /** * Free Shipping option "For matching items only" */ const FREE_SHIPPING_ITEM = 1; /** * Free Shipping option "For shipment with matching items" */ const FREE_SHIPPING_ADDRESS = 2; /** * Coupon types */ const COUPON_TYPE_NO_COUPON = 1; const COUPON_TYPE_SPECIFIC = 2; const COUPON_TYPE_AUTO = 3; /** * Rule type actions */ const TO_PERCENT_ACTION = 'to_percent'; const BY_PERCENT_ACTION = 'by_percent'; const TO_FIXED_ACTION = 'to_fixed'; const BY_FIXED_ACTION = 'by_fixed'; const CART_FIXED_ACTION = 'cart_fixed'; const BUY_X_GET_Y_ACTION = 'buy_x_get_y'; /** * Store coupon code generator instance * * @var Mage_SalesRule_Model_Coupon_CodegeneratorInterface */ protected static $_couponCodeGenerator; /** * Prefix of model events names * * @var string */ protected $_eventPrefix = 'salesrule_rule'; /** * Parameter name in event * * In observe method you can use $observer->getEvent()->getRule() in this case * * @var string */ protected $_eventObject = 'rule'; /** * Contain sores labels * * @deprecated after 1.6.2.0 * * @var array */ protected $_labels = array(); /** * Rule's primary coupon * * @var Mage_SalesRule_Model_Coupon */ protected $_primaryCoupon; /** * Rule's subordinate coupons * * @var array of Mage_SalesRule_Model_Coupon */ protected $_coupons; /** * Coupon types cache for lazy getter * * @var array */ protected $_couponTypes; /** * Store already validated addresses and validation results * * @var array */ protected $_validatedAddresses = array(); /** * Set resource model and Id field name */ protected function _construct() { parent::_construct(); $this->_init('salesrule/rule'); $this->setIdFieldName('rule_id'); } /** * Returns code mass generator instance for auto generated specific coupons * * @return Mage_SalesRule_Model_Coupon_MassgneratorInterface */ public static function getCouponMassGenerator() { return Mage::getSingleton('salesrule/coupon_massgenerator'); } /** * Set coupon code and uses per coupon * * @return Mage_SalesRule_Model_Rule */ protected function _afterLoad() { $this->setCouponCode($this->getPrimaryCoupon()->getCode()); if ($this->getUsesPerCoupon() !== null && !$this->getUseAutoGeneration()) { $this->setUsesPerCoupon($this->getPrimaryCoupon()->getUsageLimit()); } return parent::_afterLoad(); } /** * Save/delete coupon * * @return Mage_SalesRule_Model_Rule */ protected function _afterSave() { $couponCode = trim($this->getCouponCode()); if (strlen($couponCode) && $this->getCouponType() == self::COUPON_TYPE_SPECIFIC && !$this->getUseAutoGeneration() ) { $this->getPrimaryCoupon() ->setCode($couponCode) ->setUsageLimit($this->getUsesPerCoupon() ? $this->getUsesPerCoupon() : null) ->setUsagePerCustomer($this->getUsesPerCustomer() ? $this->getUsesPerCustomer() : null) ->setExpirationDate($this->getToDate()) ->save(); } else { $this->getPrimaryCoupon()->delete(); } parent::_afterSave(); return $this; } /** * Initialize rule model data from array. * Set store labels if applicable. * * @param array $data * * @return Mage_SalesRule_Model_Rule */ public function loadPost(array $data) { parent::loadPost($data); if (isset($data['store_labels'])) { $this->setStoreLabels($data['store_labels']); } return $this; } /** * Get rule condition combine model instance * * @return Mage_SalesRule_Model_Rule_Condition_Combine */ public function getConditionsInstance() { return Mage::getModel('salesrule/rule_condition_combine'); } /** * Get rule condition product combine model instance * * @return Mage_SalesRule_Model_Rule_Condition_Product_Combine */ public function getActionsInstance() { return Mage::getModel('salesrule/rule_condition_product_combine'); } /** * Returns code generator instance for auto generated coupons * * @return Mage_SalesRule_Model_Coupon_CodegeneratorInterface */ public static function getCouponCodeGenerator() { if (!self::$_couponCodeGenerator) { return Mage::getSingleton('salesrule/coupon_codegenerator', array('length' => 16)); } return self::$_couponCodeGenerator; } /** * Set code generator instance for auto generated coupons * * @param Mage_SalesRule_Model_Coupon_CodegeneratorInterface */ public static function setCouponCodeGenerator(Mage_SalesRule_Model_Coupon_CodegeneratorInterface $codeGenerator) { self::$_couponCodeGenerator = $codeGenerator; } /** * Retrieve rule's primary coupon * * @return Mage_SalesRule_Model_Coupon */ public function getPrimaryCoupon() { if ($this->_primaryCoupon === null) { $this->_primaryCoupon = Mage::getModel('salesrule/coupon'); $this->_primaryCoupon->loadPrimaryByRule($this->getId()); $this->_primaryCoupon->setRule($this)->setIsPrimary(true); } return $this->_primaryCoupon; } /** * Get sales rule customer group Ids * * @return array */ public function getCustomerGroupIds() { if (!$this->hasCustomerGroupIds()) { $customerGroupIds = $this->_getResource()->getCustomerGroupIds($this->getId()); $this->setData('customer_group_ids', (array)$customerGroupIds); } return $this->_getData('customer_group_ids'); } /** * Get Rule label by specified store * * @param Mage_Core_Model_Store|int|bool|null $store * * @return string|bool */ public function getStoreLabel($store = null) { $storeId = Mage::app()->getStore($store)->getId(); $labels = (array)$this->getStoreLabels(); if (isset($labels[$storeId])) { return $labels[$storeId]; } elseif (isset($labels[0]) && $labels[0]) { return $labels[0]; } return false; } /** * Set if not yet and retrieve rule store labels * * @return array */ public function getStoreLabels() { if (!$this->hasStoreLabels()) { $labels = $this->_getResource()->getStoreLabels($this->getId()); $this->setStoreLabels($labels); } return $this->_getData('store_labels'); } /** * Retrieve subordinate coupons * * @return array of Mage_SalesRule_Model_Coupon */ public function getCoupons() { if ($this->_coupons === null) { $collection = Mage::getResourceModel('salesrule/coupon_collection'); /** @var Mage_SalesRule_Model_Resource_Coupon_Collection */ $collection->addRuleToFilter($this); $this->_coupons = $collection->getItems(); } return $this->_coupons; } /** * Retrieve coupon types * * @return array */ public function getCouponTypes() { if ($this->_couponTypes === null) { $this->_couponTypes = array( Mage_SalesRule_Model_Rule::COUPON_TYPE_NO_COUPON => Mage::helper('salesrule')->__('No Coupon'), Mage_SalesRule_Model_Rule::COUPON_TYPE_SPECIFIC => Mage::helper('salesrule')->__('Specific Coupon'), ); $transport = new Varien_Object(array( 'coupon_types' => $this->_couponTypes, 'is_coupon_type_auto_visible' => false )); Mage::dispatchEvent('salesrule_rule_get_coupon_types', array('transport' => $transport)); $this->_couponTypes = $transport->getCouponTypes(); if ($transport->getIsCouponTypeAutoVisible()) { $this->_couponTypes[Mage_SalesRule_Model_Rule::COUPON_TYPE_AUTO] = Mage::helper('salesrule')->__('Auto'); } } return $this->_couponTypes; } /** * Acquire coupon instance * * @param bool $saveNewlyCreated Whether or not to save newly created coupon * @param int $saveAttemptCount Number of attempts to save newly created coupon * * @return Mage_SalesRule_Model_Coupon|null */ public function acquireCoupon($saveNewlyCreated = true, $saveAttemptCount = 10) { if ($this->getCouponType() == self::COUPON_TYPE_NO_COUPON) { return null; } if ($this->getCouponType() == self::COUPON_TYPE_SPECIFIC) { return $this->getPrimaryCoupon(); } /** @var Mage_SalesRule_Model_Coupon $coupon */ $coupon = Mage::getModel('salesrule/coupon'); $coupon->setRule($this) ->setIsPrimary(false) ->setUsageLimit($this->getUsesPerCoupon() ? $this->getUsesPerCoupon() : null) ->setUsagePerCustomer($this->getUsesPerCustomer() ? $this->getUsesPerCustomer() : null) ->setExpirationDate($this->getToDate()); $couponCode = self::getCouponCodeGenerator()->generateCode(); $coupon->setCode($couponCode); $ok = false; if (!$saveNewlyCreated) { $ok = true; } else if ($this->getId()) { for ($attemptNum = 0; $attemptNum < $saveAttemptCount; $attemptNum++) { try { $coupon->save(); } catch (Exception $e) { if ($e instanceof Mage_Core_Exception || $coupon->getId()) { throw $e; } $coupon->setCode( $couponCode . self::getCouponCodeGenerator()->getDelimiter() . sprintf('%04u', rand(0, 9999)) ); continue; } $ok = true; break; } } if (!$ok) { Mage::throwException(Mage::helper('salesrule')->__('Can\'t acquire coupon.')); } return $coupon; } /** * Check cached validation result for specific address * * @param Mage_Sales_Model_Quote_Address $address * @return bool */ public function hasIsValidForAddress($address) { $addressId = $this->_getAddressId($address); return isset($this->_validatedAddresses[$addressId]) ? true : false; } /** * Set validation result for specific address to results cache * * @param Mage_Sales_Model_Quote_Address $address * @param bool $validationResult * @return Mage_SalesRule_Model_Rule */ public function setIsValidForAddress($address, $validationResult) { $addressId = $this->_getAddressId($address); $this->_validatedAddresses[$addressId] = $validationResult; return $this; } /** * Get cached validation result for specific address * * @param Mage_Sales_Model_Quote_Address $address * @return bool */ public function getIsValidForAddress($address) { $addressId = $this->_getAddressId($address); return isset($this->_validatedAddresses[$addressId]) ? $this->_validatedAddresses[$addressId] : false; } /** * Return id for address * * @param Mage_Sales_Model_Quote_Address $address * @return string */ private function _getAddressId($address) { if($address instanceof Mage_Sales_Model_Quote_Address) { return $address->getId(); } return $address; } /** * Collect all product attributes used in serialized rule's action or condition * * @deprecated after 1.6.2.0 use Mage_SalesRule_Model_Resource_Rule::getProductAttributes() instead * * @param string $serializedString * * @return array */ protected function _getUsedAttributes($serializedString) { return $this->_getResource()->getProductAttributes($serializedString); } /** * @deprecated after 1.6.2.0 * * @param string $format * * @return string */ public function toString($format='') { return ''; } /** * Returns rule as an array for admin interface * * @deprecated after 1.6.2.0 * * @param array $arrAttributes * * Output example: * array( * 'name'=>'Example rule', * 'conditions'=>{condition_combine::toArray} * 'actions'=>{action_collection::toArray} * ) * * @return array */ public function toArray(array $arrAttributes = array()) { return parent::toArray($arrAttributes); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_SalesRule_Model_Rule_Condition_Combine extends Mage_Rule_Model_Condition_Combine { public function __construct() { parent::__construct(); $this->setType('salesrule/rule_condition_combine'); } public function getNewChildSelectOptions() { $addressCondition = Mage::getModel('salesrule/rule_condition_address'); $addressAttributes = $addressCondition->loadAttributeOptions()->getAttributeOption(); $attributes = array(); foreach ($addressAttributes as $code=>$label) { $attributes[] = array('value'=>'salesrule/rule_condition_address|'.$code, 'label'=>$label); } $conditions = parent::getNewChildSelectOptions(); $conditions = array_merge_recursive($conditions, array( array('value'=>'salesrule/rule_condition_product_found', 'label'=>Mage::helper('salesrule')->__('Product attribute combination')), array('value'=>'salesrule/rule_condition_product_subselect', 'label'=>Mage::helper('salesrule')->__('Products subselection')), array('value'=>'salesrule/rule_condition_combine', 'label'=>Mage::helper('salesrule')->__('Conditions combination')), array('label'=>Mage::helper('salesrule')->__('Cart Attribute'), 'value'=>$attributes), )); $additional = new Varien_Object(); Mage::dispatchEvent('salesrule_rule_condition_combine', array('additional' => $additional)); if ($additionalConditions = $additional->getConditions()) { $conditions = array_merge_recursive($conditions, $additionalConditions); } return $conditions; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product rule condition data model * * @category Mage * @package Mage_SalesRule * @author Magento Core Team */ class Mage_SalesRule_Model_Rule_Condition_Product extends Mage_Rule_Model_Condition_Product_Abstract { /** * Add special attributes * * @param array $attributes */ protected function _addSpecialAttributes(array &$attributes) { parent::_addSpecialAttributes($attributes); $attributes['quote_item_qty'] = Mage::helper('salesrule')->__('Quantity in cart'); $attributes['quote_item_price'] = Mage::helper('salesrule')->__('Price in cart'); $attributes['quote_item_row_total'] = Mage::helper('salesrule')->__('Row total in cart'); } /** * Validate Product Rule Condition * * @param Varien_Object $object * * @return bool */ public function validate(Varien_Object $object) { $product = false; if ($object->getProduct() instanceof Mage_Catalog_Model_Product) { $product = $object->getProduct(); } else { $product = Mage::getModel('catalog/product') ->load($object->getProductId()); } $product ->setQuoteItemQty($object->getQty()) ->setQuoteItemPrice($object->getPrice()) // possible bug: need to use $object->getBasePrice() ->setQuoteItemRowTotal($object->getBaseRowTotal()); return parent::validate($product); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_SalesRule_Model_Rule_Condition_Product_Combine extends Mage_Rule_Model_Condition_Combine { public function __construct() { parent::__construct(); $this->setType('salesrule/rule_condition_product_combine'); } public function getNewChildSelectOptions() { $productCondition = Mage::getModel('salesrule/rule_condition_product'); $productAttributes = $productCondition->loadAttributeOptions()->getAttributeOption(); $pAttributes = array(); $iAttributes = array(); foreach ($productAttributes as $code=>$label) { if (strpos($code, 'quote_item_')===0) { $iAttributes[] = array('value'=>'salesrule/rule_condition_product|'.$code, 'label'=>$label); } else { $pAttributes[] = array('value'=>'salesrule/rule_condition_product|'.$code, 'label'=>$label); } } $conditions = parent::getNewChildSelectOptions(); $conditions = array_merge_recursive($conditions, array( array('value'=>'salesrule/rule_condition_product_combine', 'label'=>Mage::helper('catalog')->__('Conditions Combination')), array('label'=>Mage::helper('catalog')->__('Cart Item Attribute'), 'value'=>$iAttributes), array('label'=>Mage::helper('catalog')->__('Product Attribute'), 'value'=>$pAttributes), )); return $conditions; } public function collectValidatedAttributes($productCollection) { foreach ($this->getConditions() as $condition) { $condition->collectValidatedAttributes($productCollection); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * SalesRule Rule Customer Model * * @method Mage_SalesRule_Model_Resource_Rule_Customer _getResource() * @method Mage_SalesRule_Model_Resource_Rule_Customer getResource() * @method int getRuleId() * @method Mage_SalesRule_Model_Rule_Customer setRuleId(int $value) * @method int getCustomerId() * @method Mage_SalesRule_Model_Rule_Customer setCustomerId(int $value) * @method int getTimesUsed() * @method Mage_SalesRule_Model_Rule_Customer setTimesUsed(int $value) * * @category Mage * @package Mage_SalesRule * @author Magento Core Team */ class Mage_SalesRule_Model_Rule_Customer extends Mage_Core_Model_Abstract { protected function _construct() { parent::_construct(); $this->_init('salesrule/rule_customer'); } public function loadByCustomerRule($customerId, $ruleId) { $this->_getResource()->loadByCustomerRule($this, $customerId, $ruleId); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_SalesRule * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * SalesRule Validator Model * * Allows dispatching before and after events for each controller action * * @category Mage * @package Mage_SalesRule * @author Magento Core Team */ class Mage_SalesRule_Model_Validator extends Mage_Core_Model_Abstract { /** * Rule source collection * * @var Mage_SalesRule_Model_Mysql4_Rule_Collection */ protected $_rules; protected $_roundingDeltas = array(); protected $_baseRoundingDeltas = array(); /** * Defines if method Mage_SalesRule_Model_Validator::process() was already called * Used for clearing applied rule ids in Quote and in Address * * @deprecated since 1.4.2.0 * @var bool */ protected $_isFirstTimeProcessRun = false; /** * Defines if method Mage_SalesRule_Model_Validator::reset() wasn't called * Used for clearing applied rule ids in Quote and in Address * * @var bool */ protected $_isFirstTimeResetRun = true; /** * Information about item totals for rules. * @var array */ protected $_rulesItemTotals = array(); /** * Store information about addresses which cart fixed rule applied for * * @var array */ protected $_cartFixedRuleUsedForAddress = array(); /** * Init validator * Init process load collection of rules for specific website, * customer group and coupon code * * @param int $websiteId * @param int $customerGroupId * @param string $couponCode * @return Mage_SalesRule_Model_Validator */ public function init($websiteId, $customerGroupId, $couponCode) { $this->setWebsiteId($websiteId) ->setCustomerGroupId($customerGroupId) ->setCouponCode($couponCode); $key = $websiteId . '_' . $customerGroupId . '_' . $couponCode; if (!isset($this->_rules[$key])) { $this->_rules[$key] = Mage::getResourceModel('salesrule/rule_collection') ->setValidationFilter($websiteId, $customerGroupId, $couponCode) ->load(); } return $this; } /** * Get rules collection for current object state * * @return Mage_SalesRule_Model_Mysql4_Rule_Collection */ protected function _getRules() { $key = $this->getWebsiteId() . '_' . $this->getCustomerGroupId() . '_' . $this->getCouponCode(); return $this->_rules[$key]; } /** * Get address object which can be used for discount calculation * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return Mage_Sales_Model_Quote_Address */ protected function _getAddress(Mage_Sales_Model_Quote_Item_Abstract $item) { if ($item instanceof Mage_Sales_Model_Quote_Address_Item) { $address = $item->getAddress(); } elseif ($item->getQuote()->getItemVirtualQty() > 0) { $address = $item->getQuote()->getBillingAddress(); } else { $address = $item->getQuote()->getShippingAddress(); } return $address; } /** * Check if rule can be applied for specific address/quote/customer * * @param Mage_SalesRule_Model_Rule $rule * @param Mage_Sales_Model_Quote_Address $address * @return bool */ protected function _canProcessRule($rule, $address) { if ($rule->hasIsValidForAddress($address) && !$address->isObjectNew()) { return $rule->getIsValidForAddress($address); } /** * check per coupon usage limit */ if ($rule->getCouponType() != Mage_SalesRule_Model_Rule::COUPON_TYPE_NO_COUPON) { $couponCode = $address->getQuote()->getCouponCode(); if (strlen($couponCode)) { $coupon = Mage::getModel('salesrule/coupon'); $coupon->load($couponCode, 'code'); if ($coupon->getId()) { // check entire usage limit if ($coupon->getUsageLimit() && $coupon->getTimesUsed() >= $coupon->getUsageLimit()) { $rule->setIsValidForAddress($address, false); return false; } // check per customer usage limit $customerId = $address->getQuote()->getCustomerId(); if ($customerId && $coupon->getUsagePerCustomer()) { $couponUsage = new Varien_Object(); Mage::getResourceModel('salesrule/coupon_usage')->loadByCustomerCoupon( $couponUsage, $customerId, $coupon->getId()); if ($couponUsage->getCouponId() && $couponUsage->getTimesUsed() >= $coupon->getUsagePerCustomer() ) { $rule->setIsValidForAddress($address, false); return false; } } } } } /** * check per rule usage limit */ $ruleId = $rule->getId(); if ($ruleId && $rule->getUsesPerCustomer()) { $customerId = $address->getQuote()->getCustomerId(); $ruleCustomer = Mage::getModel('salesrule/rule_customer'); $ruleCustomer->loadByCustomerRule($customerId, $ruleId); if ($ruleCustomer->getId()) { if ($ruleCustomer->getTimesUsed() >= $rule->getUsesPerCustomer()) { $rule->setIsValidForAddress($address, false); return false; } } } $rule->afterLoad(); /** * quote does not meet rule's conditions */ if (!$rule->validate($address)) { $rule->setIsValidForAddress($address, false); return false; } /** * passed all validations, remember to be valid */ $rule->setIsValidForAddress($address, true); return true; } /** * Quote item free shipping ability check * This process not affect information about applied rules, coupon code etc. * This information will be added during discount amounts processing * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return Mage_SalesRule_Model_Validator */ public function processFreeShipping(Mage_Sales_Model_Quote_Item_Abstract $item) { $address = $this->_getAddress($item); $item->setFreeShipping(false); foreach ($this->_getRules() as $rule) { /* @var $rule Mage_SalesRule_Model_Rule */ if (!$this->_canProcessRule($rule, $address)) { continue; } if (!$rule->getActions()->validate($item)) { continue; } switch ($rule->getSimpleFreeShipping()) { case Mage_SalesRule_Model_Rule::FREE_SHIPPING_ITEM: $item->setFreeShipping($rule->getDiscountQty() ? $rule->getDiscountQty() : true); break; case Mage_SalesRule_Model_Rule::FREE_SHIPPING_ADDRESS: $address->setFreeShipping(true); break; } if ($rule->getStopRulesProcessing()) { break; } } return $this; } /** * Reset quote and address applied rules * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_SalesRule_Model_Validator */ public function reset(Mage_Sales_Model_Quote_Address $address) { if ($this->_isFirstTimeResetRun) { $address->setAppliedRuleIds(''); $address->getQuote()->setAppliedRuleIds(''); $this->_isFirstTimeResetRun = false; } return $this; } /** * Quote item discount calculation process * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return Mage_SalesRule_Model_Validator */ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) { $item->setDiscountAmount(0); $item->setBaseDiscountAmount(0); $item->setDiscountPercent(0); $quote = $item->getQuote(); $address = $this->_getAddress($item); $itemPrice = $this->_getItemPrice($item); $baseItemPrice = $this->_getItemBasePrice($item); $itemOriginalPrice = $this->_getItemOriginalPrice($item); $baseItemOriginalPrice = $this->_getItemBaseOriginalPrice($item); if ($itemPrice < 0) { return $this; } $appliedRuleIds = array(); foreach ($this->_getRules() as $rule) { /* @var $rule Mage_SalesRule_Model_Rule */ if (!$this->_canProcessRule($rule, $address)) { continue; } if (!$rule->getActions()->validate($item)) { continue; } $qty = $this->_getItemQty($item, $rule); $rulePercent = min(100, $rule->getDiscountAmount()); $discountAmount = 0; $baseDiscountAmount = 0; //discount for original price $originalDiscountAmount = 0; $baseOriginalDiscountAmount = 0; switch ($rule->getSimpleAction()) { case Mage_SalesRule_Model_Rule::TO_PERCENT_ACTION: $rulePercent = max(0, 100-$rule->getDiscountAmount()); //no break; case Mage_SalesRule_Model_Rule::BY_PERCENT_ACTION: $step = $rule->getDiscountStep(); if ($step) { $qty = floor($qty/$step)*$step; } $_rulePct = $rulePercent/100; $discountAmount = ($qty*$itemPrice - $item->getDiscountAmount()) * $_rulePct; $baseDiscountAmount= ($qty*$baseItemPrice - $item->getBaseDiscountAmount()) * $_rulePct; //get discount for original price $originalDiscountAmount = ($qty*$itemOriginalPrice - $item->getDiscountAmount()) * $_rulePct; $baseOriginalDiscountAmount= ($qty*$baseItemOriginalPrice - $item->getDiscountAmount()) * $_rulePct; if (!$rule->getDiscountQty() || $rule->getDiscountQty()>$qty) { $discountPercent = min(100, $item->getDiscountPercent()+$rulePercent); $item->setDiscountPercent($discountPercent); } break; case Mage_SalesRule_Model_Rule::TO_FIXED_ACTION: $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount()); $discountAmount = $qty*($itemPrice-$quoteAmount); $baseDiscountAmount= $qty*($baseItemPrice-$rule->getDiscountAmount()); //get discount for original price $originalDiscountAmount = $qty*($itemOriginalPrice-$quoteAmount); $baseOriginalDiscountAmount= $qty*($baseItemOriginalPrice-$rule->getDiscountAmount()); break; case Mage_SalesRule_Model_Rule::BY_FIXED_ACTION: $step = $rule->getDiscountStep(); if ($step) { $qty = floor($qty/$step)*$step; } $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount()); $discountAmount = $qty*$quoteAmount; $baseDiscountAmount = $qty*$rule->getDiscountAmount(); break; case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION: if (empty($this->_rulesItemTotals[$rule->getId()])) { Mage::throwException(Mage::helper('salesrule')->__('Item totals are not set for rule.')); } /** * prevent applying whole cart discount for every shipping order, but only for first order */ if ($quote->getIsMultiShipping()) { $usedForAddressId = $this->getCartFixedRuleUsedForAddress($rule->getId()); if ($usedForAddressId && $usedForAddressId != $address->getId()) { break; } else { $this->setCartFixedRuleUsedForAddress($rule->getId(), $address->getId()); } } $cartRules = $address->getCartFixedRules(); if (!isset($cartRules[$rule->getId()])) { $cartRules[$rule->getId()] = $rule->getDiscountAmount(); } if ($cartRules[$rule->getId()] > 0) { if ($this->_rulesItemTotals[$rule->getId()]['items_count'] <= 1) { $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]); $baseDiscountAmount = min($baseItemPrice * $qty, $cartRules[$rule->getId()]); } else { $discountRate = $baseItemPrice * $qty / $this->_rulesItemTotals[$rule->getId()]['base_items_price']; $maximumItemDiscount = $rule->getDiscountAmount() * $discountRate; $quoteAmount = $quote->getStore()->convertPrice($maximumItemDiscount); $baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount); $this->_rulesItemTotals[$rule->getId()]['items_count']--; } $discountAmount = min($itemPrice * $qty, $quoteAmount); $discountAmount = $quote->getStore()->roundPrice($discountAmount); $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount); //get discount for original price $originalDiscountAmount = min($itemOriginalPrice * $qty, $quoteAmount); $baseOriginalDiscountAmount = $quote->getStore()->roundPrice($originalDiscountAmount); $baseOriginalDiscountAmount = $quote->getStore()->roundPrice($baseItemOriginalPrice); $cartRules[$rule->getId()] -= $baseDiscountAmount; } $address->setCartFixedRules($cartRules); break; case Mage_SalesRule_Model_Rule::BUY_X_GET_Y_ACTION: $x = $rule->getDiscountStep(); $y = $rule->getDiscountAmount(); if (!$x || $y > $x) { break; } $buyAndDiscountQty = $x + $y; $fullRuleQtyPeriod = floor($qty / $buyAndDiscountQty); $freeQty = $qty - $fullRuleQtyPeriod * $buyAndDiscountQty; $discountQty = $fullRuleQtyPeriod * $y; if ($freeQty > $x) { $discountQty += $freeQty - $x; } $discountAmount = $discountQty * $itemPrice; $baseDiscountAmount= $discountQty * $baseItemPrice; //get discount for original price $originalDiscountAmount = $discountQty * $itemOriginalPrice; $baseOriginalDiscountAmount= $discountQty * $baseItemOriginalPrice; break; } $result = new Varien_Object(array( 'discount_amount' => $discountAmount, 'base_discount_amount' => $baseDiscountAmount, )); Mage::dispatchEvent('salesrule_validator_process', array( 'rule' => $rule, 'item' => $item, 'address' => $address, 'quote' => $quote, 'qty' => $qty, 'result' => $result, )); $discountAmount = $result->getDiscountAmount(); $baseDiscountAmount = $result->getBaseDiscountAmount(); $percentKey = $item->getDiscountPercent(); /** * Process "delta" rounding */ if ($percentKey) { $delta = isset($this->_roundingDeltas[$percentKey]) ? $this->_roundingDeltas[$percentKey] : 0; $baseDelta = isset($this->_baseRoundingDeltas[$percentKey]) ? $this->_baseRoundingDeltas[$percentKey] : 0; $discountAmount+= $delta; $baseDiscountAmount+=$baseDelta; $this->_roundingDeltas[$percentKey] = $discountAmount - $quote->getStore()->roundPrice($discountAmount); $this->_baseRoundingDeltas[$percentKey] = $baseDiscountAmount - $quote->getStore()->roundPrice($baseDiscountAmount); $discountAmount = $quote->getStore()->roundPrice($discountAmount); $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount); } else { $discountAmount = $quote->getStore()->roundPrice($discountAmount); $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount); } /** * We can't use row total here because row total not include tax * Discount can be applied on price included tax */ $itemDiscountAmount = $item->getDiscountAmount(); $itemBaseDiscountAmount = $item->getBaseDiscountAmount(); $discountAmount = min($itemDiscountAmount + $discountAmount, $itemPrice * $qty); $baseDiscountAmount = min($itemBaseDiscountAmount + $baseDiscountAmount, $baseItemPrice * $qty); $item->setDiscountAmount($discountAmount); $item->setBaseDiscountAmount($baseDiscountAmount); $item->setOriginalDiscountAmount($originalDiscountAmount); $item->setBaseOriginalDiscountAmount($baseOriginalDiscountAmount); $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId(); $this->_maintainAddressCouponCode($address, $rule); $this->_addDiscountDescription($address, $rule); if ($rule->getStopRulesProcessing()) { break; } } $item->setAppliedRuleIds(join(',',$appliedRuleIds)); $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds)); $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds)); return $this; } /** * Apply discounts to shipping amount * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_SalesRule_Model_Validator */ public function processShippingAmount(Mage_Sales_Model_Quote_Address $address) { $shippingAmount = $address->getShippingAmountForDiscount(); if ($shippingAmount!==null) { $baseShippingAmount = $address->getBaseShippingAmountForDiscount(); } else { $shippingAmount = $address->getShippingAmount(); $baseShippingAmount = $address->getBaseShippingAmount(); } $quote = $address->getQuote(); $appliedRuleIds = array(); foreach ($this->_getRules() as $rule) { /* @var $rule Mage_SalesRule_Model_Rule */ if (!$rule->getApplyToShipping() || !$this->_canProcessRule($rule, $address)) { continue; } $discountAmount = 0; $baseDiscountAmount = 0; $rulePercent = min(100, $rule->getDiscountAmount()); switch ($rule->getSimpleAction()) { case Mage_SalesRule_Model_Rule::TO_PERCENT_ACTION: $rulePercent = max(0, 100-$rule->getDiscountAmount()); case Mage_SalesRule_Model_Rule::BY_PERCENT_ACTION: $discountAmount = ($shippingAmount - $address->getShippingDiscountAmount()) * $rulePercent/100; $baseDiscountAmount= ($baseShippingAmount - $address->getBaseShippingDiscountAmount()) * $rulePercent/100; $discountPercent = min(100, $address->getShippingDiscountPercent()+$rulePercent); $address->setShippingDiscountPercent($discountPercent); break; case Mage_SalesRule_Model_Rule::TO_FIXED_ACTION: $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount()); $discountAmount = $shippingAmount-$quoteAmount; $baseDiscountAmount= $baseShippingAmount-$rule->getDiscountAmount(); break; case Mage_SalesRule_Model_Rule::BY_FIXED_ACTION: $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount()); $discountAmount = $quoteAmount; $baseDiscountAmount = $rule->getDiscountAmount(); break; case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION: $cartRules = $address->getCartFixedRules(); if (!isset($cartRules[$rule->getId()])) { $cartRules[$rule->getId()] = $rule->getDiscountAmount(); } if ($cartRules[$rule->getId()] > 0) { $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]); $discountAmount = min( $shippingAmount-$address->getShippingDiscountAmount(), $quoteAmount ); $baseDiscountAmount = min( $baseShippingAmount-$address->getBaseShippingDiscountAmount(), $cartRules[$rule->getId()] ); $cartRules[$rule->getId()] -= $baseDiscountAmount; } $address->setCartFixedRules($cartRules); break; } $discountAmount = min($address->getShippingDiscountAmount()+$discountAmount, $shippingAmount); $baseDiscountAmount = min( $address->getBaseShippingDiscountAmount()+$baseDiscountAmount, $baseShippingAmount ); $address->setShippingDiscountAmount($discountAmount); $address->setBaseShippingDiscountAmount($baseDiscountAmount); $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId(); $this->_maintainAddressCouponCode($address, $rule); $this->_addDiscountDescription($address, $rule); if ($rule->getStopRulesProcessing()) { break; } } $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds)); $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds)); return $this; } /** * Merge two sets of ids * * @param array|string $a1 * @param array|string $a2 * @param bool $asString * @return array */ public function mergeIds($a1, $a2, $asString = true) { if (!is_array($a1)) { $a1 = empty($a1) ? array() : explode(',', $a1); } if (!is_array($a2)) { $a2 = empty($a2) ? array() : explode(',', $a2); } $a = array_unique(array_merge($a1, $a2)); if ($asString) { $a = implode(',', $a); } return $a; } /** * Set information about usage cart fixed rule by quote address * * @param int $ruleId * @param int $itemId * @return void */ public function setCartFixedRuleUsedForAddress($ruleId, $itemId) { $this->_cartFixedRuleUsedForAddress[$ruleId] = $itemId; } /** * Retrieve information about usage cart fixed rule by quote address * * @param int $ruleId * @return int|null */ public function getCartFixedRuleUsedForAddress($ruleId) { if (isset($this->_cartFixedRuleUsedForAddress[$ruleId])) { return $this->_cartFixedRuleUsedForAddress[$ruleId]; } return null; } /** * Calculate quote totals for each rule and save results * * @param mixed $items * @param Mage_Sales_Model_Quote_Address $address * @return Mage_SalesRule_Model_Validator */ public function initTotals($items, Mage_Sales_Model_Quote_Address $address) { $address->setCartFixedRules(array()); if (!$items) { return $this; } foreach ($this->_getRules() as $rule) { if (Mage_SalesRule_Model_Rule::CART_FIXED_ACTION == $rule->getSimpleAction() && $this->_canProcessRule($rule, $address)) { $ruleTotalItemsPrice = 0; $ruleTotalBaseItemsPrice = 0; $validItemsCount = 0; foreach ($items as $item) { //Skipping child items to avoid double calculations if ($item->getParentItemId()) { continue; } if (!$rule->getActions()->validate($item)) { continue; } $qty = $this->_getItemQty($item, $rule); $ruleTotalItemsPrice += $this->_getItemPrice($item) * $qty; $ruleTotalBaseItemsPrice += $this->_getItemBasePrice($item) * $qty; $validItemsCount++; } $this->_rulesItemTotals[$rule->getId()] = array( 'items_price' => $ruleTotalItemsPrice, 'base_items_price' => $ruleTotalBaseItemsPrice, 'items_count' => $validItemsCount, ); } } return $this; } /** * Set coupon code to address if $rule contains validated coupon * * @param Mage_Sales_Model_Quote_Address $address * @param Mage_SalesRule_Model_Rule $rule * * @return Mage_SalesRule_Model_Validator */ protected function _maintainAddressCouponCode($address, $rule) { /* Rule is a part of rules collection, which includes only rules with 'No Coupon' type or with validated coupon. As a result, if rule uses coupon code(s) ('Specific' or 'Auto' Coupon Type), it always contains validated coupon */ if ($rule->getCouponType() != Mage_SalesRule_Model_Rule::COUPON_TYPE_NO_COUPON) { $address->setCouponCode($this->getCouponCode()); } return $this; } /** * Add rule discount description label to address object * * @param Mage_Sales_Model_Quote_Address $address * @param Mage_SalesRule_Model_Rule $rule * @return Mage_SalesRule_Model_Validator */ protected function _addDiscountDescription($address, $rule) { $description = $address->getDiscountDescriptionArray(); $ruleLabel = $rule->getStoreLabel($address->getQuote()->getStore()); $label = ''; if ($ruleLabel) { $label = $ruleLabel; } else if (strlen($address->getCouponCode())) { $label = $address->getCouponCode(); } if (strlen($label)) { $description[$rule->getId()] = $label; } $address->setDiscountDescriptionArray($description); return $this; } /** * Return item price * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return float */ protected function _getItemPrice($item) { $price = $item->getDiscountCalculationPrice(); $calcPrice = $item->getCalculationPrice(); return ($price !== null) ? $price : $calcPrice; } /** * Return item original price * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return float */ protected function _getItemOriginalPrice($item) { return Mage::helper('tax')->getPrice($item, $item->getOriginalPrice(), true); } /** * Return item base price * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return float */ protected function _getItemBasePrice($item) { $price = $item->getDiscountCalculationPrice(); return ($price !== null) ? $item->getBaseDiscountCalculationPrice() : $item->getBaseCalculationPrice(); } /** * Return item base original price * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return float */ protected function _getItemBaseOriginalPrice($item) { return Mage::helper('tax')->getPrice($item, $item->getBaseOriginalPrice(), true); } /** * Return discount item qty * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @param Mage_SalesRule_Model_Rule $rule * @return int */ protected function _getItemQty($item, $rule) { $qty = $item->getTotalQty(); return $rule->getDiscountQty() ? min($qty, $rule->getDiscountQty()) : $qty; } /** * Convert address discount description array to string * * @param Mage_Sales_Model_Quote_Address $address * @param string $separator * @return Mage_SalesRule_Model_Validator */ public function prepareDescription($address, $separator=', ') { $description = $address->getDiscountDescriptionArray(); if (is_array($description) && !empty($description)) { $description = array_unique($description); $description = implode($separator, $description); } else { $description = ''; } $address->setDiscountDescription($description); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales module base helper * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Helper_Data extends Mage_Core_Helper_Data { /** * Maximum available number */ const MAXIMUM_AVAILABLE_NUMBER = 99999999; /** * Check quote amount * * @param Mage_Sales_Model_Quote $quote * @param decimal $amount * @return Mage_Sales_Helper_Data */ public function checkQuoteAmount(Mage_Sales_Model_Quote $quote, $amount) { if (!$quote->getHasError() && ($amount>=self::MAXIMUM_AVAILABLE_NUMBER)) { $quote->setHasError(true); $quote->addMessage( $this->__('Items maximum quantity or price do not allow checkout.') ); } return $this; } /** * Check allow to send new order confirmation email * * @param mixed $store * @return bool */ public function canSendNewOrderConfirmationEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order::XML_PATH_EMAIL_ENABLED, $store); } /** * Check allow to send new order email * * @param mixed $store * @return bool */ public function canSendNewOrderEmail($store = null) { return $this->canSendNewOrderConfirmationEmail($store); } /** * Check allow to send order comment email * * @param mixed $store * @return bool */ public function canSendOrderCommentEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order::XML_PATH_UPDATE_EMAIL_ENABLED, $store); } /** * Check allow to send new shipment email * * @param mixed $store * @return bool */ public function canSendNewShipmentEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order_Shipment::XML_PATH_EMAIL_ENABLED, $store); } /** * Check allow to send shipment comment email * * @param mixed $store * @return bool */ public function canSendShipmentCommentEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order_Shipment::XML_PATH_UPDATE_EMAIL_ENABLED, $store); } /** * Check allow to send new invoice email * * @param mixed $store * @return bool */ public function canSendNewInvoiceEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order_Invoice::XML_PATH_EMAIL_ENABLED, $store); } /** * Check allow to send invoice comment email * * @param mixed $store * @return bool */ public function canSendInvoiceCommentEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order_Invoice::XML_PATH_UPDATE_EMAIL_ENABLED, $store); } /** * Check allow to send new creditmemo email * * @param mixed $store * @return bool */ public function canSendNewCreditmemoEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order_Creditmemo::XML_PATH_EMAIL_ENABLED, $store); } /** * Check allow to send creditmemo comment email * * @param mixed $store * @return bool */ public function canSendCreditmemoCommentEmail($store = null) { return Mage::getStoreConfigFlag(Mage_Sales_Model_Order_Creditmemo::XML_PATH_UPDATE_EMAIL_ENABLED, $store); } /** * Get old field map * * @param string $entityId * @return array */ public function getOldFieldMap($entityId) { $node = Mage::getConfig()->getNode('global/sales/old_fields_map/' . $entityId); if ($node === false) { return array(); } return (array) $node; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote model * * Supported events: * sales_quote_load_after * sales_quote_save_before * sales_quote_save_after * sales_quote_delete_before * sales_quote_delete_after * * @method Mage_Sales_Model_Resource_Quote _getResource() * @method Mage_Sales_Model_Resource_Quote getResource() * @method Mage_Sales_Model_Quote setStoreId(int $value) * @method string getCreatedAt() * @method Mage_Sales_Model_Quote setCreatedAt(string $value) * @method string getUpdatedAt() * @method Mage_Sales_Model_Quote setUpdatedAt(string $value) * @method string getConvertedAt() * @method Mage_Sales_Model_Quote setConvertedAt(string $value) * @method int getIsActive() * @method Mage_Sales_Model_Quote setIsActive(int $value) * @method Mage_Sales_Model_Quote setIsVirtual(int $value) * @method int getIsMultiShipping() * @method Mage_Sales_Model_Quote setIsMultiShipping(int $value) * @method int getItemsCount() * @method Mage_Sales_Model_Quote setItemsCount(int $value) * @method float getItemsQty() * @method Mage_Sales_Model_Quote setItemsQty(float $value) * @method int getOrigOrderId() * @method Mage_Sales_Model_Quote setOrigOrderId(int $value) * @method float getStoreToBaseRate() * @method Mage_Sales_Model_Quote setStoreToBaseRate(float $value) * @method float getStoreToQuoteRate() * @method Mage_Sales_Model_Quote setStoreToQuoteRate(float $value) * @method string getBaseCurrencyCode() * @method Mage_Sales_Model_Quote setBaseCurrencyCode(string $value) * @method string getStoreCurrencyCode() * @method Mage_Sales_Model_Quote setStoreCurrencyCode(string $value) * @method string getQuoteCurrencyCode() * @method Mage_Sales_Model_Quote setQuoteCurrencyCode(string $value) * @method float getGrandTotal() * @method Mage_Sales_Model_Quote setGrandTotal(float $value) * @method float getBaseGrandTotal() * @method Mage_Sales_Model_Quote setBaseGrandTotal(float $value) * @method Mage_Sales_Model_Quote setCheckoutMethod(string $value) * @method int getCustomerId() * @method Mage_Sales_Model_Quote setCustomerId(int $value) * @method Mage_Sales_Model_Quote setCustomerTaxClassId(int $value) * @method Mage_Sales_Model_Quote setCustomerGroupId(int $value) * @method string getCustomerEmail() * @method Mage_Sales_Model_Quote setCustomerEmail(string $value) * @method string getCustomerPrefix() * @method Mage_Sales_Model_Quote setCustomerPrefix(string $value) * @method string getCustomerFirstname() * @method Mage_Sales_Model_Quote setCustomerFirstname(string $value) * @method string getCustomerMiddlename() * @method Mage_Sales_Model_Quote setCustomerMiddlename(string $value) * @method string getCustomerLastname() * @method Mage_Sales_Model_Quote setCustomerLastname(string $value) * @method string getCustomerSuffix() * @method Mage_Sales_Model_Quote setCustomerSuffix(string $value) * @method string getCustomerDob() * @method Mage_Sales_Model_Quote setCustomerDob(string $value) * @method string getCustomerNote() * @method Mage_Sales_Model_Quote setCustomerNote(string $value) * @method int getCustomerNoteNotify() * @method Mage_Sales_Model_Quote setCustomerNoteNotify(int $value) * @method int getCustomerIsGuest() * @method Mage_Sales_Model_Quote setCustomerIsGuest(int $value) * @method string getRemoteIp() * @method Mage_Sales_Model_Quote setRemoteIp(string $value) * @method string getAppliedRuleIds() * @method Mage_Sales_Model_Quote setAppliedRuleIds(string $value) * @method string getReservedOrderId() * @method Mage_Sales_Model_Quote setReservedOrderId(string $value) * @method string getPasswordHash() * @method Mage_Sales_Model_Quote setPasswordHash(string $value) * @method string getCouponCode() * @method Mage_Sales_Model_Quote setCouponCode(string $value) * @method string getGlobalCurrencyCode() * @method Mage_Sales_Model_Quote setGlobalCurrencyCode(string $value) * @method float getBaseToGlobalRate() * @method Mage_Sales_Model_Quote setBaseToGlobalRate(float $value) * @method float getBaseToQuoteRate() * @method Mage_Sales_Model_Quote setBaseToQuoteRate(float $value) * @method string getCustomerTaxvat() * @method Mage_Sales_Model_Quote setCustomerTaxvat(string $value) * @method string getCustomerGender() * @method Mage_Sales_Model_Quote setCustomerGender(string $value) * @method float getSubtotal() * @method Mage_Sales_Model_Quote setSubtotal(float $value) * @method float getBaseSubtotal() * @method Mage_Sales_Model_Quote setBaseSubtotal(float $value) * @method float getSubtotalWithDiscount() * @method Mage_Sales_Model_Quote setSubtotalWithDiscount(float $value) * @method float getBaseSubtotalWithDiscount() * @method Mage_Sales_Model_Quote setBaseSubtotalWithDiscount(float $value) * @method int getIsChanged() * @method Mage_Sales_Model_Quote setIsChanged(int $value) * @method int getTriggerRecollect() * @method Mage_Sales_Model_Quote setTriggerRecollect(int $value) * @method string getExtShippingInfo() * @method Mage_Sales_Model_Quote setExtShippingInfo(string $value) * @method int getGiftMessageId() * @method Mage_Sales_Model_Quote setGiftMessageId(int $value) * @method bool|null getIsPersistent() * @method Mage_Sales_Model_Quote setIsPersistent(bool $value) * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Quote extends Mage_Core_Model_Abstract { protected $_eventPrefix = 'sales_quote'; protected $_eventObject = 'quote'; /** * Quote customer model object * * @var Mage_Customer_Model_Customer */ protected $_customer; /** * Quote addresses collection * * @var Mage_Eav_Model_Entity_Collection_Abstract */ protected $_addresses = null; /** * Quote items collection * * @var Mage_Eav_Model_Entity_Collection_Abstract */ protected $_items = null; /** * Quote payments * * @var Mage_Eav_Model_Entity_Collection_Abstract */ protected $_payments = null; /** * Different groups of error infos * * @var array */ protected $_errorInfoGroups = array(); /** * Whether quote should not be saved * * @var bool */ protected $_preventSaving = false; /** * Init resource model */ protected function _construct() { $this->_init('sales/quote'); } /** * Init mapping array of short fields to * its full names * * @return Varien_Object */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = Mage::helper('sales')->getOldFieldMap('quote'); return $this; } /** * Get quote store identifier * * @return int */ public function getStoreId() { if (!$this->hasStoreId()) { return Mage::app()->getStore()->getId(); } return $this->_getData('store_id'); } /** * Get quote store model object * * @return Mage_Core_Model_Store */ public function getStore() { return Mage::app()->getStore($this->getStoreId()); } /** * Declare quote store model * * @param Mage_Core_Model_Store $store * @return Mage_Sales_Model_Quote */ public function setStore(Mage_Core_Model_Store $store) { $this->setStoreId($store->getId()); return $this; } /** * Get all available store ids for quote * * @return array */ public function getSharedStoreIds() { $ids = $this->_getData('shared_store_ids'); if (is_null($ids) || !is_array($ids)) { if ($website = $this->getWebsite()) { return $website->getStoreIds(); } return $this->getStore()->getWebsite()->getStoreIds(); } return $ids; } /** * Prepare data before save * * @return Mage_Sales_Model_Quote */ protected function _beforeSave() { /** * Currency logic * * global - currency which is set for default in backend * base - currency which is set for current website. all attributes that * have 'base_' prefix saved in this currency * store - all the time it was currency of website and all attributes * with 'base_' were saved in this currency. From now on it is * deprecated and will be duplication of base currency code. * quote/order - currency which was selected by customer or configured by * admin for current store. currency in which customer sees * price thought all checkout. * * Rates: * store_to_base & store_to_quote/store_to_order - are deprecated * base_to_global & base_to_quote/base_to_order - must be used instead */ $globalCurrencyCode = Mage::app()->getBaseCurrencyCode(); $baseCurrency = $this->getStore()->getBaseCurrency(); if ($this->hasForcedCurrency()){ $quoteCurrency = $this->getForcedCurrency(); } else { $quoteCurrency = $this->getStore()->getCurrentCurrency(); } $this->setGlobalCurrencyCode($globalCurrencyCode); $this->setBaseCurrencyCode($baseCurrency->getCode()); $this->setStoreCurrencyCode($baseCurrency->getCode()); $this->setQuoteCurrencyCode($quoteCurrency->getCode()); //deprecated, read above $this->setStoreToBaseRate($baseCurrency->getRate($globalCurrencyCode)); $this->setStoreToQuoteRate($baseCurrency->getRate($quoteCurrency)); $this->setBaseToGlobalRate($baseCurrency->getRate($globalCurrencyCode)); $this->setBaseToQuoteRate($baseCurrency->getRate($quoteCurrency)); if (!$this->hasChangedFlag() || $this->getChangedFlag() == true) { $this->setIsChanged(1); } else { $this->setIsChanged(0); } if ($this->_customer) { $this->setCustomerId($this->_customer->getId()); } parent::_beforeSave(); } /** * Save related items * * @return Mage_Sales_Model_Quote */ protected function _afterSave() { parent::_afterSave(); if (null !== $this->_addresses) { $this->getAddressesCollection()->save(); } if (null !== $this->_items) { $this->getItemsCollection()->save(); } if (null !== $this->_payments) { $this->getPaymentsCollection()->save(); } return $this; } /** * Loading quote data by customer * * @return Mage_Sales_Model_Quote */ public function loadByCustomer($customer) { if ($customer instanceof Mage_Customer_Model_Customer) { $customerId = $customer->getId(); } else { $customerId = (int) $customer; } $this->_getResource()->loadByCustomerId($this, $customerId); $this->_afterLoad(); return $this; } /** * Loading only active quote * * @param int $quoteId * @return Mage_Sales_Model_Quote */ public function loadActive($quoteId) { $this->_getResource()->loadActive($this, $quoteId); $this->_afterLoad(); return $this; } /** * Loading quote by identifier * * @param int $quoteId * @return Mage_Sales_Model_Quote */ public function loadByIdWithoutStore($quoteId) { $this->_getResource()->loadByIdWithoutStore($this, $quoteId); $this->_afterLoad(); return $this; } /** * Assign customer model object data to quote * * @param Mage_Customer_Model_Customer $customer * @return Mage_Sales_Model_Quote */ public function assignCustomer(Mage_Customer_Model_Customer $customer) { return $this->assignCustomerWithAddressChange($customer); } /** * Assign customer model to quote with billing and shipping address change * * @param Mage_Customer_Model_Customer $customer * @param Mage_Sales_Model_Quote_Address $billingAddress * @param Mage_Sales_Model_Quote_Address $shippingAddress * @return Mage_Sales_Model_Quote */ public function assignCustomerWithAddressChange( Mage_Customer_Model_Customer $customer, Mage_Sales_Model_Quote_Address $billingAddress = null, Mage_Sales_Model_Quote_Address $shippingAddress = null ) { if ($customer->getId()) { $this->setCustomer($customer); if (!is_null($billingAddress)) { $this->setBillingAddress($billingAddress); } else { $defaultBillingAddress = $customer->getDefaultBillingAddress(); if ($defaultBillingAddress && $defaultBillingAddress->getId()) { $billingAddress = Mage::getModel('sales/quote_address') ->importCustomerAddress($defaultBillingAddress); $this->setBillingAddress($billingAddress); } } if (is_null($shippingAddress)) { $defaultShippingAddress = $customer->getDefaultShippingAddress(); if ($defaultShippingAddress && $defaultShippingAddress->getId()) { $shippingAddress = Mage::getModel('sales/quote_address') ->importCustomerAddress($defaultShippingAddress); } else { $shippingAddress = Mage::getModel('sales/quote_address'); } } $this->setShippingAddress($shippingAddress); } return $this; } /** * Define customer object * * @param Mage_Customer_Model_Customer $customer * @return Mage_Sales_Model_Quote */ public function setCustomer(Mage_Customer_Model_Customer $customer) { $this->_customer = $customer; $this->setCustomerId($customer->getId()); Mage::helper('core')->copyFieldset('customer_account', 'to_quote', $customer, $this); return $this; } /** * Retrieve customer model object * * @return Mage_Customer_Model_Customer */ public function getCustomer() { if (is_null($this->_customer)) { $this->_customer = Mage::getModel('customer/customer'); if ($customerId = $this->getCustomerId()) { $this->_customer->load($customerId); if (!$this->_customer->getId()) { $this->_customer->setCustomerId(null); } } } return $this->_customer; } /** * Retrieve customer group id * * @return int */ public function getCustomerGroupId() { if ($this->hasData('customer_group_id')) { return $this->getData('customer_group_id'); } else if ($this->getCustomerId()) { return $this->getCustomer()->getGroupId(); } else { return Mage_Customer_Model_Group::NOT_LOGGED_IN_ID; } } public function getCustomerTaxClassId() { /* * tax class can vary at any time. so instead of using the value from session, * we need to retrieve from db every time to get the correct tax class */ //if (!$this->getData('customer_group_id') && !$this->getData('customer_tax_class_id')) { $classId = Mage::getModel('customer/group')->getTaxClassId($this->getCustomerGroupId()); $this->setCustomerTaxClassId($classId); //} return $this->getData('customer_tax_class_id'); } /** * Retrieve quote address collection * * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function getAddressesCollection() { if (is_null($this->_addresses)) { $this->_addresses = Mage::getModel('sales/quote_address')->getCollection() ->setQuoteFilter($this->getId()); if ($this->getId()) { foreach ($this->_addresses as $address) { $address->setQuote($this); } } } return $this->_addresses; } /** * Retrieve quote address by type * * @param string $type * @return Mage_Sales_Model_Quote_Address */ protected function _getAddressByType($type) { foreach ($this->getAddressesCollection() as $address) { if ($address->getAddressType() == $type && !$address->isDeleted()) { return $address; } } $address = Mage::getModel('sales/quote_address')->setAddressType($type); $this->addAddress($address); return $address; } /** * Retrieve quote billing address * * @return Mage_Sales_Model_Quote_Address */ public function getBillingAddress() { return $this->_getAddressByType(Mage_Sales_Model_Quote_Address::TYPE_BILLING); } /** * retrieve quote shipping address * * @return Mage_Sales_Model_Quote_Address */ public function getShippingAddress() { return $this->_getAddressByType(Mage_Sales_Model_Quote_Address::TYPE_SHIPPING); } public function getAllShippingAddresses() { $addresses = array(); foreach ($this->getAddressesCollection() as $address) { if ($address->getAddressType()==Mage_Sales_Model_Quote_Address::TYPE_SHIPPING && !$address->isDeleted()) { $addresses[] = $address; } } return $addresses; } public function getAllAddresses() { $addresses = array(); foreach ($this->getAddressesCollection() as $address) { if (!$address->isDeleted()) { $addresses[] = $address; } } return $addresses; } /** * * @param int $addressId * @return Mage_Sales_Model_Quote_Address */ public function getAddressById($addressId) { foreach ($this->getAddressesCollection() as $address) { if ($address->getId()==$addressId) { return $address; } } return false; } public function getAddressByCustomerAddressId($addressId) { foreach ($this->getAddressesCollection() as $address) { if (!$address->isDeleted() && $address->getCustomerAddressId()==$addressId) { return $address; } } return false; } public function getShippingAddressByCustomerAddressId($addressId) { foreach ($this->getAddressesCollection() as $address) { if (!$address->isDeleted() && $address->getAddressType()==Mage_Sales_Model_Quote_Address::TYPE_SHIPPING && $address->getCustomerAddressId()==$addressId) { return $address; } } return false; } public function removeAddress($addressId) { foreach ($this->getAddressesCollection() as $address) { if ($address->getId()==$addressId) { $address->isDeleted(true); break; } } return $this; } public function removeAllAddresses() { foreach ($this->getAddressesCollection() as $address) { $address->isDeleted(true); } return $this; } public function addAddress(Mage_Sales_Model_Quote_Address $address) { $address->setQuote($this); if (!$address->getId()) { $this->getAddressesCollection()->addItem($address); } return $this; } /** * Enter description here... * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote */ public function setBillingAddress(Mage_Sales_Model_Quote_Address $address) { $old = $this->getBillingAddress(); if (!empty($old)) { $old->addData($address->getData()); } else { $this->addAddress($address->setAddressType(Mage_Sales_Model_Quote_Address::TYPE_BILLING)); } return $this; } /** * Enter description here... * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote */ public function setShippingAddress(Mage_Sales_Model_Quote_Address $address) { if ($this->getIsMultiShipping()) { $this->addAddress($address->setAddressType(Mage_Sales_Model_Quote_Address::TYPE_SHIPPING)); } else { $old = $this->getShippingAddress(); if (!empty($old)) { $old->addData($address->getData()); } else { $this->addAddress($address->setAddressType(Mage_Sales_Model_Quote_Address::TYPE_SHIPPING)); } } return $this; } public function addShippingAddress(Mage_Sales_Model_Quote_Address $address) { $this->setShippingAddress($address); return $this; } /** * Retrieve quote items collection * * @param bool $loaded * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function getItemsCollection($useCache = true) { if ($this->hasItemsCollection()) { return $this->getData('items_collection'); } if (is_null($this->_items)) { $this->_items = Mage::getModel('sales/quote_item')->getCollection(); $this->_items->setQuote($this); } return $this->_items; } /** * Retrieve quote items array * * @return array */ public function getAllItems() { $items = array(); foreach ($this->getItemsCollection() as $item) { if (!$item->isDeleted()) { $items[] = $item; } } return $items; } /** * Get array of all items what can be display directly * * @return array */ public function getAllVisibleItems() { $items = array(); foreach ($this->getItemsCollection() as $item) { if (!$item->isDeleted() && !$item->getParentItemId()) { $items[] = $item; } } return $items; } /** * Checking items availability * * @return bool */ public function hasItems() { return sizeof($this->getAllItems())>0; } /** * Checking availability of items with decimal qty * * @return bool */ public function hasItemsWithDecimalQty() { foreach ($this->getAllItems() as $item) { if ($item->getProduct()->getStockItem() && $item->getProduct()->getStockItem()->getIsQtyDecimal()) { return true; } } return false; } /** * Checking product exist in Quote * * @param int $productId * @return bool */ public function hasProductId($productId) { foreach ($this->getAllItems() as $item) { if ($item->getProductId() == $productId) { return true; } } return false; } /** * Retrieve item model object by item identifier * * @param int $itemId * @return Mage_Sales_Model_Quote_Item */ public function getItemById($itemId) { return $this->getItemsCollection()->getItemById($itemId); } /** * Remove quote item by item identifier * * @param int $itemId * @return Mage_Sales_Model_Quote */ public function removeItem($itemId) { $item = $this->getItemById($itemId); if ($item) { $item->setQuote($this); /** * If we remove item from quote - we can't use multishipping mode */ $this->setIsMultiShipping(false); $item->isDeleted(true); if ($item->getHasChildren()) { foreach ($item->getChildren() as $child) { $child->isDeleted(true); } } $parent = $item->getParentItem(); if ($parent) { $parent->isDeleted(true); } Mage::dispatchEvent('sales_quote_remove_item', array('quote_item' => $item)); } return $this; } /** * Mark all quote items as deleted (empty quote) * * @return Mage_Sales_Model_Quote */ public function removeAllItems() { foreach ($this->getItemsCollection() as $itemId => $item) { if (is_null($item->getId())) { $this->getItemsCollection()->removeItemByKey($itemId); } else { $item->isDeleted(true); } } return $this; } /** * Adding new item to quote * * @param Mage_Sales_Model_Quote_Item $item * @return Mage_Sales_Model_Quote */ public function addItem(Mage_Sales_Model_Quote_Item $item) { /** * Temporary workaround for purchase process: it is too dangerous to purchase more than one nominal item * or a mixture of nominal and non-nominal items, although technically possible. * * The problem is that currently it is implemented as sequential submission of nominal items and order, by one click. * It makes logically impossible to make the process of the purchase failsafe. * Proper solution is to submit items one by one with customer confirmation each time. */ if ($item->isNominal() && $this->hasItems() || $this->hasNominalItems()) { Mage::throwException( Mage::helper('sales')->__('Nominal item can be purchased standalone only. To proceed please remove other items from the quote.') ); } $item->setQuote($this); if (!$item->getId()) { $this->getItemsCollection()->addItem($item); Mage::dispatchEvent('sales_quote_add_item', array('quote_item' => $item)); } return $this; } /** * Advanced func to add product to quote - processing mode can be specified there. * Returns error message if product type instance can't prepare product. * * @param mixed $product * @param null|float|Varien_Object $request * @param null|string $processMode * @return Mage_Sales_Model_Quote_Item|string */ public function addProductAdvanced(Mage_Catalog_Model_Product $product, $request = null, $processMode = null) { if ($request === null) { $request = 1; } if (is_numeric($request)) { $request = new Varien_Object(array('qty'=>$request)); } if (!($request instanceof Varien_Object)) { Mage::throwException(Mage::helper('sales')->__('Invalid request for adding product to quote.')); } $cartCandidates = $product->getTypeInstance(true) ->prepareForCartAdvanced($request, $product, $processMode); /** * Error message */ if (is_string($cartCandidates)) { return $cartCandidates; } /** * If prepare process return one object */ if (!is_array($cartCandidates)) { $cartCandidates = array($cartCandidates); } $parentItem = null; $errors = array(); $items = array(); foreach ($cartCandidates as $candidate) { // Child items can be sticked together only within their parent $stickWithinParent = $candidate->getParentProductId() ? $parentItem : null; $candidate->setStickWithinParent($stickWithinParent); $item = $this->_addCatalogProduct($candidate, $candidate->getCartQty()); if($request->getResetCount() && !$stickWithinParent && $item->getId() === $request->getId()) { $item->setData('qty', 0); } $items[] = $item; /** * As parent item we should always use the item of first added product */ if (!$parentItem) { $parentItem = $item; } if ($parentItem && $candidate->getParentProductId()) { $item->setParentItem($parentItem); } /** * We specify qty after we know about parent (for stock) */ $item->addQty($candidate->getCartQty()); // collect errors instead of throwing first one if ($item->getHasError()) { $message = $item->getMessage(); if (!in_array($message, $errors)) { // filter duplicate messages $errors[] = $message; } } } if (!empty($errors)) { Mage::throwException(implode("\n", $errors)); } Mage::dispatchEvent('sales_quote_product_add_after', array('items' => $items)); return $item; } /** * Add product to quote * * return error message if product type instance can't prepare product * * @param mixed $product * @param null|float|Varien_Object $request * @return Mage_Sales_Model_Quote_Item|string */ public function addProduct(Mage_Catalog_Model_Product $product, $request = null) { return $this->addProductAdvanced( $product, $request, Mage_Catalog_Model_Product_Type_Abstract::PROCESS_MODE_FULL ); } /** * Adding catalog product object data to quote * * @param Mage_Catalog_Model_Product $product * @return Mage_Sales_Model_Quote_Item */ protected function _addCatalogProduct(Mage_Catalog_Model_Product $product, $qty = 1) { $newItem = false; $item = $this->getItemByProduct($product); if (!$item) { $item = Mage::getModel('sales/quote_item'); $item->setQuote($this); if (Mage::app()->getStore()->isAdmin()) { $item->setStoreId($this->getStore()->getId()); } else { $item->setStoreId(Mage::app()->getStore()->getId()); } $newItem = true; } /** * We can't modify existing child items */ if ($item->getId() && $product->getParentProductId()) { return $item; } $item->setOptions($product->getCustomOptions()) ->setProduct($product); // Add only item that is not in quote already (there can be other new or already saved item if ($newItem) { $this->addItem($item); } return $item; } /** * Updates quote item with new configuration * * $params sets how current item configuration must be taken into account and additional options. * It's passed to Mage_Catalog_Helper_Product->addParamsToBuyRequest() to compose resulting buyRequest. * * Basically it can hold * - 'current_config', Varien_Object or array - current buyRequest that configures product in this item, * used to restore currently attached files * - 'files_prefix': string[a-z0-9_] - prefix that was added at frontend to names of file options (file inputs), so they won't * intersect with other submitted options * * For more options see Mage_Catalog_Helper_Product->addParamsToBuyRequest() * * @param int $itemId * @param Varien_Object $buyRequest * @param null|array|Varien_Object $params * @return Mage_Sales_Model_Quote_Item * * @see Mage_Catalog_Helper_Product::addParamsToBuyRequest() */ public function updateItem($itemId, $buyRequest, $params = null) { $item = $this->getItemById($itemId); if (!$item) { Mage::throwException(Mage::helper('sales')->__('Wrong quote item id to update configuration.')); } $productId = $item->getProduct()->getId(); //We need to create new clear product instance with same $productId //to set new option values from $buyRequest $product = Mage::getModel('catalog/product') ->setStoreId($this->getStore()->getId()) ->load($productId); if (!$params) { $params = new Varien_Object(); } else if (is_array($params)) { $params = new Varien_Object($params); } $params->setCurrentConfig($item->getBuyRequest()); $buyRequest = Mage::helper('catalog/product')->addParamsToBuyRequest($buyRequest, $params); $buyRequest->setResetCount(true); $resultItem = $this->addProduct($product, $buyRequest); if (is_string($resultItem)) { Mage::throwException($resultItem); } if ($resultItem->getParentItem()) { $resultItem = $resultItem->getParentItem(); } if ($resultItem->getId() != $itemId) { /* * Product configuration didn't stick to original quote item * It either has same configuration as some other quote item's product or completely new configuration */ $this->removeItem($itemId); $items = $this->getAllItems(); foreach ($items as $item) { if (($item->getProductId() == $productId) && ($item->getId() != $resultItem->getId())) { if ($resultItem->compare($item)) { // Product configuration is same as in other quote item $resultItem->setQty($resultItem->getQty() + $item->getQty()); $this->removeItem($item->getId()); break; } } } } else { $resultItem->setQty($buyRequest->getQty()); } return $resultItem; } /** * Retrieve quote item by product id * * @param Mage_Catalog_Model_Product $product * @return Mage_Sales_Model_Quote_Item || false */ public function getItemByProduct($product) { foreach ($this->getAllItems() as $item) { if ($item->representProduct($product)) { return $item; } } return false; } public function getItemsSummaryQty() { $qty = $this->getData('all_items_qty'); if (is_null($qty)) { $qty = 0; foreach ($this->getAllItems() as $item) { if ($item->getParentItem()) { continue; } if (($children = $item->getChildren()) && $item->isShipSeparately()) { foreach ($children as $child) { $qty+= $child->getQty()*$item->getQty(); } } else { $qty+= $item->getQty(); } } $this->setData('all_items_qty', $qty); } return $qty; } public function getItemVirtualQty() { $qty = $this->getData('virtual_items_qty'); if (is_null($qty)) { $qty = 0; foreach ($this->getAllItems() as $item) { if ($item->getParentItem()) { continue; } if (($children = $item->getChildren()) && $item->isShipSeparately()) { foreach ($children as $child) { if ($child->getProduct()->getIsVirtual()) { $qty+= $child->getQty(); } } } else { if ($item->getProduct()->getIsVirtual()) { $qty+= $item->getQty(); } } } $this->setData('virtual_items_qty', $qty); } return $qty; } /*********************** PAYMENTS ***************************/ public function getPaymentsCollection() { if (is_null($this->_payments)) { $this->_payments = Mage::getModel('sales/quote_payment')->getCollection() ->setQuoteFilter($this->getId()); if ($this->getId()) { foreach ($this->_payments as $payment) { $payment->setQuote($this); } } } return $this->_payments; } /** * @return Mage_Sales_Model_Quote_Payment */ public function getPayment() { foreach ($this->getPaymentsCollection() as $payment) { if (!$payment->isDeleted()) { return $payment; } } $payment = Mage::getModel('sales/quote_payment'); $this->addPayment($payment); return $payment; } public function getPaymentById($paymentId) { foreach ($this->getPaymentsCollection() as $payment) { if ($payment->getId()==$paymentId) { return $payment; } } return false; } public function addPayment(Mage_Sales_Model_Quote_Payment $payment) { $payment->setQuote($this); if (!$payment->getId()) { $this->getPaymentsCollection()->addItem($payment); } return $this; } public function setPayment(Mage_Sales_Model_Quote_Payment $payment) { if (!$this->getIsMultiPayment() && ($old = $this->getPayment())) { $payment->setId($old->getId()); } $this->addPayment($payment); return $payment; } public function removePayment() { $this->getPayment()->isDeleted(true); return $this; } /** * Collect totals * * @return Mage_Sales_Model_Quote */ public function collectTotals() { /** * Protect double totals collection */ if ($this->getTotalsCollectedFlag()) { return $this; } Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_before', array($this->_eventObject => $this)); $this->setSubtotal(0); $this->setBaseSubtotal(0); $this->setSubtotalWithDiscount(0); $this->setBaseSubtotalWithDiscount(0); $this->setGrandTotal(0); $this->setBaseGrandTotal(0); foreach ($this->getAllAddresses() as $address) { $address->setSubtotal(0); $address->setBaseSubtotal(0); $address->setGrandTotal(0); $address->setBaseGrandTotal(0); $address->collectTotals(); $this->setSubtotal((float) $this->getSubtotal() + $address->getSubtotal()); $this->setBaseSubtotal((float) $this->getBaseSubtotal() + $address->getBaseSubtotal()); $this->setSubtotalWithDiscount( (float) $this->getSubtotalWithDiscount() + $address->getSubtotalWithDiscount() ); $this->setBaseSubtotalWithDiscount( (float) $this->getBaseSubtotalWithDiscount() + $address->getBaseSubtotalWithDiscount() ); $this->setGrandTotal((float) $this->getGrandTotal() + $address->getGrandTotal()); $this->setBaseGrandTotal((float) $this->getBaseGrandTotal() + $address->getBaseGrandTotal()); } Mage::helper('sales')->checkQuoteAmount($this, $this->getGrandTotal()); Mage::helper('sales')->checkQuoteAmount($this, $this->getBaseGrandTotal()); $this->setItemsCount(0); $this->setItemsQty(0); $this->setVirtualItemsQty(0); foreach ($this->getAllVisibleItems() as $item) { if ($item->getParentItem()) { continue; } $children = $item->getChildren(); if ($children && $item->isShipSeparately()) { foreach ($children as $child) { if ($child->getProduct()->getIsVirtual()) { $this->setVirtualItemsQty($this->getVirtualItemsQty() + $child->getQty()*$item->getQty()); } } } if ($item->getProduct()->getIsVirtual()) { $this->setVirtualItemsQty($this->getVirtualItemsQty() + $item->getQty()); } $this->setItemsCount($this->getItemsCount()+1); $this->setItemsQty((float) $this->getItemsQty()+$item->getQty()); } $this->setData('trigger_recollect', 0); $this->_validateCouponCode(); Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_after', array($this->_eventObject => $this)); $this->setTotalsCollectedFlag(true); return $this; } /** * Get all quote totals (sorted by priority) * Method process quote states isVirtual and isMultiShipping * * @return array */ public function getTotals() { /** * If quote is virtual we are using totals of billing address because * all items assigned to it */ if ($this->isVirtual()) { return $this->getBillingAddress()->getTotals(); } $shippingAddress = $this->getShippingAddress(); $totals = $shippingAddress->getTotals(); // Going through all quote addresses and merge their totals foreach ($this->getAddressesCollection() as $address) { if ($address->isDeleted() || $address === $shippingAddress) { continue; } foreach ($address->getTotals() as $code => $total) { if (isset($totals[$code])) { $totals[$code]->merge($total); } else { $totals[$code] = $total; } } } $sortedTotals = array(); foreach ($this->getBillingAddress()->getTotalModels() as $total) { /* @var $total Mage_Sales_Model_Quote_Address_Total_Abstract */ if (isset($totals[$total->getCode()])) { $sortedTotals[$total->getCode()] = $totals[$total->getCode()]; } } return $sortedTotals; } public function addMessage($message, $index = 'error') { $messages = $this->getData('messages'); if (is_null($messages)) { $messages = array(); } if (isset($messages[$index])) { return $this; } if (is_string($message)) { $message = Mage::getSingleton('core/message')->error($message); } $messages[$index] = $message; $this->setData('messages', $messages); return $this; } /** * Retrieve current quote messages * * @return array */ public function getMessages() { $messages = $this->getData('messages'); if (is_null($messages)) { $messages = array(); $this->setData('messages', $messages); } return $messages; } /** * Retrieve current quote errors * * @return array */ public function getErrors() { $errors = array(); foreach ($this->getMessages() as $message) { /* @var $error Mage_Core_Model_Message_Abstract */ if ($message->getType() == Mage_Core_Model_Message::ERROR) { array_push($errors, $message); } } return $errors; } /** * Sets flag, whether this quote has some error associated with it. * * @param bool $flag * @return Mage_Sales_Model_Quote */ protected function _setHasError($flag) { return $this->setData('has_error', $flag); } /** * Sets flag, whether this quote has some error associated with it. * When TRUE - also adds 'unknown' error information to list of quote errors. * When FALSE - clears whole list of quote errors. * It's recommended to use addErrorInfo() instead - to be able to remove error statuses later. * * @param bool $flag * @return Mage_Sales_Model_Quote * @see addErrorInfo() */ public function setHasError($flag) { if ($flag) { $this->addErrorInfo(); } else { $this->_clearErrorInfo(); } return $this; } /** * Clears list of errors, associated with this quote. * Also automatically removes error-flag from oneself. * * @return Mage_Sales_Model_Quote */ protected function _clearErrorInfo() { $this->_errorInfoGroups = array(); $this->_setHasError(false); return $this; } /** * Adds error information to the quote. * Automatically sets error flag. * * @param string $type An internal error type ('error', 'qty', etc.), passed then to adding messages routine * @param string|null $origin Usually a name of module, that embeds error * @param int|null $code Error code, unique for origin, that sets it * @param string|null $message Error message * @param Varien_Object|null $additionalData Any additional data, that caller would like to store * @return Mage_Sales_Model_Quote */ public function addErrorInfo($type = 'error', $origin = null, $code = null, $message = null, $additionalData = null) { if (!isset($this->_errorInfoGroups[$type])) { $this->_errorInfoGroups[$type] = Mage::getModel('sales/status_list'); } $this->_errorInfoGroups[$type]->addItem($origin, $code, $message, $additionalData); if ($message !== null) { $this->addMessage($message, $type); } $this->_setHasError(true); return $this; } /** * Removes error infos, that have parameters equal to passed in $params. * $params can have following keys (if not set - then any item is good for this key): * 'origin', 'code', 'message' * * @param string $type An internal error type ('error', 'qty', etc.), passed then to adding messages routine * @param array $params * @return Mage_Sales_Model_Quote */ public function removeErrorInfosByParams($type = 'error', $params) { if ($type && !isset($this->_errorInfoGroups[$type])) { return $this; } $errorLists = array(); if ($type) { $errorLists[] = $this->_errorInfoGroups[$type]; } else { $errorLists = $this->_errorInfoGroups; } foreach ($errorLists as $type => $errorList) { $removedItems = $errorList->removeItemsByParams($params); foreach ($removedItems as $item) { if ($item['message'] !== null) { $this->removeMessageByText($type, $item['message']); } } } $errorsExist = false; foreach ($this->_errorInfoGroups as $errorListCheck) { if ($errorListCheck->getItems()) { $errorsExist = true; break; } } if (!$errorsExist) { $this->_setHasError(false); } return $this; } /** * Removes message by text * * @param string $type * @param string $text * @return Mage_Sales_Model_Quote */ public function removeMessageByText($type = 'error', $text) { $messages = $this->getData('messages'); if (is_null($messages)) { $messages = array(); } if (!isset($messages[$type])) { return $this; } $message = $messages[$type]; if ($message instanceof Mage_Core_Model_Message_Abstract) { $message = $message->getText(); } else if (!is_string($message)) { return $this; } if ($message == $text) { unset($messages[$type]); $this->setData('messages', $messages); } return $this; } /** * Generate new increment order id and associate it with current quote * * @return Mage_Sales_Model_Quote */ public function reserveOrderId() { if (!$this->getReservedOrderId()) { $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this)); } else { //checking if reserved order id was already used for some order //if yes reserving new one if not using old one if ($this->_getResource()->isOrderIncrementIdUsed($this->getReservedOrderId())) { $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this)); } } return $this; } public function validateMinimumAmount($multishipping = false) { $storeId = $this->getStoreId(); $minOrderActive = Mage::getStoreConfigFlag('sales/minimum_order/active', $storeId); $minOrderMulti = Mage::getStoreConfigFlag('sales/minimum_order/multi_address', $storeId); $minAmount = Mage::getStoreConfig('sales/minimum_order/amount', $storeId); if (!$minOrderActive) { return true; } $addresses = $this->getAllAddresses(); if ($multishipping) { if ($minOrderMulti) { foreach ($addresses as $address) { foreach ($address->getQuote()->getItemsCollection() as $item) { $amount = $item->getBaseRowTotal() - $item->getBaseDiscountAmount(); if ($amount < $minAmount) { return false; } } } } else { $baseTotal = 0; foreach ($addresses as $address) { /* @var $address Mage_Sales_Model_Quote_Address */ $baseTotal += $address->getBaseSubtotalWithDiscount(); } if ($baseTotal < $minAmount) { return false; } } } else { foreach ($addresses as $address) { /* @var $address Mage_Sales_Model_Quote_Address */ if (!$address->validateMinimumAmount()) { return false; } } } return true; } /** * Check quote for virtual product only * * @return bool */ public function isVirtual() { $isVirtual = true; $countItems = 0; foreach ($this->getItemsCollection() as $_item) { /* @var $_item Mage_Sales_Model_Quote_Item */ if ($_item->isDeleted() || $_item->getParentItemId()) { continue; } $countItems ++; if (!$_item->getProduct()->getIsVirtual()) { $isVirtual = false; break; } } return $countItems == 0 ? false : $isVirtual; } /** * Check quote for virtual product only * * @return bool */ public function getIsVirtual() { return intval($this->isVirtual()); } /** * Has a virtual products on quote * * @return bool */ public function hasVirtualItems() { $hasVirtual = false; foreach ($this->getItemsCollection() as $_item) { if ($_item->getParentItemId()) { continue; } if ($_item->getProduct()->isVirtual()) { $hasVirtual = true; } } return $hasVirtual; } /** * Merge quotes * * @param Mage_Sales_Model_Quote $quote * @return Mage_Sales_Model_Quote */ public function merge(Mage_Sales_Model_Quote $quote) { Mage::dispatchEvent( $this->_eventPrefix . '_merge_before', array( $this->_eventObject=>$this, 'source'=>$quote ) ); foreach ($quote->getAllVisibleItems() as $item) { $found = false; foreach ($this->getAllItems() as $quoteItem) { if ($quoteItem->compare($item)) { $quoteItem->setQty($quoteItem->getQty() + $item->getQty()); $found = true; break; } } if (!$found) { $newItem = clone $item; $this->addItem($newItem); if ($item->getHasChildren()) { foreach ($item->getChildren() as $child) { $newChild = clone $child; $newChild->setParentItem($newItem); $this->addItem($newChild); } } } } /** * Init shipping and billing address if quote is new */ if (!$this->getId()) { $this->getShippingAddress(); $this->getBillingAddress(); } if ($quote->getCouponCode()) { $this->setCouponCode($quote->getCouponCode()); } Mage::dispatchEvent( $this->_eventPrefix . '_merge_after', array( $this->_eventObject=>$this, 'source'=>$quote ) ); return $this; } /** * Whether there are recurring items * * @return bool */ public function hasRecurringItems() { foreach ($this->getAllVisibleItems() as $item) { if ($item->getProduct() && $item->getProduct()->isRecurring()) { return true; } } return false; } /** * Getter whether quote has nominal items * Can bypass treating virtual items as nominal * * @param bool $countVirtual * @return bool */ public function hasNominalItems($countVirtual = true) { foreach ($this->getAllVisibleItems() as $item) { if ($item->isNominal()) { if ((!$countVirtual) && $item->getProduct()->isVirtual()) { continue; } return true; } } return false; } /** * Whether quote has nominal items only * * @return bool */ public function isNominal() { foreach ($this->getAllVisibleItems() as $item) { if (!$item->isNominal()) { return false; } } return true; } /** * Create recurring payment profiles basing on the current items * * @return array */ public function prepareRecurringPaymentProfiles() { if (!$this->getTotalsCollectedFlag()) { // Whoops! Make sure nominal totals must be calculated here. throw new Exception('Quote totals must be collected before this operation.'); } $result = array(); foreach ($this->getAllVisibleItems() as $item) { $product = $item->getProduct(); if (is_object($product) && ($product->isRecurring()) && $profile = Mage::getModel('sales/recurring_profile')->importProduct($product) ) { $profile->importQuote($this); $profile->importQuoteItem($item); $result[] = $profile; } } return $result; } protected function _validateCouponCode() { $code = $this->_getData('coupon_code'); if (strlen($code)) { $addressHasCoupon = false; $addresses = $this->getAllAddresses(); if (count($addresses)>0) { foreach ($addresses as $address) { if ($address->hasCouponCode()) { $addressHasCoupon = true; } } if (!$addressHasCoupon) { $this->setCouponCode(''); } } } return $this; } /** * Trigger collect totals after loading, if required * * @return Mage_Sales_Model_Quote */ protected function _afterLoad() { // collect totals and save me, if required if (1 == $this->getData('trigger_recollect')) { $this->collectTotals()->save(); } return parent::_afterLoad(); } /** * @deprecated after 1.4 beta1 - one page checkout responsibility */ const CHECKOUT_METHOD_REGISTER = 'register'; const CHECKOUT_METHOD_GUEST = 'guest'; const CHECKOUT_METHOD_LOGIN_IN = 'login_in'; /** * Return quote checkout method code * * @deprecated after 1.4 beta1 it is checkout module responsibility * @param boolean $originalMethod if true return defined method from begining * @return string */ public function getCheckoutMethod($originalMethod = false) { if ($this->getCustomerId() && !$originalMethod) { return self::CHECKOUT_METHOD_LOGIN_IN; } return $this->_getData('checkout_method'); } /** * Check is allow Guest Checkout * * @deprecated after 1.4 beta1 it is checkout module responsibility * @return bool */ public function isAllowedGuestCheckout() { return Mage::helper('checkout')->isAllowedGuestCheckout($this, $this->getStoreId()); } /** * Prevent quote from saving * * @return Mage_Sales_Model_Quote */ public function preventSaving() { $this->_preventSaving = true; return $this; } /** * Save quote with prevention checking * * @return Mage_Sales_Model_Quote */ public function save() { if ($this->_preventSaving) { return $this; } return parent::save(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales Quote address model * * @method Mage_Sales_Model_Resource_Quote_Address _getResource() * @method Mage_Sales_Model_Resource_Quote_Address getResource() * @method int getQuoteId() * @method Mage_Sales_Model_Quote_Address setQuoteId(int $value) * @method string getCreatedAt() * @method Mage_Sales_Model_Quote_Address setCreatedAt(string $value) * @method string getUpdatedAt() * @method Mage_Sales_Model_Quote_Address setUpdatedAt(string $value) * @method int getCustomerId() * @method Mage_Sales_Model_Quote_Address setCustomerId(int $value) * @method int getSaveInAddressBook() * @method Mage_Sales_Model_Quote_Address setSaveInAddressBook(int $value) * @method int getCustomerAddressId() * @method Mage_Sales_Model_Quote_Address setCustomerAddressId(int $value) * @method string getAddressType() * @method Mage_Sales_Model_Quote_Address setAddressType(string $value) * @method string getEmail() * @method Mage_Sales_Model_Quote_Address setEmail(string $value) * @method string getPrefix() * @method Mage_Sales_Model_Quote_Address setPrefix(string $value) * @method string getFirstname() * @method Mage_Sales_Model_Quote_Address setFirstname(string $value) * @method string getMiddlename() * @method Mage_Sales_Model_Quote_Address setMiddlename(string $value) * @method string getLastname() * @method Mage_Sales_Model_Quote_Address setLastname(string $value) * @method string getSuffix() * @method Mage_Sales_Model_Quote_Address setSuffix(string $value) * @method string getCompany() * @method Mage_Sales_Model_Quote_Address setCompany(string $value) * @method string getCity() * @method Mage_Sales_Model_Quote_Address setCity(string $value) * @method Mage_Sales_Model_Quote_Address setRegion(string $value) * @method Mage_Sales_Model_Quote_Address setRegionId(int $value) * @method string getPostcode() * @method Mage_Sales_Model_Quote_Address setPostcode(string $value) * @method string getCountryId() * @method Mage_Sales_Model_Quote_Address setCountryId(string $value) * @method string getTelephone() * @method Mage_Sales_Model_Quote_Address setTelephone(string $value) * @method string getFax() * @method Mage_Sales_Model_Quote_Address setFax(string $value) * @method int getSameAsBilling() * @method Mage_Sales_Model_Quote_Address setSameAsBilling(int $value) * @method int getFreeShipping() * @method Mage_Sales_Model_Quote_Address setFreeShipping(int $value) * @method int getCollectShippingRates() * @method Mage_Sales_Model_Quote_Address setCollectShippingRates(int $value) * @method string getShippingMethod() * @method Mage_Sales_Model_Quote_Address setShippingMethod(string $value) * @method string getShippingDescription() * @method Mage_Sales_Model_Quote_Address setShippingDescription(string $value) * @method float getWeight() * @method Mage_Sales_Model_Quote_Address setWeight(float $value) * @method float getSubtotal() * @method Mage_Sales_Model_Quote_Address setSubtotal(float $value) * @method float getBaseSubtotal() * @method Mage_Sales_Model_Quote_Address setBaseSubtotal(float $value) * @method Mage_Sales_Model_Quote_Address setSubtotalWithDiscount(float $value) * @method Mage_Sales_Model_Quote_Address setBaseSubtotalWithDiscount(float $value) * @method float getTaxAmount() * @method Mage_Sales_Model_Quote_Address setTaxAmount(float $value) * @method float getBaseTaxAmount() * @method Mage_Sales_Model_Quote_Address setBaseTaxAmount(float $value) * @method float getShippingAmount() * @method float getBaseShippingAmount() * @method float getShippingTaxAmount() * @method Mage_Sales_Model_Quote_Address setShippingTaxAmount(float $value) * @method float getBaseShippingTaxAmount() * @method Mage_Sales_Model_Quote_Address setBaseShippingTaxAmount(float $value) * @method float getDiscountAmount() * @method Mage_Sales_Model_Quote_Address setDiscountAmount(float $value) * @method float getBaseDiscountAmount() * @method Mage_Sales_Model_Quote_Address setBaseDiscountAmount(float $value) * @method float getGrandTotal() * @method Mage_Sales_Model_Quote_Address setGrandTotal(float $value) * @method float getBaseGrandTotal() * @method Mage_Sales_Model_Quote_Address setBaseGrandTotal(float $value) * @method string getCustomerNotes() * @method Mage_Sales_Model_Quote_Address setCustomerNotes(string $value) * @method string getDiscountDescription() * @method Mage_Sales_Model_Quote_Address setDiscountDescription(string $value) * @method float getShippingDiscountAmount() * @method Mage_Sales_Model_Quote_Address setShippingDiscountAmount(float $value) * @method float getBaseShippingDiscountAmount() * @method Mage_Sales_Model_Quote_Address setBaseShippingDiscountAmount(float $value) * @method float getSubtotalInclTax() * @method Mage_Sales_Model_Quote_Address setSubtotalInclTax(float $value) * @method float getBaseSubtotalTotalInclTax() * @method Mage_Sales_Model_Quote_Address setBaseSubtotalTotalInclTax(float $value) * @method int getGiftMessageId() * @method Mage_Sales_Model_Quote_Address setGiftMessageId(int $value) * @method float getHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Address setHiddenTaxAmount(float $value) * @method float getBaseHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Address setBaseHiddenTaxAmount(float $value) * @method float getShippingHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Address setShippingHiddenTaxAmount(float $value) * @method float getBaseShippingHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Address setBaseShippingHiddenTaxAmount(float $value) * @method float getShippingInclTax() * @method Mage_Sales_Model_Quote_Address setShippingInclTax(float $value) * @method float getBaseShippingInclTax() * @method Mage_Sales_Model_Quote_Address setBaseShippingInclTax(float $value) * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Quote_Address extends Mage_Customer_Model_Address_Abstract { const RATES_FETCH = 1; const RATES_RECALCULATE = 2; /** * Prefix of model events * * @var string */ protected $_eventPrefix = 'sales_quote_address'; /** * Name of event object * * @var string */ protected $_eventObject = 'quote_address'; /** * Quote object * * @var Mage_Sales_Model_Quote */ protected $_items = null; /** * Quote object * * @var Mage_Sales_Model_Quote */ protected $_quote = null; /** * Sales Quote address rates * * @var Mage_Sales_Model_Quote_Address_Rate */ protected $_rates = null; /** * Total models collector * * @var Mage_Sales_Model_Quote_Address_Totla_Collector */ protected $_totalCollector = null; /** * Total data as array * * @var array */ protected $_totals = array(); protected $_totalAmounts = array(); protected $_baseTotalAmounts = array(); /** * Whether to segregate by nominal items only * * @var bool */ protected $_nominalOnly = null; /** * Initialize resource */ protected function _construct() { $this->_init('sales/quote_address'); } /** * Init mapping array of short fields to its full names * * @return Mage_Sales_Model_Quote_Address */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = Mage::helper('sales')->getOldFieldMap('quote_address'); return $this; } /** * Initialize quote identifier before save * * @return Mage_Sales_Model_Quote_Address */ protected function _beforeSave() { parent::_beforeSave(); if ($this->getQuote()) { $quoteId = $this->getQuote()->getId(); if ($quoteId) { $this->setQuoteId($quoteId); } else { $this->_dataSaveAllowed = false; } $this->setCustomerId($this->getQuote()->getCustomerId()); /** * Init customer address id if customer address is assigned */ if ($this->getCustomerAddress()) { $this->setCustomerAddressId($this->getCustomerAddress()->getId()); } } if ($this->getAddressType() == Mage_Sales_Model_Quote_Address::TYPE_SHIPPING && $this->getSameAsBilling() === null ) { $this->setSameAsBilling(1); } return $this; } /** * Save child collections * * @return Mage_Sales_Model_Quote_Address */ protected function _afterSave() { parent::_afterSave(); if (null !== $this->_items) { $this->getItemsCollection()->save(); } if (null !== $this->_rates) { $this->getShippingRatesCollection()->save(); } return $this; } /** * Declare adress quote model object * * @param Mage_Sales_Model_Quote $quote * @return Mage_Sales_Model_Quote_Address */ public function setQuote(Mage_Sales_Model_Quote $quote) { $this->_quote = $quote; $this->setQuoteId($quote->getId()); return $this; } /** * Retrieve quote object * * @return Mage_Sales_Model_Quote */ public function getQuote() { return $this->_quote; } /** * Import quote address data from customer address object * * @param Mage_Customer_Model_Address $address * @return Mage_Sales_Model_Quote_Address */ public function importCustomerAddress(Mage_Customer_Model_Address $address) { Mage::helper('core')->copyFieldset('customer_address', 'to_quote_address', $address, $this); $email = null; if ($address->hasEmail()) { $email = $address->getEmail(); } elseif ($address->getCustomer()) { $email = $address->getCustomer()->getEmail(); } if ($email) { $this->setEmail($email); } return $this; } /** * Export data to customer address object * * @return Mage_Customer_Model_Address */ public function exportCustomerAddress() { $address = Mage::getModel('customer/address'); Mage::helper('core')->copyFieldset('sales_convert_quote_address', 'to_customer_address', $this, $address); return $address; } /** * Import address data from order address * * @param Mage_Sales_Model_Order_Address $address * @return Mage_Sales_Model_Quote_Address */ public function importOrderAddress(Mage_Sales_Model_Order_Address $address) { $this->setAddressType($address->getAddressType()) ->setCustomerId($address->getCustomerId()) ->setCustomerAddressId($address->getCustomerAddressId()) ->setEmail($address->getEmail()); Mage::helper('core')->copyFieldset('sales_convert_order_address', 'to_quote_address', $address, $this); return $this; } /** * Convert object to array * * @param array $arrAttributes * @return array */ public function toArray(array $arrAttributes = array()) { $arr = parent::toArray($arrAttributes); $arr['rates'] = $this->getShippingRatesCollection()->toArray($arrAttributes); $arr['items'] = $this->getItemsCollection()->toArray($arrAttributes); foreach ($this->getTotals() as $k=>$total) { $arr['totals'][$k] = $total->toArray(); } return $arr; } /** * Retrieve address items collection * * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function getItemsCollection() { if (is_null($this->_items)) { $this->_items = Mage::getModel('sales/quote_address_item')->getCollection() ->setAddressFilter($this->getId()); if ($this->getId()) { foreach ($this->_items as $item) { $item->setAddress($this); } } } return $this->_items; } /** * Get all available address items * * @return array */ public function getAllItems() { // We calculate item list once and cache it in three arrays - all items, nominal, non-nominal $cachedItems = $this->_nominalOnly ? 'nominal' : ($this->_nominalOnly === false ? 'nonnominal' : 'all'); $key = 'cached_items_' . $cachedItems; if (!$this->hasData($key)) { // For compatibility we will use $this->_filterNominal to divide nominal items from non-nominal // (because it can be overloaded) // So keep current flag $this->_nominalOnly and restore it after cycle $wasNominal = $this->_nominalOnly; $this->_nominalOnly = true; // Now $this->_filterNominal() will return positive values for nominal items $quoteItems = $this->getQuote()->getItemsCollection(); $addressItems = $this->getItemsCollection(); $items = array(); $nominalItems = array(); $nonNominalItems = array(); if ($this->getQuote()->getIsMultiShipping() && $addressItems->count() > 0) { foreach ($addressItems as $aItem) { if ($aItem->isDeleted()) { continue; } if (!$aItem->getQuoteItemImported()) { $qItem = $this->getQuote()->getItemById($aItem->getQuoteItemId()); if ($qItem) { $aItem->importQuoteItem($qItem); } } $items[] = $aItem; if ($this->_filterNominal($aItem)) { $nominalItems[] = $aItem; } else { $nonNominalItems[] = $aItem; } } } else { /* * For virtual quote we assign items only to billing address, otherwise - only to shipping address */ $addressType = $this->getAddressType(); $canAddItems = $this->getQuote()->isVirtual() ? ($addressType == self::TYPE_BILLING) : ($addressType == self::TYPE_SHIPPING); if ($canAddItems) { foreach ($quoteItems as $qItem) { if ($qItem->isDeleted()) { continue; } $items[] = $qItem; if ($this->_filterNominal($qItem)) { $nominalItems[] = $qItem; } else { $nonNominalItems[] = $qItem; } } } } // Cache calculated lists $this->setData('cached_items_all', $items); $this->setData('cached_items_nominal', $nominalItems); $this->setData('cached_items_nonnominal', $nonNominalItems); $this->_nominalOnly = $wasNominal; // Restore original value before we changed it } $items = $this->getData($key); return $items; } /** * Getter for all non-nominal items * * @return array */ public function getAllNonNominalItems() { $this->_nominalOnly = false; $result = $this->getAllItems(); $this->_nominalOnly = null; return $result; } /** * Getter for all nominal items * * @return array */ public function getAllNominalItems() { $this->_nominalOnly = true; $result = $this->getAllItems(); $this->_nominalOnly = null; return $result; } /** * Segregate by nominal criteria * * true: get nominals only * false: get non-nominals only * null: get all * * @param Mage_Sales_Model_Quote_Item_Abstract * @return Mage_Sales_Model_Quote_Item_Abstract|false */ protected function _filterNominal($item) { return (null === $this->_nominalOnly) || ((false === $this->_nominalOnly) && !$item->isNominal()) || ((true === $this->_nominalOnly) && $item->isNominal()) ? $item : false; } /** * Retrieve all visible items * * @return array */ public function getAllVisibleItems() { $items = array(); foreach ($this->getAllItems() as $item) { if (!$item->getParentItemId()) { $items[] = $item; } } return $items; } /** * Retrieve item quantity by id * * @param int $itemId * @return float|int */ public function getItemQty($itemId = 0) { if ($this->hasData('item_qty')) { return $this->getData('item_qty'); } $qty = 0; if ($itemId == 0) { foreach ($this->getAllItems() as $item) { $qty += $item->getQty(); } } else { $item = $this->getItemById($itemId); if ($item) { $qty = $item->getQty(); } } return $qty; } /** * Check Quote address has Items * * @return bool */ public function hasItems() { return sizeof($this->getAllItems())>0; } /** * Get address item object by id without * * @param int $itemId * @return Mage_Sales_Model_Quote_Address_Item */ public function getItemById($itemId) { foreach ($this->getItemsCollection() as $item) { if ($item->getId()==$itemId) { return $item; } } return false; } /** * Get prepared not deleted item * * @param $itemId * @return Mage_Sales_Model_Quote_Address_Item */ public function getValidItemById($itemId) { foreach ($this->getAllItems() as $item) { if ($item->getId()==$itemId) { return $item; } } return false; } /** * Retrieve item object by quote item Id * * @param int $itemId * @return Mage_Sales_Model_Quote_Address_Item */ public function getItemByQuoteItemId($itemId) { foreach ($this->getItemsCollection() as $item) { if ($item->getQuoteItemId()==$itemId) { return $item; } } return false; } /** * Remove item from collection * * @param int $itemId * @return Mage_Sales_Model_Quote_Address */ public function removeItem($itemId) { $item = $this->getItemById($itemId); if ($item) { $item->isDeleted(true); } return $this; } /** * Add item to address * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @param int $qty * @return Mage_Sales_Model_Quote_Address */ public function addItem(Mage_Sales_Model_Quote_Item_Abstract $item, $qty=null) { if ($item instanceof Mage_Sales_Model_Quote_Item) { if ($item->getParentItemId()) { return $this; } $addressItem = Mage::getModel('sales/quote_address_item') ->setAddress($this) ->importQuoteItem($item); $this->getItemsCollection()->addItem($addressItem); if ($item->getHasChildren()) { foreach ($item->getChildren() as $child) { $addressChildItem = Mage::getModel('sales/quote_address_item') ->setAddress($this) ->importQuoteItem($child) ->setParentItem($addressItem); $this->getItemsCollection()->addItem($addressChildItem); } } } else { $addressItem = $item; $addressItem->setAddress($this); if (!$addressItem->getId()) { $this->getItemsCollection()->addItem($addressItem); } } if ($qty) { $addressItem->setQty($qty); } return $this; } /** * Retrieve collection of quote shipping rates * * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function getShippingRatesCollection() { if (is_null($this->_rates)) { $this->_rates = Mage::getModel('sales/quote_address_rate')->getCollection() ->setAddressFilter($this->getId()); if ($this->getQuote()->hasNominalItems(false)) { $this->_rates->setFixedOnlyFilter(true); } if ($this->getId()) { foreach ($this->_rates as $rate) { $rate->setAddress($this); } } } return $this->_rates; } /** * Retrieve all address shipping rates * * @return array */ public function getAllShippingRates() { $rates = array(); foreach ($this->getShippingRatesCollection() as $rate) { if (!$rate->isDeleted()) { $rates[] = $rate; } } return $rates; } /** * Retrieve all grouped shipping rates * * @return array */ public function getGroupedAllShippingRates() { $rates = array(); foreach ($this->getShippingRatesCollection() as $rate) { if (!$rate->isDeleted() && $rate->getCarrierInstance()) { if (!isset($rates[$rate->getCarrier()])) { $rates[$rate->getCarrier()] = array(); } $rates[$rate->getCarrier()][] = $rate; $rates[$rate->getCarrier()][0]->carrier_sort_order = $rate->getCarrierInstance()->getSortOrder(); } } uasort($rates, array($this, '_sortRates')); return $rates; } /** * Sort rates recursive callback * * @param array $a * @param array $b * @return int */ protected function _sortRates($a, $b) { if ((int)$a[0]->carrier_sort_order < (int)$b[0]->carrier_sort_order) { return -1; } elseif ((int)$a[0]->carrier_sort_order > (int)$b[0]->carrier_sort_order) { return 1; } else { return 0; } } /** * Retrieve shipping rate by identifier * * @param int $rateId * @return Mage_Sales_Model_Quote_Address_Rate | false */ public function getShippingRateById($rateId) { foreach ($this->getShippingRatesCollection() as $rate) { if ($rate->getId()==$rateId) { return $rate; } } return false; } /** * Retrieve shipping rate by code * * @param string $code * @return Mage_Sales_Model_Quote_Address_Rate */ public function getShippingRateByCode($code) { foreach ($this->getShippingRatesCollection() as $rate) { if ($rate->getCode()==$code) { return $rate; } } return false; } /** * Mark all shipping rates as deleted * * @return Mage_Sales_Model_Quote_Address */ public function removeAllShippingRates() { foreach ($this->getShippingRatesCollection() as $rate) { $rate->isDeleted(true); } return $this; } /** * Add shipping rate * * @param Mage_Sales_Model_Quote_Address_Rate $rate * @return Mage_Sales_Model_Quote_Address */ public function addShippingRate(Mage_Sales_Model_Quote_Address_Rate $rate) { $rate->setAddress($this); $this->getShippingRatesCollection()->addItem($rate); return $this; } /** * Collecting shipping rates by address * * @return Mage_Sales_Model_Quote_Address */ public function collectShippingRates() { if (!$this->getCollectShippingRates()) { return $this; } $this->setCollectShippingRates(false); $this->removeAllShippingRates(); if (!$this->getCountryId()) { return $this; } $found = $this->requestShippingRates(); if (!$found) { $this->setShippingAmount(0) ->setBaseShippingAmount(0) ->setShippingMethod('') ->setShippingDescription(''); } return $this; } /** * Request shipping rates for entire address or specified address item * Returns true if current selected shipping method code corresponds to one of the found rates * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return bool */ public function requestShippingRates(Mage_Sales_Model_Quote_Item_Abstract $item = null) { /** @var $request Mage_Shipping_Model_Rate_Request */ $request = Mage::getModel('shipping/rate_request'); $request->setAllItems($item ? array($item) : $this->getAllItems()); $request->setDestCountryId($this->getCountryId()); $request->setDestRegionId($this->getRegionId()); $request->setDestRegionCode($this->getRegionCode()); /** * need to call getStreet with -1 * to get data in string instead of array */ $request->setDestStreet($this->getStreet(-1)); $request->setDestCity($this->getCity()); $request->setDestPostcode($this->getPostcode()); $request->setPackageValue($item ? $item->getBaseRowTotal() : $this->getBaseSubtotal()); $packageValueWithDiscount = $item ? $item->getBaseRowTotal() - $item->getBaseDiscountAmount() : $this->getBaseSubtotalWithDiscount(); $request->setPackageValueWithDiscount($packageValueWithDiscount); $request->setPackageWeight($item ? $item->getRowWeight() : $this->getWeight()); $request->setPackageQty($item ? $item->getQty() : $this->getItemQty()); /** * Need for shipping methods that use insurance based on price of physical products */ $packagePhysicalValue = $item ? $item->getBaseRowTotal() : $this->getBaseSubtotal() - $this->getBaseVirtualAmount(); $request->setPackagePhysicalValue($packagePhysicalValue); $request->setFreeMethodWeight($item ? 0 : $this->getFreeMethodWeight()); /** * Store and website identifiers need specify from quote */ /*$request->setStoreId(Mage::app()->getStore()->getId()); $request->setWebsiteId(Mage::app()->getStore()->getWebsiteId());*/ $request->setStoreId($this->getQuote()->getStore()->getId()); $request->setWebsiteId($this->getQuote()->getStore()->getWebsiteId()); $request->setFreeShipping($this->getFreeShipping()); /** * Currencies need to convert in free shipping */ $request->setBaseCurrency($this->getQuote()->getStore()->getBaseCurrency()); $request->setPackageCurrency($this->getQuote()->getStore()->getCurrentCurrency()); $request->setLimitCarrier($this->getLimitCarrier()); $request->setBaseSubtotalInclTax($this->getBaseSubtotalInclTax()); $result = Mage::getModel('shipping/shipping')->collectRates($request)->getResult(); $found = false; if ($result) { $shippingRates = $result->getAllRates(); foreach ($shippingRates as $shippingRate) { $rate = Mage::getModel('sales/quote_address_rate') ->importShippingRate($shippingRate); if (!$item) { $this->addShippingRate($rate); } if ($this->getShippingMethod() == $rate->getCode()) { if ($item) { $item->setBaseShippingAmount($rate->getPrice()); } else { /** * possible bug: this should be setBaseShippingAmount(), * see Mage_Sales_Model_Quote_Address_Total_Shipping::collect() * where this value is set again from the current specified rate price * (looks like a workaround for this bug) */ $this->setShippingAmount($rate->getPrice()); } $found = true; } } } return $found; } /** * Get totals collector model * * @return Mage_Sales_Model_Quote_Address_Total_Collector */ public function getTotalCollector() { if ($this->_totalCollector === null) { $this->_totalCollector = Mage::getSingleton( 'sales/quote_address_total_collector', array('store'=>$this->getQuote()->getStore()) ); } return $this->_totalCollector; } /** * Retrieve total models * * @deprecated * @return array */ public function getTotalModels() { return $this->getTotalCollector()->getRetrievers(); } /** * Collect address totals * * @return Mage_Sales_Model_Quote_Address */ public function collectTotals() { Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_before', array($this->_eventObject => $this)); foreach ($this->getTotalCollector()->getCollectors() as $model) { $model->collect($this); } Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_after', array($this->_eventObject => $this)); return $this; } /** * Get address totals as array * * @return array */ public function getTotals() { foreach ($this->getTotalCollector()->getRetrievers() as $model) { $model->fetch($this); } return $this->_totals; } /** * Add total data or model * * @param Mage_Sales_Model_Quote_Total|array $total * @return Mage_Sales_Model_Quote_Address */ public function addTotal($total) { if (is_array($total)) { $totalInstance = Mage::getModel('sales/quote_address_total') ->setData($total); } elseif ($total instanceof Mage_Sales_Model_Quote_Total) { $totalInstance = $total; } $totalInstance->setAddress($this); $this->_totals[$totalInstance->getCode()] = $totalInstance; return $this; } /** * Rewrite clone method * * @return Mage_Sales_Model_Quote_Address */ public function __clone() { $this->setId(null); } /** * Validate minimum amount * * @return bool */ public function validateMinimumAmount() { $storeId = $this->getQuote()->getStoreId(); if (!Mage::getStoreConfigFlag('sales/minimum_order/active', $storeId)) { return true; } if ($this->getQuote()->getIsVirtual() && $this->getAddressType() == self::TYPE_SHIPPING) { return true; } elseif (!$this->getQuote()->getIsVirtual() && $this->getAddressType() != self::TYPE_SHIPPING) { return true; } $amount = Mage::getStoreConfig('sales/minimum_order/amount', $storeId); if ($this->getBaseSubtotalWithDiscount() < $amount) { return false; } return true; } /** * Retrieve applied taxes * * @return array */ public function getAppliedTaxes() { return unserialize($this->getData('applied_taxes')); } /** * Set applied taxes * * @param array $data * @return Mage_Sales_Model_Quote_Address */ public function setAppliedTaxes($data) { return $this->setData('applied_taxes', serialize($data)); } /** * Set shipping amount * * @param float $value * @param bool $alreadyExclTax * @return Mage_Sales_Model_Quote_Address */ public function setShippingAmount($value, $alreadyExclTax = false) { return $this->setData('shipping_amount', $value); } /** * Set base shipping amount * * @param float $value * @param bool $alreadyExclTax * @return Mage_Sales_Model_Quote_Address */ public function setBaseShippingAmount($value, $alreadyExclTax = false) { return $this->setData('base_shipping_amount', $value); } /** * Set total amount value * * @param string $code * @param float $amount * @return Mage_Sales_Model_Quote_Address */ public function setTotalAmount($code, $amount) { $this->_totalAmounts[$code] = $amount; if ($code != 'subtotal') { $code = $code.'_amount'; } $this->setData($code, $amount); return $this; } /** * Set total amount value in base store currency * * @param string $code * @param float $amount * @return Mage_Sales_Model_Quote_Address */ public function setBaseTotalAmount($code, $amount) { $this->_baseTotalAmounts[$code] = $amount; if ($code != 'subtotal') { $code = $code.'_amount'; } $this->setData('base_'.$code, $amount); return $this; } /** * Add amount total amount value * * @param string $code * @param float $amount * @return Mage_Sales_Model_Quote_Address */ public function addTotalAmount($code, $amount) { $amount = $this->getTotalAmount($code)+$amount; $this->setTotalAmount($code, $amount); return $this; } /** * Add amount total amount value in base store currency * * @param string $code * @param float $amount * @return Mage_Sales_Model_Quote_Address */ public function addBaseTotalAmount($code, $amount) { $amount = $this->getBaseTotalAmount($code)+$amount; $this->setBaseTotalAmount($code, $amount); return $this; } /** * Get total amount value by code * * @param string $code * @return float */ public function getTotalAmount($code) { if (isset($this->_totalAmounts[$code])) { return $this->_totalAmounts[$code]; } return 0; } /** * Get total amount value by code in base store curncy * * @param string $code * @return float */ public function getBaseTotalAmount($code) { if (isset($this->_baseTotalAmounts[$code])) { return $this->_baseTotalAmounts[$code]; } return 0; } /** * Get all total amount values * * @return array */ public function getAllTotalAmounts() { return $this->_totalAmounts; } /** * Get all total amount values in base currency * * @return array */ public function getAllBaseTotalAmounts() { return $this->_baseTotalAmounts; } /** * Get subtotal amount with applied discount in base currency * * @return float */ public function getBaseSubtotalWithDiscount() { return $this->getBaseSubtotal()+$this->getBaseDiscountAmount(); } /** * Get subtotal amount with applied discount * * @return float */ public function getSubtotalWithDiscount() { return $this->getSubtotal()+$this->getDiscountAmount(); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Product configurational item interface * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ interface Mage_Catalog_Model_Product_Configuration_Item_Interface { /** * Retrieve associated product * * @return Mage_Catalog_Model_Product */ function getProduct(); /** * Get item option by code * * @param string $code * @return Mage_Catalog_Model_Product_Configuration_Item_Option_Interface */ public function getOptionByCode($code); /** * Returns special download params (if needed) for custom option with type = 'file'' * Return null, if not special params needed' * Or return Varien_Object with any of the following indexes: * - 'url' - url of controller to give the file * - 'urlParams' - additional parameters for url (custom option id, or item id, for example) * * @return null|Varien_Object */ public function getFileDownloadParams(); } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote item abstract model * * Price attributes: * - price - initial item price, declared during product association * - original_price - product price before any calculations * - calculation_price - prices for item totals calculation * - custom_price - new price that can be declared by user and recalculated during calculation process * - original_custom_price - original defined value of custom price without any convertion * * @category Mage * @package Mage_Sales * @author Magento Core Team */ abstract class Mage_Sales_Model_Quote_Item_Abstract extends Mage_Core_Model_Abstract implements Mage_Catalog_Model_Product_Configuration_Item_Interface { protected $_parentItem = null; protected $_children = array(); protected $_messages = array(); /** * Retrieve Quote instance * * @return Mage_Sales_Model_Quote */ abstract function getQuote(); /** * Retrieve product model object associated with item * * @return Mage_Catalog_Model_Product */ public function getProduct() { $product = $this->_getData('product'); if (($product === null) && $this->getProductId()) { $product = Mage::getModel('catalog/product') ->setStoreId($this->getQuote()->getStoreId()) ->load($this->getProductId()); $this->setProduct($product); } /** * Reset product final price because it related to custom options */ $product->setFinalPrice(null); if (is_array($this->_optionsByCode)) { $product->setCustomOptions($this->_optionsByCode); } return $product; } /** * Returns special download params (if needed) for custom option with type = 'file' * Needed to implement Mage_Catalog_Model_Product_Configuration_Item_Interface. * Return null, as quote item needs no additional configuration. * * @return null|Varien_Object */ public function getFileDownloadParams() { return null; } /** * Specify parent item id before saving data * * @return Mage_Sales_Model_Quote_Item_Abstract */ protected function _beforeSave() { parent::_beforeSave(); if ($this->getParentItem()) { $this->setParentItemId($this->getParentItem()->getId()); } return $this; } /** * Set parent item * * @param Mage_Sales_Model_Quote_Item $parentItem * @return Mage_Sales_Model_Quote_Item */ public function setParentItem($parentItem) { if ($parentItem) { $this->_parentItem = $parentItem; $parentItem->addChild($this); } return $this; } /** * Get parent item * * @return Mage_Sales_Model_Quote_Item */ public function getParentItem() { return $this->_parentItem; } /** * Get chil items * * @return array */ public function getChildren() { return $this->_children; } /** * Add child item * * @param Mage_Sales_Model_Quote_Item_Abstract $child * @return Mage_Sales_Model_Quote_Item_Abstract */ public function addChild($child) { $this->setHasChildren(true); $this->_children[] = $child; return $this; } /** * Adds message(s) for quote item. Duplicated messages are not added. * * @param mixed $messages * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setMessage($messages) { $messagesExists = $this->getMessage(false); if (!is_array($messages)) { $messages = array($messages); } foreach ($messages as $message) { if (!in_array($message, $messagesExists)) { $this->addMessage($message); } } return $this; } /** * Add message of quote item to array of messages * * @param string $message * @return Mage_Sales_Model_Quote_Item_Abstract */ public function addMessage($message) { $this->_messages[] = $message; return $this; } /** * Get messages array of quote item * * @param bool $string flag for converting messages to string * @return array|string */ public function getMessage($string = true) { if ($string) { return join("\n", $this->_messages); } return $this->_messages; } /** * Removes message by text * * @param string $text * @return Mage_Sales_Model_Quote_Item_Abstract */ public function removeMessageByText($text) { foreach ($this->_messages as $key => $message) { if ($message == $text) { unset($this->_messages[$key]); } } return $this; } /** * Clears all messages * * @return Mage_Sales_Model_Quote_Item_Abstract */ public function clearMessage() { $this->unsMessage(); // For older compatibility, when we kept message inside data array $this->_messages = array(); return $this; } /** * Retrieve store model object * * @return Mage_Core_Model_Store */ public function getStore() { return $this->getQuote()->getStore(); } /** * Checking item data * * @return Mage_Sales_Model_Quote_Item_Abstract */ public function checkData() { $this->setHasError(false); $this->clearMessage(); $qty = $this->_getData('qty'); try { $this->setQty($qty); } catch (Mage_Core_Exception $e){ $this->setHasError(true); $this->setMessage($e->getMessage()); } catch (Exception $e){ $this->setHasError(true); $this->setMessage(Mage::helper('sales')->__('Item qty declaration error.')); } try { $this->getProduct()->getTypeInstance(true)->checkProductBuyState($this->getProduct()); } catch (Mage_Core_Exception $e) { $this->setHasError(true); $this->setMessage($e->getMessage()); $this->getQuote()->setHasError(true); $this->getQuote()->addMessage( Mage::helper('sales')->__('Some of the products below do not have all the required options. Please edit them and configure all the required options.') ); } catch (Exception $e) { $this->setHasError(true); $this->setMessage(Mage::helper('sales')->__('Item options declaration error.')); $this->getQuote()->setHasError(true); $this->getQuote()->addMessage(Mage::helper('sales')->__('Items options declaration error.')); } return $this; } /** * Get original (not related with parent item) item quantity * * @return int|float */ public function getQty() { return $this->_getData('qty'); } /** * Get total item quantity (include parent item relation) * * @return int|float */ public function getTotalQty() { if ($this->getParentItem()) { return $this->getQty()*$this->getParentItem()->getQty(); } return $this->getQty(); } /** * Calculate item row total price * * @return Mage_Sales_Model_Quote_Item */ public function calcRowTotal() { $qty = $this->getTotalQty(); // Round unit price before multiplying to prevent losing 1 cent on subtotal $total = $this->getStore()->roundPrice($this->getCalculationPriceOriginal()) * $qty; $baseTotal = $this->getBaseCalculationPriceOriginal() * $qty; $this->setRowTotal($this->getStore()->roundPrice($total)); $this->setBaseRowTotal($this->getStore()->roundPrice($baseTotal)); return $this; } /** * Get item price used for quote calculation process. * This method get custom price (if it is defined) or original product final price * * @return float */ public function getCalculationPrice() { $price = $this->_getData('calculation_price'); if (is_null($price)) { if ($this->hasCustomPrice()) { $price = $this->getCustomPrice(); } else { $price = $this->getConvertedPrice(); } $this->setData('calculation_price', $price); } return $price; } /** * Get item price used for quote calculation process. * This method get original custom price applied before tax calculation * * @return float */ public function getCalculationPriceOriginal() { $price = $this->_getData('calculation_price'); if (is_null($price)) { if ($this->hasOriginalCustomPrice()) { $price = $this->getOriginalCustomPrice(); } else { $price = $this->getConvertedPrice(); } $this->setData('calculation_price', $price); } return $price; } /** * Get calculation price used for quote calculation in base currency. * * @return float */ public function getBaseCalculationPrice() { if (!$this->hasBaseCalculationPrice()) { if ($this->hasCustomPrice()) { $price = (float) $this->getCustomPrice(); if ($price) { $rate = $this->getStore()->convertPrice($price) / $price; $price = $price / $rate; } } else { $price = $this->getPrice(); } $this->setBaseCalculationPrice($price); } return $this->_getData('base_calculation_price'); } /** * Get original calculation price used for quote calculation in base currency. * * @return float */ public function getBaseCalculationPriceOriginal() { if (!$this->hasBaseCalculationPrice()) { if ($this->hasOriginalCustomPrice()) { $price = (float) $this->getOriginalCustomPrice(); if ($price) { $rate = $this->getStore()->convertPrice($price) / $price; $price = $price / $rate; } } else { $price = $this->getPrice(); } $this->setBaseCalculationPrice($price); } return $this->_getData('base_calculation_price'); } /** * Get whether the item is nominal * TODO: fix for multishipping checkout * * @return bool */ public function isNominal() { if (!$this->hasData('is_nominal')) { $this->setData('is_nominal', $this->getProduct() ? '1' == $this->getProduct()->getIsRecurring() : false); } return $this->_getData('is_nominal'); } /** * Data getter for 'is_nominal' * Used for converting item to order item * * @return int */ public function getIsNominal() { return (int)$this->isNominal(); } /** * Get original price (retrieved from product) for item. * Original price value is in quote selected currency * * @return float */ public function getOriginalPrice() { $price = $this->_getData('original_price'); if (is_null($price)) { $price = $this->getStore()->convertPrice($this->getBaseOriginalPrice()); $this->setData('original_price', $price); } return $price; } /** * Set original price to item (calculation price will be refreshed too) * * @param float $price * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setOriginalPrice($price) { return $this->setData('original_price', $price); } /** * Get Original item price (got from product) in base website currency * * @return float */ public function getBaseOriginalPrice() { return $this->_getData('base_original_price'); } /** * Specify custom item price (used in case whe we have apply not product price to item) * * @param float $value * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setCustomPrice($value) { $this->setCalculationPrice($value); $this->setBaseCalculationPrice(null); return $this->setData('custom_price', $value); } /** * Get item price. Item price currency is website base currency. * * @return decimal */ public function getPrice() { return $this->_getData('price'); } /** * Specify item price (base calculation price and converted price will be refreshed too) * * @param float $value * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setPrice($value) { $this->setBaseCalculationPrice(null); $this->setConvertedPrice(null); return $this->setData('price', $value); } /** * Get item price converted to quote currency * @return float */ public function getConvertedPrice() { $price = $this->_getData('converted_price'); if (is_null($price)) { $price = $this->getStore()->convertPrice($this->getPrice()); $this->setData('converted_price', $price); } return $price; } /** * Set new value for converted price * @param float $value * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setConvertedPrice($value) { $this->setCalculationPrice(null); $this->setData('converted_price', $value); return $this; } /** * Clone quote item * * @return Mage_Sales_Model_Quote_Item */ public function __clone() { $this->setId(null); $this->_parentItem = null; $this->_children = array(); $this->_messages = array(); return $this; } /** * Checking if there children calculated or parent item * when we have parent quote item and its children * * @return bool */ public function isChildrenCalculated() { if ($this->getParentItem()) { $calculate = $this->getParentItem()->getProduct()->getPriceType(); } else { $calculate = $this->getProduct()->getPriceType(); } if ((null !== $calculate) && (int)$calculate === Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD) { return true; } return false; } /** * Checking can we ship product separatelly (each child separately) * or each parent product item can be shipped only like one item * * @return bool */ public function isShipSeparately() { if ($this->getParentItem()) { $shipmentType = $this->getParentItem()->getProduct()->getShipmentType(); } else { $shipmentType = $this->getProduct()->getShipmentType(); } if ((null !== $shipmentType) && (int)$shipmentType === Mage_Catalog_Model_Product_Type_Abstract::SHIPMENT_SEPARATELY) { return true; } return false; } /** * Calculate item tax amount * * @deprecated logic moved to tax totals calculation model * @return Mage_Sales_Model_Quote_Item */ public function calcTaxAmount() { $store = $this->getStore(); if (!Mage::helper('tax')->priceIncludesTax($store)) { if (Mage::helper('tax')->applyTaxAfterDiscount($store)) { $rowTotal = $this->getRowTotalWithDiscount(); $rowBaseTotal = $this->getBaseRowTotalWithDiscount(); } else { $rowTotal = $this->getRowTotal(); $rowBaseTotal = $this->getBaseRowTotal(); } $taxPercent = $this->getTaxPercent()/100; $this->setTaxAmount($store->roundPrice($rowTotal * $taxPercent)); $this->setBaseTaxAmount($store->roundPrice($rowBaseTotal * $taxPercent)); $rowTotal = $this->getRowTotal(); $rowBaseTotal = $this->getBaseRowTotal(); $this->setTaxBeforeDiscount($store->roundPrice($rowTotal * $taxPercent)); $this->setBaseTaxBeforeDiscount($store->roundPrice($rowBaseTotal * $taxPercent)); } else { if (Mage::helper('tax')->applyTaxAfterDiscount($store)) { $totalBaseTax = $this->getBaseTaxAmount(); $totalTax = $this->getTaxAmount(); if ($totalTax && $totalBaseTax) { $totalTax -= $this->getDiscountAmount()*($this->getTaxPercent()/100); $totalBaseTax -= $this->getBaseDiscountAmount()*($this->getTaxPercent()/100); $this->setBaseTaxAmount($store->roundPrice($totalBaseTax)); $this->setTaxAmount($store->roundPrice($totalTax)); } } } if (Mage::helper('tax')->discountTax($store) && !Mage::helper('tax')->applyTaxAfterDiscount($store)) { if ($this->getDiscountPercent()) { $baseTaxAmount = $this->getBaseTaxBeforeDiscount(); $taxAmount = $this->getTaxBeforeDiscount(); $baseDiscountDisposition = $baseTaxAmount/100*$this->getDiscountPercent(); $discountDisposition = $taxAmount/100*$this->getDiscountPercent(); $this->setDiscountAmount($this->getDiscountAmount()+$discountDisposition); $this->setBaseDiscountAmount($this->getBaseDiscountAmount()+$baseDiscountDisposition); } } return $this; } /** * Get item tax amount * * @deprecated * @return decimal */ public function getTaxAmount() { return $this->_getData('tax_amount'); } /** * Get item base tax amount * * @deprecated * @return decimal */ public function getBaseTaxAmount() { return $this->_getData('base_tax_amount'); } /** * Get item price (item price always exclude price) * * @deprecated * @return decimal */ protected function _calculatePrice($value, $saveTaxes = true) { $store = $this->getQuote()->getStore(); if (Mage::helper('tax')->priceIncludesTax($store)) { $bAddress = $this->getQuote()->getBillingAddress(); $sAddress = $this->getQuote()->getShippingAddress(); $address = $this->getAddress(); if ($address) { switch ($address->getAddressType()) { case Mage_Sales_Model_Quote_Address::TYPE_BILLING: $bAddress = $address; break; case Mage_Sales_Model_Quote_Address::TYPE_SHIPPING: $sAddress = $address; break; } } if ($this->getProduct()->getIsVirtual()) { $sAddress = $bAddress; } $priceExcludingTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $value, false, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); $priceIncludingTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $value, true, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); if ($saveTaxes) { $qty = $this->getQty(); if ($this->getParentItem()) { $qty = $qty*$this->getParentItem()->getQty(); } if (Mage::helper('tax')->displayCartPriceInclTax($store)) { $rowTotal = $value*$qty; $rowTotalExcTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $rowTotal, false, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); $rowTotalIncTax = Mage::helper('tax')->getPrice( $this->getProduct()->setTaxPercent(null), $rowTotal, true, $sAddress, $bAddress, $this->getQuote()->getCustomerTaxClassId(), $store ); $totalBaseTax = $rowTotalIncTax-$rowTotalExcTax; $this->setRowTotalExcTax($rowTotalExcTax); } else { $taxAmount = $priceIncludingTax - $priceExcludingTax; $this->setTaxPercent($this->getProduct()->getTaxPercent()); $totalBaseTax = $taxAmount*$qty; } $totalTax = $this->getStore()->convertPrice($totalBaseTax); $this->setTaxBeforeDiscount($totalTax); $this->setBaseTaxBeforeDiscount($totalBaseTax); $this->setTaxAmount($totalTax); $this->setBaseTaxAmount($totalBaseTax); } $value = $priceExcludingTax; } return $value; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Enter description here ... * * @method Mage_Sales_Model_Resource_Quote_Address_Item _getResource() * @method Mage_Sales_Model_Resource_Quote_Address_Item getResource() * @method int getParentItemId() * @method Mage_Sales_Model_Quote_Address_Item setParentItemId(int $value) * @method int getQuoteAddressId() * @method Mage_Sales_Model_Quote_Address_Item setQuoteAddressId(int $value) * @method int getQuoteItemId() * @method Mage_Sales_Model_Quote_Address_Item setQuoteItemId(int $value) * @method string getCreatedAt() * @method Mage_Sales_Model_Quote_Address_Item setCreatedAt(string $value) * @method string getUpdatedAt() * @method Mage_Sales_Model_Quote_Address_Item setUpdatedAt(string $value) * @method string getAppliedRuleIds() * @method Mage_Sales_Model_Quote_Address_Item setAppliedRuleIds(string $value) * @method string getAdditionalData() * @method Mage_Sales_Model_Quote_Address_Item setAdditionalData(string $value) * @method float getWeight() * @method Mage_Sales_Model_Quote_Address_Item setWeight(float $value) * @method Mage_Sales_Model_Quote_Address_Item setQty(float $value) * @method float getDiscountAmount() * @method Mage_Sales_Model_Quote_Address_Item setDiscountAmount(float $value) * @method Mage_Sales_Model_Quote_Address_Item setTaxAmount(float $value) * @method float getRowTotal() * @method Mage_Sales_Model_Quote_Address_Item setRowTotal(float $value) * @method float getBaseRowTotal() * @method Mage_Sales_Model_Quote_Address_Item setBaseRowTotal(float $value) * @method float getRowTotalWithDiscount() * @method Mage_Sales_Model_Quote_Address_Item setRowTotalWithDiscount(float $value) * @method float getBaseDiscountAmount() * @method Mage_Sales_Model_Quote_Address_Item setBaseDiscountAmount(float $value) * @method Mage_Sales_Model_Quote_Address_Item setBaseTaxAmount(float $value) * @method float getRowWeight() * @method Mage_Sales_Model_Quote_Address_Item setRowWeight(float $value) * @method int getProductId() * @method Mage_Sales_Model_Quote_Address_Item setProductId(int $value) * @method int getSuperProductId() * @method Mage_Sales_Model_Quote_Address_Item setSuperProductId(int $value) * @method int getParentProductId() * @method Mage_Sales_Model_Quote_Address_Item setParentProductId(int $value) * @method string getSku() * @method Mage_Sales_Model_Quote_Address_Item setSku(string $value) * @method string getImage() * @method Mage_Sales_Model_Quote_Address_Item setImage(string $value) * @method string getName() * @method Mage_Sales_Model_Quote_Address_Item setName(string $value) * @method string getDescription() * @method Mage_Sales_Model_Quote_Address_Item setDescription(string $value) * @method int getFreeShipping() * @method Mage_Sales_Model_Quote_Address_Item setFreeShipping(int $value) * @method int getIsQtyDecimal() * @method Mage_Sales_Model_Quote_Address_Item setIsQtyDecimal(int $value) * @method float getDiscountPercent() * @method Mage_Sales_Model_Quote_Address_Item setDiscountPercent(float $value) * @method int getNoDiscount() * @method Mage_Sales_Model_Quote_Address_Item setNoDiscount(int $value) * @method float getTaxPercent() * @method Mage_Sales_Model_Quote_Address_Item setTaxPercent(float $value) * @method float getBasePrice() * @method Mage_Sales_Model_Quote_Address_Item setBasePrice(float $value) * @method float getBaseCost() * @method Mage_Sales_Model_Quote_Address_Item setBaseCost(float $value) * @method float getPriceInclTax() * @method Mage_Sales_Model_Quote_Address_Item setPriceInclTax(float $value) * @method float getBasePriceInclTax() * @method Mage_Sales_Model_Quote_Address_Item setBasePriceInclTax(float $value) * @method float getRowTotalInclTax() * @method Mage_Sales_Model_Quote_Address_Item setRowTotalInclTax(float $value) * @method float getBaseRowTotalInclTax() * @method Mage_Sales_Model_Quote_Address_Item setBaseRowTotalInclTax(float $value) * @method int getGiftMessageId() * @method Mage_Sales_Model_Quote_Address_Item setGiftMessageId(int $value) * @method float getHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Address_Item setHiddenTaxAmount(float $value) * @method float getBaseHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Address_Item setBaseHiddenTaxAmount(float $value) * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Quote_Address_Item extends Mage_Sales_Model_Quote_Item_Abstract { /** * Quote address model object * * @var Mage_Sales_Model_Quote_Address */ protected $_address; protected $_quote; protected function _construct() { $this->_init('sales/quote_address_item'); } protected function _beforeSave() { parent::_beforeSave(); if ($this->getAddress()) { $this->setQuoteAddressId($this->getAddress()->getId()); } return $this; } /** * Declare address model * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Item */ public function setAddress(Mage_Sales_Model_Quote_Address $address) { $this->_address = $address; $this->_quote = $address->getQuote(); return $this; } /** * Retrieve address model * * @return Mage_Sales_Model_Quote_Address */ public function getAddress() { return $this->_address; } /** * Retrieve quote model instance * * @return Mage_Sales_Model_Quote */ public function getQuote() { return $this->_quote; } public function importQuoteItem(Mage_Sales_Model_Quote_Item $quoteItem) { $this->_quote = $quoteItem->getQuote(); $this->setQuoteItem($quoteItem) ->setQuoteItemId($quoteItem->getId()) ->setProductId($quoteItem->getProductId()) ->setProduct($quoteItem->getProduct()) ->setSku($quoteItem->getSku()) ->setName($quoteItem->getName()) ->setDescription($quoteItem->getDescription()) ->setWeight($quoteItem->getWeight()) ->setPrice($quoteItem->getPrice()) ->setCost($quoteItem->getCost()); if (!$this->hasQty()) { $this->setQty($quoteItem->getQty()); } $this->setQuoteItemImported(true); return $this; } public function getOptionBycode($code) { if ($this->getQuoteItem()) { return $this->getQuoteItem()->getOptionBycode($code); } return null; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Shipping * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ abstract class Mage_Shipping_Model_Rate_Abstract extends Mage_Core_Model_Abstract { static protected $_instances; public function getCarrierInstance() { $code = $this->getCarrier(); if (!isset(self::$_instances[$code])) { self::$_instances[$code] = Mage::getModel('shipping/config')->getCarrierInstance($code); } return self::$_instances[$code]; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Enter description here ... * * @method Mage_Sales_Model_Resource_Quote_Address_Rate _getResource() * @method Mage_Sales_Model_Resource_Quote_Address_Rate getResource() * @method int getAddressId() * @method Mage_Sales_Model_Quote_Address_Rate setAddressId(int $value) * @method string getCreatedAt() * @method Mage_Sales_Model_Quote_Address_Rate setCreatedAt(string $value) * @method string getUpdatedAt() * @method Mage_Sales_Model_Quote_Address_Rate setUpdatedAt(string $value) * @method string getCarrier() * @method Mage_Sales_Model_Quote_Address_Rate setCarrier(string $value) * @method string getCarrierTitle() * @method Mage_Sales_Model_Quote_Address_Rate setCarrierTitle(string $value) * @method string getCode() * @method Mage_Sales_Model_Quote_Address_Rate setCode(string $value) * @method string getMethod() * @method Mage_Sales_Model_Quote_Address_Rate setMethod(string $value) * @method string getMethodDescription() * @method Mage_Sales_Model_Quote_Address_Rate setMethodDescription(string $value) * @method float getPrice() * @method Mage_Sales_Model_Quote_Address_Rate setPrice(float $value) * @method string getErrorMessage() * @method Mage_Sales_Model_Quote_Address_Rate setErrorMessage(string $value) * @method string getMethodTitle() * @method Mage_Sales_Model_Quote_Address_Rate setMethodTitle(string $value) * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Quote_Address_Rate extends Mage_Shipping_Model_Rate_Abstract { protected $_address; protected function _construct() { $this->_init('sales/quote_address_rate'); } protected function _beforeSave() { parent::_beforeSave(); if ($this->getAddress()) { $this->setAddressId($this->getAddress()->getId()); } return $this; } public function setAddress(Mage_Sales_Model_Quote_Address $address) { $this->_address = $address; return $this; } public function getAddress() { return $this->_address; } public function importShippingRate(Mage_Shipping_Model_Rate_Result_Abstract $rate) { if ($rate instanceof Mage_Shipping_Model_Rate_Result_Error) { $this ->setCode($rate->getCarrier().'_error') ->setCarrier($rate->getCarrier()) ->setCarrierTitle($rate->getCarrierTitle()) ->setErrorMessage($rate->getErrorMessage()) ; } elseif ($rate instanceof Mage_Shipping_Model_Rate_Result_Method) { $this ->setCode($rate->getCarrier().'_'.$rate->getMethod()) ->setCarrier($rate->getCarrier()) ->setCarrierTitle($rate->getCarrierTitle()) ->setMethod($rate->getMethod()) ->setMethodTitle($rate->getMethodTitle()) ->setMethodDescription($rate->getMethodDescription()) ->setPrice($rate->getPrice()) ; } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Address_Total extends Varien_Object { /** * Merge numeric total values * * @param Mage_Sales_Model_Quote_Address_Total $total * @return Mage_Sales_Model_Quote_Address_Total */ public function merge(Mage_Sales_Model_Quote_Address_Total $total) { $newData = $total->getData(); foreach ($newData as $key => $value) { if (is_numeric($value)) { $this->setData($key, $this->_getData($key)+$value); } } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales Quote Address Total abstract model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ abstract class Mage_Sales_Model_Quote_Address_Total_Abstract { /** * Total Code name * * @var string */ protected $_code; protected $_address = null; /** * Various abstract abilities * @var bool */ protected $_canAddAmountToAddress = true; protected $_canSetAddressAmount = true; /** * Key for item row total getting * * @var string */ protected $_itemRowTotalKey = null; /** * Set total code code name * * @param string $code * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ public function setCode($code) { $this->_code = $code; return $this; } /** * Retrieve total code name * * @return unknown */ public function getCode() { return $this->_code; } /** * Label getter * * @return string */ public function getLabel() { return ''; } /** * Collect totals process. * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ public function collect(Mage_Sales_Model_Quote_Address $address) { $this->_setAddress($address); /** * Reset amounts */ $this->_setAmount(0); $this->_setBaseAmount(0); return $this; } /** * Fetch (Retrieve data as array) * * @param Mage_Sales_Model_Quote_Address $address * @return array */ public function fetch(Mage_Sales_Model_Quote_Address $address) { $this->_setAddress($address); return array(); } /** * Set address shich can be used inside totals calculation * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ protected function _setAddress(Mage_Sales_Model_Quote_Address $address) { $this->_address = $address; return $this; } /** * Get quote address object * * @throw Mage_Core_Exception if address not declared * @return Mage_Sales_Model_Quote_Address */ protected function _getAddress() { if ($this->_address === null) { Mage::throwException( Mage::helper('sales')->__('Address model is not defined.') ); } return $this->_address; } /** * Set total model amount value to address * * @param float $amount * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ protected function _setAmount($amount) { if ($this->_canSetAddressAmount) { $this->_getAddress()->setTotalAmount($this->getCode(), $amount); } return $this; } /** * Set total model base amount value to address * * @param float $amount * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ protected function _setBaseAmount($baseAmount) { if ($this->_canSetAddressAmount) { $this->_getAddress()->setBaseTotalAmount($this->getCode(), $baseAmount); } return $this; } /** * Add total model amount value to address * * @param float $amount * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ protected function _addAmount($amount) { if ($this->_canAddAmountToAddress) { $this->_getAddress()->addTotalAmount($this->getCode(),$amount); } return $this; } /** * Add total model base amount value to address * * @param float $amount * @return Mage_Sales_Model_Quote_Address_Total_Abstract */ protected function _addBaseAmount($baseAmount) { if ($this->_canAddAmountToAddress) { $this->_getAddress()->addBaseTotalAmount($this->getCode(), $baseAmount); } return $this; } /** * Get all items except nominals * * @param Mage_Sales_Model_Quote_Address $address * @return array */ protected function _getAddressItems(Mage_Sales_Model_Quote_Address $address) { return $address->getAllNonNominalItems(); } /** * Getter for row default total * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return float */ public function getItemRowTotal(Mage_Sales_Model_Quote_Item_Abstract $item) { if (!$this->_itemRowTotalKey) { return 0; } return $item->getDataUsingMethod($this->_itemRowTotalKey); } /** * Getter for row default base total * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return float */ public function getItemBaseRowTotal(Mage_Sales_Model_Quote_Item_Abstract $item) { if (!$this->_itemRowTotalKey) { return 0; } return $item->getDataUsingMethod('base_' . $this->_itemRowTotalKey); } /** * Whether the item row total may be compouded with others * * @param Mage_Sales_Model_Quote_Item_Abstract $item * @return bool */ public function getIsItemRowTotalCompoundable(Mage_Sales_Model_Quote_Item_Abstract $item) { if ($item->getData("skip_compound_{$this->_itemRowTotalKey}")) { return false; } return true; } /** * Process model configuration array. * This method can be used for changing models apply sort order * * @param array $config * @param store $store * @return array */ public function processConfigArray($config, $store) { return $config; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Address_Total_Discount extends Mage_Sales_Model_Quote_Address_Total_Abstract { public function collect(Mage_Sales_Model_Quote_Address $address) { $quote = $address->getQuote(); $eventArgs = array( 'website_id'=>Mage::app()->getStore($quote->getStoreId())->getWebsiteId(), 'customer_group_id'=>$quote->getCustomerGroupId(), 'coupon_code'=>$quote->getCouponCode(), ); $address->setFreeShipping(0); $totalDiscountAmount = 0; $subtotalWithDiscount= 0; $baseTotalDiscountAmount = 0; $baseSubtotalWithDiscount= 0; $items = $address->getAllItems(); if (!count($items)) { $address->setDiscountAmount($totalDiscountAmount); $address->setSubtotalWithDiscount($subtotalWithDiscount); $address->setBaseDiscountAmount($baseTotalDiscountAmount); $address->setBaseSubtotalWithDiscount($baseSubtotalWithDiscount); return $this; } $hasDiscount = false; foreach ($items as $item) { if ($item->getNoDiscount()) { $item->setDiscountAmount(0); $item->setBaseDiscountAmount(0); $item->setRowTotalWithDiscount($item->getRowTotal()); $item->setBaseRowTotalWithDiscount($item->getRowTotal()); $subtotalWithDiscount+=$item->getRowTotal(); $baseSubtotalWithDiscount+=$item->getBaseRowTotal(); } else { /** * Child item discount we calculate for parent */ if ($item->getParentItemId()) { continue; } /** * Composite item discount calculation */ if ($item->getHasChildren() && $item->isChildrenCalculated()) { foreach ($item->getChildren() as $child) { $eventArgs['item'] = $child; Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs); if ($child->getDiscountAmount() || $child->getFreeShipping()) { $hasDiscount = true; } /** * Parent free shipping we apply to all children */ if ($item->getFreeShipping()) { $child->setFreeShipping($item->getFreeShipping()); } /** * @todo Parent discount we apply for all children without discount */ if (!$child->getDiscountAmount() && $item->getDiscountPercent()) { } $totalDiscountAmount += $child->getDiscountAmount();//*$item->getQty(); $baseTotalDiscountAmount += $child->getBaseDiscountAmount();//*$item->getQty(); $child->setRowTotalWithDiscount($child->getRowTotal()-$child->getDiscountAmount()); $child->setBaseRowTotalWithDiscount($child->getBaseRowTotal()-$child->getBaseDiscountAmount()); $subtotalWithDiscount+=$child->getRowTotalWithDiscount(); $baseSubtotalWithDiscount+=$child->getBaseRowTotalWithDiscount(); } } else { $eventArgs['item'] = $item; Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs); if ($item->getDiscountAmount() || $item->getFreeShipping()) { $hasDiscount = true; } $totalDiscountAmount += $item->getDiscountAmount(); $baseTotalDiscountAmount += $item->getBaseDiscountAmount(); $item->setRowTotalWithDiscount($item->getRowTotal()-$item->getDiscountAmount()); $item->setBaseRowTotalWithDiscount($item->getBaseRowTotal()-$item->getBaseDiscountAmount()); $subtotalWithDiscount+=$item->getRowTotalWithDiscount(); $baseSubtotalWithDiscount+=$item->getBaseRowTotalWithDiscount(); } } } $address->setDiscountAmount($totalDiscountAmount); $address->setSubtotalWithDiscount($subtotalWithDiscount); $address->setBaseDiscountAmount($baseTotalDiscountAmount); $address->setBaseSubtotalWithDiscount($baseSubtotalWithDiscount); $address->setGrandTotal($address->getGrandTotal() - $address->getDiscountAmount()); $address->setBaseGrandTotal($address->getBaseGrandTotal()-$address->getBaseDiscountAmount()); return $this; } public function fetch(Mage_Sales_Model_Quote_Address $address) { $amount = $address->getDiscountAmount(); if ($amount!=0) { $title = Mage::helper('sales')->__('Discount'); $code = $address->getCouponCode(); if (strlen($code)) { $title = Mage::helper('sales')->__('Discount (%s)', $code); } $address->addTotal(array( 'code'=>$this->getCode(), 'title'=>$title, 'value'=>-$amount )); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Address_Total_Grand extends Mage_Sales_Model_Quote_Address_Total_Abstract { /** * Collect grand total address amount * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Grand */ public function collect(Mage_Sales_Model_Quote_Address $address) { $grandTotal = $address->getGrandTotal(); $baseGrandTotal = $address->getBaseGrandTotal(); $totals = array_sum($address->getAllTotalAmounts()); $baseTotals = array_sum($address->getAllBaseTotalAmounts()); $address->setGrandTotal($grandTotal+$totals); $address->setBaseGrandTotal($baseGrandTotal+$baseTotals); return $this; } /** * Add grand total information to address * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Grand */ public function fetch(Mage_Sales_Model_Quote_Address $address) { $address->addTotal(array( 'code' => $this->getCode(), 'title' => Mage::helper('sales')->__('Grand Total'), 'value' => $address->getGrandTotal(), 'area' => 'footer', )); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Address_Total_Shipping extends Mage_Sales_Model_Quote_Address_Total_Abstract { public function __construct() { $this->setCode('shipping'); } /** * Collect totals information about shipping * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Shipping */ public function collect(Mage_Sales_Model_Quote_Address $address) { parent::collect($address); $oldWeight = $address->getWeight(); $address->setWeight(0); $address->setFreeMethodWeight(0); $this->_setAmount(0) ->_setBaseAmount(0); $items = $this->_getAddressItems($address); if (!count($items)) { return $this; } $method = $address->getShippingMethod(); $freeAddress= $address->getFreeShipping(); $addressWeight = $address->getWeight(); $freeMethodWeight = $address->getFreeMethodWeight(); $addressQty = 0; foreach ($items as $item) { /** * Skip if this item is virtual */ if ($item->getProduct()->isVirtual()) { continue; } /** * Children weight we calculate for parent */ if ($item->getParentItem()) { continue; } if ($item->getHasChildren() && $item->isShipSeparately()) { foreach ($item->getChildren() as $child) { if ($child->getProduct()->isVirtual()) { continue; } $addressQty += $child->getTotalQty(); if (!$item->getProduct()->getWeightType()) { $itemWeight = $child->getWeight(); $itemQty = $child->getTotalQty(); $rowWeight = $itemWeight*$itemQty; $addressWeight += $rowWeight; if ($freeAddress || $child->getFreeShipping()===true) { $rowWeight = 0; } elseif (is_numeric($child->getFreeShipping())) { $freeQty = $child->getFreeShipping(); if ($itemQty>$freeQty) { $rowWeight = $itemWeight*($itemQty-$freeQty); } else { $rowWeight = 0; } } $freeMethodWeight += $rowWeight; $item->setRowWeight($rowWeight); } } if ($item->getProduct()->getWeightType()) { $itemWeight = $item->getWeight(); $rowWeight = $itemWeight*$item->getQty(); $addressWeight+= $rowWeight; if ($freeAddress || $item->getFreeShipping()===true) { $rowWeight = 0; } elseif (is_numeric($item->getFreeShipping())) { $freeQty = $item->getFreeShipping(); if ($item->getQty()>$freeQty) { $rowWeight = $itemWeight*($item->getQty()-$freeQty); } else { $rowWeight = 0; } } $freeMethodWeight+= $rowWeight; $item->setRowWeight($rowWeight); } } else { if (!$item->getProduct()->isVirtual()) { $addressQty += $item->getQty(); } $itemWeight = $item->getWeight(); $rowWeight = $itemWeight*$item->getQty(); $addressWeight+= $rowWeight; if ($freeAddress || $item->getFreeShipping()===true) { $rowWeight = 0; } elseif (is_numeric($item->getFreeShipping())) { $freeQty = $item->getFreeShipping(); if ($item->getQty()>$freeQty) { $rowWeight = $itemWeight*($item->getQty()-$freeQty); } else { $rowWeight = 0; } } $freeMethodWeight+= $rowWeight; $item->setRowWeight($rowWeight); } } if (isset($addressQty)) { $address->setItemQty($addressQty); } $address->setWeight($addressWeight); $address->setFreeMethodWeight($freeMethodWeight); $address->collectShippingRates(); $this->_setAmount(0) ->_setBaseAmount(0); $method = $address->getShippingMethod(); if ($method) { foreach ($address->getAllShippingRates() as $rate) { if ($rate->getCode()==$method) { $amountPrice = $address->getQuote()->getStore()->convertPrice($rate->getPrice(), false); $this->_setAmount($amountPrice); $this->_setBaseAmount($rate->getPrice()); $shippingDescription = $rate->getCarrierTitle() . ' - ' . $rate->getMethodTitle(); $address->setShippingDescription(trim($shippingDescription, ' -')); break; } } } return $this; } /** * Add shipping totals information to address object * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Shipping */ public function fetch(Mage_Sales_Model_Quote_Address $address) { $amount = $address->getShippingAmount(); if ($amount != 0 || $address->getShippingDescription()) { $title = Mage::helper('sales')->__('Shipping & Handling'); if ($address->getShippingDescription()) { $title .= ' ' . $address->getShippingDescription() . ''; } $address->addTotal(array( 'code' => $this->getCode(), 'title' => $title, 'value' => $address->getShippingAmount() )); } return $this; } /** * Get Shipping label * * @return string */ public function getLabel() { return Mage::helper('sales')->__('Shipping'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Address_Total_Subtotal extends Mage_Sales_Model_Quote_Address_Total_Abstract { /** * Collect address subtotal * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Subtotal */ public function collect(Mage_Sales_Model_Quote_Address $address) { parent::collect($address); $address->setTotalQty(0); $baseVirtualAmount = $virtualAmount = 0; /** * Process address items */ $items = $this->_getAddressItems($address); foreach ($items as $item) { if ($this->_initItem($address, $item) && $item->getQty() > 0) { /** * Separatly calculate subtotal only for virtual products */ if ($item->getProduct()->isVirtual()) { $virtualAmount += $item->getRowTotal(); $baseVirtualAmount += $item->getBaseRowTotal(); } } else { $this->_removeItem($address, $item); } } $address->setBaseVirtualAmount($baseVirtualAmount); $address->setVirtualAmount($virtualAmount); /** * Initialize grand totals */ Mage::helper('sales')->checkQuoteAmount($address->getQuote(), $address->getSubtotal()); Mage::helper('sales')->checkQuoteAmount($address->getQuote(), $address->getBaseSubtotal()); return $this; } /** * Address item initialization * * @param $item * @return bool */ protected function _initItem($address, $item) { if ($item instanceof Mage_Sales_Model_Quote_Address_Item) { $quoteItem = $item->getAddress()->getQuote()->getItemById($item->getQuoteItemId()); } else { $quoteItem = $item; } $product = $quoteItem->getProduct(); $product->setCustomerGroupId($quoteItem->getQuote()->getCustomerGroupId()); /** * Quote super mode flag mean what we work with quote without restriction */ if ($item->getQuote()->getIsSuperMode()) { if (!$product) { return false; } } else { if (!$product || !$product->isVisibleInCatalog()) { return false; } } if ($quoteItem->getParentItem() && $quoteItem->isChildrenCalculated()) { $finalPrice = $quoteItem->getParentItem()->getProduct()->getPriceModel()->getChildFinalPrice( $quoteItem->getParentItem()->getProduct(), $quoteItem->getParentItem()->getQty(), $quoteItem->getProduct(), $quoteItem->getQty() ); $item->setPrice($finalPrice) ->setBaseOriginalPrice($finalPrice); $item->calcRowTotal(); } else if (!$quoteItem->getParentItem()) { $finalPrice = $product->getFinalPrice($quoteItem->getQty()); $item->setPrice($finalPrice) ->setBaseOriginalPrice($finalPrice); $item->calcRowTotal(); $this->_addAmount($item->getRowTotal()); $this->_addBaseAmount($item->getBaseRowTotal()); $address->setTotalQty($address->getTotalQty() + $item->getQty()); } return true; } /** * Remove item * * @param $address * @param $item * @return Mage_Sales_Model_Quote_Address_Total_Subtotal */ protected function _removeItem($address, $item) { if ($item instanceof Mage_Sales_Model_Quote_Item) { $address->removeItem($item->getId()); if ($address->getQuote()) { $address->getQuote()->removeItem($item->getId()); } } elseif ($item instanceof Mage_Sales_Model_Quote_Address_Item) { $address->removeItem($item->getId()); if ($address->getQuote()) { $address->getQuote()->removeItem($item->getQuoteItemId()); } } return $this; } /** * Assign subtotal amount and label to address object * * @param Mage_Sales_Model_Quote_Address $address * @return Mage_Sales_Model_Quote_Address_Total_Subtotal */ public function fetch(Mage_Sales_Model_Quote_Address $address) { $address->addTotal(array( 'code' => $this->getCode(), 'title' => Mage::helper('sales')->__('Subtotal'), 'value' => $address->getSubtotal() )); return $this; } /** * Get Subtotal label * * @return string */ public function getLabel() { return Mage::helper('sales')->__('Subtotal'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Address_Total_Tax extends Mage_Sales_Model_Quote_Address_Total_Abstract { protected $_appliedTaxes = array(); public function __construct(){ $this->setCode('tax'); } public function collect(Mage_Sales_Model_Quote_Address $address) { $store = $address->getQuote()->getStore(); $address->setTaxAmount(0); $address->setBaseTaxAmount(0); //$address->setShippingTaxAmount(0); //$address->setBaseShippingTaxAmount(0); $address->setAppliedTaxes(array()); $items = $address->getAllItems(); if (!count($items)) { return $this; } $custTaxClassId = $address->getQuote()->getCustomerTaxClassId(); $taxCalculationModel = Mage::getSingleton('tax/calculation'); /* @var $taxCalculationModel Mage_Tax_Model_Calculation */ $request = $taxCalculationModel->getRateRequest( $address, $address->getQuote()->getBillingAddress(), $custTaxClassId, $store ); foreach ($items as $item) { /** * Child item's tax we calculate for parent */ if ($item->getParentItemId()) { continue; } /** * We calculate parent tax amount as sum of children's tax amounts */ if ($item->getHasChildren() && $item->isChildrenCalculated()) { foreach ($item->getChildren() as $child) { $discountBefore = $item->getDiscountAmount(); $baseDiscountBefore = $item->getBaseDiscountAmount(); $rate = $taxCalculationModel->getRate( $request->setProductClassId($child->getProduct()->getTaxClassId()) ); $child->setTaxPercent($rate); $child->calcTaxAmount(); if ($discountBefore != $item->getDiscountAmount()) { $address->setDiscountAmount( $address->getDiscountAmount() + ($item->getDiscountAmount() - $discountBefore) ); $address->setBaseDiscountAmount( $address->getBaseDiscountAmount() + ($item->getBaseDiscountAmount() - $baseDiscountBefore) ); $address->setGrandTotal( $address->getGrandTotal() - ($item->getDiscountAmount() - $discountBefore) ); $address->setBaseGrandTotal( $address->getBaseGrandTotal() - ($item->getBaseDiscountAmount() - $baseDiscountBefore) ); } $this->_saveAppliedTaxes( $address, $taxCalculationModel->getAppliedRates($request), $child->getTaxAmount(), $child->getBaseTaxAmount(), $rate ); } $itemTaxAmount = $item->getTaxAmount() + $item->getDiscountTaxCompensation(); $address->setTaxAmount($address->getTaxAmount() + $itemTaxAmount); $itemBaseTaxAmount = $item->getBaseTaxAmount() + $item->getBaseDiscountTaxCompensation(); $address->setBaseTaxAmount($address->getBaseTaxAmount() + $itemBaseTaxAmount); } else { $discountBefore = $item->getDiscountAmount(); $baseDiscountBefore = $item->getBaseDiscountAmount(); $rate = $taxCalculationModel->getRate( $request->setProductClassId($item->getProduct()->getTaxClassId()) ); $item->setTaxPercent($rate); $item->calcTaxAmount(); if ($discountBefore != $item->getDiscountAmount()) { $address->setDiscountAmount( $address->getDiscountAmount() + ($item->getDiscountAmount() - $discountBefore) ); $address->setBaseDiscountAmount( $address->getBaseDiscountAmount() + ($item->getBaseDiscountAmount() - $baseDiscountBefore) ); $address->setGrandTotal( $address->getGrandTotal() - ($item->getDiscountAmount() - $discountBefore) ); $address->setBaseGrandTotal( $address->getBaseGrandTotal() - ($item->getBaseDiscountAmount() - $baseDiscountBefore) ); } $itemTaxAmount = $item->getTaxAmount() + $item->getDiscountTaxCompensation(); $address->setTaxAmount($address->getTaxAmount() + $itemTaxAmount); $itemBaseTaxAmount = $item->getBaseTaxAmount() + $item->getBaseDiscountTaxCompensation(); $address->setBaseTaxAmount($address->getBaseTaxAmount() + $itemBaseTaxAmount); $applied = $taxCalculationModel->getAppliedRates($request); $this->_saveAppliedTaxes( $address, $applied, $item->getTaxAmount(), $item->getBaseTaxAmount(), $rate ); } } $shippingTaxClass = Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_SHIPPING_TAX_CLASS, $store); $shippingTax = 0; $shippingBaseTax = 0; if ($shippingTaxClass) { if ($rate = $taxCalculationModel->getRate($request->setProductClassId($shippingTaxClass))) { if (!Mage::helper('tax')->shippingPriceIncludesTax()) { $shippingTax = $address->getShippingAmount() * $rate/100; $shippingBaseTax= $address->getBaseShippingAmount() * $rate/100; } else { $shippingTax = $address->getShippingTaxAmount(); $shippingBaseTax= $address->getBaseShippingTaxAmount(); } $shippingTax = $store->roundPrice($shippingTax); $shippingBaseTax= $store->roundPrice($shippingBaseTax); $address->setTaxAmount($address->getTaxAmount() + $shippingTax); $address->setBaseTaxAmount($address->getBaseTaxAmount() + $shippingBaseTax); $this->_saveAppliedTaxes( $address, $taxCalculationModel->getAppliedRates($request), $shippingTax, $shippingBaseTax, $rate ); } } if (!Mage::helper('tax')->shippingPriceIncludesTax()) { $address->setShippingTaxAmount($shippingTax); $address->setBaseShippingTaxAmount($shippingBaseTax); } $address->setGrandTotal($address->getGrandTotal() + $address->getTaxAmount()); $address->setBaseGrandTotal($address->getBaseGrandTotal() + $address->getBaseTaxAmount()); return $this; } protected function _saveAppliedTaxes(Mage_Sales_Model_Quote_Address $address, $applied, $amount, $baseAmount, $rate) { $previouslyAppliedTaxes = $address->getAppliedTaxes(); $process = count($previouslyAppliedTaxes); foreach ($applied as $row) { if (!isset($previouslyAppliedTaxes[$row['id']])) { $row['process'] = $process; $row['amount'] = 0; $row['base_amount'] = 0; $previouslyAppliedTaxes[$row['id']] = $row; } if (!is_null($row['percent'])) { $row['percent'] = $row['percent'] ? $row['percent'] : 1; $rate = $rate ? $rate : 1; $appliedAmount = $amount/$rate*$row['percent']; $baseAppliedAmount = $baseAmount/$rate*$row['percent']; } else { $appliedAmount = 0; $baseAppliedAmount = 0; foreach ($row['rates'] as $rate) { $appliedAmount += $rate['amount']; $baseAppliedAmount += $rate['base_amount']; } } if ($appliedAmount || $previouslyAppliedTaxes[$row['id']]['amount']) { $previouslyAppliedTaxes[$row['id']]['amount'] += $appliedAmount; $previouslyAppliedTaxes[$row['id']]['base_amount'] += $baseAppliedAmount; } else { unset($previouslyAppliedTaxes[$row['id']]); } } $address->setAppliedTaxes($previouslyAppliedTaxes); } public function fetch(Mage_Sales_Model_Quote_Address $address) { $applied = $address->getAppliedTaxes(); $store = $address->getQuote()->getStore(); $amount = $address->getTaxAmount(); if (($amount!=0) || (Mage::helper('tax')->displayZeroTax($store))) { $address->addTotal(array( 'code'=>$this->getCode(), 'title'=>Mage::helper('sales')->__('Tax'), 'full_info'=>$applied ? $applied : array(), 'value'=>$amount )); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Sales_Model_Quote_Config { const XML_PATH_QUOTE_PRODUCT_ATTRIBUTES = 'global/sales/quote/item/product_attributes'; public function getProductAttributes() { $attributes = Mage::getConfig()->getNode(self::XML_PATH_QUOTE_PRODUCT_ATTRIBUTES)->asArray(); $transfer = new Varien_Object($attributes); Mage::dispatchEvent('sales_quote_config_get_product_attributes', array('attributes' => $transfer)); $attributes = $transfer->getData(); return array_keys($attributes); } public function getTotalModels() { } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales Quote Item Model * * @method Mage_Sales_Model_Resource_Quote_Item _getResource() * @method Mage_Sales_Model_Resource_Quote_Item getResource() * @method int getQuoteId() * @method Mage_Sales_Model_Quote_Item setQuoteId(int $value) * @method string getCreatedAt() * @method Mage_Sales_Model_Quote_Item setCreatedAt(string $value) * @method string getUpdatedAt() * @method Mage_Sales_Model_Quote_Item setUpdatedAt(string $value) * @method int getProductId() * @method Mage_Sales_Model_Quote_Item setProductId(int $value) * @method int getStoreId() * @method Mage_Sales_Model_Quote_Item setStoreId(int $value) * @method int getParentItemId() * @method Mage_Sales_Model_Quote_Item setParentItemId(int $value) * @method int getIsVirtual() * @method Mage_Sales_Model_Quote_Item setIsVirtual(int $value) * @method string getSku() * @method Mage_Sales_Model_Quote_Item setSku(string $value) * @method string getName() * @method Mage_Sales_Model_Quote_Item setName(string $value) * @method string getDescription() * @method Mage_Sales_Model_Quote_Item setDescription(string $value) * @method string getAppliedRuleIds() * @method Mage_Sales_Model_Quote_Item setAppliedRuleIds(string $value) * @method string getAdditionalData() * @method Mage_Sales_Model_Quote_Item setAdditionalData(string $value) * @method int getFreeShipping() * @method Mage_Sales_Model_Quote_Item setFreeShipping(int $value) * @method int getIsQtyDecimal() * @method Mage_Sales_Model_Quote_Item setIsQtyDecimal(int $value) * @method int getNoDiscount() * @method Mage_Sales_Model_Quote_Item setNoDiscount(int $value) * @method float getWeight() * @method Mage_Sales_Model_Quote_Item setWeight(float $value) * @method float getBasePrice() * @method Mage_Sales_Model_Quote_Item setBasePrice(float $value) * @method float getCustomPrice() * @method float getDiscountPercent() * @method Mage_Sales_Model_Quote_Item setDiscountPercent(float $value) * @method float getDiscountAmount() * @method Mage_Sales_Model_Quote_Item setDiscountAmount(float $value) * @method float getBaseDiscountAmount() * @method Mage_Sales_Model_Quote_Item setBaseDiscountAmount(float $value) * @method float getTaxPercent() * @method Mage_Sales_Model_Quote_Item setTaxPercent(float $value) * @method Mage_Sales_Model_Quote_Item setTaxAmount(float $value) * @method Mage_Sales_Model_Quote_Item setBaseTaxAmount(float $value) * @method float getRowTotal() * @method Mage_Sales_Model_Quote_Item setRowTotal(float $value) * @method float getBaseRowTotal() * @method Mage_Sales_Model_Quote_Item setBaseRowTotal(float $value) * @method float getRowTotalWithDiscount() * @method Mage_Sales_Model_Quote_Item setRowTotalWithDiscount(float $value) * @method float getRowWeight() * @method Mage_Sales_Model_Quote_Item setRowWeight(float $value) * @method Mage_Sales_Model_Quote_Item setProductType(string $value) * @method float getBaseTaxBeforeDiscount() * @method Mage_Sales_Model_Quote_Item setBaseTaxBeforeDiscount(float $value) * @method float getTaxBeforeDiscount() * @method Mage_Sales_Model_Quote_Item setTaxBeforeDiscount(float $value) * @method float getOriginalCustomPrice() * @method Mage_Sales_Model_Quote_Item setOriginalCustomPrice(float $value) * @method string getRedirectUrl() * @method Mage_Sales_Model_Quote_Item setRedirectUrl(string $value) * @method float getBaseCost() * @method Mage_Sales_Model_Quote_Item setBaseCost(float $value) * @method float getPriceInclTax() * @method Mage_Sales_Model_Quote_Item setPriceInclTax(float $value) * @method float getBasePriceInclTax() * @method Mage_Sales_Model_Quote_Item setBasePriceInclTax(float $value) * @method float getRowTotalInclTax() * @method Mage_Sales_Model_Quote_Item setRowTotalInclTax(float $value) * @method float getBaseRowTotalInclTax() * @method Mage_Sales_Model_Quote_Item setBaseRowTotalInclTax(float $value) * @method int getGiftMessageId() * @method Mage_Sales_Model_Quote_Item setGiftMessageId(int $value) * @method string getWeeeTaxApplied() * @method Mage_Sales_Model_Quote_Item setWeeeTaxApplied(string $value) * @method float getWeeeTaxAppliedAmount() * @method Mage_Sales_Model_Quote_Item setWeeeTaxAppliedAmount(float $value) * @method float getWeeeTaxAppliedRowAmount() * @method Mage_Sales_Model_Quote_Item setWeeeTaxAppliedRowAmount(float $value) * @method float getBaseWeeeTaxAppliedAmount() * @method Mage_Sales_Model_Quote_Item setBaseWeeeTaxAppliedAmount(float $value) * @method float getBaseWeeeTaxAppliedRowAmount() * @method Mage_Sales_Model_Quote_Item setBaseWeeeTaxAppliedRowAmount(float $value) * @method float getWeeeTaxDisposition() * @method Mage_Sales_Model_Quote_Item setWeeeTaxDisposition(float $value) * @method float getWeeeTaxRowDisposition() * @method Mage_Sales_Model_Quote_Item setWeeeTaxRowDisposition(float $value) * @method float getBaseWeeeTaxDisposition() * @method Mage_Sales_Model_Quote_Item setBaseWeeeTaxDisposition(float $value) * @method float getBaseWeeeTaxRowDisposition() * @method Mage_Sales_Model_Quote_Item setBaseWeeeTaxRowDisposition(float $value) * @method float getHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Item setHiddenTaxAmount(float $value) * @method float getBaseHiddenTaxAmount() * @method Mage_Sales_Model_Quote_Item setBaseHiddenTaxAmount(float $value) * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Quote_Item extends Mage_Sales_Model_Quote_Item_Abstract { /** * Prefix of model events names * * @var string */ protected $_eventPrefix = 'sales_quote_item'; /** * Parameter name in event * * In observe method you can use $observer->getEvent()->getObject() in this case * * @var string */ protected $_eventObject = 'item'; /** * Quote model object * * @var Mage_Sales_Model_Quote */ protected $_quote; /** * Item options array * * @var array */ protected $_options = array(); /** * Item options by code cache * * @var array */ protected $_optionsByCode = array(); /** * Not Represent options * * @var array */ protected $_notRepresentOptions = array('info_buyRequest'); /** * Flag stating that options were successfully saved * */ protected $_flagOptionsSaved = null; /** * Array of errors associated with this quote item * * @var Mage_Sales_Model_Status_List */ protected $_errorInfos = null; /** * Initialize resource model * */ protected function _construct() { $this->_init('sales/quote_item'); $this->_errorInfos = Mage::getModel('sales/status_list'); } /** * Init mapping array of short fields to * its full names * * @return Varien_Object */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = Mage::helper('sales')->getOldFieldMap('quote_item'); return $this; } /** * Quote Item Before Save prepare data process * * @return Mage_Sales_Model_Quote_Item */ protected function _beforeSave() { parent::_beforeSave(); $this->setIsVirtual($this->getProduct()->getIsVirtual()); if ($this->getQuote()) { $this->setQuoteId($this->getQuote()->getId()); } return $this; } /** * Declare quote model object * * @param Mage_Sales_Model_Quote $quote * @return Mage_Sales_Model_Quote_Item */ public function setQuote(Mage_Sales_Model_Quote $quote) { $this->_quote = $quote; $this->setQuoteId($quote->getId()); return $this; } /** * Retrieve quote model object * * @return Mage_Sales_Model_Quote */ public function getQuote() { return $this->_quote; } /** * Prepare quantity * * @param float|int $qty * @return int|float */ protected function _prepareQty($qty) { $qty = Mage::app()->getLocale()->getNumber($qty); $qty = ($qty > 0) ? $qty : 1; return $qty; } /** * Adding quantity to quote item * * @param float $qty * @return Mage_Sales_Model_Quote_Item */ public function addQty($qty) { $oldQty = $this->getQty(); $qty = $this->_prepareQty($qty); /** * We can't modify quontity of existing items which have parent * This qty declared just once duering add process and is not editable */ if (!$this->getParentItem() || !$this->getId()) { $this->setQtyToAdd($qty); $this->setQty($oldQty+$qty); } return $this; } /** * Declare quote item quantity * * @param float $qty * @return Mage_Sales_Model_Quote_Item */ public function setQty($qty) { $qty = $this->_prepareQty($qty); $oldQty = $this->_getData('qty'); $this->setData('qty', $qty); Mage::dispatchEvent('sales_quote_item_qty_set_after', array('item'=>$this)); if ($this->getQuote() && $this->getQuote()->getIgnoreOldQty()) { return $this; } if ($this->getUseOldQty()) { $this->setData('qty', $oldQty); } return $this; } /** * Retrieve option product with Qty * * Return array * 'qty' => the qty * 'product' => the product model * * @return array */ public function getQtyOptions() { $qtyOptions = $this->getData('qty_options'); if (is_null($qtyOptions)) { $productIds = array(); $qtyOptions = array(); foreach ($this->getOptions() as $option) { /** @var $option Mage_Sales_Model_Quote_Item_Option */ if (is_object($option->getProduct()) && $option->getProduct()->getId() != $this->getProduct()->getId() ) { $productIds[$option->getProduct()->getId()] = $option->getProduct()->getId(); } } foreach ($productIds as $productId) { $option = $this->getOptionByCode('product_qty_' . $productId); if ($option) { $qtyOptions[$productId] = $option; } } $this->setData('qty_options', $qtyOptions); } return $qtyOptions; } /** * Set option product with Qty * * @param $qtyOptions * @return Mage_Sales_Model_Quote_Item */ public function setQtyOptions($qtyOptions) { return $this->setData('qty_options', $qtyOptions); } /** * Checking item data * * @return Mage_Sales_Model_Quote_Item_Abstract */ public function checkData() { $parent = parent::checkData(); if ($this->getProduct()->getHasError()) { $this->setHasError(true); $this->setMessage(Mage::helper('sales')->__('Item options declaration error.')); $this->getQuote()->setHasError(true); $this->getQuote()->addMessage($this->getProduct()->getMessage(), 'options'); } return $parent; } /** * Setup product for quote item * * @param Mage_Catalog_Model_Product $product * @return Mage_Sales_Model_Quote_Item */ public function setProduct($product) { if ($this->getQuote()) { $product->setStoreId($this->getQuote()->getStoreId()); $product->setCustomerGroupId($this->getQuote()->getCustomerGroupId()); } $this->setData('product', $product) ->setProductId($product->getId()) ->setProductType($product->getTypeId()) ->setSku($this->getProduct()->getSku()) ->setName($product->getName()) ->setWeight($this->getProduct()->getWeight()) ->setTaxClassId($product->getTaxClassId()) ->setBaseCost($product->getCost()) ->setIsRecurring($product->getIsRecurring()) ; if ($product->getStockItem()) { $this->setIsQtyDecimal($product->getStockItem()->getIsQtyDecimal()); } Mage::dispatchEvent('sales_quote_item_set_product', array( 'product' => $product, 'quote_item'=>$this )); // if ($options = $product->getCustomOptions()) { // foreach ($options as $option) { // $this->addOption($option); // } // } return $this; } /** * Check product representation in item * * @param Mage_Catalog_Model_Product $product * @return bool */ public function representProduct($product) { $itemProduct = $this->getProduct(); if (!$product || $itemProduct->getId() != $product->getId()) { return false; } /** * Check maybe product is planned to be a child of some quote item - in this case we limit search * only within same parent item */ $stickWithinParent = $product->getStickWithinParent(); if ($stickWithinParent) { if ($this->getParentItem() !== $stickWithinParent) { return false; } } // Check options $itemOptions = $this->getOptionsByCode(); $productOptions = $product->getCustomOptions(); if(!$this->compareOptions($itemOptions, $productOptions)){ return false; } if(!$this->compareOptions($productOptions, $itemOptions)){ return false; } return true; } /** * Check if two options array are identical * First options array is prerogative * Second options array checked against first one * * @param array $options1 * @param array $options2 * @return bool */ public function compareOptions($options1, $options2) { foreach ($options1 as $option) { $code = $option->getCode(); if (in_array($code, $this->_notRepresentOptions )) { continue; } if ( !isset($options2[$code]) || ($options2[$code]->getValue() === null) || $options2[$code]->getValue() != $option->getValue()) { return false; } } return true; } /** * Compare item * * @param Mage_Sales_Model_Quote_Item $item * @return bool */ public function compare($item) { if ($this->getProductId() != $item->getProductId()) { return false; } foreach ($this->getOptions() as $option) { if (in_array($option->getCode(), $this->_notRepresentOptions)) { continue; } if ($itemOption = $item->getOptionByCode($option->getCode())) { $itemOptionValue = $itemOption->getValue(); $optionValue = $option->getValue(); // dispose of some options params, that can cramp comparing of arrays if (is_string($itemOptionValue) && is_string($optionValue)) { $_itemOptionValue = @unserialize($itemOptionValue); $_optionValue = @unserialize($optionValue); if (is_array($_itemOptionValue) && is_array($_optionValue)) { $itemOptionValue = $_itemOptionValue; $optionValue = $_optionValue; // looks like it does not break bundle selection qty unset($itemOptionValue['qty'], $itemOptionValue['uenc']); unset($optionValue['qty'], $optionValue['uenc']); } } if ($itemOptionValue != $optionValue) { return false; } } else { return false; } } return true; } /** * Get item product type * * @return string */ public function getProductType() { if ($option = $this->getOptionByCode('product_type')) { return $option->getValue(); } if ($product = $this->getProduct()) { return $product->getTypeId(); } return $this->_getData('product_type'); } /** * Return real product type of item * * @return unknown */ public function getRealProductType() { return $this->_getData('product_type'); } /** * Convert Quote Item to array * * @param array $arrAttributes * @return array */ public function toArray(array $arrAttributes=array()) { $data = parent::toArray($arrAttributes); if ($product = $this->getProduct()) { $data['product'] = $product->toArray(); } return $data; } /** * Initialize quote item options * * @param array $options * @return Mage_Sales_Model_Quote_Item */ public function setOptions($options) { foreach ($options as $option) { $this->addOption($option); } return $this; } /** * Get all item options * * @return array */ public function getOptions() { return $this->_options; } /** * Get all item options as array with codes in array key * * @return array */ public function getOptionsByCode() { return $this->_optionsByCode; } /** * Add option to item * * @param Mage_Sales_Model_Quote_Item_Option|Varien_Object $option * @return Mage_Sales_Model_Quote_Item */ public function addOption($option) { if (is_array($option)) { $option = Mage::getModel('sales/quote_item_option')->setData($option) ->setItem($this); } elseif (($option instanceof Varien_Object) && !($option instanceof Mage_Sales_Model_Quote_Item_Option)) { $option = Mage::getModel('sales/quote_item_option')->setData($option->getData()) ->setProduct($option->getProduct()) ->setItem($this); } elseif($option instanceof Mage_Sales_Model_Quote_Item_Option) { $option->setItem($this); } else { Mage::throwException(Mage::helper('sales')->__('Invalid item option format.')); } if ($exOption = $this->getOptionByCode($option->getCode())) { $exOption->addData($option->getData()); } else { $this->_addOptionCode($option); $this->_options[] = $option; } return $this; } /** * Can specify specific actions for ability to change given quote options values * Exemple: cataloginventory decimal qty validation may change qty to int, * so need to change quote item qty option value. * * @param array $options * @param Varien_Object $option * @param mixed $value * * @return object Mage_Catalog_Model_Product_Type_Abstract */ public function updateQtyOption(Varien_Object $option, $value) { $optionProduct = $option->getProduct(); $options = $this->getQtyOptions(); if (isset($options[$optionProduct->getId()])) { $options[$optionProduct->getId()]->setValue($value); } $this->getProduct()->getTypeInstance(true) ->updateQtyOption($this->getOptions(), $option, $value, $this->getProduct()); return $this; } /** *Remove option from item options * * @param string $code * @return Mage_Sales_Model_Quote_Item */ public function removeOption($code) { $option = $this->getOptionByCode($code); if ($option) { $option->isDeleted(true); } return $this; } /** * Register option code * * @param Mage_Sales_Model_Quote_Item_Option $option * @return Mage_Sales_Model_Quote_Item */ protected function _addOptionCode($option) { if (!isset($this->_optionsByCode[$option->getCode()])) { $this->_optionsByCode[$option->getCode()] = $option; } else { Mage::throwException(Mage::helper('sales')->__('An item option with code %s already exists.', $option->getCode())); } return $this; } /** * Get item option by code * * @param string $code * @return Mage_Sales_Model_Quote_Item_Option || null */ public function getOptionByCode($code) { if (isset($this->_optionsByCode[$code]) && !$this->_optionsByCode[$code]->isDeleted()) { return $this->_optionsByCode[$code]; } return null; } /** * Checks that item model has data changes. * Call save item options if model isn't need to save in DB * * @return boolean */ protected function _hasModelChanged() { if (!$this->hasDataChanges()) { return false; } return $this->_getResource()->hasDataChanged($this); } /** * Save item options * * @return Mage_Sales_Model_Quote_Item */ protected function _saveItemOptions() { foreach ($this->_options as $index => $option) { if ($option->isDeleted()) { $option->delete(); unset($this->_options[$index]); unset($this->_optionsByCode[$option->getCode()]); } else { $option->save(); } } $this->_flagOptionsSaved = true; // Report to watchers that options were saved return $this; } /** * Save model plus its options * Ensures saving options in case when resource model was not changed */ public function save() { $hasDataChanges = $this->hasDataChanges(); $this->_flagOptionsSaved = false; parent::save(); if ($hasDataChanges && !$this->_flagOptionsSaved) { $this->_saveItemOptions(); } } /** * Save item options after item saved * * @return Mage_Sales_Model_Quote_Item */ protected function _afterSave() { $this->_saveItemOptions(); return parent::_afterSave(); } /** * Clone quote item * * @return Mage_Sales_Model_Quote_Item */ public function __clone() { parent::__clone(); $options = $this->getOptions(); $this->_quote = null; $this->_options = array(); $this->_optionsByCode = array(); foreach ($options as $option) { $this->addOption(clone $option); } return $this; } /** * Returns formatted buy request - object, holding request received from * product view page with keys and options for configured product * * @return Varien_Object */ public function getBuyRequest() { $option = $this->getOptionByCode('info_buyRequest'); $buyRequest = new Varien_Object($option ? unserialize($option->getValue()) : null); // Overwrite standard buy request qty, because item qty could have changed since adding to quote $buyRequest->setOriginalQty($buyRequest->getQty()) ->setQty($this->getQty() * 1); return $buyRequest; } /** * Sets flag, whether this quote item has some error associated with it. * * @param bool $flag * @return Mage_Sales_Model_Quote_Item */ protected function _setHasError($flag) { return $this->setData('has_error', $flag); } /** * Sets flag, whether this quote item has some error associated with it. * When TRUE - also adds 'unknown' error information to list of quote item errors. * When FALSE - clears whole list of quote item errors. * It's recommended to use addErrorInfo() instead - to be able to remove error statuses later. * * @param bool $flag * @return Mage_Sales_Model_Quote_Item * @see addErrorInfo() */ public function setHasError($flag) { if ($flag) { $this->addErrorInfo(); } else { $this->_clearErrorInfo(); } return $this; } /** * Clears list of errors, associated with this quote item. * Also automatically removes error-flag from oneself. * * @return Mage_Sales_Model_Quote_Item */ protected function _clearErrorInfo() { $this->_errorInfos->clear(); $this->_setHasError(false); return $this; } /** * Adds error information to the quote item. * Automatically sets error flag. * * @param string|null $origin Usually a name of module, that embeds error * @param int|null $code Error code, unique for origin, that sets it * @param string|null $message Error message * @param Varien_Object|null $additionalData Any additional data, that caller would like to store * @return Mage_Sales_Model_Quote_Item */ public function addErrorInfo($origin = null, $code = null, $message = null, $additionalData = null) { $this->_errorInfos->addItem($origin, $code, $message, $additionalData); if ($message !== null) { $this->setMessage($message); } $this->_setHasError(true); return $this; } /** * Retrieves all error infos, associated with this item * * @return array */ public function getErrorInfos() { return $this->_errorInfos->getItems(); } /** * Removes error infos, that have parameters equal to passed in $params. * $params can have following keys (if not set - then any item is good for this key): * 'origin', 'code', 'message' * * @param array $params * @return Mage_Sales_Model_Quote_Item */ public function removeErrorInfosByParams($params) { $removedItems = $this->_errorInfos->removeItemsByParams($params); foreach ($removedItems as $item) { if ($item['message'] !== null) { $this->removeMessageByText($item['message']); } } if (!$this->_errorInfos->getItems()) { $this->_setHasError(false); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Catalog * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Interface of product configurational item option * * @category Mage * @package Mage_Catalog * @author Magento Core Team */ interface Mage_Catalog_Model_Product_Configuration_Item_Option_Interface { /** * Retrieve value associated with this option * * @return mixed */ function getValue(); } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Item option model * * @method Mage_Sales_Model_Resource_Quote_Item_Option _getResource() * @method Mage_Sales_Model_Resource_Quote_Item_Option getResource() * @method int getItemId() * @method Mage_Sales_Model_Quote_Item_Option setItemId(int $value) * @method int getProductId() * @method Mage_Sales_Model_Quote_Item_Option setProductId(int $value) * @method string getCode() * @method Mage_Sales_Model_Quote_Item_Option setCode(string $value) * @method string getValue() * @method Mage_Sales_Model_Quote_Item_Option setValue(string $value) * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Quote_Item_Option extends Mage_Core_Model_Abstract implements Mage_Catalog_Model_Product_Configuration_Item_Option_Interface { protected $_item; protected $_product; /** * Initialize resource model */ protected function _construct() { $this->_init('sales/quote_item_option'); } /** * Checks that item option model has data changes * * @return boolean */ protected function _hasModelChanged() { if (!$this->hasDataChanges()) { return false; } return $this->_getResource()->hasDataChanged($this); } /** * Set quote item * * @param Mage_Sales_Model_Quote_Item $item * @return Mage_Sales_Model_Quote_Item_Option */ public function setItem($item) { $this->setItemId($item->getId()); $this->_item = $item; return $this; } /** * Get option item * * @return Mage_Sales_Model_Quote_Item */ public function getItem() { return $this->_item; } /** * Set option product * * @param Mage_Catalog_Model_Product $product * @return Mage_Sales_Model_Quote_Item_Option */ public function setProduct($product) { $this->setProductId($product->getId()); $this->_product = $product; return $this; } /** * Get option product * * @return Mage_Catalog_Model_Product */ public function getProduct() { return $this->_product; } /** * Get option value * * @return mixed */ public function getValue() { return $this->_getData('value'); } /** * Initialize item identifier before save data * * @return Mage_Sales_Model_Quote_Item_Option */ protected function _beforeSave() { if ($this->getItem()) { $this->setItemId($this->getItem()->getId()); } return parent::_beforeSave(); } /** * Clone option object * * @return Mage_Sales_Model_Quote_Item_Option */ public function __clone() { $this->setId(null); $this->_item = null; return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Sales abstract resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ abstract class Mage_Sales_Model_Resource_Abstract extends Mage_Core_Model_Resource_Db_Abstract { /** * Prepare data for save * * @param Mage_Core_Model_Abstract $object * @return array */ protected function _prepareDataForSave(Mage_Core_Model_Abstract $object) { $currentTime = Varien_Date::now(); if ((!$object->getId() || $object->isObjectNew()) && !$object->getCreatedAt()) { $object->setCreatedAt($currentTime); } $object->setUpdatedAt($currentTime); $data = parent::_prepareDataForSave($object); return $data; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote extends Mage_Sales_Model_Resource_Abstract { /** * Initialize table nad PK name * */ protected function _construct() { $this->_init('sales/quote', 'entity_id'); } /** * Retrieve select object for load object data * * @param string $field * @param mixed $value * @param Mage_Core_Model_Abstract $object * @return Varien_Db_Select */ protected function _getLoadSelect($field, $value, $object) { $select = parent::_getLoadSelect($field, $value, $object); $storeIds = $object->getSharedStoreIds(); if ($storeIds) { $select->where('store_id IN (?)', $storeIds); } else { /** * For empty result */ $select->where('store_id < ?', 0); } return $select; } /** * Load quote data by customer identifier * * @param Mage_Sales_Model_Quote $quote * @param int $customerId * @return Mage_Sales_Model_Resource_Quote */ public function loadByCustomerId($quote, $customerId) { $adapter = $this->_getReadAdapter(); $select = $this->_getLoadSelect('customer_id', $customerId, $quote) ->where('is_active = ?', 1) ->order('updated_at ' . Varien_Db_Select::SQL_DESC) ->limit(1); $data = $adapter->fetchRow($select); if ($data) { $quote->setData($data); } $this->_afterLoad($quote); return $this; } /** * Load only active quote * * @param Mage_Sales_Model_Quote $quote * @param int $quoteId * @return Mage_Sales_Model_Resource_Quote */ public function loadActive($quote, $quoteId) { $adapter = $this->_getReadAdapter(); $select = $this->_getLoadSelect('entity_id', $quoteId, $quote) ->where('is_active = ?', 1); $data = $adapter->fetchRow($select); if ($data) { $quote->setData($data); } $this->_afterLoad($quote); return $this; } /** * Load quote data by identifier without store * * @param Mage_Sales_Model_Quote $quote * @param int $quoteId * @return Mage_Sales_Model_Resource_Quote */ public function loadByIdWithoutStore($quote, $quoteId) { $read = $this->_getReadAdapter(); if ($read) { $select = parent::_getLoadSelect('entity_id', $quoteId, $quote); $data = $read->fetchRow($select); if ($data) { $quote->setData($data); } } $this->_afterLoad($quote); return $this; } /** * Get reserved order id * * @param Mage_Sales_Model_Quote $quote * @return string */ public function getReservedOrderId($quote) { $storeId = (int)$quote->getStoreId(); return Mage::getSingleton('eav/config')->getEntityType(Mage_Sales_Model_Order::ENTITY) ->fetchNewIncrementId($storeId); } /** * Check is order increment id use in sales/order table * * @param int $orderIncrementId * @return boolean */ public function isOrderIncrementIdUsed($orderIncrementId) { $adapter = $this->_getReadAdapter(); $bind = array(':increment_id' => (int)$orderIncrementId); $select = $adapter->select(); $select->from($this->getTable('sales/order'), 'entity_id') ->where('increment_id = :increment_id'); $entity_id = $adapter->fetchOne($select, $bind); if ($entity_id > 0) { return true; } return false; } /** * Mark quotes - that depend on catalog price rules - to be recollected on demand * * @return Mage_Sales_Model_Resource_Quote */ public function markQuotesRecollectOnCatalogRules() { $tableQuote = $this->getTable('sales/quote'); $subSelect = $this->_getReadAdapter() ->select() ->from(array('t2' => $this->getTable('sales/quote_item')), array('entity_id' => 'quote_id')) ->from(array('t3' => $this->getTable('catalogrule/rule_product_price')), array()) ->where('t2.product_id = t3.product_id') ->group('quote_id'); $select = $this->_getReadAdapter()->select()->join( array('t2' => $subSelect), 't1.entity_id = t2.entity_id', array('trigger_recollect' => new Zend_Db_Expr('1')) ); $updateQuery = $select->crossUpdateFromSelect(array('t1' => $tableQuote)); $this->_getWriteAdapter()->query($updateQuery); return $this; } /** * Subtract product from all quotes quantities * * @param Mage_Catalog_Model_Product $product * @return Mage_Sales_Model_Resource_Quote */ public function substractProductFromQuotes($product) { $productId = (int)$product->getId(); if (!$productId) { return $this; } $adapter = $this->_getWriteAdapter(); $subSelect = $adapter->select(); $subSelect->from(false, array( 'items_qty' => new Zend_Db_Expr( $adapter->quoteIdentifier('q.items_qty') . ' - ' . $adapter->quoteIdentifier('qi.qty')), 'items_count' => new Zend_Db_Expr($adapter->quoteIdentifier('q.items_count') . ' - 1') )) ->join( array('qi' => $this->getTable('sales/quote_item')), implode(' AND ', array( 'q.entity_id = qi.quote_id', 'qi.parent_item_id IS NULL', $adapter->quoteInto('qi.product_id = ?', $productId) )), array() ); $updateQuery = $adapter->updateFromSelect($subSelect, array('q' => $this->getTable('sales/quote'))); $adapter->query($updateQuery); return $this; } /** * Mark recollect contain product(s) quotes * * @param array|int|Zend_Db_Expr $productIds * @return Mage_Sales_Model_Resource_Quote */ public function markQuotesRecollect($productIds) { $tableQuote = $this->getTable('sales/quote'); $tableItem = $this->getTable('sales/quote_item'); $subSelect = $this->_getReadAdapter() ->select() ->from($tableItem, array('entity_id' => 'quote_id')) ->where('product_id IN ( ? )', $productIds) ->group('quote_id'); $select = $this->_getReadAdapter()->select()->join( array('t2' => $subSelect), 't1.entity_id = t2.entity_id', array('trigger_recollect' => new Zend_Db_Expr('1')) ); $updateQuery = $select->crossUpdateFromSelect(array('t1' => $tableQuote)); $this->_getWriteAdapter()->query($updateQuery); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote address resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Address extends Mage_Sales_Model_Resource_Abstract { /** * Main table and field initialization * */ protected function _construct() { $this->_init('sales/quote_address', 'address_id'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote addresses collection * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Address_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Event prefix * * @var string */ protected $_eventPrefix = 'sales_quote_address_collection'; /** * Event object name * * @var string */ protected $_eventObject = 'quote_address_collection'; /** * Resource initialization * */ protected function _construct() { $this->_init('sales/quote_address'); } /** * Setting filter on quote_id field but if quote_id is 0 * we should exclude loading junk data from DB * * @param int $quoteId * @return Mage_Sales_Model_Resource_Quote_Address_Collection */ public function setQuoteFilter($quoteId) { $this->addFieldToFilter('quote_id', $quoteId ? $quoteId : array('null' => 1)); return $this; } /** * Redeclare after load method for dispatch event * * @return Mage_Sales_Model_Resource_Quote_Address_Collection */ protected function _afterLoad() { parent::_afterLoad(); Mage::dispatchEvent($this->_eventPrefix.'_load_after', array( $this->_eventObject => $this )); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote address item resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Address_Item extends Mage_Sales_Model_Resource_Abstract { /** * Main table and field initialization * */ protected function _construct() { $this->_init('sales/quote_address_item', 'address_item_id'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote addresses collection * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Address_Item_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Resource initialization * */ protected function _construct() { $this->_init('sales/quote_address_item'); } /** * Set parent items * * @return Mage_Sales_Model_Resource_Quote_Address_Item_Collection */ protected function _afterLoad() { parent::_afterLoad(); /** * Assign parent items */ foreach ($this as $item) { if ($item->getParentItemId()) { $item->setParentItem($this->getItemById($item->getParentItemId())); } } return $this; } /** * Set address filter * * @param int $addressId * @return Mage_Sales_Model_Resource_Quote_Address_Item_Collection */ public function setAddressFilter($addressId) { if ($addressId) { $this->addFieldToFilter('quote_address_id', $addressId); } else { $this->_totalRecords = 0; $this->_setIsLoaded(true); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote address shipping rate resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Address_Rate extends Mage_Sales_Model_Resource_Abstract { /** * Main table and field initialization * */ protected function _construct() { $this->_init('sales/quote_address_shipping_rate', 'rate_id'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote addresses shiping rates collection * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Address_Rate_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Whether to load fixed items only * * @var bool */ protected $_allowFixedOnly = false; /** * Resource initialization * */ protected function _construct() { $this->_init('sales/quote_address_rate'); } /** * Set filter by address id * * @param int $addressId * @return Mage_Sales_Model_Resource_Quote_Address_Rate_Collection */ public function setAddressFilter($addressId) { if ($addressId) { $this->addFieldToFilter('address_id', $addressId); } else { $this->_totalRecords = 0; $this->_setIsLoaded(true); } return $this; } /** * Setter for loading fixed items only * * @param bool $value * @return Mage_Sales_Model_Resource_Quote_Address_Rate_Collection */ public function setFixedOnlyFilter($value) { $this->_allowFixedOnly = (bool)$value; return $this; } /** * Don't add item to the collection if only fixed are allowed and its carrier is not fixed * * @param Mage_Sales_Model_Quote_Address_Rate $rate * @return Mage_Sales_Model_Resource_Quote_Address_Rate_Collection */ public function addItem(Varien_Object $rate) { if ($this->_allowFixedOnly && (!$rate->getCarrierInstance() || !$rate->getCarrierInstance()->isFixed())) { return $this; } return parent::addItem($rate); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Item extends Mage_Sales_Model_Resource_Abstract { /** * Main table and field initialization * */ protected function _construct() { $this->_init('sales/quote_item', 'item_id'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Quote item resource collection * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Item_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Collection quote instance * * @var Mage_Sales_Model_Quote */ protected $_quote; /** * Product Ids array * * @var array */ protected $_productIds = array(); /** * Initialize resource model * */ protected function _construct() { $this->_init('sales/quote_item'); } /** * Retrieve store Id (From Quote) * * @return int */ public function getStoreId() { return (int)$this->_quote->getStoreId(); } /** * Set Quote object to Collection * * @param Mage_Sales_Model_Quote $quote * @return Mage_Sales_Model_Resource_Quote_Item_Collection */ public function setQuote($quote) { $this->_quote = $quote; $quoteId = $quote->getId(); if ($quoteId) { $this->addFieldToFilter('quote_id', $quote->getId()); } else { $this->_totalRecords = 0; $this->_setIsLoaded(true); } return $this; } /** * Reset the collection and inner join it to quotes table * Optionally can select items with specified product id only * * @param string $quotesTableName * @param int $productId * @return Mage_Sales_Model_Resource_Quote_Item_Collection */ public function resetJoinQuotes($quotesTableName, $productId = null) { $this->getSelect()->reset() ->from( array('qi' => $this->getResource()->getMainTable()), array('item_id', 'qty', 'quote_id')) ->joinInner( array('q' => $quotesTableName), 'qi.quote_id = q.entity_id', array('store_id', 'items_qty', 'items_count') ); if ($productId) { $this->getSelect()->where('qi.product_id = ?', (int)$productId); } return $this; } /** * After load processing * * @return Mage_Sales_Model_Resource_Quote_Item_Collection */ protected function _afterLoad() { parent::_afterLoad(); /** * Assign parent items */ foreach ($this as $item) { if ($item->getParentItemId()) { $item->setParentItem($this->getItemById($item->getParentItemId())); } if ($this->_quote) { $item->setQuote($this->_quote); } } /** * Assign options and products */ $this->_assignOptions(); $this->_assignProducts(); $this->resetItemsDataChanged(); return $this; } /** * Add options to items * * @return Mage_Sales_Model_Resource_Quote_Item_Collection */ protected function _assignOptions() { $itemIds = array_keys($this->_items); $optionCollection = Mage::getModel('sales/quote_item_option')->getCollection() ->addItemFilter($itemIds); foreach ($this as $item) { $item->setOptions($optionCollection->getOptionsByItem($item)); } $productIds = $optionCollection->getProductIds(); $this->_productIds = array_merge($this->_productIds, $productIds); return $this; } /** * Add products to items and item options * * @return Mage_Sales_Model_Resource_Quote_Item_Collection */ protected function _assignProducts() { Varien_Profiler::start('QUOTE:'.__METHOD__); $productIds = array(); foreach ($this as $item) { $productIds[] = (int)$item->getProductId(); } $this->_productIds = array_merge($this->_productIds, $productIds); $productCollection = Mage::getModel('catalog/product')->getCollection() ->setStoreId($this->getStoreId()) ->addIdFilter($this->_productIds) ->addAttributeToSelect(Mage::getSingleton('sales/quote_config')->getProductAttributes()) ->addOptionsToResult() ->addStoreFilter() ->addUrlRewrite() ->addTierPriceData(); Mage::dispatchEvent('prepare_catalog_product_collection_prices', array( 'collection' => $productCollection, 'store_id' => $this->getStoreId(), )); Mage::dispatchEvent('sales_quote_item_collection_products_after_load', array( 'product_collection' => $productCollection )); $recollectQuote = false; foreach ($this as $item) { $product = $productCollection->getItemById($item->getProductId()); if ($product) { $product->setCustomOptions(array()); $qtyOptions = array(); $optionProductIds = array(); foreach ($item->getOptions() as $option) { /** * Call type specified logic for product associated with quote item */ $product->getTypeInstance(true)->assignProductToOption( $productCollection->getItemById($option->getProductId()), $option, $product ); if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) { $optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId(); } } if ($optionProductIds) { foreach ($optionProductIds as $optionProductId) { $qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId); if ($qtyOption) { $qtyOptions[$optionProductId] = $qtyOption; } } } $item->setQtyOptions($qtyOptions); $item->setProduct($product); } else { $item->isDeleted(true); $recollectQuote = true; } $item->checkData(); } if ($recollectQuote && $this->_quote) { $this->_quote->collectTotals(); } Varien_Profiler::stop('QUOTE:'.__METHOD__); return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Item option resource model * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Item_Option extends Mage_Core_Model_Resource_Db_Abstract { /** * Main table and field initialization * */ protected function _construct() { $this->_init('sales/quote_item_option', 'option_id'); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Sales * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Item option collection * * @category Mage * @package Mage_Sales * @author Magento Core Team */ class Mage_Sales_Model_Resource_Quote_Item_Option_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { /** * Array of option ids grouped by item id * * @var array */ protected $_optionsByItem = array(); /** * Array of option ids grouped by product id * * @var array */ protected $_optionsByProduct = array(); /** * Define resource model for collection * */ protected function _construct() { $this->_init('sales/quote_item_option'); } /** * Fill array of options by item and product * * @return Mage_Sales_Model_Resource_Quote_Item_Option_Collection */ protected function _afterLoad() { parent::_afterLoad(); foreach ($this as $option) { $optionId = $option->getId(); $itemId = $option->getItemId(); $productId = $option->getProductId(); if (isset($this->_optionsByItem[$itemId])) { $this->_optionsByItem[$itemId][] = $optionId; } else { $this->_optionsByItem[$itemId] = array($optionId); } if (isset($this->_optionsByProduct[$productId])) { $this->_optionsByProduct[$productId][] = $optionId; } else { $this->_optionsByProduct[$productId] = array($optionId); } } return $this; } /** * Apply quote item(s) filter to collection * * @param int | array $item * @return Mage_Sales_Model_Resource_Quote_Item_Option_Collection */ public function addItemFilter($item) { if (empty($item)) { $this->_totalRecords = 0; $this->_setIsLoaded(true); //$this->addFieldToFilter('item_id', ''); } elseif (is_array($item)) { $this->addFieldToFilter('item_id', array('in' => $item)); } elseif ($item instanceof Mage_Sales_Model_Quote_Item) { $this->addFieldToFilter('item_id', $item->getId()); } else { $this->addFieldToFilter('item_id', $item); } return $this; } /** * Get array of all product ids * * @return array */ public function getProductIds() { $this->load(); return array_keys($this->_optionsByProduct); } /** * Get all option for item * * @param mixed $item * @return array */ public function getOptionsByItem($item) { if ($item instanceof Mage_Sales_Model_Quote_Item) { $itemId = $item->getId(); } else { $itemId = $item; } $this->load(); $options = array(); if (isset($this->_optionsByItem[$itemId])) { foreach ($this->_optionsByItem[$itemId] as $optionId) { $options[] = $this->_items[$optionId]; } } return $options; } /** * Get all option for item * * @param int | Mage_Catalog_Model_Product $product * @return array */ public function getOptionsByProduct($product) { if ($product instanceof Mage_Catalog_Model_Product) { $productId = $product->getId(); } else { $productId = $product; } $this->load(); $options = array(); if (isset($this->_optionsByProduct[$productId])) { foreach ($this->_optionsByProduct[$productId] as $optionId) { $options[] = $this->_items[$optionId]; } } return $options; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Tax * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Catalog data helper */ class Mage_Tax_Helper_Data extends Mage_Core_Helper_Abstract { const PRICE_CONVERSION_PLUS = 1; const PRICE_CONVERSION_MINUS = 2; /** * Tax configuration object * * @var Mage_Tax_Model_Config */ protected $_config = null; protected $_calculator = null; protected $_displayTaxColumn; protected $_taxData; protected $_priceIncludesTax; protected $_shippingPriceIncludesTax; protected $_applyTaxAfterDiscount; protected $_priceDisplayType; protected $_shippingPriceDisplayType; /** * Postcode cut to this length when creating search templates * * @var integer */ protected $_postCodeSubStringLength = 10; public function __construct() { $this->_config = Mage::getSingleton('tax/config'); } /** * Return max postcode length to create search templates * * @return integer $len */ public function getPostCodeSubStringLength() { $len = (int)$this->_postCodeSubStringLength; if ($len <= 0) { $len = 10; } return $len; } /** * Get tax configuration object * * @return Mage_Tax_Model_Config */ public function getConfig() { return $this->_config; } /** * Get tax calculation object * * @return Mage_Tac_Model_Calculation */ public function getCalculator() { if ($this->_calculator === null) { $this->_calculator = Mage::getSingleton('tax/calculation'); } return $this->_calculator; } /** * Get product price including store convertion rate * * @param Mage_Catalog_Model_Product $product * @param null|string $format * @return float|string */ public function getProductPrice($product, $format=null) { try { $value = $product->getPrice(); $value = Mage::app()->getStore()->convertPrice($value, $format); } catch (Exception $e){ $value = $e->getMessage(); } return $value; } /** * Check if product prices inputed include tax * * @param mix $store * @return bool */ public function priceIncludesTax($store=null) { return $this->_config->priceIncludesTax($store) || $this->_config->getNeedUseShippingExcludeTax(); } /** * Check what taxes should be applied after discount * * @param mixed $store * @return bool */ public function applyTaxAfterDiscount($store=null) { return $this->_config->applyTaxAfterDiscount($store); } /** * Output * * @param boolean $includes */ public function getIncExcText($flag, $store=null) { if ($flag) { $s = $this->__('Incl. Tax'); } else { $s = $this->__('Excl. Tax'); } return $s; } /** * Get product price display type * 1 - Excluding tax * 2 - Including tax * 3 - Both * * @param mixed $store * @return int */ public function getPriceDisplayType($store = null) { return $this->_config->getPriceDisplayType($store); } /** * Check if necessary do product price conversion * If it necessary will be returned conversion type (minus or plus) * * @param mixed $store * @return false | int */ public function needPriceConversion($store = null) { $res = false; if ($this->priceIncludesTax($store)) { switch ($this->getPriceDisplayType($store)) { case Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX: case Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH: return self::PRICE_CONVERSION_MINUS; case Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX: $res = true; } } else { switch ($this->getPriceDisplayType($store)) { case Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX: case Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH: return self::PRICE_CONVERSION_PLUS; case Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX: $res = false; } } if ($res === false) { $res = $this->displayTaxColumn($store); } return $res; } /** * Check if need display full tax summary information in totals block * * @param mixed $store * @return bool */ public function displayFullSummary($store = null) { return $this->_config->displayCartFullSummary($store); } /** * Check if need display zero tax in subtotal * * @param mixed $store * @return bool */ public function displayZeroTax($store = null) { return $this->_config->displayCartZeroTax($store); } /** * Check if need display cart prices included tax * * @param mixed $store * @return bool */ public function displayCartPriceInclTax($store = null) { return $this->_config->displayCartPricesInclTax($store); } /** * Check if need display cart prices excluding price * * @param mixed $store * @return bool */ public function displayCartPriceExclTax($store = null) { return $this->_config->displayCartPricesExclTax($store); } /** * Check if need display cart prices excluding and including tax * * @param mixed $store * @return bool */ public function displayCartBothPrices($store = null) { return $this->_config->displayCartPricesBoth($store); } /** * Check if need display order prices included tax * * @param mixed $store * @return bool */ public function displaySalesPriceInclTax($store = null) { return $this->_config->displaySalesPricesInclTax($store); } /** * Check if need display order prices excluding price * * @param mixed $store * @return bool */ public function displaySalesPriceExclTax($store = null) { return $this->_config->displaySalesPricesExclTax($store); } /** * Check if need display order prices excluding and including tax * * @param mixed $store * @return bool */ public function displaySalesBothPrices($store = null) { return $this->_config->displaySalesPricesBoth($store); } /** * Check if we need display price include and exclude tax for order/invoice subtotal * * @param mixed $store * @return bool */ public function displaySalesSubtotalBoth($store = null) { return $this->_config->displaySalesSubtotalBoth($store); } /** * Check if we need display price include tax for order/invoice subtotal * * @param mixed $store * @return bool */ public function displaySalesSubtotalInclTax($store = null) { return $this->_config->displaySalesSubtotalInclTax($store); } /** * Check if we need display price exclude tax for order/invoice subtotal * * @param mixed $store * @return bool */ public function displaySalesSubtotalExclTax($store = null) { return $this->_config->displaySalesSubtotalExclTax($store); } /** * Check if need display tax column in for shopping cart/order items * * @param mixed $store * @return bool */ public function displayTaxColumn($store = null) { return $this->_config->displayCartPricesBoth(); } /** * Get prices javascript format json * * @param mixed $store * @return string */ public function getPriceFormat($store = null) { Mage::app()->getLocale()->emulate($store); $priceFormat = Mage::app()->getLocale()->getJsPriceFormat(); Mage::app()->getLocale()->revert(); if ($store) { $priceFormat['pattern'] = Mage::app()->getStore($store)->getCurrentCurrency()->getOutputFormat(); } return Mage::helper('core')->jsonEncode($priceFormat); } /** * Get all tax rates JSON for all product tax classes * * array( * value_{$productTaxVlassId} => $rate * ) * @deprecated after 1.4 - please use getAllRatesByProductClass * @return string */ public function getTaxRatesByProductClass() { return $this->_getAllRatesByProductClass(); } /** * Get all tax rates JSON for all product tax classes of specific store * * array( * value_{$productTaxVlassId} => $rate * ) * @return string */ public function getAllRatesByProductClass($store=null) { return $this->_getAllRatesByProductClass($store); } /** * Get all tax rates JSON for all product tax classes of specific store * * array( * value_{$productTaxVlassId} => $rate * ) * @return string */ protected function _getAllRatesByProductClass($store=null) { $result = array(); $calc = Mage::getSingleton('tax/calculation'); $rates = $calc->getRatesForAllProductTaxClasses($calc->getRateOriginRequest($store)); foreach ($rates as $class=>$rate) { $result["value_{$class}"] = $rate; } return Mage::helper('core')->jsonEncode($result); } /** * Get product price with all tax settings processing * * @param Mage_Catalog_Model_Product $product * @param float $price inputed product price * @param bool $includingTax return price include tax flag * @param null|Mage_Customer_Model_Address $shippingAddress * @param null|Mage_Customer_Model_Address $billingAddress * @param null|int $ctc customer tax class * @param mixed $store * @param bool $priceIncludesTax flag what price parameter contain tax * @return float */ public function getPrice($product, $price, $includingTax = null, $shippingAddress = null, $billingAddress = null, $ctc = null, $store = null, $priceIncludesTax = null ) { if (!$price) { return $price; } $store = Mage::app()->getStore($store); if (!$this->needPriceConversion($store)) { return $store->roundPrice($price); } if (is_null($priceIncludesTax)) { $priceIncludesTax = $this->priceIncludesTax($store); } $percent = $product->getTaxPercent(); $includingPercent = null; $taxClassId = $product->getTaxClassId(); if (is_null($percent)) { if ($taxClassId) { $request = Mage::getSingleton('tax/calculation') ->getRateRequest($shippingAddress, $billingAddress, $ctc, $store); $percent = Mage::getSingleton('tax/calculation') ->getRate($request->setProductClassId($taxClassId)); } } if ($taxClassId && $priceIncludesTax) { $request = Mage::getSingleton('tax/calculation')->getRateRequest(false, false, false, $store); $includingPercent = Mage::getSingleton('tax/calculation') ->getRate($request->setProductClassId($taxClassId)); } if ($percent === false || is_null($percent)) { if ($priceIncludesTax && !$includingPercent) { return $price; } } $product->setTaxPercent($percent); if (!is_null($includingTax)) { if ($priceIncludesTax) { if ($includingTax) { /** * Recalculate price include tax in case of different rates */ if ($includingPercent != $percent) { $price = $this->_calculatePrice($price, $includingPercent, false); /** * Using regular rounding. Ex: * price incl tax = 52.76 * store tax rate = 19.6% * customer tax rate= 19% * * price excl tax = 52.76 / 1.196 = 44.11371237 ~ 44.11 * tax = 44.11371237 * 0.19 = 8.381605351 ~ 8.38 * price incl tax = 52.49531773 ~ 52.50 != 52.49 * * that why we need round prices excluding tax before applying tax * this calculation is used for showing prices on catalog pages */ if ($percent != 0) { $price = $this->getCalculator()->round($price); $price = $this->_calculatePrice($price, $percent, true); } } } else { $price = $this->_calculatePrice($price, $includingPercent, false); } } else { if ($includingTax) { $price = $this->_calculatePrice($price, $percent, true); } } } else { if ($priceIncludesTax) { switch ($this->getPriceDisplayType($store)) { case Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX: case Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH: $price = $this->_calculatePrice($price, $includingPercent, false); break; case Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX: $price = $this->_calculatePrice($price, $includingPercent, false); $price = $this->_calculatePrice($price, $percent, true); break; } } else { switch ($this->getPriceDisplayType($store)) { case Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX: $price = $this->_calculatePrice($price, $percent, true); break; case Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH: case Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX: break; } } } return $store->roundPrice($price); } /** * Check if we have display in catalog prices including tax * * @return bool */ public function displayPriceIncludingTax() { return $this->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX; } /** * Check if we have display in catalog prices excluding tax * * @return bool */ public function displayPriceExcludingTax() { return $this->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX; } /** * Check if we have display in catalog prices including and excluding tax * * @return bool */ public function displayBothPrices() { return $this->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH; } /** * Calculate price imcluding/excluding tax base on tax rate percent * * @param float $price * @param float $percent * @param bool $type true - for calculate price including tax and false if price excluding tax * @return float */ protected function _calculatePrice($price, $percent, $type) { $calculator = Mage::getSingleton('tax/calculation'); if ($type) { $taxAmount = $calculator->calcTaxAmount($price, $percent, false, false); return $price + $taxAmount; } else { $taxAmount = $calculator->calcTaxAmount($price, $percent, true, false); return $price - $taxAmount; } } public function getIncExcTaxLabel($flag) { $text = $this->getIncExcText($flag); return $text ? ' ('.$text.')' : ''; } public function shippingPriceIncludesTax($store = null) { return $this->_config->shippingPriceIncludesTax($store); } public function getShippingPriceDisplayType($store = null) { return $this->_config->getShippingPriceDisplayType($store); } public function displayShippingPriceIncludingTax() { return $this->getShippingPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX; } public function displayShippingPriceExcludingTax() { return $this->getShippingPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX; } public function displayShippingBothPrices() { return $this->getShippingPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_BOTH; } public function getShippingTaxClass($store) { return $this->_config->getShippingTaxClass($store); } /** * Get shipping price * * @return float */ public function getShippingPrice($price, $includingTax = null, $shippingAddress = null, $ctc = null, $store = null) { $pseudoProduct = new Varien_Object(); $pseudoProduct->setTaxClassId($this->getShippingTaxClass($store)); $billingAddress = false; if ($shippingAddress && $shippingAddress->getQuote() && $shippingAddress->getQuote()->getBillingAddress()) { $billingAddress = $shippingAddress->getQuote()->getBillingAddress(); } $price = $this->getPrice( $pseudoProduct, $price, $includingTax, $shippingAddress, $billingAddress, $ctc, $store, $this->shippingPriceIncludesTax($store) ); return $price; } public function getPriceTaxSql($priceField, $taxClassField) { if (!$this->priceIncludesTax() && $this->displayPriceExcludingTax()) { return ''; } $request = Mage::getSingleton('tax/calculation')->getRateRequest(false, false, false); $defaultTaxes = Mage::getSingleton('tax/calculation')->getRatesForAllProductTaxClasses($request); $request = Mage::getSingleton('tax/calculation')->getRateRequest(); $currentTaxes = Mage::getSingleton('tax/calculation')->getRatesForAllProductTaxClasses($request); $defaultTaxString = $currentTaxString = ''; $rateToVariable = array( 'defaultTaxString'=>'defaultTaxes', 'currentTaxString'=>'currentTaxes', ); foreach ($rateToVariable as $rateVariable=>$rateArray) { if ($$rateArray && is_array($$rateArray)) { $$rateVariable = ''; foreach ($$rateArray as $classId=>$rate) { if ($rate) { $$rateVariable .= sprintf("WHEN %d THEN %12.4f ", $classId, $rate/100); } } if ($$rateVariable) { $$rateVariable = "CASE {$taxClassField} {$$rateVariable} ELSE 0 END"; } } } $result = ''; if ($this->priceIncludesTax()) { if ($defaultTaxString) { $result = "-({$priceField}/(1+({$defaultTaxString}))*{$defaultTaxString})"; } if (!$this->displayPriceExcludingTax() && $currentTaxString) { $result .= "+(({$priceField}{$result})*{$currentTaxString})"; } } else { if ($this->displayPriceIncludingTax()) { if ($currentTaxString) { $result .= "+({$priceField}*{$currentTaxString})"; } } } return $result; } /** * Join tax class * @param Varien_Db_Select $select * @param int $storeId * @param string $priceTable * @return Mage_Tax_Helper_Data */ public function joinTaxClass($select, $storeId, $priceTable = 'main_table') { $taxClassAttribute = Mage::getModel('eav/entity_attribute') ->loadByCode(Mage_Catalog_Model_Product::ENTITY, 'tax_class_id'); $joinConditionD = implode(' AND ',array( "tax_class_d.entity_id = {$priceTable}.entity_id", $select->getAdapter()->quoteInto('tax_class_d.attribute_id = ?', (int)$taxClassAttribute->getId()), 'tax_class_d.store_id = 0' )); $joinConditionC = implode(' AND ',array( "tax_class_c.entity_id = {$priceTable}.entity_id", $select->getAdapter()->quoteInto('tax_class_c.attribute_id = ?', (int)$taxClassAttribute->getId()), $select->getAdapter()->quoteInto('tax_class_c.store_id = ?', (int)$storeId) )); $select ->joinLeft( array('tax_class_d' => $taxClassAttribute->getBackend()->getTable()), $joinConditionD, array()) ->joinLeft( array('tax_class_c' => $taxClassAttribute->getBackend()->getTable()), $joinConditionC, array()); return $this; } /** * Get configuration setting "Apply Discount On Prices Including Tax" value * * @param null|int $store * @return 0|1 */ public function discountTax($store=null) { return $this->_config->discountTax($store); } /** * Get value of "Apply Tax On" custom/original price configuration settings * * @param $store * @return 0|1 */ public function getTaxBasedOn($store = null) { return Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_BASED_ON, $store); } /** * Check if tax can be applied to custom price * * @param $store * @return bool */ public function applyTaxOnCustomPrice($store = null) { return ((int) Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_APPLY_ON, $store) == 0); } /** * Check if tax should be applied just to original price * * @param $store * @return bool */ public function applyTaxOnOriginalPrice($store = null) { return ((int) Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_APPLY_ON, $store) == 1); } /** * Get taxes/discounts calculation sequence. * This sequence depends on "Catalog price include tax", "Apply Tax After Discount" * and "Apply Discount On Prices Including Tax" configuration options. * * @param null|int|string|Mage_Core_Model_Store $store * @return string */ public function getCalculationSequence($store=null) { return $this->_config->getCalculationSequence($store); } /** * Get tax caclulation algorithm code * * @param null|int $store * @return string */ public function getCalculationAgorithm($store=null) { return $this->_config->getAlgorithm($store); } /** * Get calculated taxes for each tax class * * This method returns array with format: * array( * $index => array( * 'tax_amount' => $taxAmount, * 'base_tax_amount' => $baseTaxAmount, * 'hidden_tax_amount' => $hiddenTaxAmount * 'title' => $title * 'percent' => $percent * ) * ) * * @param Mage_Sales_Model_Order $source * @return array */ public function getCalculatedTaxes($source) { if (Mage::registry('current_invoice')) { $current = Mage::registry('current_invoice'); } elseif (Mage::registry('current_creditmemo')) { $current = Mage::registry('current_creditmemo'); } else { $current = $source; } $taxClassAmount = array(); if ($current && $source) { foreach($current->getItemsCollection() as $item) { $taxCollection = Mage::getResourceModel('tax/sales_order_tax_item') ->getTaxItemsByItemId( $item->getOrderItemId() ? $item->getOrderItemId() : $item->getItemId() ); foreach ($taxCollection as $tax) { $taxClassId = $tax['tax_id']; $percent = $tax['tax_percent']; $price = $item->getRowTotal(); $basePrice = $item->getBaseRowTotal(); if ($this->applyTaxAfterDiscount($item->getStoreId())) { $price = $price - $item->getDiscountAmount() + $item->getHiddenTaxAmount(); $basePrice = $basePrice - $item->getBaseDiscountAmount() + $item->getBaseHiddenTaxAmount(); } if (isset($taxClassAmount[$taxClassId])) { $taxClassAmount[$taxClassId]['tax_amount'] += $price * $percent / 100; $taxClassAmount[$taxClassId]['base_tax_amount'] += $basePrice * $percent / 100; } else { $taxClassAmount[$taxClassId]['tax_amount'] = $price * $percent / 100; $taxClassAmount[$taxClassId]['base_tax_amount'] = $basePrice * $percent / 100; $taxClassAmount[$taxClassId]['title'] = $tax['title']; $taxClassAmount[$taxClassId]['percent'] = $tax['percent']; } } } foreach ($taxClassAmount as $key=>$tax) { if ($tax['tax_amount'] == 0 && $tax['base_tax_amount'] == 0) { unset($taxClassAmount[$key]); } } $taxClassAmount = array_values($taxClassAmount); } return $taxClassAmount; } /** * Get calculated Shipping & Handling Tax * * This method returns array with format: * array( * $index => array( * 'tax_amount' => $taxAmount, * 'base_tax_amount' => $baseTaxAmount, * 'hidden_tax_amount' => $hiddenTaxAmount * 'title' => $title * 'percent' => $percent * ) * ) * * @param Mage_Sales_Model_Order $source * @return array */ public function getShippingTax($source) { if (Mage::registry('current_invoice')) { $current = Mage::registry('current_invoice'); } elseif (Mage::registry('current_creditmemo')) { $current = Mage::registry('current_creditmemo'); } else { $current = $source; } $taxClassAmount = array(); if ($current && $source) { if ($current->getShippingTaxAmount() != 0 && $current->getBaseShippingTaxAmount() != 0) { $taxClassAmount[0]['tax_amount'] = $current->getShippingTaxAmount(); $taxClassAmount[0]['base_tax_amount'] = $current->getBaseShippingTaxAmount(); if ($current->getShippingHiddenTaxAmount() > 0) { $taxClassAmount[0]['hidden_tax_amount'] = $current->getShippingHiddenTaxAmount(); } $taxClassAmount[0]['title'] = $this->__('Shipping & Handling Tax'); $taxClassAmount[0]['percent'] = NULL; } } return $taxClassAmount; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Tax * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Tax Calculation Model * * @author Magento Core Team */ class Mage_Tax_Model_Calculation extends Mage_Core_Model_Abstract { const CALC_TAX_BEFORE_DISCOUNT_ON_EXCL = '0_0'; const CALC_TAX_BEFORE_DISCOUNT_ON_INCL = '0_1'; const CALC_TAX_AFTER_DISCOUNT_ON_EXCL = '1_0'; const CALC_TAX_AFTER_DISCOUNT_ON_INCL = '1_1'; const CALC_UNIT_BASE = 'UNIT_BASE_CALCULATION'; const CALC_ROW_BASE = 'ROW_BASE_CALCULATION'; const CALC_TOTAL_BASE = 'TOTAL_BASE_CALCULATION'; protected $_rates = array(); protected $_ctc = array(); protected $_ptc = array(); protected $_rateCache = array(); protected $_rateCalculationProcess = array(); protected $_customer = null; protected $_defaultCustomerTaxClass = null; protected function _construct() { $this->_init('tax/calculation'); } /** * Specify customer object which can be used for rate calculation * * @param Mage_Customer_Model_Customer $customer * @return Mage_Tax_Model_Calculation */ public function setCustomer(Mage_Customer_Model_Customer $customer) { $this->_customer = $customer; return $this; } public function getDefaultCustomerTaxClass($store = null) { if ($this->_defaultCustomerTaxClass === null) { $defaultCustomerGroup = Mage::helper('customer')->getDefaultCustomerGroupId($store); $this->_defaultCustomerTaxClass = Mage::getModel('customer/group')->getTaxClassId($defaultCustomerGroup); } return $this->_defaultCustomerTaxClass; } /** * Get customer object * * @return Mage_Customer_Model_Customer | false */ public function getCustomer() { if ($this->_customer === null) { $session = Mage::getSingleton('customer/session'); if ($session->isLoggedIn()) { $this->_customer = $session->getCustomer(); } elseif ($session->getCustomerId()) { $this->_customer = Mage::getModel('customer/customer')->load($session->getCustomerId()); } else { $this->_customer = false; } } return $this->_customer; } /** * Delete calculation settings by rule id * * @param int $ruleId * @return Mage_Tax_Model_Calculation */ public function deleteByRuleId($ruleId) { $this->_getResource()->deleteByRuleId($ruleId); return $this; } /** * Get calculation rates by rule id * * @param int $ruleId * @return array */ public function getRates($ruleId) { if (!isset($this->_rates[$ruleId])) { $this->_rates[$ruleId] = $this->_getResource()->getDistinct('tax_calculation_rate_id', $ruleId); } return $this->_rates[$ruleId]; } /** * Get allowed customer tax classes by rule id * * @param int $ruleId * @return array */ public function getCustomerTaxClasses($ruleId) { if (!isset($this->_ctc[$ruleId])) { $this->_ctc[$ruleId] = $this->_getResource()->getDistinct('customer_tax_class_id', $ruleId); } return $this->_ctc[$ruleId]; } /** * Get allowed product tax classes by rule id * * @param int $ruleId * @return array */ public function getProductTaxClasses($ruleId) { if (!isset($this->_ptc[$ruleId])) { $this->_ptc[$ruleId] = $this->getResource()->getDistinct('product_tax_class_id', $ruleId); } return $this->_ptc[$ruleId]; } /** * Aggregate tax calculation data to array * * @return array */ protected function _formCalculationProcess() { $title = $this->getRateTitle(); $value = $this->getRateValue(); $id = $this->getRateId(); $rate = array('code'=>$title, 'title'=>$title, 'percent'=>$value, 'position'=>1, 'priority'=>1); $process = array(); $process['percent'] = $value; $process['id'] = "{$id}-{$value}"; $process['rates'][] = $rate; return array($process); } /** * Get calculation tax rate by specific request * * @param Varien_Object $request * @return float */ public function getRate($request) { if (!$request->getCountryId() || !$request->getCustomerClassId() || !$request->getProductClassId()) { return 0; } $cacheKey = $this->_getRequestCacheKey($request); if (!isset($this->_rateCache[$cacheKey])) { $this->unsRateValue(); $this->unsCalculationProcess(); $this->unsEventModuleId(); Mage::dispatchEvent('tax_rate_data_fetch', array('request'=>$request)); if (!$this->hasRateValue()) { $rateInfo = $this->_getResource()->getRateInfo($request); $this->setCalculationProcess($rateInfo['process']); $this->setRateValue($rateInfo['value']); } else { $this->setCalculationProcess($this->_formCalculationProcess()); } $this->_rateCache[$cacheKey] = $this->getRateValue(); $this->_rateCalculationProcess[$cacheKey] = $this->getCalculationProcess(); } return $this->_rateCache[$cacheKey]; } /** * Get cache key value for specific tax rate request * * @param $request * @return string */ protected function _getRequestCacheKey($request) { $key = $request->getStore() ? $request->getStore()->getId() . '|' : ''; $key.= $request->getProductClassId() . '|' . $request->getCustomerClassId() . '|' . $request->getCountryId() . '|'. $request->getRegionId() . '|' . $request->getPostcode(); return $key; } /** * Get tax rate based on store shipping origin address settings * This rate can be used for conversion store price including tax to * store price excluding tax * * @param Varien_Object $request * @return float */ public function getStoreRate($request, $store=null) { $storeRequest = $this->getRateOriginRequest($store) ->setProductClassId($request->getProductClassId()); return $this->getRate($storeRequest); } /** * Get request object for getting tax rate based on store shippig original address * * @param null|store $store * @return Varien_Object */ public function getRateOriginRequest($store = null) { $request = new Varien_Object(); $request->setCountryId(Mage::getStoreConfig(Mage_Shipping_Model_Config::XML_PATH_ORIGIN_COUNTRY_ID, $store)) ->setRegionId(Mage::getStoreConfig(Mage_Shipping_Model_Config::XML_PATH_ORIGIN_REGION_ID, $store)) ->setPostcode(Mage::getStoreConfig(Mage_Shipping_Model_Config::XML_PATH_ORIGIN_POSTCODE, $store)) ->setCustomerClassId($this->getDefaultCustomerTaxClass($store)) ->setStore($store); return $request; } /** * Get request object with information necessary for getting tax rate * Request object contain: * country_id (->getCountryId()) * region_id (->getRegionId()) * postcode (->getPostcode()) * customer_class_id (->getCustomerClassId()) * store (->getStore()) * * @param null|false|Varien_Object $shippingAddress * @param null|false|Varien_Object $billingAddress * @param null|int $customerTaxClass * @param null|int $store * @return Varien_Object */ public function getRateRequest( $shippingAddress = null, $billingAddress = null, $customerTaxClass = null, $store = null) { if ($shippingAddress === false && $billingAddress === false && $customerTaxClass === false) { return $this->getRateOriginRequest($store); } $address = new Varien_Object(); $customer = $this->getCustomer(); $basedOn = Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_BASED_ON, $store); if (($shippingAddress === false && $basedOn == 'shipping') || ($billingAddress === false && $basedOn == 'billing')) { $basedOn = 'default'; } else { if ((($billingAddress === false || is_null($billingAddress) || !$billingAddress->getCountryId()) && $basedOn == 'billing') || (($shippingAddress === false || is_null($shippingAddress) || !$shippingAddress->getCountryId()) && $basedOn == 'shipping') ){ if ($customer) { $defBilling = $customer->getDefaultBillingAddress(); $defShipping = $customer->getDefaultShippingAddress(); if ($basedOn == 'billing' && $defBilling && $defBilling->getCountryId()) { $billingAddress = $defBilling; } else if ($basedOn == 'shipping' && $defShipping && $defShipping->getCountryId()) { $shippingAddress = $defShipping; } else { $basedOn = 'default'; } } else { $basedOn = 'default'; } } } switch ($basedOn) { case 'billing': $address = $billingAddress; break; case 'shipping': $address = $shippingAddress; break; case 'origin': $address = $this->getRateOriginRequest($store); break; case 'default': $address ->setCountryId(Mage::getStoreConfig( Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_COUNTRY, $store)) ->setRegionId(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_REGION, $store)) ->setPostcode(Mage::getStoreConfig( Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_POSTCODE, $store)); break; } if (is_null($customerTaxClass) && $customer) { $customerTaxClass = $customer->getTaxClassId(); } elseif (($customerTaxClass === false) || !$customer) { $customerTaxClass = $this->getDefaultCustomerTaxClass($store); } $request = new Varien_Object(); $request ->setCountryId($address->getCountryId()) ->setRegionId($address->getRegionId()) ->setPostcode($address->getPostcode()) ->setStore($store) ->setCustomerClassId($customerTaxClass); return $request; } /** * Compare data and rates for two tax rate requests for same products (product tax class ids). * Returns true if requests are similar (i.e. equal taxes rates will be applied to them) * * Notice: * a) productClassId MUST be identical for both requests, because we intend to check selling SAME products to DIFFERENT locations * b) due to optimization productClassId can be array of ids, not only single id * * @param Varien_Object $first * @param Varien_Object $second * @return bool */ public function compareRequests($first, $second) { $country = $first->getCountryId() == $second->getCountryId(); // "0" support for admin dropdown with --please select-- $region = (int)$first->getRegionId() == (int)$second->getRegionId(); $postcode= $first->getPostcode() == $second->getPostcode(); $taxClass= $first->getCustomerClassId() == $second->getCustomerClassId(); if ($country && $region && $postcode && $taxClass) { return true; } /** * Compare available tax rates for both requests */ $firstReqRates = $this->_getResource()->getRateIds($first); $secondReqRates = $this->_getResource()->getRateIds($second); if ($firstReqRates === $secondReqRates) { return true; } /** * If rates are not equal by ids then compare actual values * All product classes must have same rates to assume requests been similar */ $productClassId1 = $first->getProductClassId(); // Save to set it back later $productClassId2 = $second->getProductClassId(); // Save to set it back later // Ids are equal for both requests, so take any of them to process $ids = is_array($productClassId1) ? $productClassId1 : array($productClassId1); $identical = true; foreach ($ids as $productClassId) { $first->setProductClassId($productClassId); $rate1 = $this->getRate($first); $second->setProductClassId($productClassId); $rate2 = $this->getRate($second); if ($rate1 != $rate2) { $identical = false; break; } } $first->setProductClassId($productClassId1); $second->setProductClassId($productClassId2); return $identical; } protected function _getRates($request, $fieldName, $type) { $result = array(); $classes = Mage::getModel('tax/class')->getCollection() ->addFieldToFilter('class_type', $type) ->load(); foreach ($classes as $class) { $request->setData($fieldName, $class->getId()); $result[$class->getId()] = $this->getRate($request); } return $result; } public function getRatesForAllProductTaxClasses($request) { return $this->_getRates($request, 'product_class_id', Mage_Tax_Model_Class::TAX_CLASS_TYPE_PRODUCT); } public function getRatesForAllCustomerTaxClasses($request) { return $this->_getRates($request, 'customer_class_id', Mage_Tax_Model_Class::TAX_CLASS_TYPE_CUSTOMER); } /** * Get information about tax rates applied to request * * @param Varien_Object $request * @return array */ public function getAppliedRates($request) { $cacheKey = $this->_getRequestCacheKey($request); if (!isset($this->_rateCalculationProcess[$cacheKey])) { $this->_rateCalculationProcess[$cacheKey] = $this->_getResource()->getCalculationProcess($request); } return $this->_rateCalculationProcess[$cacheKey]; } public function reproduceProcess($rates) { return $this->getResource()->getCalculationProcess(null, $rates); } public function getRatesByCustomerTaxClass($customerTaxClass) { return $this->getResource()->getRatesByCustomerTaxClass($customerTaxClass); } public function getRatesByCustomerAndProductTaxClasses($customerTaxClass, $productTaxClass) { return $this->getResource()->getRatesByCustomerTaxClass($customerTaxClass, $productTaxClass); } /** * Calculate rated tax abount based on price and tax rate. * If you are using price including tax $priceIncludeTax should be true. * * @param float $price * @param float $taxRate * @param boolean $priceIncludeTax * @return float */ public function calcTaxAmount($price, $taxRate, $priceIncludeTax=false, $round=true) { $taxRate = $taxRate/100; if ($priceIncludeTax) { $amount = $price*(1-1/(1+$taxRate)); } else { $amount = $price*$taxRate; } $amount = $this->truncate($amount, 3); if ($round) { return $this->round($amount); } return $amount; } /** * Truncate number to specified precision * * @param float $price * @param int $precision * @return float */ public function truncate($price, $precision=4) { $exp = pow(10,$precision); $price = floor($price*$exp)/$exp; return $price; } /** * Round tax amount * * @param float $price * @return float */ public function round($price) { return Mage::app()->getStore()->roundPrice($price); } /** * Round price up * * @param float $price * @return float */ public function roundUp($price) { return ceil($price*100)/100; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Tax * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Configuration pathes storage * * @category Mage * @package Mage_Tax * @author Magento Core Team */ class Mage_Tax_Model_Config { // tax classes const CONFIG_XML_PATH_SHIPPING_TAX_CLASS = 'tax/classes/shipping_tax_class'; // tax calculation const CONFIG_XML_PATH_PRICE_INCLUDES_TAX = 'tax/calculation/price_includes_tax'; const CONFIG_XML_PATH_SHIPPING_INCLUDES_TAX = 'tax/calculation/shipping_includes_tax'; const CONFIG_XML_PATH_BASED_ON = 'tax/calculation/based_on'; const CONFIG_XML_PATH_APPLY_ON = 'tax/calculation/apply_tax_on'; const CONFIG_XML_PATH_APPLY_AFTER_DISCOUNT = 'tax/calculation/apply_after_discount'; const CONFIG_XML_PATH_DISCOUNT_TAX = 'tax/calculation/discount_tax'; const XML_PATH_ALGORITHM = 'tax/calculation/algorithm'; // tax defaults const CONFIG_XML_PATH_DEFAULT_COUNTRY = 'tax/defaults/country'; const CONFIG_XML_PATH_DEFAULT_REGION = 'tax/defaults/region'; const CONFIG_XML_PATH_DEFAULT_POSTCODE = 'tax/defaults/postcode'; /** * Prices display settings */ const CONFIG_XML_PATH_PRICE_DISPLAY_TYPE = 'tax/display/type'; const CONFIG_XML_PATH_DISPLAY_SHIPPING = 'tax/display/shipping'; /** * Shopping cart display settings */ const XML_PATH_DISPLAY_CART_PRICE = 'tax/cart_display/price'; const XML_PATH_DISPLAY_CART_SUBTOTAL = 'tax/cart_display/subtotal'; const XML_PATH_DISPLAY_CART_SHIPPING = 'tax/cart_display/shipping'; const XML_PATH_DISPLAY_CART_DISCOUNT = 'tax/cart_display/discount'; const XML_PATH_DISPLAY_CART_GRANDTOTAL = 'tax/cart_display/grandtotal'; const XML_PATH_DISPLAY_CART_FULL_SUMMARY= 'tax/cart_display/full_summary'; const XML_PATH_DISPLAY_CART_ZERO_TAX = 'tax/cart_display/zero_tax'; /** * Shopping cart display settings */ const XML_PATH_DISPLAY_SALES_PRICE = 'tax/sales_display/price'; const XML_PATH_DISPLAY_SALES_SUBTOTAL = 'tax/sales_display/subtotal'; const XML_PATH_DISPLAY_SALES_SHIPPING = 'tax/sales_display/shipping'; const XML_PATH_DISPLAY_SALES_DISCOUNT = 'tax/sales_display/discount'; const XML_PATH_DISPLAY_SALES_GRANDTOTAL = 'tax/sales_display/grandtotal'; const XML_PATH_DISPLAY_SALES_FULL_SUMMARY= 'tax/sales_display/full_summary'; const XML_PATH_DISPLAY_SALES_ZERO_TAX = 'tax/sales_display/zero_tax'; const CALCULATION_STRING_SEPARATOR = '|'; const DISPLAY_TYPE_EXCLUDING_TAX = 1; const DISPLAY_TYPE_INCLUDING_TAX = 2; const DISPLAY_TYPE_BOTH = 3; /** * @deprecated */ const CONFIG_XML_PATH_SHOW_IN_CATALOG = 'tax/display/show_in_catalog'; const CONFIG_XML_PATH_DEFAULT_PRODUCT_TAX_GROUP = 'catalog/product/default_tax_group'; const CONFIG_XML_PATH_DISPLAY_TAX_COLUMN = 'tax/display/column_in_summary'; const CONFIG_XML_PATH_DISPLAY_FULL_SUMMARY = 'tax/display/full_summary'; const CONFIG_XML_PATH_DISPLAY_ZERO_TAX = 'tax/display/zero_tax'; /** * Flag which notify what we need use prices exclude tax for calculations * * @var bool */ protected $_needUsePriceExcludeTax = false; /** * Flag which notify what we need use shipping prices exclude tax for calculations * * @var bool */ protected $_needUseShippingExcludeTax = false; /** * @var $_shippingPriceIncludeTax bool */ protected $_shippingPriceIncludeTax = null; /** * Check if product prices inputed include tax * * @param mix $store * @return bool */ public function priceIncludesTax($store=null) { if ($this->_needUsePriceExcludeTax) { return false; } return (bool)Mage::getStoreConfig(self::CONFIG_XML_PATH_PRICE_INCLUDES_TAX, $store); } /** * Check what taxes should be applied after discount * * @param mixed $store * @return bool */ public function applyTaxAfterDiscount($store=null) { return (bool)Mage::getStoreConfig(self::CONFIG_XML_PATH_APPLY_AFTER_DISCOUNT, $store); } /** * Get product price display type * 1 - Excluding tax * 2 - Including tax * 3 - Both * * @param mixed $store * @return int */ public function getPriceDisplayType($store = null) { return (int)Mage::getStoreConfig(self::CONFIG_XML_PATH_PRICE_DISPLAY_TYPE, $store); } /** * Get configuration setting "Apply Discount On Prices Including Tax" value * * @param null|int $store * @return 0|1 */ public function discountTax($store=null) { return ((int)Mage::getStoreConfig(self::CONFIG_XML_PATH_DISCOUNT_TAX, $store) == 1); } /** * Get taxes/discounts calculation sequence. * This sequence depends on "Apply Customer Tax" and "Apply Discount On Prices" configuration options. * * @param null|int|string|Mage_Core_Model_Store $store * @return string */ public function getCalculationSequence($store=null) { if ($this->applyTaxAfterDiscount($store)) { if ($this->discountTax($store)) { $seq = Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL; } else { $seq = Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL; } } else { if ($this->discountTax($store)) { $seq = Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_INCL; } else { $seq = Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_EXCL; } } return $seq; } /** * Specify flag what we need use price exclude tax * * @param bool $flag * @return Mage_Tax_Model_Config */ public function setNeedUsePriceExcludeTax($flag) { $this->_needUsePriceExcludeTax = $flag; return $this; } /** * Get flag what we need use price exclude tax * * @return bool $flag */ public function getNeedUsePriceExcludeTax() { return $this->_needUsePriceExcludeTax; } /** * Specify flag what we need use shipping price exclude tax * * @param bool $flag * @return Mage_Tax_Model_Config */ public function setNeedUseShippingExcludeTax($flag) { $this->_needUseShippingExcludeTax = $flag; return $this; } /** * Get flag what we need use shipping price exclude tax * * @return bool $flag */ public function getNeedUseShippingExcludeTax() { return $this->_needUseShippingExcludeTax; } /** * Get defined tax calculation agorithm * * @param store $store * @return string */ public function getAlgorithm($store=null) { return Mage::getStoreConfig(self::XML_PATH_ALGORITHM, $store); } /** * Get tax class id specified for shipping tax estimation * * @param store $store * @return int */ public function getShippingTaxClass($store=null) { return (int)Mage::getStoreConfig(self::CONFIG_XML_PATH_SHIPPING_TAX_CLASS, $store); } /** * Get shipping methods prices display type * * @param store $store * @return int */ public function getShippingPriceDisplayType($store = null) { return (int)Mage::getStoreConfig(self::CONFIG_XML_PATH_DISPLAY_SHIPPING, $store); } /** * Check if shiping prices include tax * * @param store $store * @return bool */ public function shippingPriceIncludesTax($store = null) { if ($this->_shippingPriceIncludeTax === null) { $this->_shippingPriceIncludeTax = (bool)Mage::getStoreConfig( self::CONFIG_XML_PATH_SHIPPING_INCLUDES_TAX, $store ); } return $this->_shippingPriceIncludeTax; } /** * Declare shipping prices type * @param bool $flag */ public function setShippingPriceIncludeTax($flag) { $this->_shippingPriceIncludeTax = $flag; return $this; } /** * Check if need display full tax summary information in totals block * * @deprecated please use displayCartFullSummary or displaySalesFullSummary * @param mixed $store * @return bool */ public function displayFullSummary($store = null) { return $this->displayCartFullSummary($store); } /** * Check if need display zero tax in subtotal * * @deprecated please use displayCartZeroTax or displaySalesZeroTax * @param mixed $store * @return bool */ public function displayZeroTax($store = null) { return $this->displayCartZeroTax($store); } /** * Get shopping cart prices display type * * @deprecated please use displayCartPrice or displaySalesZeroTax * @param mixed $store * @return bool */ public function displayTaxColumn($store = null) { return (bool)Mage::getStoreConfig(self::CONFIG_XML_PATH_DISPLAY_TAX_COLUMN, $store); } public function displayCartPricesInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_PRICE, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displayCartPricesExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_PRICE, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displayCartPricesBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_PRICE, $store) == self::DISPLAY_TYPE_BOTH; } public function displayCartSubtotalInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_SUBTOTAL, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displayCartSubtotalExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_SUBTOTAL, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displayCartSubtotalBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_SUBTOTAL, $store) == self::DISPLAY_TYPE_BOTH; } public function displayCartShippingInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_SHIPPING, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displayCartShippingExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_SHIPPING, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displayCartShippingBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_SHIPPING, $store) == self::DISPLAY_TYPE_BOTH; } public function displayCartDiscountInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_DISCOUNT, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displayCartDiscountExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_DISCOUNT, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displayCartDiscountBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_DISCOUNT, $store) == self::DISPLAY_TYPE_BOTH; } public function displayCartTaxWithGrandTotal($store = null) { return (bool)Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_GRANDTOTAL, $store); } public function displayCartFullSummary($store = null) { return (bool)Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_FULL_SUMMARY, $store); } public function displayCartZeroTax($store = null) { return (bool)Mage::getStoreConfig(self::XML_PATH_DISPLAY_CART_ZERO_TAX, $store); } public function displaySalesPricesInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_PRICE, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displaySalesPricesExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_PRICE, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displaySalesPricesBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_PRICE, $store) == self::DISPLAY_TYPE_BOTH; } public function displaySalesSubtotalInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_SUBTOTAL, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displaySalesSubtotalExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_SUBTOTAL, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displaySalesSubtotalBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_SUBTOTAL, $store) == self::DISPLAY_TYPE_BOTH; } public function displaySalesShippingInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_SHIPPING, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displaySalesShippingExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_SHIPPING, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displaySalesShippingBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_SHIPPING, $store) == self::DISPLAY_TYPE_BOTH; } public function displaySalesDiscountInclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_DISCOUNT, $store) == self::DISPLAY_TYPE_INCLUDING_TAX; } public function displaySalestDiscountExclTax($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_DISCOUNT, $store) == self::DISPLAY_TYPE_EXCLUDING_TAX; } public function displaySalesDiscountBoth($store = null) { return Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_DISCOUNT, $store) == self::DISPLAY_TYPE_BOTH; } public function displaySalesTaxWithGrandTotal($store = null) { return (bool)Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_GRANDTOTAL, $store); } public function displaySalesFullSummary($store = null) { return (bool)Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_FULL_SUMMARY, $store); } public function displaySalesZeroTax($store = null) { return (bool)Mage::getStoreConfig(self::XML_PATH_DISPLAY_SALES_ZERO_TAX, $store); } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Tax * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Tax Event Observer * * @author Magento Core Team */ class Mage_Tax_Model_Observer { /** * Put quote address tax information into order * * @param Varien_Event_Observer $observer */ public function salesEventConvertQuoteAddressToOrder(Varien_Event_Observer $observer) { $address = $observer->getEvent()->getAddress(); $order = $observer->getEvent()->getOrder(); $taxes = $address->getAppliedTaxes(); if (is_array($taxes)) { if (is_array($order->getAppliedTaxes())) { $taxes = array_merge($order->getAppliedTaxes(), $taxes); } $order->setAppliedTaxes($taxes); $order->setConvertingFromQuote(true); } } /** * Save order tax information * * @param Varien_Event_Observer $observer */ public function salesEventOrderAfterSave(Varien_Event_Observer $observer) { $order = $observer->getEvent()->getOrder(); if (!$order->getConvertingFromQuote() || $order->getAppliedTaxIsSaved()) { return; } $getTaxesForItems = $order->getQuote()->getTaxesForItems(); $taxes = $order->getAppliedTaxes(); $ratesIdQuoteItemId = array(); if (!is_array($getTaxesForItems)) { $getTaxesForItems = array(); } foreach ($getTaxesForItems as $quoteItemId => $taxesArray) { foreach ($taxesArray as $rates) { if (count($rates['rates']) == 1) { $ratesIdQuoteItemId[$rates['id']][] = array( 'id' => $quoteItemId, 'percent' => $rates['percent'], 'code' => $rates['rates'][0]['code'] ); } else { $percentDelta = $rates['percent']; $percentSum = 0; foreach ($rates['rates'] as $rate) { $ratesIdQuoteItemId[$rates['id']][] = array( 'id' => $quoteItemId, 'percent' => $rate['percent'], 'code' => $rate['code'] ); $percentSum += $rate['percent']; } if ($percentDelta != $percentSum) { $delta = $percentDelta - $percentSum; foreach ($ratesIdQuoteItemId[$rates['id']] as &$rateTax) { if ($rateTax['id'] == $quoteItemId) { $rateTax['percent'] = (($rateTax['percent'] / $percentSum) * $delta) + $rateTax['percent']; } } } } } } foreach ($taxes as $id => $row) { foreach ($row['rates'] as $tax) { if (is_null($row['percent'])) { $baseRealAmount = $row['base_amount']; } else { if ($row['percent'] == 0 || $tax['percent'] == 0) { continue; } $baseRealAmount = $row['base_amount'] / $row['percent'] * $tax['percent']; } $hidden = (isset($row['hidden']) ? $row['hidden'] : 0); $data = array( 'order_id' => $order->getId(), 'code' => $tax['code'], 'title' => $tax['title'], 'hidden' => $hidden, 'percent' => $tax['percent'], 'priority' => $tax['priority'], 'position' => $tax['position'], 'amount' => $row['amount'], 'base_amount' => $row['base_amount'], 'process' => $row['process'], 'base_real_amount' => $baseRealAmount, ); $result = Mage::getModel('tax/sales_order_tax')->setData($data)->save(); if (isset($ratesIdQuoteItemId[$id])) { foreach ($ratesIdQuoteItemId[$id] as $quoteItemId) { if ($quoteItemId['code'] == $tax['code']) { $item = $order->getItemByQuoteItemId($quoteItemId['id']); if ($item) { $data = array( 'item_id' => $item->getId(), 'tax_id' => $result->getTaxId(), 'tax_percent' => $quoteItemId['percent'] ); Mage::getModel('tax/sales_order_tax_item')->setData($data)->save(); } } } } } } $order->setAppliedTaxIsSaved(true); } /** * Prepare select which is using to select index data for layered navigation * * @param Varien_Event_Observer $observer * @return Mage_Tax_Model_Observer */ public function prepareCatalogIndexPriceSelect(Varien_Event_Observer $observer) { $table = $observer->getEvent()->getTable(); $response = $observer->getEvent()->getResponseObject(); $select = $observer->getEvent()->getSelect(); $storeId = $observer->getEvent()->getStoreId(); $additionalCalculations = $response->getAdditionalCalculations(); $calculation = Mage::helper('tax')->getPriceTaxSql( $table . '.min_price', $table.'.tax_class_id' ); if (!empty($calculation)) { $additionalCalculations[] = $calculation; $response->setAdditionalCalculations($additionalCalculations); /** * Tax class presented in price index table */ //Mage::helper('tax')->joinTaxClass($select, $storeId, $table); } return $this; } /** * Add tax percent values to product collection items * * @param Varien_Event_Observer $observer * @return Mage_Tax_Model_Observer */ public function addTaxPercentToProductCollection($observer) { $helper = Mage::helper('tax'); $collection = $observer->getEvent()->getCollection(); $store = $collection->getStoreId(); if (!$helper->needPriceConversion($store)) { return $this; } if ($collection->requireTaxPercent()) { $request = Mage::getSingleton('tax/calculation')->getRateRequest(); foreach ($collection as $item) { if (null === $item->getTaxClassId()) { $item->setTaxClassId($item->getMinimalTaxClassId()); } if (!isset($classToRate[$item->getTaxClassId()])) { $request->setProductClassId($item->getTaxClassId()); $classToRate[$item->getTaxClassId()] = Mage::getSingleton('tax/calculation')->getRate($request); } $item->setTaxPercent($classToRate[$item->getTaxClassId()]); } } return $this; } /** * Refresh sales tax report statistics for last day * * @param Mage_Cron_Model_Schedule $schedule * @return Mage_Tax_Model_Observer */ public function aggregateSalesReportTaxData($schedule) { Mage::app()->getLocale()->emulate(0); $currentDate = Mage::app()->getLocale()->date(); $date = $currentDate->subHour(25); Mage::getResourceModel('tax/report_tax')->aggregate($date); Mage::app()->getLocale()->revert(); return $this; } /** * Reset extra tax amounts on quote addresses before recollecting totals * * @param Varien_Event_Observer $observer * @return Mage_Tax_Model_Observer */ public function quoteCollectTotalsBefore(Varien_Event_Observer $observer) { /* @var $quote Mage_Sales_Model_Quote */ $quote = $observer->getEvent()->getQuote(); foreach ($quote->getAllAddresses() as $address) { $address->setExtraTaxAmount(0); $address->setBaseExtraTaxAmount(0); } return $this; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Mage * @package Mage_Tax * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Tax Calculation Resource Model * * @category Mage * @package Mage_Tax * @author Magento Core Team */ class Mage_Tax_Model_Resource_Calculation extends Mage_Core_Model_Resource_Db_Abstract { /** * Rates cache * * @var unknown */ protected $_ratesCache = array(); /** * Primery key auto increment flag * * @var bool */ protected $_isPkAutoIncrement = false; /** * Resource initialization * */ protected function _construct() { $this->_setMainTable('tax/tax_calculation'); } /** * Delete calculation settings by rule id * * @param int $ruleId * @return Mage_Tax_Model_Resource_Calculation */ public function deleteByRuleId($ruleId) { $conn = $this->_getWriteAdapter(); $where = $conn->quoteInto('tax_calculation_rule_id = ?', (int)$ruleId); $conn->delete($this->getMainTable(), $where); return $this; } /** * Retreive distinct calculation * * @param string $field * @param int $ruleId * @return array */ public function getDistinct($field, $ruleId) { $select = $this->_getReadAdapter()->select(); $select->from($this->getMainTable(), $field) ->where('tax_calculation_rule_id = ?', (int)$ruleId); return $this->_getReadAdapter()->fetchCol($select); } /** * Get tax rate information: calculation process data and tax rate * * @param Varien_Object $request * @return array */ public function getRateInfo($request) { $rates = $this->_getRates($request); return array( 'process' => $this->getCalculationProcess($request, $rates), 'value' => $this->_calculateRate($rates) ); } /** * Get tax rate for specific tax rate request * * @param Varien_Object $request * @return int */ public function getRate($request) { return $this->_calculateRate($this->_getRates($request)); } /** * Retrieve Calculation Process * * @param Varien_Object $request * @param array $rates * @return array */ public function getCalculationProcess($request, $rates = null) { if (is_null($rates)) { $rates = $this->_getRates($request); } $result = array(); $row = array(); $ids = array(); $currentRate = 0; $totalPercent = 0; $countedRates = count($rates); for ($i = 0; $i < $countedRates; $i++) { $rate = $rates[$i]; $value = (isset($rate['value']) ? $rate['value'] : $rate['percent'])*1; $oneRate = array( 'code'=>$rate['code'], 'title'=>$rate['title'], 'percent'=>$value, 'position'=>$rate['position'], 'priority'=>$rate['priority'], ); if (isset($rate['tax_calculation_rule_id'])) { $oneRate['rule_id'] = $rate['tax_calculation_rule_id']; } if (isset($rate['hidden'])) { $row['hidden'] = $rate['hidden']; } if (isset($rate['amount'])) { $row['amount'] = $rate['amount']; } if (isset($rate['base_amount'])) { $row['base_amount'] = $rate['base_amount']; } if (isset($rate['base_real_amount'])) { $row['base_real_amount'] = $rate['base_real_amount']; } $row['rates'][] = $oneRate; if (isset($rates[$i+1]['tax_calculation_rule_id'])) { $rule = $rate['tax_calculation_rule_id']; } $priority = $rate['priority']; $ids[] = $rate['code']; if (isset($rates[$i+1]['tax_calculation_rule_id'])) { while(isset($rates[$i+1]) && $rates[$i+1]['tax_calculation_rule_id'] == $rule) { $i++; } } $currentRate += $value; if (!isset($rates[$i+1]) || $rates[$i+1]['priority'] != $priority || (isset($rates[$i+1]['process']) && $rates[$i+1]['process'] != $rate['process']) ) { $row['percent'] = (100+$totalPercent)*($currentRate/100); $row['id'] = implode($ids); $result[] = $row; $row = array(); $ids = array(); $totalPercent += (100+$totalPercent)*($currentRate/100); $currentRate = 0; } } return $result; } /** * Create search templates for postcode * * @param string $postcode * @return array $strArr */ protected function _createSearchPostCodeTemplates($postcode) { $len = Mage::helper('tax')->getPostCodeSubStringLength(); $strlen = strlen($postcode); if ($strlen > $len) { $postcode = substr($postcode, 0, $len); $strlen = $len; } $strArr = array($postcode, $postcode . '*'); if ($strlen > 1) { for ($i = 1; $i < $strlen; $i++) { $strArr[] = sprintf('%s*', substr($postcode, 0, - $i)); } } return $strArr; } /** * Returns tax rates for request - either pereforms SELECT from DB, or returns already cached result * Notice that productClassId due to optimization can be array of ids * * @param Varien_Object $request * @return array */ protected function _getRates($request) { // Extract params that influence our SELECT statement and use them to create cache key $storeId = Mage::app()->getStore($request->getStore())->getId(); $customerClassId = $request->getCustomerClassId(); $countryId = $request->getCountryId(); $regionId = $request->getRegionId(); $postcode = $request->getPostcode(); // Process productClassId as it can be array or usual value. Form best key for cache. $productClassId = $request->getProductClassId(); $ids = is_array($productClassId) ? $productClassId : array($productClassId); foreach ($ids as $key => $val) { $ids[$key] = (int) $val; // Make it integer for equal cache keys even in case of null/false/0 values } $ids = array_unique($ids); sort($ids); $productClassKey = implode(',', $ids); // Form cache key and either get data from cache or from DB $cacheKey = implode('|', array($storeId, $customerClassId, $productClassKey, $countryId, $regionId, $postcode)); if (!isset($this->_ratesCache[$cacheKey])) { // Make SELECT and get data $select = $this->_getReadAdapter()->select(); $select ->from(array('main_table' => $this->getMainTable()), array( 'tax_calculation_rate_id', 'tax_calculation_rule_id', 'customer_tax_class_id', 'product_tax_class_id' ) ) ->where('customer_tax_class_id = ?', (int)$customerClassId); if ($productClassId) { $select->where('product_tax_class_id IN (?)', $productClassId); } $ifnullTitleValue = $this->_getReadAdapter()->getCheckSql( 'title_table.value IS NULL', 'rate.code', 'title_table.value' ); $ruleTableAliasName = $this->_getReadAdapter()->quoteIdentifier('rule.tax_calculation_rule_id'); $select ->join( array('rule' => $this->getTable('tax/tax_calculation_rule')), $ruleTableAliasName . ' = main_table.tax_calculation_rule_id', array('rule.priority', 'rule.position')) ->join( array('rate'=>$this->getTable('tax/tax_calculation_rate')), 'rate.tax_calculation_rate_id = main_table.tax_calculation_rate_id', array( 'value' => 'rate.rate', 'rate.tax_country_id', 'rate.tax_region_id', 'rate.tax_postcode', 'rate.tax_calculation_rate_id', 'rate.code' )) ->joinLeft( array('title_table' => $this->getTable('tax/tax_calculation_rate_title')), "rate.tax_calculation_rate_id = title_table.tax_calculation_rate_id " . "AND title_table.store_id = '{$storeId}'", array('title' => $ifnullTitleValue)) ->where('rate.tax_country_id = ?', $countryId) ->where("rate.tax_region_id IN(?)", array(0, (int)$regionId)); $postcodeIsNumeric = is_numeric($postcode); $postcodeIsRange = is_string($postcode) && preg_match('/^(.+)-(.+)$/', $postcode, $matches); if ($postcodeIsRange) { $zipFrom = $matches[1]; $zipTo = $matches[2]; } if ($postcodeIsNumeric || $postcodeIsRange) { $selectClone = clone $select; $selectClone->where('rate.zip_is_range IS NOT NULL'); } $select->where('rate.zip_is_range IS NULL'); if ($postcode != '*' || $postcodeIsRange) { $select ->where("rate.tax_postcode IS NULL OR rate.tax_postcode IN('*', '', ?)", $postcodeIsRange ? $postcode : $this->_createSearchPostCodeTemplates($postcode)); if ($postcodeIsNumeric) { $selectClone ->where('? BETWEEN rate.zip_from AND rate.zip_to', $postcode); } else if ($postcodeIsRange) { $selectClone->where('rate.zip_from >= ?', $zipFrom) ->where('rate.zip_to <= ?', $zipTo); } } /** * @see ZF-7592 issue http://framework.zend.com/issues/browse/ZF-7592 */ if ($postcodeIsNumeric || $postcodeIsRange) { $select = $this->_getReadAdapter()->select()->union( array( '(' . $select . ')', '(' . $selectClone . ')' ) ); } $select->order('priority ' . Varien_Db_Select::SQL_ASC) ->order('tax_calculation_rule_id ' . Varien_Db_Select::SQL_ASC) ->order('tax_country_id ' . Varien_Db_Select::SQL_DESC) ->order('tax_region_id ' . Varien_Db_Select::SQL_DESC) ->order('tax_postcode ' . Varien_Db_Select::SQL_DESC) ->order('value ' . Varien_Db_Select::SQL_DESC); $this->_ratesCache[$cacheKey] = $this->_getReadAdapter()->fetchAll($select); } return $this->_ratesCache[$cacheKey]; } /** * Calculate rate * * @param array $rates * @return int */ protected function _calculateRate($rates) { $result = 0; $currentRate = 0; $countedRates = count($rates); for ($i = 0; $i < $countedRates; $i++) { $rate = $rates[$i]; $rule = $rate['tax_calculation_rule_id']; $value = $rate['value']; $priority = $rate['priority']; while(isset($rates[$i+1]) && $rates[$i+1]['tax_calculation_rule_id'] == $rule) { $i++; } $currentRate += $value; if (!isset($rates[$i+1]) || $rates[$i+1]['priority'] != $priority) { $result += (100+$result)*($currentRate/100); $currentRate = 0; } } return $result; } /** * Retrieve rate ids * * @param Varien_Object $request * @return array */ public function getRateIds($request) { $result = array(); $rates = $this->_getRates($request); $countedRates = count($rates); for ($i = 0; $i < $countedRates; $i++) { $rate = $rates[$i]; $rule = $rate['tax_calculation_rule_id']; $result[] = $rate['tax_calculation_rate_id']; while(isset($rates[$i+1]) && $rates[$i+1]['tax_calculation_rule_id'] == $rule) { $i++; } } return $result; } /** * Retrieve rates by customer tax class * * @param int $customerTaxClassId * @param int $productTaxClassId * @return array */ public function getRatesByCustomerTaxClass($customerTaxClass, $productTaxClass = null) { $adapter = $this->_getReadAdapter(); $customerTaxClassId = (int)$customerTaxClass; $calcJoinConditions = array( 'calc_table.tax_calculation_rate_id = main_table.tax_calculation_rate_id', $adapter->quoteInto('calc_table.customer_tax_class_id = ?', $customerTaxClassId), ); if ($productTaxClass !== null) { $productTaxClassId = (int)$productTaxClass; $calcJoinConditions[] = $adapter->quoteInto('calc_table.product_tax_class_id = ?', $productTaxClassId); } $selectCSP = $adapter->select(); $selectCSP ->from( array('main_table' => $this->getTable('tax/tax_calculation_rate')), array('country' => 'tax_country_id', 'region_id' => 'tax_region_id', 'postcode' => 'tax_postcode')) ->joinInner( array('calc_table' => $this->getTable('tax/tax_calculation')), implode(' AND ', $calcJoinConditions), array('product_class' => 'calc_table.product_tax_class_id')) ->joinLeft( array('state_table' => $this->getTable('directory/country_region')), 'state_table.region_id = main_table.tax_region_id', array('region_code' => 'state_table.code')) ->distinct(true); $CSP = $adapter->fetchAll($selectCSP); $result = array(); foreach ($CSP as $one) { $request = new Varien_Object(); $request->setCountryId($one['country']) ->setRegionId($one['region_id']) ->setPostcode($one['postcode']) ->setCustomerClassId($customerTaxClassId) ->setProductClassId($one['product_class']); $rate = $this->getRate($request); if ($rate) { $row = array( 'value' => $rate/100, 'country' => $one['country'], 'state' => $one['region_code'], 'postcode' => $one['postcode'], 'product_class' => $one['product_class'], ); $result[] = $row; } } return $result; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Varien * @package Varien_Crypt * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Crypt factory * * @category Varien * @package Varien_Crypt * @author Magento Core Team */ class Varien_Crypt { /** * Factory method to return requested cipher logic * * @param string $method * @return Varien_Crypt_Abstract */ static public function factory($method='mcrypt') { $uc = str_replace(' ','_',ucwords(str_replace('_',' ',$method))); $className = 'Varien_Crypt_'.$uc; return new $className; } } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Varien * @package Varien_Crypt * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Abstract class for crypter classes * * @category Varien * @package Varien_Crypt * @author Magento Core Team */ abstract class Varien_Crypt_Abstract extends Varien_Object { } /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://opensource.org/licenses/osl-3.0.php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@magentocommerce.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade Magento to newer * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * * @category Varien * @package Varien_Crypt * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Mcrypt plugin * * @category Varien * @package Varien_Crypt * @author Magento Core Team */ class Varien_Crypt_Mcrypt extends Varien_Crypt_Abstract { /** * Constuctor * * @param array $data */ public function __construct(array $data=array()) { parent::__construct($data); } /** * Initialize mcrypt module * * @param string $key cipher private key * @return Varien_Crypt_Mcrypt */ public function init($key) { if (!$this->getCipher()) { $this->setCipher(MCRYPT_BLOWFISH); } if (!$this->getMode()) { $this->setMode(MCRYPT_MODE_ECB); } $this->setHandler(mcrypt_module_open($this->getCipher(), '', $this->getMode(), '')); if (!$this->getInitVector()) { if (MCRYPT_MODE_CBC == $this->getMode()) { $this->setInitVector(substr( md5(mcrypt_create_iv (mcrypt_enc_get_iv_size($this->getHandler()), MCRYPT_RAND)), - mcrypt_enc_get_iv_size($this->getHandler()) )); } else { $this->setInitVector(mcrypt_create_iv (mcrypt_enc_get_iv_size($this->getHandler()), MCRYPT_RAND)); } } $maxKeySize = mcrypt_enc_get_key_size($this->getHandler()); if (strlen($key) > $maxKeySize) { // strlen() intentionally, to count bytes, rather than characters $this->setHandler(null); throw new Varien_Exception('Maximum key size must be smaller '.$maxKeySize); } mcrypt_generic_init($this->getHandler(), $key, $this->getInitVector()); return $this; } /** * Encrypt data * * @param string $data source string * @return string */ public function encrypt($data) { if (!$this->getHandler()) { throw new Varien_Exception('Crypt module is not initialized.'); } if (strlen($data) == 0) { return $data; } return mcrypt_generic($this->getHandler(), $data); } /** * Decrypt data * * @param string $data encrypted string * @return string */ public function decrypt($data) { if (!$this->getHandler()) { throw new Varien_Exception('Crypt module is not initialized.'); } if (strlen($data) == 0) { return $data; } return mdecrypt_generic($this->getHandler(), $data); } /** * Desctruct cipher module * */ public function __destruct() { if ($this->getHandler()) { $this->_reset(); } } protected function _reset() { mcrypt_generic_deinit($this->getHandler()); mcrypt_module_close($this->getHandler()); } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Currency * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * include needed classes */ #require_once 'Zend/Locale.php'; #require_once 'Zend/Locale/Data.php'; #require_once 'Zend/Locale/Format.php'; /** * Class for handling currency notations * * @category Zend * @package Zend_Currency * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Currency { // Constants for defining what currency symbol should be displayed const NO_SYMBOL = 1; const USE_SYMBOL = 2; const USE_SHORTNAME = 3; const USE_NAME = 4; // Constants for defining the position of the currencysign const STANDARD = 8; const RIGHT = 16; const LEFT = 32; /** * Options array * * The following options are available * 'position' => Position for the currency sign * 'script' => Script for the output * 'format' => Locale for numeric output * 'display' => Currency detail to show * 'precision' => Precision for the currency * 'name' => Name for this currency * 'currency' => 3 lettered international abbreviation * 'symbol' => Currency symbol * 'locale' => Locale for this currency * 'value' => Money value * 'service' => Exchange service to use * * @var array * @see Zend_Locale */ protected $_options = array( 'position' => self::STANDARD, 'script' => null, 'format' => null, 'display' => self::NO_SYMBOL, 'precision' => 2, 'name' => null, 'currency' => null, 'symbol' => null, 'locale' => null, 'value' => 0, 'service' => null, 'tag' => 'Zend_Locale' ); /** * Creates a currency instance. Every supressed parameter is used from the actual or the given locale. * * @param string|array $options OPTIONAL Options array or currency short name * when string is given * @param string|Zend_Locale $locale OPTIONAL locale name * @throws Zend_Currency_Exception When currency is invalid */ public function __construct($options = null, $locale = null) { if (is_array($options)) { $this->setLocale($locale); $this->setFormat($options); } else if (Zend_Locale::isLocale($options, false, false)) { $this->setLocale($options); $options = $locale; } else { $this->setLocale($locale); } // Get currency details if (!isset($this->_options['currency']) || !is_array($options)) { $this->_options['currency'] = self::getShortName($options, $this->_options['locale']); } if (!isset($this->_options['name']) || !is_array($options)) { $this->_options['name'] = self::getName($options, $this->_options['locale']); } if (!isset($this->_options['symbol']) || !is_array($options)) { $this->_options['symbol'] = self::getSymbol($options, $this->_options['locale']); } if (($this->_options['currency'] === null) and ($this->_options['name'] === null)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("Currency '$options' not found"); } // Get the format if (!empty($this->_options['symbol'])) { $this->_options['display'] = self::USE_SYMBOL; } else if (!empty($this->_options['currency'])) { $this->_options['display'] = self::USE_SHORTNAME; } } /** * Returns a localized currency string * * @param integer|float $value OPTIONAL Currency value * @param array $options OPTIONAL options to set temporary * @throws Zend_Currency_Exception When the value is not a number * @return string */ public function toCurrency($value = null, array $options = array()) { if ($value === null) { if (is_array($options) && isset($options['value'])) { $value = $options['value']; } else { $value = $this->_options['value']; } } if (is_array($value)) { $options += $value; if (isset($options['value'])) { $value = $options['value']; } } // Validate the passed number if (!(isset($value)) or (is_numeric($value) === false)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("Value '$value' has to be numeric"); } if (isset($options['currency'])) { if (!isset($options['locale'])) { $options['locale'] = $this->_options['locale']; } $options['currency'] = self::getShortName($options['currency'], $options['locale']); $options['name'] = self::getName($options['currency'], $options['locale']); $options['symbol'] = self::getSymbol($options['currency'], $options['locale']); } $options = $this->_checkOptions($options) + $this->_options; // Format the number $format = $options['format']; $locale = $options['locale']; if (empty($format)) { $format = Zend_Locale_Data::getContent($locale, 'currencynumber'); } else if (Zend_Locale::isLocale($format, true, false)) { $locale = $format; $format = Zend_Locale_Data::getContent($format, 'currencynumber'); } $original = $value; $value = Zend_Locale_Format::toNumber($value, array('locale' => $locale, 'number_format' => $format, 'precision' => $options['precision'])); if ($options['position'] !== self::STANDARD) { $value = str_replace('¤', '', $value); $space = ''; if (iconv_strpos($value, ' ') !== false) { $value = str_replace(' ', '', $value); $space = ' '; } if ($options['position'] == self::LEFT) { $value = '¤' . $space . $value; } else { $value = $value . $space . '¤'; } } // Localize the number digits if (empty($options['script']) === false) { $value = Zend_Locale_Format::convertNumerals($value, 'Latn', $options['script']); } // Get the sign to be placed next to the number if (is_numeric($options['display']) === false) { $sign = $options['display']; } else { switch($options['display']) { case self::USE_SYMBOL: $sign = $this->_extractPattern($options['symbol'], $original); break; case self::USE_SHORTNAME: $sign = $options['currency']; break; case self::USE_NAME: $sign = $options['name']; break; default: $sign = ''; $value = str_replace(' ', '', $value); break; } } $value = str_replace('¤', $sign, $value); return $value; } /** * Internal method to extract the currency pattern * when a choice is given based on the given value * * @param string $pattern * @param float|integer $value * @return string */ private function _extractPattern($pattern, $value) { if (strpos($pattern, '|') === false) { return $pattern; } $patterns = explode('|', $pattern); $token = $pattern; $value = trim(str_replace('¤', '', $value)); krsort($patterns); foreach($patterns as $content) { if (strpos($content, '<') !== false) { $check = iconv_substr($content, 0, iconv_strpos($content, '<')); $token = iconv_substr($content, iconv_strpos($content, '<') + 1); if ($check < $value) { return $token; } } else { $check = iconv_substr($content, 0, iconv_strpos($content, '≤')); $token = iconv_substr($content, iconv_strpos($content, '≤') + 1); if ($check <= $value) { return $token; } } } return $token; } /** * Sets the formating options of the localized currency string * If no parameter is passed, the standard setting of the * actual set locale will be used * * @param array $options (Optional) Options to set * @return Zend_Currency */ public function setFormat(array $options = array()) { $this->_options = $this->_checkOptions($options) + $this->_options; return $this; } /** * Internal function for checking static given locale parameter * * @param string $currency (Optional) Currency name * @param string|Zend_Locale $locale (Optional) Locale to display informations * @throws Zend_Currency_Exception When locale contains no region * @return string The extracted locale representation as string */ private function _checkParams($currency = null, $locale = null) { // Manage the params if ((empty($locale)) and (!empty($currency)) and (Zend_Locale::isLocale($currency, true, false))) { $locale = $currency; $currency = null; } // Validate the locale and get the country short name $country = null; if ((Zend_Locale::isLocale($locale, true, false)) and (strlen($locale) > 4)) { $country = substr($locale, (strpos($locale, '_') + 1)); } else { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("No region found within the locale '" . (string) $locale . "'"); } // Get the available currencies for this country $data = Zend_Locale_Data::getContent($locale, 'currencytoregion', $country); if ((empty($currency) === false) and (empty($data) === false)) { $abbreviation = $currency; } else { $abbreviation = $data; } return array('locale' => $locale, 'currency' => $currency, 'name' => $abbreviation, 'country' => $country); } /** * Returns the actual or details of other currency symbols, * when no symbol is available it returns the currency shortname (f.e. FIM for Finnian Mark) * * @param string $currency (Optional) Currency name * @param string|Zend_Locale $locale (Optional) Locale to display informations * @return string */ public function getSymbol($currency = null, $locale = null) { if (($currency === null) and ($locale === null)) { return $this->_options['symbol']; } $params = self::_checkParams($currency, $locale); // Get the symbol $symbol = Zend_Locale_Data::getContent($params['locale'], 'currencysymbol', $params['currency']); if (empty($symbol) === true) { $symbol = Zend_Locale_Data::getContent($params['locale'], 'currencysymbol', $params['name']); } if (empty($symbol) === true) { return null; } return $symbol; } /** * Returns the actual or details of other currency shortnames * * @param string $currency OPTIONAL Currency's name * @param string|Zend_Locale $locale OPTIONAL The locale * @return string */ public function getShortName($currency = null, $locale = null) { if (($currency === null) and ($locale === null)) { return $this->_options['currency']; } $params = self::_checkParams($currency, $locale); // Get the shortname if (empty($params['currency']) === true) { return $params['name']; } $list = Zend_Locale_Data::getContent($params['locale'], 'currencytoname', $params['currency']); if (empty($list) === true) { $list = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['currency']); if (empty($list) === false) { $list = $params['currency']; } } if (empty($list) === true) { return null; } return $list; } /** * Returns the actual or details of other currency names * * @param string $currency (Optional) Currency's short name * @param string|Zend_Locale $locale (Optional) The locale * @return string */ public function getName($currency = null, $locale = null) { if (($currency === null) and ($locale === null)) { return $this->_options['name']; } $params = self::_checkParams($currency, $locale); // Get the name $name = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['currency']); if (empty($name) === true) { $name = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['name']); } if (empty($name) === true) { return null; } return $name; } /** * Returns a list of regions where this currency is or was known * * @param string $currency OPTIONAL Currency's short name * @throws Zend_Currency_Exception When no currency was defined * @return array List of regions */ public function getRegionList($currency = null) { if ($currency === null) { $currency = $this->_options['currency']; } if (empty($currency) === true) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception('No currency defined'); } $data = Zend_Locale_Data::getContent($this->_options['locale'], 'regiontocurrency', $currency); $result = explode(' ', $data); return $result; } /** * Returns a list of currencies which are used in this region * a region name should be 2 charachters only (f.e. EG, DE, US) * If no region is given, the actual region is used * * @param string $region OPTIONAL Region to return the currencies for * @return array List of currencies */ public function getCurrencyList($region = null) { if (empty($region) === true) { if (strlen($this->_options['locale']) > 4) { $region = substr($this->_options['locale'], (strpos($this->_options['locale'], '_') + 1)); } } $data = Zend_Locale_Data::getContent($this->_options['locale'], 'currencytoregion', $region); $result = explode(' ', $data); return $result; } /** * Returns the actual currency name * * @return string */ public function toString() { return $this->toCurrency(); } /** * Returns the currency name * * @return string */ public function __toString() { return $this->toString(); } /** * Returns the set cache * * @return Zend_Cache_Core The set cache */ public static function getCache() { return Zend_Locale_Data::getCache(); } /** * Sets a cache for Zend_Currency * * @param Zend_Cache_Core $cache Cache to set * @return void */ public static function setCache(Zend_Cache_Core $cache) { Zend_Locale_Data::setCache($cache); } /** * Returns true when a cache is set * * @return boolean */ public static function hasCache() { return Zend_Locale_Data::hasCache(); } /** * Removes any set cache * * @return void */ public static function removeCache() { Zend_Locale_Data::removeCache(); } /** * Clears all set cache data * * @param string $tag Tag to clear when the default tag name is not used * @return void */ public static function clearCache($tag = null) { Zend_Locale_Data::clearCache($tag); } /** * Sets a new locale for data retreivement * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist * 'xx_YY' will be set to 'root' because 'xx' does not exist * * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @throws Zend_Currency_Exception When the given locale does not exist * @return Zend_Currency Provides fluent interface */ public function setLocale($locale = null) { #require_once 'Zend/Locale.php'; try { $locale = Zend_Locale::findLocale($locale); if (strlen($locale) > 4) { $this->_options['locale'] = $locale; } else { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("No region found within the locale '" . (string) $locale . "'"); } } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception($e->getMessage()); } // Get currency details $this->_options['currency'] = $this->getShortName(null, $this->_options['locale']); $this->_options['name'] = $this->getName(null, $this->_options['locale']); $this->_options['symbol'] = $this->getSymbol(null, $this->_options['locale']); return $this; } /** * Returns the actual set locale * * @return string */ public function getLocale() { return $this->_options['locale']; } /** * Returns the value * * @return float */ public function getValue() { return $this->_options['value']; } /** * Adds a currency * * @param float|integer|Zend_Currency $value Add this value to currency * @param string|Zend_Currency $currency The currency to add * @return Zend_Currency */ public function setValue($value, $currency = null) { $this->_options['value'] = $this->_exchangeCurrency($value, $currency); return $this; } /** * Adds a currency * * @param float|integer|Zend_Currency $value Add this value to currency * @param string|Zend_Currency $currency The currency to add * @return Zend_Currency */ public function add($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); $this->_options['value'] += (float) $value; return $this; } /** * Substracts a currency * * @param float|integer|Zend_Currency $value Substracts this value from currency * @param string|Zend_Currency $currency The currency to substract * @return Zend_Currency */ public function sub($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); $this->_options['value'] -= (float) $value; return $this; } /** * Divides a currency * * @param float|integer|Zend_Currency $value Divides this value from currency * @param string|Zend_Currency $currency The currency to divide * @return Zend_Currency */ public function div($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); $this->_options['value'] /= (float) $value; return $this; } /** * Multiplies a currency * * @param float|integer|Zend_Currency $value Multiplies this value from currency * @param string|Zend_Currency $currency The currency to multiply * @return Zend_Currency */ public function mul($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); $this->_options['value'] *= (float) $value; return $this; } /** * Calculates the modulo from a currency * * @param float|integer|Zend_Currency $value Calculate modulo from this value * @param string|Zend_Currency $currency The currency to calculate the modulo * @return Zend_Currency */ public function mod($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); $this->_options['value'] %= (float) $value; return $this; } /** * Compares two currencies * * @param float|integer|Zend_Currency $value Compares the currency with this value * @param string|Zend_Currency $currency The currency to compare this value from * @return Zend_Currency */ public function compare($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); $value = $this->_options['value'] - $value; if ($value < 0) { return -1; } else if ($value > 0) { return 1; } return 0; } /** * Returns true when the two currencies are equal * * @param float|integer|Zend_Currency $value Compares the currency with this value * @param string|Zend_Currency $currency The currency to compare this value from * @return boolean */ public function equals($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); if ($this->_options['value'] == $value) { return true; } return false; } /** * Returns true when the currency is more than the given value * * @param float|integer|Zend_Currency $value Compares the currency with this value * @param string|Zend_Currency $currency The currency to compare this value from * @return boolean */ public function isMore($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); if ($this->_options['value'] > $value) { return true; } return false; } /** * Returns true when the currency is less than the given value * * @param float|integer|Zend_Currency $value Compares the currency with this value * @param string|Zend_Currency $currency The currency to compare this value from * @return boolean */ public function isLess($value, $currency = null) { $value = $this->_exchangeCurrency($value, $currency); if ($this->_options['value'] < $value) { return true; } return false; } /** * Internal method which calculates the exchanges currency * * @param float|integer|Zend_Currency $value Compares the currency with this value * @param string|Zend_Currency $currency The currency to compare this value from * @return unknown */ protected function _exchangeCurrency($value, $currency) { if ($value instanceof Zend_Currency) { $currency = $value->getShortName(); $value = $value->getValue(); } else { $currency = $this->getShortName($currency, $this->getLocale()); } $rate = 1; if ($currency !== $this->getShortName()) { $service = $this->getService(); if (!($service instanceof Zend_Currency_CurrencyInterface)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception('No exchange service applied'); } $rate = $service->getRate($currency, $this->getShortName()); } $value *= $rate; return $value; } /** * Returns the set service class * * @return Zend_Service */ public function getService() { return $this->_options['service']; } /** * Sets a new exchange service * * @param string|Zend_Currency_CurrencyInterface $service Service class * @return Zend_Currency */ public function setService($service) { if (is_string($service)) { #require_once 'Zend/Loader.php'; if (!class_exists($service)) { $file = str_replace('_', DIRECTORY_SEPARATOR, $service) . '.php'; if (Zend_Loader::isReadable($file)) { Zend_Loader::loadClass($class); } } $service = new $service; } if (!($service instanceof Zend_Currency_CurrencyInterface)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception('A currency service must implement Zend_Currency_CurrencyInterface'); } $this->_options['service'] = $service; return $this; } /** * Internal method for checking the options array * * @param array $options Options to check * @throws Zend_Currency_Exception On unknown position * @throws Zend_Currency_Exception On unknown locale * @throws Zend_Currency_Exception On unknown display * @throws Zend_Currency_Exception On precision not between -1 and 30 * @throws Zend_Currency_Exception On problem with script conversion * @throws Zend_Currency_Exception On unknown options * @return array */ protected function _checkOptions(array $options = array()) { if (count($options) === 0) { return $this->_options; } foreach ($options as $name => $value) { $name = strtolower($name); if ($name !== 'format') { if (gettype($value) === 'string') { $value = strtolower($value); } } switch($name) { case 'position': if (($value !== self::STANDARD) and ($value !== self::RIGHT) and ($value !== self::LEFT)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("Unknown position '" . $value . "'"); } break; case 'format': if ((empty($value) === false) and (Zend_Locale::isLocale($value, null, false) === false)) { if (!is_string($value) || (strpos($value, '0') === false)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("'" . ((gettype($value) === 'object') ? get_class($value) : $value) . "' is no format token"); } } break; case 'display': if (is_numeric($value) and ($value !== self::NO_SYMBOL) and ($value !== self::USE_SYMBOL) and ($value !== self::USE_SHORTNAME) and ($value !== self::USE_NAME)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("Unknown display '$value'"); } break; case 'precision': if ($value === null) { $value = -1; } if (($value < -1) or ($value > 30)) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception("'$value' precision has to be between -1 and 30."); } break; case 'script': try { Zend_Locale_Format::convertNumerals(0, $options['script']); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Currency/Exception.php'; throw new Zend_Currency_Exception($e->getMessage()); } break; default: break; } } return $options; } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Date * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @category Zend * @package Zend_Date * @subpackage Zend_Date_DateObject * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ abstract class Zend_Date_DateObject { /** * UNIX Timestamp */ private $_unixTimestamp; protected static $_cache = null; protected static $_cacheTags = false; protected static $_defaultOffset = 0; /** * active timezone */ private $_timezone = 'UTC'; private $_offset = 0; private $_syncronised = 0; // turn off DST correction if UTC or GMT protected $_dst = true; /** * Table of Monthdays */ private static $_monthTable = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); /** * Table of Years */ private static $_yearTable = array( 1970 => 0, 1960 => -315619200, 1950 => -631152000, 1940 => -946771200, 1930 => -1262304000, 1920 => -1577923200, 1910 => -1893456000, 1900 => -2208988800, 1890 => -2524521600, 1880 => -2840140800, 1870 => -3155673600, 1860 => -3471292800, 1850 => -3786825600, 1840 => -4102444800, 1830 => -4417977600, 1820 => -4733596800, 1810 => -5049129600, 1800 => -5364662400, 1790 => -5680195200, 1780 => -5995814400, 1770 => -6311347200, 1760 => -6626966400, 1750 => -6942499200, 1740 => -7258118400, 1730 => -7573651200, 1720 => -7889270400, 1710 => -8204803200, 1700 => -8520336000, 1690 => -8835868800, 1680 => -9151488000, 1670 => -9467020800, 1660 => -9782640000, 1650 => -10098172800, 1640 => -10413792000, 1630 => -10729324800, 1620 => -11044944000, 1610 => -11360476800, 1600 => -11676096000); /** * Set this object to have a new UNIX timestamp. * * @param string|integer $timestamp OPTIONAL timestamp; defaults to local time using time() * @return string|integer old timestamp * @throws Zend_Date_Exception */ protected function setUnixTimestamp($timestamp = null) { $old = $this->_unixTimestamp; if (is_numeric($timestamp)) { $this->_unixTimestamp = $timestamp; } else if ($timestamp === null) { $this->_unixTimestamp = time(); } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('\'' . $timestamp . '\' is not a valid UNIX timestamp', 0, null, $timestamp); } return $old; } /** * Returns this object's UNIX timestamp * A timestamp greater then the integer range will be returned as string * This function does not return the timestamp as object. Use copy() instead. * * @return integer|string timestamp */ protected function getUnixTimestamp() { if ($this->_unixTimestamp === intval($this->_unixTimestamp)) { return (int) $this->_unixTimestamp; } else { return (string) $this->_unixTimestamp; } } /** * Internal function. * Returns time(). This method exists to allow unit tests to work-around methods that might otherwise * be hard-coded to use time(). For example, this makes it possible to test isYesterday() in Date.php. * * @param integer $sync OPTIONAL time syncronisation value * @return integer timestamp */ protected function _getTime($sync = null) { if ($sync !== null) { $this->_syncronised = round($sync); } return (time() + $this->_syncronised); } /** * Internal mktime function used by Zend_Date. * The timestamp returned by mktime() can exceed the precision of traditional UNIX timestamps, * by allowing PHP to auto-convert to using a float value. * * Returns a timestamp relative to 1970/01/01 00:00:00 GMT/UTC. * DST (Summer/Winter) is depriciated since php 5.1.0. * Year has to be 4 digits otherwise it would be recognised as * year 70 AD instead of 1970 AD as expected !! * * @param integer $hour * @param integer $minute * @param integer $second * @param integer $month * @param integer $day * @param integer $year * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date * @return integer|float timestamp (number of seconds elapsed relative to 1970/01/01 00:00:00 GMT/UTC) */ protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false) { // complete date but in 32bit timestamp - use PHP internal if ((1901 < $year) and ($year < 2038)) { $oldzone = @date_default_timezone_get(); // Timezone also includes DST settings, therefor substracting the GMT offset is not enough // We have to set the correct timezone to get the right value if (($this->_timezone != $oldzone) and ($gmt === false)) { date_default_timezone_set($this->_timezone); } $result = ($gmt) ? @gmmktime($hour, $minute, $second, $month, $day, $year) : @mktime($hour, $minute, $second, $month, $day, $year); date_default_timezone_set($oldzone); return $result; } if ($gmt !== true) { $second += $this->_offset; } if (isset(self::$_cache)) { $id = strtr('Zend_DateObject_mkTime_' . $this->_offset . '_' . $year.$month.$day.'_'.$hour.$minute.$second . '_'.(int)$gmt, '-','_'); if ($result = self::$_cache->load($id)) { return unserialize($result); } } // date to integer $day = intval($day); $month = intval($month); $year = intval($year); // correct months > 12 and months < 1 if ($month > 12) { $overlap = floor($month / 12); $year += $overlap; $month -= $overlap * 12; } else { $overlap = ceil((1 - $month) / 12); $year -= $overlap; $month += $overlap * 12; } $date = 0; if ($year >= 1970) { // Date is after UNIX epoch // go through leapyears // add months from latest given year for ($count = 1970; $count <= $year; $count++) { $leapyear = self::isYearLeapYear($count); if ($count < $year) { $date += 365; if ($leapyear === true) { $date++; } } else { for ($mcount = 0; $mcount < ($month - 1); $mcount++) { $date += self::$_monthTable[$mcount]; if (($leapyear === true) and ($mcount == 1)) { $date++; } } } } $date += $day - 1; $date = (($date * 86400) + ($hour * 3600) + ($minute * 60) + $second); } else { // Date is before UNIX epoch // go through leapyears // add months from latest given year for ($count = 1969; $count >= $year; $count--) { $leapyear = self::isYearLeapYear($count); if ($count > $year) { $date += 365; if ($leapyear === true) $date++; } else { for ($mcount = 11; $mcount > ($month - 1); $mcount--) { $date += self::$_monthTable[$mcount]; if (($leapyear === true) and ($mcount == 2)) { $date++; } } } } $date += (self::$_monthTable[$month - 1] - $day); $date = -(($date * 86400) + (86400 - (($hour * 3600) + ($minute * 60) + $second))); // gregorian correction for 5.Oct.1582 if ($date < -12220185600) { $date += 864000; } else if ($date < -12219321600) { $date = -12219321600; } } if (isset(self::$_cache)) { if (self::$_cacheTags) { self::$_cache->save( serialize($date), $id, array('Zend_Date')); } else { self::$_cache->save( serialize($date), $id); } } return $date; } /** * Returns true, if given $year is a leap year. * * @param integer $year * @return boolean true, if year is leap year */ protected static function isYearLeapYear($year) { // all leapyears can be divided through 4 if (($year % 4) != 0) { return false; } // all leapyears can be divided through 400 if ($year % 400 == 0) { return true; } else if (($year > 1582) and ($year % 100 == 0)) { return false; } return true; } /** * Internal mktime function used by Zend_Date for handling 64bit timestamps. * * Returns a formatted date for a given timestamp. * * @param string $format format for output * @param mixed $timestamp * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date * @return string */ protected function date($format, $timestamp = null, $gmt = false) { $oldzone = @date_default_timezone_get(); if ($this->_timezone != $oldzone) { date_default_timezone_set($this->_timezone); } if ($timestamp === null) { $result = ($gmt) ? @gmdate($format) : @date($format); date_default_timezone_set($oldzone); return $result; } if (abs($timestamp) <= 0x7FFFFFFF) { $result = ($gmt) ? @gmdate($format, $timestamp) : @date($format, $timestamp); date_default_timezone_set($oldzone); return $result; } $jump = false; $origstamp = $timestamp; if (isset(self::$_cache)) { $idstamp = strtr('Zend_DateObject_date_' . $this->_offset . '_'. $timestamp . '_'.(int)$gmt, '-','_'); if ($result2 = self::$_cache->load($idstamp)) { $timestamp = unserialize($result2); $jump = true; } } // check on false or null alone fails if (empty($gmt) and empty($jump)) { $tempstamp = $timestamp; if ($tempstamp > 0) { while (abs($tempstamp) > 0x7FFFFFFF) { $tempstamp -= (86400 * 23376); } $dst = date("I", $tempstamp); if ($dst === 1) { $timestamp += 3600; } $temp = date('Z', $tempstamp); $timestamp += $temp; } if (isset(self::$_cache)) { if (self::$_cacheTags) { self::$_cache->save( serialize($timestamp), $idstamp, array('Zend_Date')); } else { self::$_cache->save( serialize($timestamp), $idstamp); } } } if (($timestamp < 0) and ($gmt !== true)) { $timestamp -= $this->_offset; } date_default_timezone_set($oldzone); $date = $this->getDateParts($timestamp, true); $length = strlen($format); $output = ''; for ($i = 0; $i < $length; $i++) { switch($format[$i]) { // day formats case 'd': // day of month, 2 digits, with leading zero, 01 - 31 $output .= (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']); break; case 'D': // day of week, 3 letters, Mon - Sun $output .= date('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))); break; case 'j': // day of month, without leading zero, 1 - 31 $output .= $date['mday']; break; case 'l': // day of week, full string name, Sunday - Saturday $output .= date('l', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))); break; case 'N': // ISO 8601 numeric day of week, 1 - 7 $day = self::dayOfWeek($date['year'], $date['mon'], $date['mday']); if ($day == 0) { $day = 7; } $output .= $day; break; case 'S': // english suffix for day of month, st nd rd th if (($date['mday'] % 10) == 1) { $output .= 'st'; } else if ((($date['mday'] % 10) == 2) and ($date['mday'] != 12)) { $output .= 'nd'; } else if (($date['mday'] % 10) == 3) { $output .= 'rd'; } else { $output .= 'th'; } break; case 'w': // numeric day of week, 0 - 6 $output .= self::dayOfWeek($date['year'], $date['mon'], $date['mday']); break; case 'z': // day of year, 0 - 365 $output .= $date['yday']; break; // week formats case 'W': // ISO 8601, week number of year $output .= $this->weekNumber($date['year'], $date['mon'], $date['mday']); break; // month formats case 'F': // string month name, january - december $output .= date('F', mktime(0, 0, 0, $date['mon'], 2, 1971)); break; case 'm': // number of month, with leading zeros, 01 - 12 $output .= (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']); break; case 'M': // 3 letter month name, Jan - Dec $output .= date('M',mktime(0, 0, 0, $date['mon'], 2, 1971)); break; case 'n': // number of month, without leading zeros, 1 - 12 $output .= $date['mon']; break; case 't': // number of day in month $output .= self::$_monthTable[$date['mon'] - 1]; break; // year formats case 'L': // is leap year ? $output .= (self::isYearLeapYear($date['year'])) ? '1' : '0'; break; case 'o': // ISO 8601 year number $week = $this->weekNumber($date['year'], $date['mon'], $date['mday']); if (($week > 50) and ($date['mon'] == 1)) { $output .= ($date['year'] - 1); } else { $output .= $date['year']; } break; case 'Y': // year number, 4 digits $output .= $date['year']; break; case 'y': // year number, 2 digits $output .= substr($date['year'], strlen($date['year']) - 2, 2); break; // time formats case 'a': // lower case am/pm $output .= (($date['hours'] >= 12) ? 'pm' : 'am'); break; case 'A': // upper case am/pm $output .= (($date['hours'] >= 12) ? 'PM' : 'AM'); break; case 'B': // swatch internet time $dayseconds = ($date['hours'] * 3600) + ($date['minutes'] * 60) + $date['seconds']; if ($gmt === true) { $dayseconds += 3600; } $output .= (int) (($dayseconds % 86400) / 86.4); break; case 'g': // hours without leading zeros, 12h format if ($date['hours'] > 12) { $hour = $date['hours'] - 12; } else { if ($date['hours'] == 0) { $hour = '12'; } else { $hour = $date['hours']; } } $output .= $hour; break; case 'G': // hours without leading zeros, 24h format $output .= $date['hours']; break; case 'h': // hours with leading zeros, 12h format if ($date['hours'] > 12) { $hour = $date['hours'] - 12; } else { if ($date['hours'] == 0) { $hour = '12'; } else { $hour = $date['hours']; } } $output .= (($hour < 10) ? '0'.$hour : $hour); break; case 'H': // hours with leading zeros, 24h format $output .= (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']); break; case 'i': // minutes with leading zeros $output .= (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']); break; case 's': // seconds with leading zeros $output .= (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']); break; // timezone formats case 'e': // timezone identifier if ($gmt === true) { $output .= gmdate('e', mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], 2000)); } else { $output .= date('e', mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], 2000)); } break; case 'I': // daylight saving time or not if ($gmt === true) { $output .= gmdate('I', mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], 2000)); } else { $output .= date('I', mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], 2000)); } break; case 'O': // difference to GMT in hours $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset(); $output .= sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36); break; case 'P': // difference to GMT with colon $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset(); $gmtstr = sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36); $output = $output . substr($gmtstr, 0, 3) . ':' . substr($gmtstr, 3); break; case 'T': // timezone settings if ($gmt === true) { $output .= gmdate('T', mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], 2000)); } else { $output .= date('T', mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], 2000)); } break; case 'Z': // timezone offset in seconds $output .= ($gmt === true) ? 0 : -$this->getGmtOffset(); break; // complete time formats case 'c': // ISO 8601 date format $difference = $this->getGmtOffset(); $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36); $difference = substr($difference, 0, 3) . ':' . substr($difference, 3); $output .= $date['year'] . '-' . (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']) . '-' . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . 'T' . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':' . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':' . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) . $difference; break; case 'r': // RFC 2822 date format $difference = $this->getGmtOffset(); $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36); $output .= gmdate('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))) . ', ' . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . ' ' . date('M', mktime(0, 0, 0, $date['mon'], 2, 1971)) . ' ' . $date['year'] . ' ' . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':' . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':' . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) . ' ' . $difference; break; case 'U': // Unix timestamp $output .= $origstamp; break; // special formats case "\\": // next letter to print with no format $i++; if ($i < $length) { $output .= $format[$i]; } break; default: // letter is no format so add it direct $output .= $format[$i]; break; } } return (string) $output; } /** * Returns the day of week for a Gregorian calendar date. * 0 = sunday, 6 = saturday * * @param integer $year * @param integer $month * @param integer $day * @return integer dayOfWeek */ protected static function dayOfWeek($year, $month, $day) { if ((1901 < $year) and ($year < 2038)) { return (int) date('w', mktime(0, 0, 0, $month, $day, $year)); } // gregorian correction $correction = 0; if (($year < 1582) or (($year == 1582) and (($month < 10) or (($month == 10) && ($day < 15))))) { $correction = 3; } if ($month > 2) { $month -= 2; } else { $month += 10; $year--; } $day = floor((13 * $month - 1) / 5) + $day + ($year % 100) + floor(($year % 100) / 4); $day += floor(($year / 100) / 4) - 2 * floor($year / 100) + 77 + $correction; return (int) ($day - 7 * floor($day / 7)); } /** * Internal getDateParts function for handling 64bit timestamps, similar to: * http://www.php.net/getdate * * Returns an array of date parts for $timestamp, relative to 1970/01/01 00:00:00 GMT/UTC. * * $fast specifies ALL date parts should be returned (slower) * Default is false, and excludes $dayofweek, weekday, month and timestamp from parts returned. * * @param mixed $timestamp * @param boolean $fast OPTIONAL defaults to fast (false), resulting in fewer date parts * @return array */ protected function getDateParts($timestamp = null, $fast = null) { // actual timestamp if (!is_numeric($timestamp)) { return getdate(); } // 32bit timestamp if (abs($timestamp) <= 0x7FFFFFFF) { return @getdate((int) $timestamp); } if (isset(self::$_cache)) { $id = strtr('Zend_DateObject_getDateParts_' . $timestamp.'_'.(int)$fast, '-','_'); if ($result = self::$_cache->load($id)) { return unserialize($result); } } $otimestamp = $timestamp; $numday = 0; $month = 0; // gregorian correction if ($timestamp < -12219321600) { $timestamp -= 864000; } // timestamp lower 0 if ($timestamp < 0) { $sec = 0; $act = 1970; // iterate through 10 years table, increasing speed foreach(self::$_yearTable as $year => $seconds) { if ($timestamp >= $seconds) { $i = $act; break; } $sec = $seconds; $act = $year; } $timestamp -= $sec; if (!isset($i)) { $i = $act; } // iterate the max last 10 years do { --$i; $day = $timestamp; $timestamp += 31536000; $leapyear = self::isYearLeapYear($i); if ($leapyear === true) { $timestamp += 86400; } if ($timestamp >= 0) { $year = $i; break; } } while ($timestamp < 0); $secondsPerYear = 86400 * ($leapyear ? 366 : 365) + $day; $timestamp = $day; // iterate through months for ($i = 12; --$i >= 0;) { $day = $timestamp; $timestamp += self::$_monthTable[$i] * 86400; if (($leapyear === true) and ($i == 1)) { $timestamp += 86400; } if ($timestamp >= 0) { $month = $i; $numday = self::$_monthTable[$i]; if (($leapyear === true) and ($i == 1)) { ++$numday; } break; } } $timestamp = $day; $numberdays = $numday + ceil(($timestamp + 1) / 86400); $timestamp += ($numday - $numberdays + 1) * 86400; $hours = floor($timestamp / 3600); } else { // iterate through years for ($i = 1970;;$i++) { $day = $timestamp; $timestamp -= 31536000; $leapyear = self::isYearLeapYear($i); if ($leapyear === true) { $timestamp -= 86400; } if ($timestamp < 0) { $year = $i; break; } } $secondsPerYear = $day; $timestamp = $day; // iterate through months for ($i = 0; $i <= 11; $i++) { $day = $timestamp; $timestamp -= self::$_monthTable[$i] * 86400; if (($leapyear === true) and ($i == 1)) { $timestamp -= 86400; } if ($timestamp < 0) { $month = $i; $numday = self::$_monthTable[$i]; if (($leapyear === true) and ($i == 1)) { ++$numday; } break; } } $timestamp = $day; $numberdays = ceil(($timestamp + 1) / 86400); $timestamp = $timestamp - ($numberdays - 1) * 86400; $hours = floor($timestamp / 3600); } $timestamp -= $hours * 3600; $month += 1; $minutes = floor($timestamp / 60); $seconds = $timestamp - $minutes * 60; if ($fast === true) { $array = array( 'seconds' => $seconds, 'minutes' => $minutes, 'hours' => $hours, 'mday' => $numberdays, 'mon' => $month, 'year' => $year, 'yday' => floor($secondsPerYear / 86400), ); } else { $dayofweek = self::dayOfWeek($year, $month, $numberdays); $array = array( 'seconds' => $seconds, 'minutes' => $minutes, 'hours' => $hours, 'mday' => $numberdays, 'wday' => $dayofweek, 'mon' => $month, 'year' => $year, 'yday' => floor($secondsPerYear / 86400), 'weekday' => gmdate('l', 86400 * (3 + $dayofweek)), 'month' => gmdate('F', mktime(0, 0, 0, $month, 1, 1971)), 0 => $otimestamp ); } if (isset(self::$_cache)) { if (self::$_cacheTags) { self::$_cache->save( serialize($array), $id, array('Zend_Date')); } else { self::$_cache->save( serialize($array), $id); } } return $array; } /** * Internal getWeekNumber function for handling 64bit timestamps * * Returns the ISO 8601 week number of a given date * * @param integer $year * @param integer $month * @param integer $day * @return integer */ protected function weekNumber($year, $month, $day) { if ((1901 < $year) and ($year < 2038)) { return (int) date('W', mktime(0, 0, 0, $month, $day, $year)); } $dayofweek = self::dayOfWeek($year, $month, $day); $firstday = self::dayOfWeek($year, 1, 1); if (($month == 1) and (($firstday < 1) or ($firstday > 4)) and ($day < 4)) { $firstday = self::dayOfWeek($year - 1, 1, 1); $month = 12; $day = 31; } else if (($month == 12) and ((self::dayOfWeek($year + 1, 1, 1) < 5) and (self::dayOfWeek($year + 1, 1, 1) > 0))) { return 1; } return intval (((self::dayOfWeek($year, 1, 1) < 5) and (self::dayOfWeek($year, 1, 1) > 0)) + 4 * ($month - 1) + (2 * ($month - 1) + ($day - 1) + $firstday - $dayofweek + 6) * 36 / 256); } /** * Internal _range function * Sets the value $a to be in the range of [0, $b] * * @param float $a - value to correct * @param float $b - maximum range to set */ private function _range($a, $b) { while ($a < 0) { $a += $b; } while ($a >= $b) { $a -= $b; } return $a; } /** * Calculates the sunrise or sunset based on a location * * @param array $location Location for calculation MUST include 'latitude', 'longitude', 'horizon' * @param bool $horizon true: sunrise; false: sunset * @return mixed - false: midnight sun, integer: */ protected function calcSun($location, $horizon, $rise = false) { // timestamp within 32bit if (abs($this->_unixTimestamp) <= 0x7FFFFFFF) { if ($rise === false) { return date_sunset($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'], $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600); } return date_sunrise($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'], $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600); } // self calculation - timestamp bigger than 32bit // fix circle values $quarterCircle = 0.5 * M_PI; $halfCircle = M_PI; $threeQuarterCircle = 1.5 * M_PI; $fullCircle = 2 * M_PI; // radiant conversion for coordinates $radLatitude = $location['latitude'] * $halfCircle / 180; $radLongitude = $location['longitude'] * $halfCircle / 180; // get solar coordinates $tmpRise = $rise ? $quarterCircle : $threeQuarterCircle; $radDay = $this->date('z',$this->_unixTimestamp) + ($tmpRise - $radLongitude) / $fullCircle; // solar anomoly and longitude $solAnomoly = $radDay * 0.017202 - 0.0574039; $solLongitude = $solAnomoly + 0.0334405 * sin($solAnomoly); $solLongitude += 4.93289 + 3.49066E-4 * sin(2 * $solAnomoly); // get quadrant $solLongitude = $this->_range($solLongitude, $fullCircle); if (($solLongitude / $quarterCircle) - intval($solLongitude / $quarterCircle) == 0) { $solLongitude += 4.84814E-6; } // solar ascension $solAscension = sin($solLongitude) / cos($solLongitude); $solAscension = atan2(0.91746 * $solAscension, 1); // adjust quadrant if ($solLongitude > $threeQuarterCircle) { $solAscension += $fullCircle; } else if ($solLongitude > $quarterCircle) { $solAscension += $halfCircle; } // solar declination $solDeclination = 0.39782 * sin($solLongitude); $solDeclination /= sqrt(-$solDeclination * $solDeclination + 1); $solDeclination = atan2($solDeclination, 1); $solHorizon = $horizon - sin($solDeclination) * sin($radLatitude); $solHorizon /= cos($solDeclination) * cos($radLatitude); // midnight sun, always night if (abs($solHorizon) > 1) { return false; } $solHorizon /= sqrt(-$solHorizon * $solHorizon + 1); $solHorizon = $quarterCircle - atan2($solHorizon, 1); if ($rise) { $solHorizon = $fullCircle - $solHorizon; } // time calculation $localTime = $solHorizon + $solAscension - 0.0172028 * $radDay - 1.73364; $universalTime = $localTime - $radLongitude; // determinate quadrant $universalTime = $this->_range($universalTime, $fullCircle); // radiant to hours $universalTime *= 24 / $fullCircle; // convert to time $hour = intval($universalTime); $universalTime = ($universalTime - $hour) * 60; $min = intval($universalTime); $universalTime = ($universalTime - $min) * 60; $sec = intval($universalTime); return $this->mktime($hour, $min, $sec, $this->date('m', $this->_unixTimestamp), $this->date('j', $this->_unixTimestamp), $this->date('Y', $this->_unixTimestamp), -1, true); } /** * Sets a new timezone for calculation of $this object's gmt offset. * For a list of supported timezones look here: http://php.net/timezones * If no timezone can be detected or the given timezone is wrong UTC will be set. * * @param string $zone OPTIONAL timezone for date calculation; defaults to date_default_timezone_get() * @return Zend_Date_DateObject Provides fluent interface * @throws Zend_Date_Exception */ public function setTimezone($zone = null) { $oldzone = @date_default_timezone_get(); if ($zone === null) { $zone = $oldzone; } // throw an error on false input, but only if the new date extension is available if (function_exists('timezone_open')) { if (!@timezone_open($zone)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("timezone ($zone) is not a known timezone", 0, null, $zone); } } // this can generate an error if the date extension is not available and a false timezone is given $result = @date_default_timezone_set($zone); if ($result === true) { $this->_offset = mktime(0, 0, 0, 1, 2, 1970) - gmmktime(0, 0, 0, 1, 2, 1970); $this->_timezone = $zone; } date_default_timezone_set($oldzone); if (($zone == 'UTC') or ($zone == 'GMT')) { $this->_dst = false; } else { $this->_dst = true; } return $this; } /** * Return the timezone of $this object. * The timezone is initially set when the object is instantiated. * * @return string actual set timezone string */ public function getTimezone() { return $this->_timezone; } /** * Return the offset to GMT of $this object's timezone. * The offset to GMT is initially set when the object is instantiated using the currently, * in effect, default timezone for PHP functions. * * @return integer seconds difference between GMT timezone and timezone when object was instantiated */ public function getGmtOffset() { $date = $this->getDateParts($this->getUnixTimestamp(), true); $zone = @date_default_timezone_get(); $result = @date_default_timezone_set($this->_timezone); if ($result === true) { $offset = $this->mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year'], false) - $this->mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year'], true); } date_default_timezone_set($zone); return $offset; } /** * Internal method to check if the given cache supports tags * * @param Zend_Cache $cache */ protected static function _getTagSupportForCache() { $backend = self::$_cache->getBackend(); if ($backend instanceof Zend_Cache_Backend_ExtendedInterface) { $cacheOptions = $backend->getCapabilities(); self::$_cacheTags = $cacheOptions['tags']; } else { self::$_cacheTags = false; } return self::$_cacheTags; } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Date * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * Include needed Date classes */ #require_once 'Zend/Date/DateObject.php'; #require_once 'Zend/Locale.php'; #require_once 'Zend/Locale/Format.php'; #require_once 'Zend/Locale/Math.php'; /** * This class replaces default Zend_Date because of problem described in Jira ticket MAGE-4872 * The only difference between current class and original one is overwritten implementation of mktime method * * @category Zend * @package Zend_Date * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Date extends Zend_Date_DateObject { private $_locale = null; // Fractional second variables private $_fractional = 0; private $_precision = 3; private static $_options = array( 'format_type' => 'iso', // format for date strings 'iso' or 'php' 'fix_dst' => true, // fix dst on summer/winter time change 'extend_month' => false, // false - addMonth like SQL, true like excel 'cache' => null, // cache to set 'timesync' => null // timesync server to set ); // Class wide Date Constants const DAY = 'dd'; const DAY_SHORT = 'd'; const DAY_SUFFIX = 'SS'; const DAY_OF_YEAR = 'D'; const WEEKDAY = 'EEEE'; const WEEKDAY_SHORT = 'EEE'; const WEEKDAY_NARROW = 'E'; const WEEKDAY_NAME = 'EE'; const WEEKDAY_8601 = 'eee'; const WEEKDAY_DIGIT = 'e'; const WEEK = 'ww'; const MONTH = 'MM'; const MONTH_SHORT = 'M'; const MONTH_DAYS = 'ddd'; const MONTH_NAME = 'MMMM'; const MONTH_NAME_SHORT = 'MMM'; const MONTH_NAME_NARROW = 'MMMMM'; const YEAR = 'y'; const YEAR_SHORT = 'yy'; const YEAR_8601 = 'Y'; const YEAR_SHORT_8601 = 'YY'; const LEAPYEAR = 'l'; const MERIDIEM = 'a'; const SWATCH = 'B'; const HOUR = 'HH'; const HOUR_SHORT = 'H'; const HOUR_AM = 'hh'; const HOUR_SHORT_AM = 'h'; const MINUTE = 'mm'; const MINUTE_SHORT = 'm'; const SECOND = 'ss'; const SECOND_SHORT = 's'; const MILLISECOND = 'S'; const TIMEZONE_NAME = 'zzzz'; const DAYLIGHT = 'I'; const GMT_DIFF = 'Z'; const GMT_DIFF_SEP = 'ZZZZ'; const TIMEZONE = 'z'; const TIMEZONE_SECS = 'X'; const ISO_8601 = 'c'; const RFC_2822 = 'r'; const TIMESTAMP = 'U'; const ERA = 'G'; const ERA_NAME = 'GGGG'; const ERA_NARROW = 'GGGGG'; const DATES = 'F'; const DATE_FULL = 'FFFFF'; const DATE_LONG = 'FFFF'; const DATE_MEDIUM = 'FFF'; const DATE_SHORT = 'FF'; const TIMES = 'WW'; const TIME_FULL = 'TTTTT'; const TIME_LONG = 'TTTT'; const TIME_MEDIUM = 'TTT'; const TIME_SHORT = 'TT'; const DATETIME = 'K'; const DATETIME_FULL = 'KKKKK'; const DATETIME_LONG = 'KKKK'; const DATETIME_MEDIUM = 'KKK'; const DATETIME_SHORT = 'KK'; const ATOM = 'OOO'; const COOKIE = 'CCC'; const RFC_822 = 'R'; const RFC_850 = 'RR'; const RFC_1036 = 'RRR'; const RFC_1123 = 'RRRR'; const RFC_3339 = 'RRRRR'; const RSS = 'SSS'; const W3C = 'WWW'; /** * Minimum allowed year value */ const YEAR_MIN_VALUE = -10000; /** * Maximum allowed year value */ const YEAR_MAX_VALUE = 10000; /** * Generates the standard date object, could be a unix timestamp, localized date, * string, integer, array and so on. Also parts of dates or time are supported * Always set the default timezone: http://php.net/date_default_timezone_set * For example, in your bootstrap: date_default_timezone_set('America/Los_Angeles'); * For detailed instructions please look in the docu. * * @param string|integer|Zend_Date|array $date OPTIONAL Date value or value of date part to set * ,depending on $part. If null the actual time is set * @param string $part OPTIONAL Defines the input format of $date * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date * @throws Zend_Date_Exception */ public function __construct($date = null, $part = null, $locale = null) { if (is_object($date) and !($date instanceof Zend_TimeSync_Protocol) and !($date instanceof Zend_Date)) { if ($locale instanceof Zend_Locale) { $locale = $date; $date = null; $part = null; } else { $date = (string) $date; } } if (($date !== null) and !is_array($date) and !($date instanceof Zend_TimeSync_Protocol) and !($date instanceof Zend_Date) and !defined($date) and Zend_Locale::isLocale($date, true, false)) { $locale = $date; $date = null; $part = null; } else if (($part !== null) and !defined($part) and Zend_Locale::isLocale($part, true, false)) { $locale = $part; $part = null; } $this->setLocale($locale); if (is_string($date) && ($part === null) && (strlen($date) <= 5)) { $part = $date; $date = null; } if ($date === null) { if ($part === null) { $date = time(); } else if ($part !== self::TIMESTAMP) { $date = self::now($locale); $date = $date->get($part); } } if ($date instanceof Zend_TimeSync_Protocol) { $date = $date->getInfo(); $date = $this->_getTime($date['offset']); $part = null; } else if (parent::$_defaultOffset != 0) { $date = $this->_getTime(parent::$_defaultOffset); } // set the timezone and offset for $this $zone = @date_default_timezone_get(); $this->setTimezone($zone); // try to get timezone from date-string if (!is_int($date)) { $zone = $this->getTimezoneFromString($date); $this->setTimezone($zone); } // set datepart if (($part !== null && $part !== self::TIMESTAMP) or (!is_numeric($date))) { // switch off dst handling for value setting $this->setUnixTimestamp($this->getGmtOffset()); $this->set($date, $part, $this->_locale); // DST fix if (is_array($date) === true) { if (!isset($date['hour'])) { $date['hour'] = 0; } $hour = $this->toString('H', 'iso', true); $hour = $date['hour'] - $hour; switch ($hour) { case 1 : case -23 : $this->addTimestamp(3600); break; case -1 : case 23 : $this->subTimestamp(3600); break; case 2 : case -22 : $this->addTimestamp(7200); break; case -2 : case 22 : $this->subTimestamp(7200); break; } } } else { $this->setUnixTimestamp($date); } } /** * Sets class wide options, if no option was given, the actual set options will be returned * * @param array $options Options to set * @throws Zend_Date_Exception * @return Options array if no option was given */ public static function setOptions(array $options = array()) { if (empty($options)) { return self::$_options; } foreach ($options as $name => $value) { $name = strtolower($name); if (array_key_exists($name, self::$_options)) { switch($name) { case 'format_type' : if ((strtolower($value) != 'php') && (strtolower($value) != 'iso')) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("Unknown format type ($value) for dates, only 'iso' and 'php' supported", 0, null, $value); } break; case 'fix_dst' : if (!is_bool($value)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("'fix_dst' has to be boolean", 0, null, $value); } break; case 'extend_month' : if (!is_bool($value)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("'extend_month' has to be boolean", 0, null, $value); } break; case 'cache' : if ($value === null) { parent::$_cache = null; } else { if (!$value instanceof Zend_Cache_Core) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("Instance of Zend_Cache expected"); } parent::$_cache = $value; parent::$_cacheTags = Zend_Date_DateObject::_getTagSupportForCache(); Zend_Locale_Data::setCache($value); } break; case 'timesync' : if ($value === null) { parent::$_defaultOffset = 0; } else { if (!$value instanceof Zend_TimeSync_Protocol) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("Instance of Zend_TimeSync expected"); } $date = $value->getInfo(); parent::$_defaultOffset = $date['offset']; } break; } self::$_options[$name] = $value; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("Unknown option: $name = $value"); } } } /** * Returns this object's internal UNIX timestamp (equivalent to Zend_Date::TIMESTAMP). * If the timestamp is too large for integers, then the return value will be a string. * This function does not return the timestamp as an object. * Use clone() or copyPart() instead. * * @return integer|string UNIX timestamp */ public function getTimestamp() { return $this->getUnixTimestamp(); } /** * Returns the calculated timestamp * HINT: timestamps are always GMT * * @param string $calc Type of calculation to make * @param string|integer|array|Zend_Date $stamp Timestamp to calculate, when null the actual timestamp is calculated * @return Zend_Date|integer * @throws Zend_Date_Exception */ private function _timestamp($calc, $stamp) { if ($stamp instanceof Zend_Date) { // extract timestamp from object $stamp = $stamp->getTimestamp(); } if (is_array($stamp)) { if (isset($stamp['timestamp']) === true) { $stamp = $stamp['timestamp']; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('no timestamp given in array'); } } if ($calc === 'set') { $return = $this->setUnixTimestamp($stamp); } else { $return = $this->_calcdetail($calc, $stamp, self::TIMESTAMP, null); } if ($calc != 'cmp') { return $this; } return $return; } /** * Sets a new timestamp * * @param integer|string|array|Zend_Date $timestamp Timestamp to set * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setTimestamp($timestamp) { return $this->_timestamp('set', $timestamp); } /** * Adds a timestamp * * @param integer|string|array|Zend_Date $timestamp Timestamp to add * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addTimestamp($timestamp) { return $this->_timestamp('add', $timestamp); } /** * Subtracts a timestamp * * @param integer|string|array|Zend_Date $timestamp Timestamp to sub * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subTimestamp($timestamp) { return $this->_timestamp('sub', $timestamp); } /** * Compares two timestamps, returning the difference as integer * * @param integer|string|array|Zend_Date $timestamp Timestamp to compare * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareTimestamp($timestamp) { return $this->_timestamp('cmp', $timestamp); } /** * Returns a string representation of the object * Supported format tokens are: * G - era, y - year, Y - ISO year, M - month, w - week of year, D - day of year, d - day of month * E - day of week, e - number of weekday (1-7), h - hour 1-12, H - hour 0-23, m - minute, s - second * A - milliseconds of day, z - timezone, Z - timezone offset, S - fractional second, a - period of day * * Additionally format tokens but non ISO conform are: * SS - day suffix, eee - php number of weekday(0-6), ddd - number of days per month * l - Leap year, B - swatch internet time, I - daylight saving time, X - timezone offset in seconds * r - RFC2822 format, U - unix timestamp * * Not supported ISO tokens are * u - extended year, Q - quarter, q - quarter, L - stand alone month, W - week of month * F - day of week of month, g - modified julian, c - stand alone weekday, k - hour 0-11, K - hour 1-24 * v - wall zone * * @param string $format OPTIONAL Rule for formatting output. If null the default date format is used * @param string $type OPTIONAL Type for the format string which overrides the standard setting * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return string */ public function toString($format = null, $type = null, $locale = null) { if (is_object($format)) { if ($format instanceof Zend_Locale) { $locale = $format; $format = null; } else { $format = (string) $format; } } if (is_object($type)) { if ($type instanceof Zend_Locale) { $locale = $type; $type = null; } else { $type = (string) $type; } } if (($format !== null) && !defined($format) && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT') && Zend_Locale::isLocale($format, null, false)) { $locale = $format; $format = null; } if (($type !== null) and ($type != 'php') and ($type != 'iso') and Zend_Locale::isLocale($type, null, false)) { $locale = $type; $type = null; } if ($locale === null) { $locale = $this->getLocale(); } if ($format === null) { $format = Zend_Locale_Format::getDateFormat($locale) . ' ' . Zend_Locale_Format::getTimeFormat($locale); } else if (((self::$_options['format_type'] == 'php') && ($type === null)) or ($type == 'php')) { $format = Zend_Locale_Format::convertPhpToIsoFormat($format); } return $this->date($this->_toToken($format, $locale), $this->getUnixTimestamp(), false); } /** * Returns a string representation of the date which is equal with the timestamp * * @return string */ public function __toString() { return $this->toString(null, $this->_locale); } /** * Returns a integer representation of the object * But returns false when the given part is no value f.e. Month-Name * * @param string|integer|Zend_Date $part OPTIONAL Defines the date or datepart to return as integer * @return integer|false */ public function toValue($part = null) { $result = $this->get($part); if (is_numeric($result)) { return intval("$result"); } else { return false; } } /** * Returns an array representation of the object * * @return array */ public function toArray() { return array('day' => $this->toString(self::DAY_SHORT, 'iso'), 'month' => $this->toString(self::MONTH_SHORT, 'iso'), 'year' => $this->toString(self::YEAR, 'iso'), 'hour' => $this->toString(self::HOUR_SHORT, 'iso'), 'minute' => $this->toString(self::MINUTE_SHORT, 'iso'), 'second' => $this->toString(self::SECOND_SHORT, 'iso'), 'timezone' => $this->toString(self::TIMEZONE, 'iso'), 'timestamp' => $this->toString(self::TIMESTAMP, 'iso'), 'weekday' => $this->toString(self::WEEKDAY_8601, 'iso'), 'dayofyear' => $this->toString(self::DAY_OF_YEAR, 'iso'), 'week' => $this->toString(self::WEEK, 'iso'), 'gmtsecs' => $this->toString(self::TIMEZONE_SECS, 'iso')); } /** * Returns a representation of a date or datepart * This could be for example a localized monthname, the time without date, * the era or only the fractional seconds. There are about 50 different supported date parts. * For a complete list of supported datepart values look into the docu * * @param string $part OPTIONAL Part of the date to return, if null the timestamp is returned * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return string date or datepart */ public function get($part = null, $locale = null) { if ($locale === null) { $locale = $this->getLocale(); } if (($part !== null) && !defined($part) && ($part != 'ee') && ($part != 'ss') && ($part != 'GG') && ($part != 'MM') && ($part != 'EE') && ($part != 'TT') && Zend_Locale::isLocale($part, null, false)) { $locale = $part; $part = null; } if ($part === null) { $part = self::TIMESTAMP; } else if (self::$_options['format_type'] == 'php') { $part = Zend_Locale_Format::convertPhpToIsoFormat($part); } return $this->date($this->_toToken($part, $locale), $this->getUnixTimestamp(), false); } /** * Internal method to apply tokens * * @param string $part * @param string $locale * @return string */ private function _toToken($part, $locale) { // get format tokens $comment = false; $format = ''; $orig = ''; for ($i = 0; isset($part[$i]); ++$i) { if ($part[$i] == "'") { $comment = $comment ? false : true; if (isset($part[$i+1]) && ($part[$i+1] == "'")) { $comment = $comment ? false : true; $format .= "\\'"; ++$i; } $orig = ''; continue; } if ($comment) { $format .= '\\' . $part[$i]; $orig = ''; } else { $orig .= $part[$i]; if (!isset($part[$i+1]) || (isset($orig[0]) && ($orig[0] != $part[$i+1]))) { $format .= $this->_parseIsoToDate($orig, $locale); $orig = ''; } } } return $format; } /** * Internal parsing method * * @param string $token * @param string $locale * @return string */ private function _parseIsoToDate($token, $locale) { switch($token) { case self::DAY : return 'd'; break; case self::WEEKDAY_SHORT : $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday)); return $this->_toComment(iconv_substr($day, 0, 3, 'UTF-8')); break; case self::DAY_SHORT : return 'j'; break; case self::WEEKDAY : $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday))); break; case self::WEEKDAY_8601 : return 'N'; break; case 'ee' : return $this->_toComment(str_pad($this->date('N', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); break; case self::DAY_SUFFIX : return 'S'; break; case self::WEEKDAY_DIGIT : return 'w'; break; case self::DAY_OF_YEAR : return 'z'; break; case 'DDD' : return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 3, '0', STR_PAD_LEFT)); break; case 'DD' : return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); break; case self::WEEKDAY_NARROW : case 'EEEEE' : $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday)); return $this->_toComment(iconv_substr($day, 0, 1, 'UTF-8')); break; case self::WEEKDAY_NAME : $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday))); break; case 'w' : $week = $this->date('W', $this->getUnixTimestamp(), false); return $this->_toComment(($week[0] == '0') ? $week[1] : $week); break; case self::WEEK : return 'W'; break; case self::MONTH_NAME : $month = $this->date('n', $this->getUnixTimestamp(), false); return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'wide', $month))); break; case self::MONTH : return 'm'; break; case self::MONTH_NAME_SHORT : $month = $this->date('n', $this->getUnixTimestamp(), false); return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month))); break; case self::MONTH_SHORT : return 'n'; break; case self::MONTH_DAYS : return 't'; break; case self::MONTH_NAME_NARROW : $month = $this->date('n', $this->getUnixTimestamp(), false); $mon = Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month)); return $this->_toComment(iconv_substr($mon, 0, 1, 'UTF-8')); break; case self::LEAPYEAR : return 'L'; break; case self::YEAR_8601 : return 'o'; break; case self::YEAR : return 'Y'; break; case self::YEAR_SHORT : return 'y'; break; case self::YEAR_SHORT_8601 : return $this->_toComment(substr($this->date('o', $this->getUnixTimestamp(), false), -2, 2)); break; case self::MERIDIEM : $am = $this->date('a', $this->getUnixTimestamp(), false); if ($am == 'am') { return $this->_toComment(Zend_Locale_Data::getContent($locale, 'am')); } return $this->_toComment(Zend_Locale_Data::getContent($locale, 'pm')); break; case self::SWATCH : return 'B'; break; case self::HOUR_SHORT_AM : return 'g'; break; case self::HOUR_SHORT : return 'G'; break; case self::HOUR_AM : return 'h'; break; case self::HOUR : return 'H'; break; case self::MINUTE : return $this->_toComment(str_pad($this->date('i', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); break; case self::SECOND : return $this->_toComment(str_pad($this->date('s', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); break; case self::MINUTE_SHORT : return 'i'; break; case self::SECOND_SHORT : return 's'; break; case self::MILLISECOND : return $this->_toComment($this->getMilliSecond()); break; case self::TIMEZONE_NAME : case 'vvvv' : return 'e'; break; case self::DAYLIGHT : return 'I'; break; case self::GMT_DIFF : case 'ZZ' : case 'ZZZ' : return 'O'; break; case self::GMT_DIFF_SEP : return 'P'; break; case self::TIMEZONE : case 'v' : case 'zz' : case 'zzz' : return 'T'; break; case self::TIMEZONE_SECS : return 'Z'; break; case self::ISO_8601 : return 'c'; break; case self::RFC_2822 : return 'r'; break; case self::TIMESTAMP : return 'U'; break; case self::ERA : case 'GG' : case 'GGG' : $year = $this->date('Y', $this->getUnixTimestamp(), false); if ($year < 0) { return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0'))); } return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1'))); break; case self::ERA_NARROW : $year = $this->date('Y', $this->getUnixTimestamp(), false); if ($year < 0) { return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0')), 0, 1, 'UTF-8')) . '.'; } return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1')), 0, 1, 'UTF-8')) . '.'; break; case self::ERA_NAME : $year = $this->date('Y', $this->getUnixTimestamp(), false); if ($year < 0) { return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '0'))); } return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '1'))); break; case self::DATES : return $this->_toToken(Zend_Locale_Format::getDateFormat($locale), $locale); break; case self::DATE_FULL : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')), $locale); break; case self::DATE_LONG : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')), $locale); break; case self::DATE_MEDIUM : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')), $locale); break; case self::DATE_SHORT : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')), $locale); break; case self::TIMES : return $this->_toToken(Zend_Locale_Format::getTimeFormat($locale), $locale); break; case self::TIME_FULL : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'full'), $locale); break; case self::TIME_LONG : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'long'), $locale); break; case self::TIME_MEDIUM : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'medium'), $locale); break; case self::TIME_SHORT : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'short'), $locale); break; case self::DATETIME : return $this->_toToken(Zend_Locale_Format::getDateTimeFormat($locale), $locale); break; case self::DATETIME_FULL : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')), $locale); break; case self::DATETIME_LONG : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')), $locale); break; case self::DATETIME_MEDIUM : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')), $locale); break; case self::DATETIME_SHORT : return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')), $locale); break; case self::ATOM : return 'Y\-m\-d\TH\:i\:sP'; break; case self::COOKIE : return 'l\, d\-M\-y H\:i\:s e'; break; case self::RFC_822 : return 'D\, d M y H\:i\:s O'; break; case self::RFC_850 : return 'l\, d\-M\-y H\:i\:s e'; break; case self::RFC_1036 : return 'D\, d M y H\:i\:s O'; break; case self::RFC_1123 : return 'D\, d M Y H\:i\:s O'; break; case self::RFC_3339 : return 'Y\-m\-d\TH\:i\:sP'; break; case self::RSS : return 'D\, d M Y H\:i\:s O'; break; case self::W3C : return 'Y\-m\-d\TH\:i\:sP'; break; } if ($token == '') { return ''; } switch ($token[0]) { case 'y' : if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { return 'Y'; } $length = iconv_strlen($token, 'UTF-8'); return $this->_toComment(str_pad($this->date('Y', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); break; case 'Y' : if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { return 'o'; } $length = iconv_strlen($token, 'UTF-8'); return $this->_toComment(str_pad($this->date('o', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); break; case 'A' : $length = iconv_strlen($token, 'UTF-8'); $result = substr($this->getMilliSecond(), 0, 3); $result += $this->date('s', $this->getUnixTimestamp(), false) * 1000; $result += $this->date('i', $this->getUnixTimestamp(), false) * 60000; $result += $this->date('H', $this->getUnixTimestamp(), false) * 3600000; return $this->_toComment(str_pad($result, $length, '0', STR_PAD_LEFT)); break; } return $this->_toComment($token); } /** * Private function to make a comment of a token * * @param string $token * @return string */ private function _toComment($token) { $token = str_split($token); $result = ''; foreach ($token as $tok) { $result .= '\\' . $tok; } return $result; } /** * Return digit from standard names (english) * Faster implementation than locale aware searching * * @param string $name * @return integer Number of this month * @throws Zend_Date_Exception */ private function _getDigitFromName($name) { switch($name) { case "Jan": return 1; case "Feb": return 2; case "Mar": return 3; case "Apr": return 4; case "May": return 5; case "Jun": return 6; case "Jul": return 7; case "Aug": return 8; case "Sep": return 9; case "Oct": return 10; case "Nov": return 11; case "Dec": return 12; default: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('Month ($name) is not a known month'); } } /** * Counts the exact year number * < 70 - 2000 added, >70 < 100 - 1900, others just returned * * @param integer $value year number * @return integer Number of year */ public static function getFullYear($value) { if ($value >= 0) { if ($value < 70) { $value += 2000; } else if ($value < 100) { $value += 1900; } } return $value; } /** * Sets the given date as new date or a given datepart as new datepart returning the new datepart * This could be for example a localized dayname, the date without time, * the month or only the seconds. There are about 50 different supported date parts. * For a complete list of supported datepart values look into the docu * * @param string|integer|array|Zend_Date $date Date or datepart to set * @param string $part OPTIONAL Part of the date to set, if null the timestamp is set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function set($date, $part = null, $locale = null) { if (self::$_options['format_type'] == 'php') { $part = Zend_Locale_Format::convertPhpToIsoFormat($part); } $zone = $this->getTimezoneFromString($date); $this->setTimezone($zone); $this->_calculate('set', $date, $part, $locale); return $this; } /** * Adds a date or datepart to the existing date, by extracting $part from $date, * and modifying this object by adding that part. The $part is then extracted from * this object and returned as an integer or numeric string (for large values, or $part's * corresponding to pre-defined formatted date strings). * This could be for example a ISO 8601 date, the hour the monthname or only the minute. * There are about 50 different supported date parts. * For a complete list of supported datepart values look into the docu. * * @param string|integer|array|Zend_Date $date Date or datepart to add * @param string $part OPTIONAL Part of the date to add, if null the timestamp is added * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function add($date, $part = self::TIMESTAMP, $locale = null) { if (self::$_options['format_type'] == 'php') { $part = Zend_Locale_Format::convertPhpToIsoFormat($part); } $this->_calculate('add', $date, $part, $locale); return $this; } /** * Subtracts a date from another date. * This could be for example a RFC2822 date, the time, * the year or only the timestamp. There are about 50 different supported date parts. * For a complete list of supported datepart values look into the docu * Be aware: Adding -2 Months is not equal to Subtracting 2 Months !!! * * @param string|integer|array|Zend_Date $date Date or datepart to subtract * @param string $part OPTIONAL Part of the date to sub, if null the timestamp is subtracted * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function sub($date, $part = self::TIMESTAMP, $locale = null) { if (self::$_options['format_type'] == 'php') { $part = Zend_Locale_Format::convertPhpToIsoFormat($part); } $this->_calculate('sub', $date, $part, $locale); return $this; } /** * Compares a date or datepart with the existing one. * Returns -1 if earlier, 0 if equal and 1 if later. * * @param string|integer|array|Zend_Date $date Date or datepart to compare with the date object * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is subtracted * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compare($date, $part = self::TIMESTAMP, $locale = null) { if (self::$_options['format_type'] == 'php') { $part = Zend_Locale_Format::convertPhpToIsoFormat($part); } $compare = $this->_calculate('cmp', $date, $part, $locale); if ($compare > 0) { return 1; } else if ($compare < 0) { return -1; } return 0; } /** * Returns a new instance of Zend_Date with the selected part copied. * To make an exact copy, use PHP's clone keyword. * For a complete list of supported date part values look into the docu. * If a date part is copied, all other date parts are set to standard values. * For example: If only YEAR is copied, the returned date object is equal to * 01-01-YEAR 00:00:00 (01-01-1970 00:00:00 is equal to timestamp 0) * If only HOUR is copied, the returned date object is equal to * 01-01-1970 HOUR:00:00 (so $this contains a timestamp equal to a timestamp of 0 plus HOUR). * * @param string $part Part of the date to compare, if null the timestamp is subtracted * @param string|Zend_Locale $locale OPTIONAL New object's locale. No adjustments to timezone are made. * @return Zend_Date New clone with requested part */ public function copyPart($part, $locale = null) { $clone = clone $this; // copy all instance variables $clone->setUnixTimestamp(0); // except the timestamp if ($locale != null) { $clone->setLocale($locale); // set an other locale if selected } $clone->set($this, $part); return $clone; } /** * Internal function, returns the offset of a given timezone * * @param string $zone * @return integer */ public function getTimezoneFromString($zone) { if (is_array($zone)) { return $this->getTimezone(); } if ($zone instanceof Zend_Date) { return $zone->getTimezone(); } $match = array(); preg_match('/\dZ$/', $zone, $match); if (!empty($match)) { return "Etc/UTC"; } preg_match('/([+-]\d{2}):{0,1}\d{2}/', $zone, $match); if (!empty($match) and ($match[count($match) - 1] <= 12) and ($match[count($match) - 1] >= -12)) { $zone = "Etc/GMT"; $zone .= ($match[count($match) - 1] < 0) ? "+" : "-"; $zone .= (int) abs($match[count($match) - 1]); return $zone; } preg_match('/([[:alpha:]\/]{3,30})(?!.*([[:alpha:]\/]{3,30}))/', $zone, $match); try { if (!empty($match) and (!is_int($match[count($match) - 1]))) { $oldzone = $this->getTimezone(); $this->setTimezone($match[count($match) - 1]); $result = $this->getTimezone(); $this->setTimezone($oldzone); if ($result !== $oldzone) { return $match[count($match) - 1]; } } } catch (Exception $e) { // fall through } return $this->getTimezone(); } /** * Calculates the date or object * * @param string $calc Calculation to make * @param string|integer $date Date for calculation * @param string|integer $comp Second date for calculation * @param boolean|integer $dst Use dst correction if option is set * @return integer|string|Zend_Date new timestamp or Zend_Date depending on calculation */ private function _assign($calc, $date, $comp = 0, $dst = false) { switch ($calc) { case 'set' : if (!empty($comp)) { $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $comp)); } $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); $value = $this->getUnixTimestamp(); break; case 'add' : $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); $value = $this->getUnixTimestamp(); break; case 'sub' : $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $date)); $value = $this->getUnixTimestamp(); break; default : // cmp - compare return call_user_func(Zend_Locale_Math::$comp, $comp, $date); break; } // dst-correction if 'fix_dst' = true and dst !== false but only for non UTC and non GMT if ((self::$_options['fix_dst'] === true) and ($dst !== false) and ($this->_dst === true)) { $hour = $this->toString(self::HOUR, 'iso'); if ($hour != $dst) { if (($dst == ($hour + 1)) or ($dst == ($hour - 23))) { $value += 3600; } else if (($dst == ($hour - 1)) or ($dst == ($hour + 23))) { $value -= 3600; } $this->setUnixTimestamp($value); } } return $this->getUnixTimestamp(); } /** * Calculates the date or object * * @param string $calc Calculation to make, one of: 'add'|'sub'|'cmp'|'copy'|'set' * @param string|integer|array|Zend_Date $date Date or datepart to calculate with * @param string $part Part of the date to calculate, if null the timestamp is used * @param string|Zend_Locale $locale Locale for parsing input * @return integer|string|Zend_Date new timestamp * @throws Zend_Date_Exception */ private function _calculate($calc, $date, $part, $locale) { if ($date === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); } if (($part !== null) && (strlen($part) !== 2) && (Zend_Locale::isLocale($part, null, false))) { $locale = $part; $part = null; } if ($locale === null) { $locale = $this->getLocale(); } $locale = (string) $locale; // Create date parts $year = $this->toString(self::YEAR, 'iso'); $month = $this->toString(self::MONTH_SHORT, 'iso'); $day = $this->toString(self::DAY_SHORT, 'iso'); $hour = $this->toString(self::HOUR_SHORT, 'iso'); $minute = $this->toString(self::MINUTE_SHORT, 'iso'); $second = $this->toString(self::SECOND_SHORT, 'iso'); // If object extract value if ($date instanceof Zend_Date) { $date = $date->toString($part, 'iso', $locale); } if (is_array($date) === true) { if (empty($part) === false) { switch($part) { // Fall through case self::DAY: case self::DAY_SHORT: if (isset($date['day']) === true) { $date = $date['day']; } break; // Fall through case self::WEEKDAY_SHORT: case self::WEEKDAY: case self::WEEKDAY_8601: case self::WEEKDAY_DIGIT: case self::WEEKDAY_NARROW: case self::WEEKDAY_NAME: if (isset($date['weekday']) === true) { $date = $date['weekday']; $part = self::WEEKDAY_DIGIT; } break; case self::DAY_OF_YEAR: if (isset($date['day_of_year']) === true) { $date = $date['day_of_year']; } break; // Fall through case self::MONTH: case self::MONTH_SHORT: case self::MONTH_NAME: case self::MONTH_NAME_SHORT: case self::MONTH_NAME_NARROW: if (isset($date['month']) === true) { $date = $date['month']; } break; // Fall through case self::YEAR: case self::YEAR_SHORT: case self::YEAR_8601: case self::YEAR_SHORT_8601: if (isset($date['year']) === true) { $date = $date['year']; } break; // Fall through case self::HOUR: case self::HOUR_AM: case self::HOUR_SHORT: case self::HOUR_SHORT_AM: if (isset($date['hour']) === true) { $date = $date['hour']; } break; // Fall through case self::MINUTE: case self::MINUTE_SHORT: if (isset($date['minute']) === true) { $date = $date['minute']; } break; // Fall through case self::SECOND: case self::SECOND_SHORT: if (isset($date['second']) === true) { $date = $date['second']; } break; // Fall through case self::TIMEZONE: case self::TIMEZONE_NAME: if (isset($date['timezone']) === true) { $date = $date['timezone']; } break; case self::TIMESTAMP: if (isset($date['timestamp']) === true) { $date = $date['timestamp']; } break; case self::WEEK: if (isset($date['week']) === true) { $date = $date['week']; } break; case self::TIMEZONE_SECS: if (isset($date['gmtsecs']) === true) { $date = $date['gmtsecs']; } break; default: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("datepart for part ($part) not found in array"); break; } } else { $hours = 0; if (isset($date['hour']) === true) { $hours = $date['hour']; } $minutes = 0; if (isset($date['minute']) === true) { $minutes = $date['minute']; } $seconds = 0; if (isset($date['second']) === true) { $seconds = $date['second']; } $months = 0; if (isset($date['month']) === true) { $months = $date['month']; } $days = 0; if (isset($date['day']) === true) { $days = $date['day']; } $years = 0; if (isset($date['year']) === true) { $years = $date['year']; } return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, $months, $days, $years, true), $this->mktime($hour, $minute, $second, $month, $day, $year, true), $hour); } } // $date as object, part of foreign date as own date switch($part) { // day formats case self::DAY: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); break; case self::WEEKDAY_SHORT: $daylist = Zend_Locale_Data::getList($locale, 'day'); $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); $cnt = 0; foreach ($daylist as $key => $value) { if (strtoupper(iconv_substr($value, 0, 3, 'UTF-8')) == strtoupper($date)) { $found = $cnt; break; } ++$cnt; } // Weekday found if ($cnt < 7) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); } // Weekday not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); break; case self::DAY_SHORT: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); break; case self::WEEKDAY: $daylist = Zend_Locale_Data::getList($locale, 'day'); $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); $cnt = 0; foreach ($daylist as $key => $value) { if (strtoupper($value) == strtoupper($date)) { $found = $cnt; break; } ++$cnt; } // Weekday found if ($cnt < 7) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); } // Weekday not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); break; case self::WEEKDAY_8601: $weekday = (int) $this->toString(self::WEEKDAY_8601, 'iso', $locale); if ((intval($date) > 0) and (intval($date) < 8)) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); } // Weekday not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); break; case self::DAY_SUFFIX: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('day suffix not supported', 0, null, $date); break; case self::WEEKDAY_DIGIT: $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); if (is_numeric($date) and (intval($date) >= 0) and (intval($date) < 7)) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $date, 1970, true), $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); } // Weekday not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); break; case self::DAY_OF_YEAR: if (is_numeric($date)) { if (($calc == 'add') || ($calc == 'sub')) { $year = 1970; ++$date; ++$day; } return $this->_assign($calc, $this->mktime(0, 0, 0, 1, $date, $year, true), $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); break; case self::WEEKDAY_NARROW: $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); $cnt = 0; foreach ($daylist as $key => $value) { if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($date)) { $found = $cnt; break; } ++$cnt; } // Weekday found if ($cnt < 7) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); } // Weekday not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); break; case self::WEEKDAY_NAME: $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); $cnt = 0; foreach ($daylist as $key => $value) { if (strtoupper($value) == strtoupper($date)) { $found = $cnt; break; } ++$cnt; } // Weekday found if ($cnt < 7) { return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); } // Weekday not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); break; // week formats case self::WEEK: if (is_numeric($date)) { $week = (int) $this->toString(self::WEEK, 'iso', $locale); return $this->_assign($calc, parent::mktime(0, 0, 0, 1, 1 + ($date * 7), 1970, true), parent::mktime(0, 0, 0, 1, 1 + ($week * 7), 1970, true), $hour); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, week expected", 0, null, $date); break; // month formats case self::MONTH_NAME: $monthlist = Zend_Locale_Data::getList($locale, 'month'); $cnt = 0; foreach ($monthlist as $key => $value) { if (strtoupper($value) == strtoupper($date)) { $found = $key; break; } ++$cnt; } $date = array_search($date, $monthlist); // Monthname found if ($cnt < 12) { $fixday = 0; if ($calc == 'add') { $date += $found; $calc = 'set'; if (self::$_options['extend_month'] == false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } else if ($calc == 'sub') { $date = $month - $found; $calc = 'set'; if (self::$_options['extend_month'] == false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); } // Monthname not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); break; case self::MONTH: if (is_numeric($date)) { $fixday = 0; if ($calc == 'add') { $date += $month; $calc = 'set'; if (self::$_options['extend_month'] == false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } else if ($calc == 'sub') { $date = $month - $date; $calc = 'set'; if (self::$_options['extend_month'] == false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); break; case self::MONTH_NAME_SHORT: $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); $cnt = 0; foreach ($monthlist as $key => $value) { if (strtoupper($value) == strtoupper($date)) { $found = $key; break; } ++$cnt; } $date = array_search($date, $monthlist); // Monthname found if ($cnt < 12) { $fixday = 0; if ($calc == 'add') { $date += $found; $calc = 'set'; if (self::$_options['extend_month'] === false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } else if ($calc == 'sub') { $date = $month - $found; $calc = 'set'; if (self::$_options['extend_month'] === false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); } // Monthname not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); break; case self::MONTH_SHORT: if (is_numeric($date) === true) { $fixday = 0; if ($calc === 'add') { $date += $month; $calc = 'set'; if (self::$_options['extend_month'] === false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } else if ($calc === 'sub') { $date = $month - $date; $calc = 'set'; if (self::$_options['extend_month'] === false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); break; case self::MONTH_DAYS: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('month days not supported', 0, null, $date); break; case self::MONTH_NAME_NARROW: $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'stand-alone', 'narrow')); $cnt = 0; foreach ($monthlist as $key => $value) { if (strtoupper($value) === strtoupper($date)) { $found = $key; break; } ++$cnt; } $date = array_search($date, $monthlist); // Monthname found if ($cnt < 12) { $fixday = 0; if ($calc === 'add') { $date += $found; $calc = 'set'; if (self::$_options['extend_month'] === false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } else if ($calc === 'sub') { $date = $month - $found; $calc = 'set'; if (self::$_options['extend_month'] === false) { $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); if ($parts['mday'] != $day) { $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); } } } return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); } // Monthname not found #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); break; // year formats case self::LEAPYEAR: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('leap year not supported', 0, null, $date); break; case self::YEAR_8601: if (is_numeric($date)) { if ($calc === 'add') { $date += $year; $calc = 'set'; } else if ($calc === 'sub') { $date = $year - $date; $calc = 'set'; } return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), $this->mktime(0, 0, 0, $month, $day, $year, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); break; case self::YEAR: if (is_numeric($date)) { if ($calc === 'add') { $date += $year; $calc = 'set'; } else if ($calc === 'sub') { $date = $year - $date; $calc = 'set'; } return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), $this->mktime(0, 0, 0, $month, $day, $year, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); break; case self::YEAR_SHORT: if (is_numeric($date)) { $date = intval($date); if (($calc == 'set') || ($calc == 'cmp')) { $date = self::getFullYear($date); } if ($calc === 'add') { $date += $year; $calc = 'set'; } else if ($calc === 'sub') { $date = $year - $date; $calc = 'set'; } return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), $this->mktime(0, 0, 0, $month, $day, $year, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); break; case self::YEAR_SHORT_8601: if (is_numeric($date)) { $date = intval($date); if (($calc === 'set') || ($calc === 'cmp')) { $date = self::getFullYear($date); } if ($calc === 'add') { $date += $year; $calc = 'set'; } else if ($calc === 'sub') { $date = $year - $date; $calc = 'set'; } return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), $this->mktime(0, 0, 0, $month, $day, $year, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); break; // time formats case self::MERIDIEM: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('meridiem not supported', 0, null, $date); break; case self::SWATCH: if (is_numeric($date)) { $rest = intval($date); $hours = floor($rest * 24 / 1000); $rest = $rest - ($hours * 1000 / 24); $minutes = floor($rest * 1440 / 1000); $rest = $rest - ($minutes * 1000 / 1440); $seconds = floor($rest * 86400 / 1000); return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, 1, 1, 1970, true), $this->mktime($hour, $minute, $second, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, swatchstamp expected", 0, null, $date); break; case self::HOUR_SHORT_AM: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); break; case self::HOUR_SHORT: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); break; case self::HOUR_AM: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); break; case self::HOUR: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); break; case self::MINUTE: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); break; case self::SECOND: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), $this->mktime(0, 0, $second, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); break; case self::MILLISECOND: if (is_numeric($date)) { switch($calc) { case 'set' : return $this->setMillisecond($date); break; case 'add' : return $this->addMillisecond($date); break; case 'sub' : return $this->subMillisecond($date); break; } return $this->compareMillisecond($date); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, milliseconds expected", 0, null, $date); break; case self::MINUTE_SHORT: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); break; case self::SECOND_SHORT: if (is_numeric($date)) { return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), $this->mktime(0, 0, $second, 1, 1, 1970, true), false); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); break; // timezone formats // break intentionally omitted case self::TIMEZONE_NAME: case self::TIMEZONE: case self::TIMEZONE_SECS: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('timezone not supported', 0, null, $date); break; case self::DAYLIGHT: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('daylight not supported', 0, null, $date); break; case self::GMT_DIFF: case self::GMT_DIFF_SEP: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('gmtdiff not supported', 0, null, $date); break; // date strings case self::ISO_8601: // (-)YYYY-MM-dd preg_match('/^(-{0,1}\d{4})-(\d{2})-(\d{2})/', $date, $datematch); // (-)YY-MM-dd if (empty($datematch)) { preg_match('/^(-{0,1}\d{2})-(\d{2})-(\d{2})/', $date, $datematch); } // (-)YYYYMMdd if (empty($datematch)) { preg_match('/^(-{0,1}\d{4})(\d{2})(\d{2})/', $date, $datematch); } // (-)YYMMdd if (empty($datematch)) { preg_match('/^(-{0,1}\d{2})(\d{2})(\d{2})/', $date, $datematch); } $tmpdate = $date; if (!empty($datematch)) { $dateMatchCharCount = iconv_strlen($datematch[0], 'UTF-8'); $tmpdate = iconv_substr($date, $dateMatchCharCount, iconv_strlen($date, 'UTF-8') - $dateMatchCharCount, 'UTF-8'); } // (T)hh:mm:ss preg_match('/[T,\s]{0,1}(\d{2}):(\d{2}):(\d{2})/', $tmpdate, $timematch); if (empty($timematch)) { preg_match('/[T,\s]{0,1}(\d{2})(\d{2})(\d{2})/', $tmpdate, $timematch); } if (empty($datematch) and empty($timematch)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("unsupported ISO8601 format ($date)", 0, null, $date); } if (!empty($timematch)) { $timeMatchCharCount = iconv_strlen($timematch[0], 'UTF-8'); $tmpdate = iconv_substr($tmpdate, $timeMatchCharCount, iconv_strlen($tmpdate, 'UTF-8') - $timeMatchCharCount, 'UTF-8'); } if (empty($datematch)) { $datematch[1] = 1970; $datematch[2] = 1; $datematch[3] = 1; } else if (iconv_strlen($datematch[1], 'UTF-8') == 2) { $datematch[1] = self::getFullYear($datematch[1]); } if (empty($timematch)) { $timematch[1] = 0; $timematch[2] = 0; $timematch[3] = 0; } if (($calc == 'set') || ($calc == 'cmp')) { --$datematch[2]; --$month; --$datematch[3]; --$day; $datematch[1] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($timematch[1], $timematch[2], $timematch[3], 1 + $datematch[2], 1 + $datematch[3], 1970 + $datematch[1], false), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); break; case self::RFC_2822: $result = preg_match('/^\w{3},\s(\d{1,2})\s(\w{3})\s(\d{4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4})$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no RFC 2822 format ($date)", 0, null, $date); } $months = $this->_getDigitFromName($match[2]); if (($calc == 'set') || ($calc == 'cmp')) { --$months; --$month; --$match[1]; --$day; $match[3] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); break; case self::TIMESTAMP: if (is_numeric($date)) { return $this->_assign($calc, $date, $this->getUnixTimestamp()); } #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, timestamp expected", 0, null, $date); break; // additional formats // break intentionally omitted case self::ERA: case self::ERA_NAME: #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('era not supported', 0, null, $date); break; case self::DATES: try { $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATE_FULL: try { $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATE_LONG: try { $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if (($calc == 'set') || ($calc == 'cmp')){ --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATE_MEDIUM: try { $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATE_SHORT: try { $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); $parsed['year'] = self::getFullYear($parsed['year']); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::TIMES: try { if ($calc != 'set') { $month = 1; $day = 1; $year = 1970; } $parsed = Zend_Locale_Format::getTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::TIME_FULL: try { $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if ($calc != 'set') { $month = 1; $day = 1; $year = 1970; } if (!isset($parsed['second'])) { $parsed['second'] = 0; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::TIME_LONG: try { $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if ($calc != 'set') { $month = 1; $day = 1; $year = 1970; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::TIME_MEDIUM: try { $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if ($calc != 'set') { $month = 1; $day = 1; $year = 1970; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::TIME_SHORT: try { $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if ($calc != 'set') { $month = 1; $day = 1; $year = 1970; } if (!isset($parsed['second'])) { $parsed['second'] = 0; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATETIME: try { $parsed = Zend_Locale_Format::getDateTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATETIME_FULL: try { $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } if (!isset($parsed['second'])) { $parsed['second'] = 0; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATETIME_LONG: try { $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if (($calc == 'set') || ($calc == 'cmp')){ --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATETIME_MEDIUM: try { $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; case self::DATETIME_SHORT: try { $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); $parsed['year'] = self::getFullYear($parsed['year']); if (($calc == 'set') || ($calc == 'cmp')) { --$parsed['month']; --$month; --$parsed['day']; --$day; $parsed['year'] -= 1970; $year -= 1970; } if (!isset($parsed['second'])) { $parsed['second'] = 0; } return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } break; // ATOM and RFC_3339 are identical case self::ATOM: case self::RFC_3339: $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\d{0,4}([+-]{1}\d{2}:\d{2}|Z)$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, ATOM format expected", 0, null, $date); } if (($calc == 'set') || ($calc == 'cmp')) { --$match[2]; --$month; --$match[3]; --$day; $match[1] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); break; case self::COOKIE: $result = preg_match("/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,20}$/", $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, COOKIE format expected", 0, null, $date); } $matchStartPos = iconv_strpos($match[0], ' ', 0, 'UTF-8') + 1; $match[0] = iconv_substr($match[0], $matchStartPos, iconv_strlen($match[0], 'UTF-8') - $matchStartPos, 'UTF-8'); $months = $this->_getDigitFromName($match[2]); $match[3] = self::getFullYear($match[3]); if (($calc == 'set') || ($calc == 'cmp')) { --$months; --$month; --$match[1]; --$day; $match[3] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); break; case self::RFC_822: case self::RFC_1036: // new RFC 822 format, identical to RFC 1036 standard $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, RFC 822 date format expected", 0, null, $date); } $months = $this->_getDigitFromName($match[2]); $match[3] = self::getFullYear($match[3]); if (($calc == 'set') || ($calc == 'cmp')) { --$months; --$month; --$match[1]; --$day; $match[3] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); break; case self::RFC_850: $result = preg_match('/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,21}$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, RFC 850 date format expected", 0, null, $date); } $months = $this->_getDigitFromName($match[2]); $match[3] = self::getFullYear($match[3]); if (($calc == 'set') || ($calc == 'cmp')) { --$months; --$month; --$match[1]; --$day; $match[3] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); break; case self::RFC_1123: $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, RFC 1123 date format expected", 0, null, $date); } $months = $this->_getDigitFromName($match[2]); if (($calc == 'set') || ($calc == 'cmp')) { --$months; --$month; --$match[1]; --$day; $match[3] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); break; case self::RSS: $result = preg_match('/^\w{3},\s(\d{2})\s(\w{3})\s(\d{2,4})\s(\d{1,2}):(\d{2}):(\d{2})\s.{1,21}$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, RSS date format expected", 0, null, $date); } $months = $this->_getDigitFromName($match[2]); $match[3] = self::getFullYear($match[3]); if (($calc == 'set') || ($calc == 'cmp')) { --$months; --$month; --$match[1]; --$day; $match[3] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); break; case self::W3C: $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})[+-]{1}\d{2}:\d{2}$/', $date, $match); if (!$result) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid date ($date) operand, W3C date format expected", 0, null, $date); } if (($calc == 'set') || ($calc == 'cmp')) { --$match[2]; --$month; --$match[3]; --$day; $match[1] -= 1970; $year -= 1970; } return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); break; default: if (!is_numeric($date) || !empty($part)) { try { if (empty($part)) { $part = Zend_Locale_Format::getDateFormat($locale) . " "; $part .= Zend_Locale_Format::getTimeFormat($locale); } $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $part, 'locale' => $locale, 'fix_date' => true, 'format_type' => 'iso')); if ((strpos(strtoupper($part), 'YY') !== false) and (strpos(strtoupper($part), 'YYYY') === false)) { $parsed['year'] = self::getFullYear($parsed['year']); } if (($calc == 'set') || ($calc == 'cmp')) { if (isset($parsed['month'])) { --$parsed['month']; } else { $parsed['month'] = 0; } if (isset($parsed['day'])) { --$parsed['day']; } else { $parsed['day'] = 0; } if (isset($parsed['year'])) { $parsed['year'] -= 1970; } else { $parsed['year'] = 0; } } return $this->_assign($calc, $this->mktime( isset($parsed['hour']) ? $parsed['hour'] : 0, isset($parsed['minute']) ? $parsed['minute'] : 0, isset($parsed['second']) ? $parsed['second'] : 0, isset($parsed['month']) ? (1 + $parsed['month']) : 1, isset($parsed['day']) ? (1 + $parsed['day']) : 1, isset($parsed['year']) ? (1970 + $parsed['year']) : 1970, false), $this->getUnixTimestamp(), false); } catch (Zend_Locale_Exception $e) { if (!is_numeric($date)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); } } } return $this->_assign($calc, $date, $this->getUnixTimestamp(), false); break; } } /** * Returns true when both date objects or date parts are equal. * For example: * 15.May.2000 <-> 15.June.2000 Equals only for Day or Year... all other will return false * * @param string|integer|array|Zend_Date $date Date or datepart to equal with * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return boolean * @throws Zend_Date_Exception */ public function equals($date, $part = self::TIMESTAMP, $locale = null) { $result = $this->compare($date, $part, $locale); if ($result == 0) { return true; } return false; } /** * Returns if the given date or datepart is earlier * For example: * 15.May.2000 <-> 13.June.1999 will return true for day, year and date, but not for month * * @param string|integer|array|Zend_Date $date Date or datepart to compare with * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return boolean * @throws Zend_Date_Exception */ public function isEarlier($date, $part = null, $locale = null) { $result = $this->compare($date, $part, $locale); if ($result == -1) { return true; } return false; } /** * Returns if the given date or datepart is later * For example: * 15.May.2000 <-> 13.June.1999 will return true for month but false for day, year and date * Returns if the given date is later * * @param string|integer|array|Zend_Date $date Date or datepart to compare with * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return boolean * @throws Zend_Date_Exception */ public function isLater($date, $part = null, $locale = null) { $result = $this->compare($date, $part, $locale); if ($result == 1) { return true; } return false; } /** * Returns only the time of the date as new Zend_Date object * For example: * 15.May.2000 10:11:23 will return a dateobject equal to 01.Jan.1970 10:11:23 * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getTime($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'H:i:s'; } else { $format = self::TIME_MEDIUM; } return $this->copyPart($format, $locale); } /** * Returns the calculated time * * @param string $calc Calculation to make * @param string|integer|array|Zend_Date $time Time to calculate with, if null the actual time is taken * @param string $format Timeformat for parsing input * @param string|Zend_Locale $locale Locale for parsing input * @return integer|Zend_Date new time * @throws Zend_Date_Exception */ private function _time($calc, $time, $format, $locale) { if ($time === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('parameter $time must be set, null is not allowed'); } if ($time instanceof Zend_Date) { // extract time from object $time = $time->toString('HH:mm:ss', 'iso'); } else { if (is_array($time)) { if ((isset($time['hour']) === true) or (isset($time['minute']) === true) or (isset($time['second']) === true)) { $parsed = $time; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no hour, minute or second given in array"); } } else { if (self::$_options['format_type'] == 'php') { $format = Zend_Locale_Format::convertPhpToIsoFormat($format); } try { if ($locale === null) { $locale = $this->getLocale(); } $parsed = Zend_Locale_Format::getTime($time, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e); } } if (!array_key_exists('hour', $parsed)) { $parsed['hour'] = 0; } if (!array_key_exists('minute', $parsed)) { $parsed['minute'] = 0; } if (!array_key_exists('second', $parsed)) { $parsed['second'] = 0; } $time = str_pad($parsed['hour'], 2, '0', STR_PAD_LEFT) . ":"; $time .= str_pad($parsed['minute'], 2, '0', STR_PAD_LEFT) . ":"; $time .= str_pad($parsed['second'], 2, '0', STR_PAD_LEFT); } $return = $this->_calcdetail($calc, $time, self::TIMES, 'de'); if ($calc != 'cmp') { return $this; } return $return; } /** * Sets a new time for the date object. Format defines how to parse the time string. * Also a complete date can be given, but only the time is used for setting. * For example: dd.MMMM.yyTHH:mm' and 'ss sec'-> 10.May.07T25:11 and 44 sec => 1h11min44sec + 1 day * Returned is the new date object and the existing date is left as it was before * * @param string|integer|array|Zend_Date $time Time to set * @param string $format OPTIONAL Timeformat for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setTime($time, $format = null, $locale = null) { return $this->_time('set', $time, $format, $locale); } /** * Adds a time to the existing date. Format defines how to parse the time string. * If only parts are given the other parts are set to 0. * If no format is given, the standardformat of this locale is used. * For example: HH:mm:ss -> 10 -> +10 hours * * @param string|integer|array|Zend_Date $time Time to add * @param string $format OPTIONAL Timeformat for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addTime($time, $format = null, $locale = null) { return $this->_time('add', $time, $format, $locale); } /** * Subtracts a time from the existing date. Format defines how to parse the time string. * If only parts are given the other parts are set to 0. * If no format is given, the standardformat of this locale is used. * For example: HH:mm:ss -> 10 -> -10 hours * * @param string|integer|array|Zend_Date $time Time to sub * @param string $format OPTIONAL Timeformat for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid inteface * @throws Zend_Date_Exception */ public function subTime($time, $format = null, $locale = null) { return $this->_time('sub', $time, $format, $locale); } /** * Compares the time from the existing date. Format defines how to parse the time string. * If only parts are given the other parts are set to default. * If no format us given, the standardformat of this locale is used. * For example: HH:mm:ss -> 10 -> 10 hours * * @param string|integer|array|Zend_Date $time Time to compare * @param string $format OPTIONAL Timeformat for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareTime($time, $format = null, $locale = null) { return $this->_time('cmp', $time, $format, $locale); } /** * Returns a clone of $this, with the time part set to 00:00:00. * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getDate($locale = null) { $orig = self::$_options['format_type']; if (self::$_options['format_type'] == 'php') { self::$_options['format_type'] = 'iso'; } $date = $this->copyPart(self::DATE_MEDIUM, $locale); $date->addTimestamp($this->getGmtOffset()); self::$_options['format_type'] = $orig; return $date; } /** * Returns the calculated date * * @param string $calc Calculation to make * @param string|integer|array|Zend_Date $date Date to calculate with, if null the actual date is taken * @param string $format Date format for parsing * @param string|Zend_Locale $locale Locale for parsing input * @return integer|Zend_Date new date * @throws Zend_Date_Exception */ private function _date($calc, $date, $format, $locale) { if ($date === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); } if ($date instanceof Zend_Date) { // extract date from object $date = $date->toString('d.M.y', 'iso'); } else { if (is_array($date)) { if ((isset($date['year']) === true) or (isset($date['month']) === true) or (isset($date['day']) === true)) { $parsed = $date; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no day,month or year given in array"); } } else { if ((self::$_options['format_type'] == 'php') && !defined($format)) { $format = Zend_Locale_Format::convertPhpToIsoFormat($format); } try { if ($locale === null) { $locale = $this->getLocale(); } $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); if ((strpos(strtoupper($format), 'YY') !== false) and (strpos(strtoupper($format), 'YYYY') === false)) { $parsed['year'] = self::getFullYear($parsed['year']); } } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e); } } if (!array_key_exists('day', $parsed)) { $parsed['day'] = 1; } if (!array_key_exists('month', $parsed)) { $parsed['month'] = 1; } if (!array_key_exists('year', $parsed)) { $parsed['year'] = 0; } $date = $parsed['day'] . "." . $parsed['month'] . "." . $parsed['year']; } $return = $this->_calcdetail($calc, $date, self::DATE_MEDIUM, 'de'); if ($calc != 'cmp') { return $this; } return $return; } /** * Sets a new date for the date object. Format defines how to parse the date string. * Also a complete date with time can be given, but only the date is used for setting. * For example: MMMM.yy HH:mm-> May.07 22:11 => 01.May.07 00:00 * Returned is the new date object and the existing time is left as it was before * * @param string|integer|array|Zend_Date $date Date to set * @param string $format OPTIONAL Date format for parsing * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setDate($date, $format = null, $locale = null) { return $this->_date('set', $date, $format, $locale); } /** * Adds a date to the existing date object. Format defines how to parse the date string. * If only parts are given the other parts are set to 0. * If no format is given, the standardformat of this locale is used. * For example: MM.dd.YYYY -> 10 -> +10 months * * @param string|integer|array|Zend_Date $date Date to add * @param string $format OPTIONAL Date format for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addDate($date, $format = null, $locale = null) { return $this->_date('add', $date, $format, $locale); } /** * Subtracts a date from the existing date object. Format defines how to parse the date string. * If only parts are given the other parts are set to 0. * If no format is given, the standardformat of this locale is used. * For example: MM.dd.YYYY -> 10 -> -10 months * Be aware: Subtracting 2 months is not equal to Adding -2 months !!! * * @param string|integer|array|Zend_Date $date Date to sub * @param string $format OPTIONAL Date format for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subDate($date, $format = null, $locale = null) { return $this->_date('sub', $date, $format, $locale); } /** * Compares the date from the existing date object, ignoring the time. * Format defines how to parse the date string. * If only parts are given the other parts are set to 0. * If no format is given, the standardformat of this locale is used. * For example: 10.01.2000 => 10.02.1999 -> false * * @param string|integer|array|Zend_Date $date Date to compare * @param string $format OPTIONAL Date format for parsing input * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareDate($date, $format = null, $locale = null) { return $this->_date('cmp', $date, $format, $locale); } /** * Returns the full ISO 8601 date from the date object. * Always the complete ISO 8601 specifiction is used. If an other ISO date is needed * (ISO 8601 defines several formats) use toString() instead. * This function does not return the ISO date as object. Use copy() instead. * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return string */ public function getIso($locale = null) { return $this->toString(self::ISO_8601, 'iso', $locale); } /** * Sets a new date for the date object. Not given parts are set to default. * Only supported ISO 8601 formats are accepted. * For example: 050901 -> 01.Sept.2005 00:00:00, 20050201T10:00:30 -> 01.Feb.2005 10h00m30s * Returned is the new date object * * @param string|integer|Zend_Date $date ISO Date to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setIso($date, $locale = null) { return $this->_calcvalue('set', $date, 'iso', self::ISO_8601, $locale); } /** * Adds a ISO date to the date object. Not given parts are set to default. * Only supported ISO 8601 formats are accepted. * For example: 050901 -> + 01.Sept.2005 00:00:00, 10:00:00 -> +10h * Returned is the new date object * * @param string|integer|Zend_Date $date ISO Date to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addIso($date, $locale = null) { return $this->_calcvalue('add', $date, 'iso', self::ISO_8601, $locale); } /** * Subtracts a ISO date from the date object. Not given parts are set to default. * Only supported ISO 8601 formats are accepted. * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h * Returned is the new date object * * @param string|integer|Zend_Date $date ISO Date to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subIso($date, $locale = null) { return $this->_calcvalue('sub', $date, 'iso', self::ISO_8601, $locale); } /** * Compares a ISO date with the date object. Not given parts are set to default. * Only supported ISO 8601 formats are accepted. * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h * Returns if equal, earlier or later * * @param string|integer|Zend_Date $date ISO Date to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareIso($date, $locale = null) { return $this->_calcvalue('cmp', $date, 'iso', self::ISO_8601, $locale); } /** * Returns a RFC 822 compilant datestring from the date object. * This function does not return the RFC date as object. Use copy() instead. * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return string */ public function getArpa($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'D\, d M y H\:i\:s O'; } else { $format = self::RFC_822; } return $this->toString($format, 'iso', $locale); } /** * Sets a RFC 822 date as new date for the date object. * Only RFC 822 compilant date strings are accepted. * For example: Sat, 14 Feb 09 00:31:30 +0100 * Returned is the new date object * * @param string|integer|Zend_Date $date RFC 822 to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setArpa($date, $locale = null) { return $this->_calcvalue('set', $date, 'arpa', self::RFC_822, $locale); } /** * Adds a RFC 822 date to the date object. * ARPA messages are used in emails or HTTP Headers. * Only RFC 822 compilant date strings are accepted. * For example: Sat, 14 Feb 09 00:31:30 +0100 * Returned is the new date object * * @param string|integer|Zend_Date $date RFC 822 Date to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addArpa($date, $locale = null) { return $this->_calcvalue('add', $date, 'arpa', self::RFC_822, $locale); } /** * Subtracts a RFC 822 date from the date object. * ARPA messages are used in emails or HTTP Headers. * Only RFC 822 compilant date strings are accepted. * For example: Sat, 14 Feb 09 00:31:30 +0100 * Returned is the new date object * * @param string|integer|Zend_Date $date RFC 822 Date to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subArpa($date, $locale = null) { return $this->_calcvalue('sub', $date, 'arpa', self::RFC_822, $locale); } /** * Compares a RFC 822 compilant date with the date object. * ARPA messages are used in emails or HTTP Headers. * Only RFC 822 compilant date strings are accepted. * For example: Sat, 14 Feb 09 00:31:30 +0100 * Returns if equal, earlier or later * * @param string|integer|Zend_Date $date RFC 822 Date to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareArpa($date, $locale = null) { return $this->_calcvalue('cmp', $date, 'arpa', self::RFC_822, $locale); } /** * Check if location is supported * * @param $location array - locations array * @return $horizon float */ private function _checkLocation($location) { if (!isset($location['longitude']) or !isset($location['latitude'])) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('Location must include \'longitude\' and \'latitude\'', 0, null, $location); } if (($location['longitude'] > 180) or ($location['longitude'] < -180)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('Longitude must be between -180 and 180', 0, null, $location); } if (($location['latitude'] > 90) or ($location['latitude'] < -90)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('Latitude must be between -90 and 90', 0, null, $location); } if (!isset($location['horizon'])){ $location['horizon'] = 'effective'; } switch ($location['horizon']) { case 'civil' : return -0.104528; break; case 'nautic' : return -0.207912; break; case 'astronomic' : return -0.309017; break; default : return -0.0145439; break; } } /** * Returns the time of sunrise for this date and a given location as new date object * For a list of cities and correct locations use the class Zend_Date_Cities * * @param $location array - location of sunrise * ['horizon'] -> civil, nautic, astronomical, effective (default) * ['longitude'] -> longitude of location * ['latitude'] -> latitude of location * @return Zend_Date * @throws Zend_Date_Exception */ public function getSunrise($location) { $horizon = $this->_checkLocation($location); $result = clone $this; $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); return $result; } /** * Returns the time of sunset for this date and a given location as new date object * For a list of cities and correct locations use the class Zend_Date_Cities * * @param $location array - location of sunset * ['horizon'] -> civil, nautic, astronomical, effective (default) * ['longitude'] -> longitude of location * ['latitude'] -> latitude of location * @return Zend_Date * @throws Zend_Date_Exception */ public function getSunset($location) { $horizon = $this->_checkLocation($location); $result = clone $this; $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); return $result; } /** * Returns an array with the sunset and sunrise dates for all horizon types * For a list of cities and correct locations use the class Zend_Date_Cities * * @param $location array - location of suninfo * ['horizon'] -> civil, nautic, astronomical, effective (default) * ['longitude'] -> longitude of location * ['latitude'] -> latitude of location * @return array - [sunset|sunrise][effective|civil|nautic|astronomic] * @throws Zend_Date_Exception */ public function getSunInfo($location) { $suninfo = array(); for ($i = 0; $i < 4; ++$i) { switch ($i) { case 0 : $location['horizon'] = 'effective'; break; case 1 : $location['horizon'] = 'civil'; break; case 2 : $location['horizon'] = 'nautic'; break; case 3 : $location['horizon'] = 'astronomic'; break; } $horizon = $this->_checkLocation($location); $result = clone $this; $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); $suninfo['sunrise'][$location['horizon']] = $result; $result = clone $this; $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); $suninfo['sunset'][$location['horizon']] = $result; } return $suninfo; } /** * Check a given year for leap year. * * @param integer|array|Zend_Date $year Year to check * @return boolean */ public static function checkLeapYear($year) { if ($year instanceof Zend_Date) { $year = (int) $year->toString(self::YEAR, 'iso'); } if (is_array($year)) { if (isset($year['year']) === true) { $year = $year['year']; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no year given in array"); } } if (!is_numeric($year)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("year ($year) has to be integer for checkLeapYear()", 0, null, $year); } return (bool) parent::isYearLeapYear($year); } /** * Returns true, if the year is a leap year. * * @return boolean */ public function isLeapYear() { return self::checkLeapYear($this); } /** * Returns if the set date is todays date * * @return boolean */ public function isToday() { $today = $this->date('Ymd', $this->_getTime()); $day = $this->date('Ymd', $this->getUnixTimestamp()); return ($today == $day); } /** * Returns if the set date is yesterdays date * * @return boolean */ public function isYesterday() { list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); // adjusts for leap days and DST changes that are timezone specific $yesterday = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day -1, $year)); $day = $this->date('Ymd', $this->getUnixTimestamp()); return $day == $yesterday; } /** * Returns if the set date is tomorrows date * * @return boolean */ public function isTomorrow() { list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); // adjusts for leap days and DST changes that are timezone specific $tomorrow = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day +1, $year)); $day = $this->date('Ymd', $this->getUnixTimestamp()); return $day == $tomorrow; } /** * Returns the actual date as new date object * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public static function now($locale = null) { return new Zend_Date(time(), self::TIMESTAMP, $locale); } /** * Calculate date details * * @param string $calc Calculation to make * @param string|integer|array|Zend_Date $date Date or Part to calculate * @param string $part Datepart for Calculation * @param string|Zend_Locale $locale Locale for parsing input * @return integer|string new date * @throws Zend_Date_Exception */ private function _calcdetail($calc, $date, $type, $locale) { $old = false; if (self::$_options['format_type'] == 'php') { self::$_options['format_type'] = 'iso'; $old = true; } switch($calc) { case 'set' : $return = $this->set($date, $type, $locale); break; case 'add' : $return = $this->add($date, $type, $locale); break; case 'sub' : $return = $this->sub($date, $type, $locale); break; default : $return = $this->compare($date, $type, $locale); break; } if ($old) { self::$_options['format_type'] = 'php'; } return $return; } /** * Internal calculation, returns the requested date type * * @param string $calc Calculation to make * @param string|integer|Zend_Date $value Datevalue to calculate with, if null the actual value is taken * @param string|Zend_Locale $locale Locale for parsing input * @return integer|Zend_Date new date * @throws Zend_Date_Exception */ private function _calcvalue($calc, $value, $type, $parameter, $locale) { if ($value === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("parameter $type must be set, null is not allowed"); } if ($locale === null) { $locale = $this->getLocale(); } if ($value instanceof Zend_Date) { // extract value from object $value = $value->toString($parameter, 'iso', $locale); } else if (!is_array($value) && !is_numeric($value) && ($type != 'iso') && ($type != 'arpa')) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid $type ($value) operand", 0, null, $value); } $return = $this->_calcdetail($calc, $value, $parameter, $locale); if ($calc != 'cmp') { return $this; } return $return; } /** * Returns only the year from the date object as new object. * For example: 10.May.2000 10:30:00 -> 01.Jan.2000 00:00:00 * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getYear($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'Y'; } else { $format = self::YEAR; } return $this->copyPart($format, $locale); } /** * Sets a new year * If the year is between 0 and 69, 2000 will be set (2000-2069) * If the year if between 70 and 99, 1999 will be set (1970-1999) * 3 or 4 digit years are set as expected. If you need to set year 0-99 * use set() instead. * Returned is the new date object * * @param string|integer|array|Zend_Date $date Year to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setYear($year, $locale = null) { return $this->_calcvalue('set', $year, 'year', self::YEAR, $locale); } /** * Adds the year to the existing date object * If the year is between 0 and 69, 2000 will be added (2000-2069) * If the year if between 70 and 99, 1999 will be added (1970-1999) * 3 or 4 digit years are added as expected. If you need to add years from 0-99 * use add() instead. * Returned is the new date object * * @param string|integer|array|Zend_Date $date Year to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addYear($year, $locale = null) { return $this->_calcvalue('add', $year, 'year', self::YEAR, $locale); } /** * Subs the year from the existing date object * If the year is between 0 and 69, 2000 will be subtracted (2000-2069) * If the year if between 70 and 99, 1999 will be subtracted (1970-1999) * 3 or 4 digit years are subtracted as expected. If you need to subtract years from 0-99 * use sub() instead. * Returned is the new date object * * @param string|integer|array|Zend_Date $date Year to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subYear($year, $locale = null) { return $this->_calcvalue('sub', $year, 'year', self::YEAR, $locale); } /** * Compares the year with the existing date object, ignoring other date parts. * For example: 10.03.2000 -> 15.02.2000 -> true * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $year Year to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareYear($year, $locale = null) { return $this->_calcvalue('cmp', $year, 'year', self::YEAR, $locale); } /** * Returns only the month from the date object as new object. * For example: 10.May.2000 10:30:00 -> 01.May.1970 00:00:00 * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getMonth($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'm'; } else { $format = self::MONTH; } return $this->copyPart($format, $locale); } /** * Returns the calculated month * * @param string $calc Calculation to make * @param string|integer|array|Zend_Date $month Month to calculate with, if null the actual month is taken * @param string|Zend_Locale $locale Locale for parsing input * @return integer|Zend_Date new time * @throws Zend_Date_Exception */ private function _month($calc, $month, $locale) { if ($month === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('parameter $month must be set, null is not allowed'); } if ($locale === null) { $locale = $this->getLocale(); } if ($month instanceof Zend_Date) { // extract month from object $found = $month->toString(self::MONTH_SHORT, 'iso', $locale); } else { if (is_numeric($month)) { $found = $month; } else if (is_array($month)) { if (isset($month['month']) === true) { $month = $month['month']; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no month given in array"); } } else { $monthlist = Zend_Locale_Data::getList($locale, 'month'); $monthlist2 = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); $monthlist = array_merge($monthlist, $monthlist2); $found = 0; $cnt = 0; foreach ($monthlist as $key => $value) { if (strtoupper($value) == strtoupper($month)) { $found = ($key % 12) + 1; break; } ++$cnt; } if ($found == 0) { foreach ($monthlist2 as $key => $value) { if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($month)) { $found = $key + 1; break; } ++$cnt; } } if ($found == 0) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("unknown month name ($month)", 0, null, $month); } } } $return = $this->_calcdetail($calc, $found, self::MONTH_SHORT, $locale); if ($calc != 'cmp') { return $this; } return $return; } /** * Sets a new month * The month can be a number or a string. Setting months lower then 0 and greater then 12 * will result in adding or subtracting the relevant year. (12 months equal one year) * If a localized monthname is given it will be parsed with the default locale or the optional * set locale. * Returned is the new date object * * @param string|integer|array|Zend_Date $month Month to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setMonth($month, $locale = null) { return $this->_month('set', $month, $locale); } /** * Adds months to the existing date object. * The month can be a number or a string. Adding months lower then 0 and greater then 12 * will result in adding or subtracting the relevant year. (12 months equal one year) * If a localized monthname is given it will be parsed with the default locale or the optional * set locale. * Returned is the new date object * * @param string|integer|array|Zend_Date $month Month to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addMonth($month, $locale = null) { return $this->_month('add', $month, $locale); } /** * Subtracts months from the existing date object. * The month can be a number or a string. Subtracting months lower then 0 and greater then 12 * will result in adding or subtracting the relevant year. (12 months equal one year) * If a localized monthname is given it will be parsed with the default locale or the optional * set locale. * Returned is the new date object * * @param string|integer|array|Zend_Date $month Month to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subMonth($month, $locale = null) { return $this->_month('sub', $month, $locale); } /** * Compares the month with the existing date object, ignoring other date parts. * For example: 10.03.2000 -> 15.03.1950 -> true * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $month Month to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareMonth($month, $locale = null) { return $this->_month('cmp', $month, $locale); } /** * Returns the day as new date object * Example: 20.May.1986 -> 20.Jan.1970 00:00:00 * * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getDay($locale = null) { return $this->copyPart(self::DAY_SHORT, $locale); } /** * Returns the calculated day * * @param $calc string Type of calculation to make * @param $day string|integer|Zend_Date Day to calculate, when null the actual day is calculated * @param $locale string|Zend_Locale Locale for parsing input * @return Zend_Date|integer */ private function _day($calc, $day, $locale) { if ($day === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('parameter $day must be set, null is not allowed'); } if ($locale === null) { $locale = $this->getLocale(); } if ($day instanceof Zend_Date) { $day = $day->toString(self::DAY_SHORT, 'iso', $locale); } if (is_numeric($day)) { $type = self::DAY_SHORT; } else if (is_array($day)) { if (isset($day['day']) === true) { $day = $day['day']; $type = self::WEEKDAY; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no day given in array"); } } else { switch (iconv_strlen($day, 'UTF-8')) { case 1 : $type = self::WEEKDAY_NARROW; break; case 2: $type = self::WEEKDAY_NAME; break; case 3: $type = self::WEEKDAY_SHORT; break; default: $type = self::WEEKDAY; break; } } $return = $this->_calcdetail($calc, $day, $type, $locale); if ($calc != 'cmp') { return $this; } return $return; } /** * Sets a new day * The day can be a number or a string. Setting days lower then 0 or greater than the number of this months days * will result in adding or subtracting the relevant month. * If a localized dayname is given it will be parsed with the default locale or the optional * set locale. * Returned is the new date object * Example: setDay('Montag', 'de_AT'); will set the monday of this week as day. * * @param string|integer|array|Zend_Date $month Day to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setDay($day, $locale = null) { return $this->_day('set', $day, $locale); } /** * Adds days to the existing date object. * The day can be a number or a string. Adding days lower then 0 or greater than the number of this months days * will result in adding or subtracting the relevant month. * If a localized dayname is given it will be parsed with the default locale or the optional * set locale. * * @param string|integer|array|Zend_Date $month Day to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addDay($day, $locale = null) { return $this->_day('add', $day, $locale); } /** * Subtracts days from the existing date object. * The day can be a number or a string. Subtracting days lower then 0 or greater than the number of this months days * will result in adding or subtracting the relevant month. * If a localized dayname is given it will be parsed with the default locale or the optional * set locale. * * @param string|integer|array|Zend_Date $month Day to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subDay($day, $locale = null) { return $this->_day('sub', $day, $locale); } /** * Compares the day with the existing date object, ignoring other date parts. * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $day Day to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareDay($day, $locale = null) { return $this->_day('cmp', $day, $locale); } /** * Returns the weekday as new date object * Weekday is always from 1-7 * Example: 09-Jan-2007 -> 2 = Tuesday -> 02-Jan-1970 (when 02.01.1970 is also Tuesday) * * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getWeekday($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'l'; } else { $format = self::WEEKDAY; } return $this->copyPart($format, $locale); } /** * Returns the calculated weekday * * @param $calc string Type of calculation to make * @param $weekday string|integer|array|Zend_Date Weekday to calculate, when null the actual weekday is calculated * @param $locale string|Zend_Locale Locale for parsing input * @return Zend_Date|integer * @throws Zend_Date_Exception */ private function _weekday($calc, $weekday, $locale) { if ($weekday === null) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('parameter $weekday must be set, null is not allowed'); } if ($locale === null) { $locale = $this->getLocale(); } if ($weekday instanceof Zend_Date) { $weekday = $weekday->toString(self::WEEKDAY_8601, 'iso', $locale); } if (is_numeric($weekday)) { $type = self::WEEKDAY_8601; } else if (is_array($weekday)) { if (isset($weekday['weekday']) === true) { $weekday = $weekday['weekday']; $type = self::WEEKDAY; } else { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("no weekday given in array"); } } else { switch(iconv_strlen($weekday, 'UTF-8')) { case 1: $type = self::WEEKDAY_NARROW; break; case 2: $type = self::WEEKDAY_NAME; break; case 3: $type = self::WEEKDAY_SHORT; break; default: $type = self::WEEKDAY; break; } } $return = $this->_calcdetail($calc, $weekday, $type, $locale); if ($calc != 'cmp') { return $this; } return $return; } /** * Sets a new weekday * The weekday can be a number or a string. If a localized weekday name is given, * then it will be parsed as a date in $locale (defaults to the same locale as $this). * Returned is the new date object. * Example: setWeekday(3); will set the wednesday of this week as day. * * @param string|integer|array|Zend_Date $month Weekday to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setWeekday($weekday, $locale = null) { return $this->_weekday('set', $weekday, $locale); } /** * Adds weekdays to the existing date object. * The weekday can be a number or a string. * If a localized dayname is given it will be parsed with the default locale or the optional * set locale. * Returned is the new date object * Example: addWeekday(3); will add the difference of days from the begining of the month until * wednesday. * * @param string|integer|array|Zend_Date $month Weekday to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addWeekday($weekday, $locale = null) { return $this->_weekday('add', $weekday, $locale); } /** * Subtracts weekdays from the existing date object. * The weekday can be a number or a string. * If a localized dayname is given it will be parsed with the default locale or the optional * set locale. * Returned is the new date object * Example: subWeekday(3); will subtract the difference of days from the begining of the month until * wednesday. * * @param string|integer|array|Zend_Date $month Weekday to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subWeekday($weekday, $locale = null) { return $this->_weekday('sub', $weekday, $locale); } /** * Compares the weekday with the existing date object, ignoring other date parts. * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $weekday Weekday to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareWeekday($weekday, $locale = null) { return $this->_weekday('cmp', $weekday, $locale); } /** * Returns the day of year as new date object * Example: 02.Feb.1986 10:00:00 -> 02.Feb.1970 00:00:00 * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getDayOfYear($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'D'; } else { $format = self::DAY_OF_YEAR; } return $this->copyPart($format, $locale); } /** * Sets a new day of year * The day of year is always a number. * Returned is the new date object * Example: 04.May.2004 -> setDayOfYear(10) -> 10.Jan.2004 * * @param string|integer|array|Zend_Date $day Day of Year to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setDayOfYear($day, $locale = null) { return $this->_calcvalue('set', $day, 'day of year', self::DAY_OF_YEAR, $locale); } /** * Adds a day of year to the existing date object. * The day of year is always a number. * Returned is the new date object * Example: addDayOfYear(10); will add 10 days to the existing date object. * * @param string|integer|array|Zend_Date $day Day of Year to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addDayOfYear($day, $locale = null) { return $this->_calcvalue('add', $day, 'day of year', self::DAY_OF_YEAR, $locale); } /** * Subtracts a day of year from the existing date object. * The day of year is always a number. * Returned is the new date object * Example: subDayOfYear(10); will subtract 10 days from the existing date object. * * @param string|integer|array|Zend_Date $day Day of Year to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subDayOfYear($day, $locale = null) { return $this->_calcvalue('sub', $day, 'day of year', self::DAY_OF_YEAR, $locale); } /** * Compares the day of year with the existing date object. * For example: compareDayOfYear(33) -> 02.Feb.2007 -> 0 * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $day Day of Year to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareDayOfYear($day, $locale = null) { return $this->_calcvalue('cmp', $day, 'day of year', self::DAY_OF_YEAR, $locale); } /** * Returns the hour as new date object * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 10:00:00 * * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getHour($locale = null) { return $this->copyPart(self::HOUR, $locale); } /** * Sets a new hour * The hour is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> setHour(7); -> 04.May.1993 07:07:25 * * @param string|integer|array|Zend_Date $hour Hour to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setHour($hour, $locale = null) { return $this->_calcvalue('set', $hour, 'hour', self::HOUR_SHORT, $locale); } /** * Adds hours to the existing date object. * The hour is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> addHour(12); -> 05.May.1993 01:07:25 * * @param string|integer|array|Zend_Date $hour Hour to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addHour($hour, $locale = null) { return $this->_calcvalue('add', $hour, 'hour', self::HOUR_SHORT, $locale); } /** * Subtracts hours from the existing date object. * The hour is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> subHour(6); -> 05.May.1993 07:07:25 * * @param string|integer|array|Zend_Date $hour Hour to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subHour($hour, $locale = null) { return $this->_calcvalue('sub', $hour, 'hour', self::HOUR_SHORT, $locale); } /** * Compares the hour with the existing date object. * For example: 10:30:25 -> compareHour(10) -> 0 * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $hour Hour to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareHour($hour, $locale = null) { return $this->_calcvalue('cmp', $hour, 'hour', self::HOUR_SHORT, $locale); } /** * Returns the minute as new date object * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:30:00 * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getMinute($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'i'; } else { $format = self::MINUTE; } return $this->copyPart($format, $locale); } /** * Sets a new minute * The minute is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> setMinute(29); -> 04.May.1993 13:29:25 * * @param string|integer|array|Zend_Date $minute Minute to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setMinute($minute, $locale = null) { return $this->_calcvalue('set', $minute, 'minute', self::MINUTE_SHORT, $locale); } /** * Adds minutes to the existing date object. * The minute is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> addMinute(65); -> 04.May.1993 13:12:25 * * @param string|integer|array|Zend_Date $minute Minute to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addMinute($minute, $locale = null) { return $this->_calcvalue('add', $minute, 'minute', self::MINUTE_SHORT, $locale); } /** * Subtracts minutes from the existing date object. * The minute is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> subMinute(9); -> 04.May.1993 12:58:25 * * @param string|integer|array|Zend_Date $minute Minute to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subMinute($minute, $locale = null) { return $this->_calcvalue('sub', $minute, 'minute', self::MINUTE_SHORT, $locale); } /** * Compares the minute with the existing date object. * For example: 10:30:25 -> compareMinute(30) -> 0 * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $minute Hour to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareMinute($minute, $locale = null) { return $this->_calcvalue('cmp', $minute, 'minute', self::MINUTE_SHORT, $locale); } /** * Returns the second as new date object * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:00:25 * * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getSecond($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 's'; } else { $format = self::SECOND; } return $this->copyPart($format, $locale); } /** * Sets new seconds to the existing date object. * The second is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> setSecond(100); -> 04.May.1993 13:08:40 * * @param string|integer|array|Zend_Date $second Second to set * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setSecond($second, $locale = null) { return $this->_calcvalue('set', $second, 'second', self::SECOND_SHORT, $locale); } /** * Adds seconds to the existing date object. * The second is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> addSecond(65); -> 04.May.1993 13:08:30 * * @param string|integer|array|Zend_Date $second Second to add * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addSecond($second, $locale = null) { return $this->_calcvalue('add', $second, 'second', self::SECOND_SHORT, $locale); } /** * Subtracts seconds from the existing date object. * The second is always a number. * Returned is the new date object * Example: 04.May.1993 13:07:25 -> subSecond(10); -> 04.May.1993 13:07:15 * * @param string|integer|array|Zend_Date $second Second to sub * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subSecond($second, $locale = null) { return $this->_calcvalue('sub', $second, 'second', self::SECOND_SHORT, $locale); } /** * Compares the second with the existing date object. * For example: 10:30:25 -> compareSecond(25) -> 0 * Returns if equal, earlier or later * * @param string|integer|array|Zend_Date $second Second to compare * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier * @throws Zend_Date_Exception */ public function compareSecond($second, $locale = null) { return $this->_calcvalue('cmp', $second, 'second', self::SECOND_SHORT, $locale); } /** * Returns the precision for fractional seconds * * @return integer */ public function getFractionalPrecision() { return $this->_precision; } /** * Sets a new precision for fractional seconds * * @param integer $precision Precision for the fractional datepart 3 = milliseconds * @throws Zend_Date_Exception * @return Zend_Date Provides fluid interface */ public function setFractionalPrecision($precision) { if (!intval($precision) or ($precision < 0) or ($precision > 9)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); } $this->_precision = (int) $precision; if ($this->_precision < strlen($this->_fractional)) { $this->_fractional = substr($this->_fractional, 0, $this->_precision); } else { $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_RIGHT); } return $this; } /** * Returns the milliseconds of the date object * * @return string */ public function getMilliSecond() { return $this->_fractional; } /** * Sets new milliseconds for the date object * Example: setMilliSecond(550, 2) -> equals +5 Sec +50 MilliSec * * @param integer|Zend_Date $milli (Optional) Millisecond to set, when null the actual millisecond is set * @param integer $precision (Optional) Fraction precision of the given milliseconds * @return Zend_Date Provides fluid interface */ public function setMilliSecond($milli = null, $precision = null) { if ($milli === null) { list($milli, $time) = explode(" ", microtime()); $milli = intval($milli); $precision = 6; } else if (!is_numeric($milli)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); } if ($precision === null) { $precision = $this->_precision; } if (!is_int($precision) || $precision < 1 || $precision > 9) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); } $this->_fractional = 0; $this->addMilliSecond($milli, $precision); return $this; } /** * Adds milliseconds to the date object * * @param integer|Zend_Date $milli (Optional) Millisecond to add, when null the actual millisecond is added * @param integer $precision (Optional) Fractional precision for the given milliseconds * @return Zend_Date Provides fluid interface */ public function addMilliSecond($milli = null, $precision = null) { if ($milli === null) { list($milli, $time) = explode(" ", microtime()); $milli = intval($milli); } else if (!is_numeric($milli)) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); } if ($precision === null) { $precision = strlen($milli); if ($milli < 0) { --$precision; } } if (!is_int($precision) || $precision < 1 || $precision > 9) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); } $this->_fractional += $milli; // Add/sub milliseconds + add/sub seconds $max = pow(10, $this->_precision); // Milli includes seconds if ($this->_fractional >= $max) { while ($this->_fractional >= $max) { $this->addSecond(1); $this->_fractional -= $max; } } if ($this->_fractional < 0) { while ($this->_fractional < 0) { $this->subSecond(1); $this->_fractional += $max; } } if ($this->_precision > strlen($this->_fractional)) { $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_LEFT); } return $this; } /** * Subtracts a millisecond * * @param integer|Zend_Date $milli (Optional) Millisecond to sub, when null the actual millisecond is subtracted * @param integer $precision (Optional) Fractional precision for the given milliseconds * @return Zend_Date Provides fluid interface */ public function subMilliSecond($milli = null, $precision = null) { $this->addMilliSecond(0 - $milli, $precision); return $this; } /** * Compares only the millisecond part, returning the difference * * @param integer|Zend_Date $milli OPTIONAL Millisecond to compare, when null the actual millisecond is compared * @param integer $precision OPTIONAL Fractional precision for the given milliseconds * @throws Zend_Date_Exception On invalid input * @return integer 0 = equal, 1 = later, -1 = earlier */ public function compareMilliSecond($milli = null, $precision = null) { if ($milli === null) { list($milli, $time) = explode(" ", microtime()); $milli = intval($milli); } else if (is_numeric($milli) === false) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); } if ($precision === null) { $precision = strlen($milli); } else if (!is_int($precision) || $precision < 1 || $precision > 9) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); } if ($precision === 0) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception('precision is 0'); } if ($precision != $this->_precision) { if ($precision > $this->_precision) { $diff = $precision - $this->_precision; $milli = (int) ($milli / (10 * $diff)); } else { $diff = $this->_precision - $precision; $milli = (int) ($milli * (10 * $diff)); } } $comp = $this->_fractional - $milli; if ($comp < 0) { return -1; } else if ($comp > 0) { return 1; } return 0; } /** * Returns the week as new date object using monday as begining of the week * Example: 12.Jan.2007 -> 08.Jan.1970 00:00:00 * * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input * @return Zend_Date */ public function getWeek($locale = null) { if (self::$_options['format_type'] == 'php') { $format = 'W'; } else { $format = self::WEEK; } return $this->copyPart($format, $locale); } /** * Sets a new week. The week is always a number. The day of week is not changed. * Returned is the new date object * Example: 09.Jan.2007 13:07:25 -> setWeek(1); -> 02.Jan.2007 13:07:25 * * @param string|integer|array|Zend_Date $week Week to set * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function setWeek($week, $locale = null) { return $this->_calcvalue('set', $week, 'week', self::WEEK, $locale); } /** * Adds a week. The week is always a number. The day of week is not changed. * Returned is the new date object * Example: 09.Jan.2007 13:07:25 -> addWeek(1); -> 16.Jan.2007 13:07:25 * * @param string|integer|array|Zend_Date $week Week to add * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function addWeek($week, $locale = null) { return $this->_calcvalue('add', $week, 'week', self::WEEK, $locale); } /** * Subtracts a week. The week is always a number. The day of week is not changed. * Returned is the new date object * Example: 09.Jan.2007 13:07:25 -> subWeek(1); -> 02.Jan.2007 13:07:25 * * @param string|integer|array|Zend_Date $week Week to sub * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return Zend_Date Provides fluid interface * @throws Zend_Date_Exception */ public function subWeek($week, $locale = null) { return $this->_calcvalue('sub', $week, 'week', self::WEEK, $locale); } /** * Compares only the week part, returning the difference * Returned is the new date object * Returns if equal, earlier or later * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0 * * @param string|integer|array|Zend_Date $week Week to compare * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input * @return integer 0 = equal, 1 = later, -1 = earlier */ public function compareWeek($week, $locale = null) { return $this->_calcvalue('cmp', $week, 'week', self::WEEK, $locale); } /** * Sets a new standard locale for the date object. * This locale will be used for all functions * Returned is the really set locale. * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist * 'xx_YY' will be set to 'root' because 'xx' does not exist * * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @throws Zend_Date_Exception When the given locale does not exist * @return Zend_Date Provides fluent interface */ public function setLocale($locale = null) { try { $this->_locale = Zend_Locale::findLocale($locale); } catch (Zend_Locale_Exception $e) { #require_once 'Zend/Date/Exception.php'; throw new Zend_Date_Exception($e->getMessage(), 0, $e); } return $this; } /** * Returns the actual set locale * * @return string */ public function getLocale() { return $this->_locale; } /** * Checks if the given date is a real date or datepart. * Returns false if a expected datepart is missing or a datepart exceeds its possible border. * But the check will only be done for the expected dateparts which are given by format. * If no format is given the standard dateformat for the actual locale is used. * f.e. 30.February.2007 will return false if format is 'dd.MMMM.YYYY' * * @param string|array|Zend_Date $date Date to parse for correctness * @param string $format (Optional) Format for parsing the date string * @param string|Zend_Locale $locale (Optional) Locale for parsing date parts * @return boolean True when all date parts are correct */ public static function isDate($date, $format = null, $locale = null) { if (!is_string($date) && !is_numeric($date) && !($date instanceof Zend_Date) && !is_array($date)) { return false; } if (($format !== null) && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT') && (Zend_Locale::isLocale($format, null, false))) { $locale = $format; $format = null; } $locale = Zend_Locale::findLocale($locale); if ($format === null) { $format = Zend_Locale_Format::getDateFormat($locale); } else if ((self::$_options['format_type'] == 'php') && !defined($format)) { $format = Zend_Locale_Format::convertPhpToIsoFormat($format); } $format = self::_getLocalizedToken($format, $locale); if (!is_array($date)) { try { $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, 'date_format' => $format, 'format_type' => 'iso', 'fix_date' => false)); } catch (Zend_Locale_Exception $e) { // Date can not be parsed return false; } } else { $parsed = $date; } if (((strpos($format, 'Y') !== false) or (strpos($format, 'y') !== false)) and (!isset($parsed['year']))) { // Year expected but not found return false; } if ((strpos($format, 'M') !== false) and (!isset($parsed['month']))) { // Month expected but not found return false; } if ((strpos($format, 'd') !== false) and (!isset($parsed['day']))) { // Day expected but not found return false; } if (((strpos($format, 'H') !== false) or (strpos($format, 'h') !== false)) and (!isset($parsed['hour']))) { // Hour expected but not found return false; } if ((strpos($format, 'm') !== false) and (!isset($parsed['minute']))) { // Minute expected but not found return false; } if ((strpos($format, 's') !== false) and (!isset($parsed['second']))) { // Second expected but not found return false; } // Set not given dateparts if (isset($parsed['hour']) === false) { $parsed['hour'] = 12; } if (isset($parsed['minute']) === false) { $parsed['minute'] = 0; } if (isset($parsed['second']) === false) { $parsed['second'] = 0; } if (isset($parsed['month']) === false) { $parsed['month'] = 1; } if (isset($parsed['day']) === false) { $parsed['day'] = 1; } if (isset($parsed['year']) === false) { $parsed['year'] = 1970; } if (self::isYearLeapYear($parsed['year'])) { $parsed['year'] = 1972; } else { $parsed['year'] = 1971; } $date = new self($parsed, null, $locale); $timestamp = $date->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $parsed['month'], $parsed['day'], $parsed['year']); if ($parsed['year'] != $date->date('Y', $timestamp)) { // Given year differs from parsed year return false; } if ($parsed['month'] != $date->date('n', $timestamp)) { // Given month differs from parsed month return false; } if ($parsed['day'] != $date->date('j', $timestamp)) { // Given day differs from parsed day return false; } if ($parsed['hour'] != $date->date('G', $timestamp)) { // Given hour differs from parsed hour return false; } if ($parsed['minute'] != $date->date('i', $timestamp)) { // Given minute differs from parsed minute return false; } if ($parsed['second'] != $date->date('s', $timestamp)) { // Given second differs from parsed second return false; } return true; } /** * Returns the ISO Token for all localized constants * * @param string $token Token to normalize * @param string $locale Locale to search * @return string */ protected static function _getLocalizedToken($token, $locale) { switch($token) { case self::ISO_8601 : return "yyyy-MM-ddThh:mm:ss"; break; case self::RFC_2822 : return "EEE, dd MMM yyyy HH:mm:ss"; break; case self::DATES : return Zend_Locale_Data::getContent($locale, 'date'); break; case self::DATE_FULL : return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); break; case self::DATE_LONG : return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); break; case self::DATE_MEDIUM : return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); break; case self::DATE_SHORT : return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); break; case self::TIMES : return Zend_Locale_Data::getContent($locale, 'time'); break; case self::TIME_FULL : return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); break; case self::TIME_LONG : return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); break; case self::TIME_MEDIUM : return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); break; case self::TIME_SHORT : return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); break; case self::DATETIME : return Zend_Locale_Data::getContent($locale, 'datetime'); break; case self::DATETIME_FULL : return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); break; case self::DATETIME_LONG : return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); break; case self::DATETIME_MEDIUM : return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); break; case self::DATETIME_SHORT : return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); break; case self::ATOM : case self::RFC_3339 : case self::W3C : return "yyyy-MM-DD HH:mm:ss"; break; case self::COOKIE : case self::RFC_850 : return "EEEE, dd-MM-yyyy HH:mm:ss"; break; case self::RFC_822 : case self::RFC_1036 : case self::RFC_1123 : case self::RSS : return "EEE, dd MM yyyy HH:mm:ss"; break; } return $token; } /** * Get unix timestamp. * Added limitation: $year value must be between -10 000 and 10 000 * Parent method implementation causes 504 error if it gets too big(small) year value * * @see Zend_Date_DateObject::mktime * @throws Zend_Date_Exception * @param $hour * @param $minute * @param $second * @param $month * @param $day * @param $year * @param bool $gmt * @return float|int */ protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false) { $day = intval($day); $month = intval($month); $year = intval($year); // correct months > 12 and months < 1 if ($month > 12) { $overlap = floor($month / 12); $year += $overlap; $month -= $overlap * 12; } else { $overlap = ceil((1 - $month) / 12); $year -= $overlap; $month += $overlap * 12; } if ($year > self::YEAR_MAX_VALUE || $year < self::YEAR_MIN_VALUE) { throw new Zend_Date_Exception('Invalid year, it must be between ' . self::YEAR_MIN_VALUE . ' and ' . self::YEAR_MAX_VALUE); } return parent::mktime($hour, $minute, $second, $month, $day, $year, $gmt); } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Locale * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * Base class for localization * * @category Zend * @package Zend_Locale * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Locale { /** * Class wide Locale Constants * * @var array $_localeData */ private static $_localeData = array( 'root' => true, 'aa_DJ' => true, 'aa_ER' => true, 'aa_ET' => true, 'aa' => true, 'af_NA' => true, 'af_ZA' => true, 'af' => true, 'ak_GH' => true, 'ak' => true, 'am_ET' => true, 'am' => true, 'ar_AE' => true, 'ar_BH' => true, 'ar_DZ' => true, 'ar_EG' => true, 'ar_IQ' => true, 'ar_JO' => true, 'ar_KW' => true, 'ar_LB' => true, 'ar_LY' => true, 'ar_MA' => true, 'ar_OM' => true, 'ar_QA' => true, 'ar_SA' => true, 'ar_SD' => true, 'ar_SY' => true, 'ar_TN' => true, 'ar_YE' => true, 'ar' => true, 'as_IN' => true, 'as' => true, 'az_AZ' => true, 'az' => true, 'be_BY' => true, 'be' => true, 'bg_BG' => true, 'bg' => true, 'bn_BD' => true, 'bn_IN' => true, 'bn' => true, 'bo_CN' => true, 'bo_IN' => true, 'bo' => true, 'bs_BA' => true, 'bs' => true, 'byn_ER'=> true, 'byn' => true, 'ca_ES' => true, 'ca' => true, 'cch_NG'=> true, 'cch' => true, 'cop' => true, 'cs_CZ' => true, 'cs' => true, 'cy_GB' => true, 'cy' => true, 'da_DK' => true, 'da' => true, 'de_AT' => true, 'de_BE' => true, 'de_CH' => true, 'de_DE' => true, 'de_LI' => true, 'de_LU' => true, 'de' => true, 'dv_MV' => true, 'dv' => true, 'dz_BT' => true, 'dz' => true, 'ee_GH' => true, 'ee_TG' => true, 'ee' => true, 'el_CY' => true, 'el_GR' => true, 'el' => true, 'en_AS' => true, 'en_AU' => true, 'en_BE' => true, 'en_BW' => true, 'en_BZ' => true, 'en_CA' => true, 'en_GB' => true, 'en_GU' => true, 'en_HK' => true, 'en_IE' => true, 'en_IN' => true, 'en_JM' => true, 'en_MH' => true, 'en_MP' => true, 'en_MT' => true, 'en_NA' => true, 'en_NZ' => true, 'en_PH' => true, 'en_PK' => true, 'en_SG' => true, 'en_TT' => true, 'en_UM' => true, 'en_US' => true, 'en_VI' => true, 'en_ZA' => true, 'en_ZW' => true, 'en' => true, 'eo' => true, 'es_AR' => true, 'es_BO' => true, 'es_CL' => true, 'es_CO' => true, 'es_CR' => true, 'es_DO' => true, 'es_EC' => true, 'es_ES' => true, 'es_GT' => true, 'es_HN' => true, 'es_MX' => true, 'es_NI' => true, 'es_PA' => true, 'es_PE' => true, 'es_PR' => true, 'es_PY' => true, 'es_SV' => true, 'es_US' => true, 'es_UY' => true, 'es_VE' => true, 'es' => true, 'et_EE' => true, 'et' => true, 'eu_ES' => true, 'eu' => true, 'fa_AF' => true, 'fa_IR' => true, 'fa' => true, 'fi_FI' => true, 'fi' => true, 'fil_PH'=> true, 'fil' => true, 'fo_FO' => true, 'fo' => true, 'fr_BE' => true, 'fr_CA' => true, 'fr_CH' => true, 'fr_FR' => true, 'fr_LU' => true, 'fr_MC' => true, 'fr_SN' => true, 'fr' => true, 'fur_IT'=> true, 'fur' => true, 'ga_IE' => true, 'ga' => true, 'gaa_GH'=> true, 'gaa' => true, 'gez_ER'=> true, 'gez_ET'=> true, 'gez' => true, 'gl_ES' => true, 'gl' => true, 'gsw_CH'=> true, 'gsw' => true, 'gu_IN' => true, 'gu' => true, 'gv_GB' => true, 'gv' => true, 'ha_GH' => true, 'ha_NE' => true, 'ha_NG' => true, 'ha_SD' => true, 'ha' => true, 'haw_US'=> true, 'haw' => true, 'he_IL' => true, 'he' => true, 'hi_IN' => true, 'hi' => true, 'hr_HR' => true, 'hr' => true, 'hu_HU' => true, 'hu' => true, 'hy_AM' => true, 'hy' => true, 'ia' => true, 'id_ID' => true, 'id' => true, 'ig_NG' => true, 'ig' => true, 'ii_CN' => true, 'ii' => true, 'in' => true, 'is_IS' => true, 'is' => true, 'it_CH' => true, 'it_IT' => true, 'it' => true, 'iu' => true, 'iw' => true, 'ja_JP' => true, 'ja' => true, 'ka_GE' => true, 'ka' => true, 'kaj_NG'=> true, 'kaj' => true, 'kam_KE'=> true, 'kam' => true, 'kcg_NG'=> true, 'kcg' => true, 'kfo_CI'=> true, 'kfo' => true, 'kk_KZ' => true, 'kk' => true, 'kl_GL' => true, 'kl' => true, 'km_KH' => true, 'km' => true, 'kn_IN' => true, 'kn' => true, 'ko_KR' => true, 'ko' => true, 'kok_IN'=> true, 'kok' => true, 'kpe_GN'=> true, 'kpe_LR'=> true, 'kpe' => true, 'ku_IQ' => true, 'ku_IR' => true, 'ku_SY' => true, 'ku_TR' => true, 'ku' => true, 'kw_GB' => true, 'kw' => true, 'ky_KG' => true, 'ky' => true, 'ln_CD' => true, 'ln_CG' => true, 'ln' => true, 'lo_LA' => true, 'lo' => true, 'lt_LT' => true, 'lt' => true, 'lv_LV' => true, 'lv' => true, 'mk_MK' => true, 'mk' => true, 'ml_IN' => true, 'ml' => true, 'mn_CN' => true, 'mn_MN' => true, 'mn' => true, 'mo' => true, 'mr_IN' => true, 'mr' => true, 'ms_BN' => true, 'ms_MY' => true, 'ms' => true, 'mt_MT' => true, 'mt' => true, 'my_MM' => true, 'my' => true, 'nb_NO' => true, 'nb' => true, 'nds_DE'=> true, 'nds' => true, 'ne_IN' => true, 'ne_NP' => true, 'ne' => true, 'nl_BE' => true, 'nl_NL' => true, 'nl' => true, 'nn_NO' => true, 'nn' => true, 'no' => true, 'nr_ZA' => true, 'nr' => true, 'nso_ZA'=> true, 'nso' => true, 'ny_MW' => true, 'ny' => true, 'oc_FR' => true, 'oc' => true, 'om_ET' => true, 'om_KE' => true, 'om' => true, 'or_IN' => true, 'or' => true, 'pa_IN' => true, 'pa_PK' => true, 'pa' => true, 'pl_PL' => true, 'pl' => true, 'ps_AF' => true, 'ps' => true, 'pt_BR' => true, 'pt_PT' => true, 'pt' => true, 'ro_MD' => true, 'ro_RO' => true, 'ro' => true, 'ru_RU' => true, 'ru_UA' => true, 'ru' => true, 'rw_RW' => true, 'rw' => true, 'sa_IN' => true, 'sa' => true, 'se_FI' => true, 'se_NO' => true, 'se' => true, 'sh_BA' => true, 'sh_CS' => true, 'sh_YU' => true, 'sh' => true, 'si_LK' => true, 'si' => true, 'sid_ET'=> true, 'sid' => true, 'sk_SK' => true, 'sk' => true, 'sl_SI' => true, 'sl' => true, 'so_DJ' => true, 'so_ET' => true, 'so_KE' => true, 'so_SO' => true, 'so' => true, 'sq_AL' => true, 'sq' => true, 'sr_BA' => true, 'sr_CS' => true, 'sr_ME' => true, 'sr_RS' => true, 'sr_YU' => true, 'sr' => true, 'ss_SZ' => true, 'ss_ZA' => true, 'ss' => true, 'st_LS' => true, 'st_ZA' => true, 'st' => true, 'sv_FI' => true, 'sv_SE' => true, 'sv' => true, 'sw_KE' => true, 'sw_TZ' => true, 'sw' => true, 'syr_SY'=> true, 'syr' => true, 'ta_IN' => true, 'ta' => true, 'te_IN' => true, 'te' => true, 'tg_TJ' => true, 'tg' => true, 'th_TH' => true, 'th' => true, 'ti_ER' => true, 'ti_ET' => true, 'ti' => true, 'tig_ER'=> true, 'tig' => true, 'tl' => true, 'tn_ZA' => true, 'tn' => true, 'to_TO' => true, 'to' => true, 'tr_TR' => true, 'tr' => true, 'trv_TW'=> true, 'trv' => true, 'ts_ZA' => true, 'ts' => true, 'tt_RU' => true, 'tt' => true, 'ug_CN' => true, 'ug' => true, 'uk_UA' => true, 'uk' => true, 'ur_IN' => true, 'ur_PK' => true, 'ur' => true, 'uz_AF' => true, 'uz_UZ' => true, 'uz' => true, 've_ZA' => true, 've' => true, 'vi_VN' => true, 'vi' => true, 'wal_ET'=> true, 'wal' => true, 'wo_SN' => true, 'wo' => true, 'xh_ZA' => true, 'xh' => true, 'yo_NG' => true, 'yo' => true, 'zh_CN' => true, 'zh_HK' => true, 'zh_MO' => true, 'zh_SG' => true, 'zh_TW' => true, 'zh' => true, 'zu_ZA' => true, 'zu' => true ); /** * Class wide Locale Constants * * @var array $_territoryData */ private static $_territoryData = array( 'AD' => 'ca_AD', 'AE' => 'ar_AE', 'AF' => 'fa_AF', 'AG' => 'en_AG', 'AI' => 'en_AI', 'AL' => 'sq_AL', 'AM' => 'hy_AM', 'AN' => 'pap_AN', 'AO' => 'pt_AO', 'AQ' => 'und_AQ', 'AR' => 'es_AR', 'AS' => 'sm_AS', 'AT' => 'de_AT', 'AU' => 'en_AU', 'AW' => 'nl_AW', 'AX' => 'sv_AX', 'AZ' => 'az_Latn_AZ', 'BA' => 'bs_BA', 'BB' => 'en_BB', 'BD' => 'bn_BD', 'BE' => 'nl_BE', 'BF' => 'mos_BF', 'BG' => 'bg_BG', 'BH' => 'ar_BH', 'BI' => 'rn_BI', 'BJ' => 'fr_BJ', 'BL' => 'fr_BL', 'BM' => 'en_BM', 'BN' => 'ms_BN', 'BO' => 'es_BO', 'BR' => 'pt_BR', 'BS' => 'en_BS', 'BT' => 'dz_BT', 'BV' => 'und_BV', 'BW' => 'en_BW', 'BY' => 'be_BY', 'BZ' => 'en_BZ', 'CA' => 'en_CA', 'CC' => 'ms_CC', 'CD' => 'sw_CD', 'CF' => 'fr_CF', 'CG' => 'fr_CG', 'CH' => 'de_CH', 'CI' => 'fr_CI', 'CK' => 'en_CK', 'CL' => 'es_CL', 'CM' => 'fr_CM', 'CN' => 'zh_Hans_CN', 'CO' => 'es_CO', 'CR' => 'es_CR', 'CU' => 'es_CU', 'CV' => 'kea_CV', 'CX' => 'en_CX', 'CY' => 'el_CY', 'CZ' => 'cs_CZ', 'DE' => 'de_DE', 'DJ' => 'aa_DJ', 'DK' => 'da_DK', 'DM' => 'en_DM', 'DO' => 'es_DO', 'DZ' => 'ar_DZ', 'EC' => 'es_EC', 'EE' => 'et_EE', 'EG' => 'ar_EG', 'EH' => 'ar_EH', 'ER' => 'ti_ER', 'ES' => 'es_ES', 'ET' => 'en_ET', 'FI' => 'fi_FI', 'FJ' => 'hi_FJ', 'FK' => 'en_FK', 'FM' => 'chk_FM', 'FO' => 'fo_FO', 'FR' => 'fr_FR', 'GA' => 'fr_GA', 'GB' => 'en_GB', 'GD' => 'en_GD', 'GE' => 'ka_GE', 'GF' => 'fr_GF', 'GG' => 'en_GG', 'GH' => 'ak_GH', 'GI' => 'en_GI', 'GL' => 'iu_GL', 'GM' => 'en_GM', 'GN' => 'fr_GN', 'GP' => 'fr_GP', 'GQ' => 'fan_GQ', 'GR' => 'el_GR', 'GS' => 'und_GS', 'GT' => 'es_GT', 'GU' => 'en_GU', 'GW' => 'pt_GW', 'GY' => 'en_GY', 'HK' => 'zh_Hant_HK', 'HM' => 'und_HM', 'HN' => 'es_HN', 'HR' => 'hr_HR', 'HT' => 'ht_HT', 'HU' => 'hu_HU', 'ID' => 'id_ID', 'IE' => 'en_IE', 'IL' => 'he_IL', 'IM' => 'en_IM', 'IN' => 'hi_IN', 'IO' => 'und_IO', 'IQ' => 'ar_IQ', 'IR' => 'fa_IR', 'IS' => 'is_IS', 'IT' => 'it_IT', 'JE' => 'en_JE', 'JM' => 'en_JM', 'JO' => 'ar_JO', 'JP' => 'ja_JP', 'KE' => 'en_KE', 'KG' => 'ky_Cyrl_KG', 'KH' => 'km_KH', 'KI' => 'en_KI', 'KM' => 'ar_KM', 'KN' => 'en_KN', 'KP' => 'ko_KP', 'KR' => 'ko_KR', 'KW' => 'ar_KW', 'KY' => 'en_KY', 'KZ' => 'ru_KZ', 'LA' => 'lo_LA', 'LB' => 'ar_LB', 'LC' => 'en_LC', 'LI' => 'de_LI', 'LK' => 'si_LK', 'LR' => 'en_LR', 'LS' => 'st_LS', 'LT' => 'lt_LT', 'LU' => 'fr_LU', 'LV' => 'lv_LV', 'LY' => 'ar_LY', 'MA' => 'ar_MA', 'MC' => 'fr_MC', 'MD' => 'ro_MD', 'ME' => 'sr_Latn_ME', 'MF' => 'fr_MF', 'MG' => 'mg_MG', 'MH' => 'mh_MH', 'MK' => 'mk_MK', 'ML' => 'bm_ML', 'MM' => 'my_MM', 'MN' => 'mn_Cyrl_MN', 'MO' => 'zh_Hant_MO', 'MP' => 'en_MP', 'MQ' => 'fr_MQ', 'MR' => 'ar_MR', 'MS' => 'en_MS', 'MT' => 'mt_MT', 'MU' => 'mfe_MU', 'MV' => 'dv_MV', 'MW' => 'ny_MW', 'MX' => 'es_MX', 'MY' => 'ms_MY', 'MZ' => 'pt_MZ', 'NA' => 'kj_NA', 'NC' => 'fr_NC', 'NE' => 'ha_Latn_NE', 'NF' => 'en_NF', 'NG' => 'en_NG', 'NI' => 'es_NI', 'NL' => 'nl_NL', 'NO' => 'nb_NO', 'NP' => 'ne_NP', 'NR' => 'en_NR', 'NU' => 'niu_NU', 'NZ' => 'en_NZ', 'OM' => 'ar_OM', 'PA' => 'es_PA', 'PE' => 'es_PE', 'PF' => 'fr_PF', 'PG' => 'tpi_PG', 'PH' => 'fil_PH', 'PK' => 'ur_PK', 'PL' => 'pl_PL', 'PM' => 'fr_PM', 'PN' => 'en_PN', 'PR' => 'es_PR', 'PS' => 'ar_PS', 'PT' => 'pt_PT', 'PW' => 'pau_PW', 'PY' => 'gn_PY', 'QA' => 'ar_QA', 'RE' => 'fr_RE', 'RO' => 'ro_RO', 'RS' => 'sr_Cyrl_RS', 'RU' => 'ru_RU', 'RW' => 'rw_RW', 'SA' => 'ar_SA', 'SB' => 'en_SB', 'SC' => 'crs_SC', 'SD' => 'ar_SD', 'SE' => 'sv_SE', 'SG' => 'en_SG', 'SH' => 'en_SH', 'SI' => 'sl_SI', 'SJ' => 'nb_SJ', 'SK' => 'sk_SK', 'SL' => 'kri_SL', 'SM' => 'it_SM', 'SN' => 'fr_SN', 'SO' => 'sw_SO', 'SR' => 'srn_SR', 'ST' => 'pt_ST', 'SV' => 'es_SV', 'SY' => 'ar_SY', 'SZ' => 'en_SZ', 'TC' => 'en_TC', 'TD' => 'fr_TD', 'TF' => 'und_TF', 'TG' => 'fr_TG', 'TH' => 'th_TH', 'TJ' => 'tg_Cyrl_TJ', 'TK' => 'tkl_TK', 'TL' => 'pt_TL', 'TM' => 'tk_TM', 'TN' => 'ar_TN', 'TO' => 'to_TO', 'TR' => 'tr_TR', 'TT' => 'en_TT', 'TV' => 'tvl_TV', 'TW' => 'zh_Hant_TW', 'TZ' => 'sw_TZ', 'UA' => 'uk_UA', 'UG' => 'sw_UG', 'UM' => 'en_UM', 'US' => 'en_US', 'UY' => 'es_UY', 'UZ' => 'uz_Cyrl_UZ', 'VA' => 'it_VA', 'VC' => 'en_VC', 'VE' => 'es_VE', 'VG' => 'en_VG', 'VI' => 'en_VI', 'VU' => 'bi_VU', 'WF' => 'wls_WF', 'WS' => 'sm_WS', 'YE' => 'ar_YE', 'YT' => 'swb_YT', 'ZA' => 'en_ZA', 'ZM' => 'en_ZM', 'ZW' => 'sn_ZW' ); /** * Autosearch constants */ const BROWSER = 'browser'; const ENVIRONMENT = 'environment'; const ZFDEFAULT = 'default'; /** * Defines if old behaviour should be supported * Old behaviour throws notices and will be deleted in future releases * * @var boolean */ public static $compatibilityMode = false; /** * Internal variable * * @var boolean */ private static $_breakChain = false; /** * Actual set locale * * @var string Locale */ protected $_locale; /** * Automatic detected locale * * @var string Locales */ protected static $_auto; /** * Browser detected locale * * @var string Locales */ protected static $_browser; /** * Environment detected locale * * @var string Locales */ protected static $_environment; /** * Default locale * * @var string Locales */ protected static $_default = array('en' => true); /** * Generates a locale object * If no locale is given a automatic search is done * Then the most probable locale will be automatically set * Search order is * 1. Given Locale * 2. HTTP Client * 3. Server Environment * 4. Framework Standard * * @param string|Zend_Locale $locale (Optional) Locale for parsing input * @throws Zend_Locale_Exception When autodetection has been failed */ public function __construct($locale = null) { $this->setLocale($locale); } /** * Serialization Interface * * @return string */ public function serialize() { return serialize($this); } /** * Returns a string representation of the object * * @return string */ public function toString() { return (string) $this->_locale; } /** * Returns a string representation of the object * Alias for toString * * @return string */ public function __toString() { return $this->toString(); } /** * Return the default locale * * @return array Returns an array of all locale string */ public static function getDefault() { if ((self::$compatibilityMode === true) or (func_num_args() > 0)) { if (!self::$_breakChain) { self::$_breakChain = true; trigger_error('You are running Zend_Locale in compatibility mode... please migrate your scripts', E_USER_NOTICE); $params = func_get_args(); $param = null; if (isset($params[0])) { $param = $params[0]; } return self::getOrder($param); } self::$_breakChain = false; } return self::$_default; } /** * Sets a new default locale which will be used when no locale can be detected * If provided you can set a quality between 0 and 1 (or 2 and 100) * which represents the percent of quality the browser * requested within HTTP * * @param string|Zend_Locale $locale Locale to set * @param float $quality The quality to set from 0 to 1 * @throws Zend_Locale_Exception When a autolocale was given * @throws Zend_Locale_Exception When a unknown locale was given * @return void */ public static function setDefault($locale, $quality = 1) { if (($locale === 'auto') or ($locale === 'root') or ($locale === 'default') or ($locale === 'environment') or ($locale === 'browser')) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception('Only full qualified locales can be used as default!'); } if (($quality < 0.1) or ($quality > 100)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Quality must be between 0.1 and 100"); } if ($quality > 1) { $quality /= 100; } $locale = self::_prepareLocale($locale); if (isset(self::$_localeData[(string) $locale]) === true) { self::$_default = array((string) $locale => $quality); } else { $elocale = explode('_', (string) $locale); if (isset(self::$_localeData[$elocale[0]]) === true) { self::$_default = array($elocale[0] => $quality); } else { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown locale '" . (string) $locale . "' can not be set as default!"); } } self::$_auto = self::getBrowser() + self::getEnvironment() + self::getDefault(); } /** * Expects the Systems standard locale * * For Windows: * f.e.: LC_COLLATE=C;LC_CTYPE=German_Austria.1252;LC_MONETARY=C * would be recognised as de_AT * * @return array */ public static function getEnvironment() { if (self::$_environment !== null) { return self::$_environment; } #require_once 'Zend/Locale/Data/Translation.php'; $language = setlocale(LC_ALL, 0); $languages = explode(';', $language); $languagearray = array(); foreach ($languages as $locale) { if (strpos($locale, '=') !== false) { $language = substr($locale, strpos($locale, '=')); $language = substr($language, 1); } if ($language !== 'C') { if (strpos($language, '.') !== false) { $language = substr($language, 0, strpos($language, '.')); } else if (strpos($language, '@') !== false) { $language = substr($language, 0, strpos($language, '@')); } $language = str_ireplace( array_keys(Zend_Locale_Data_Translation::$languageTranslation), array_values(Zend_Locale_Data_Translation::$languageTranslation), (string) $language ); $language = str_ireplace( array_keys(Zend_Locale_Data_Translation::$regionTranslation), array_values(Zend_Locale_Data_Translation::$regionTranslation), $language ); if (isset(self::$_localeData[$language]) === true) { $languagearray[$language] = 1; if (strpos($language, '_') !== false) { $languagearray[substr($language, 0, strpos($language, '_'))] = 1; } } } } self::$_environment = $languagearray; return $languagearray; } /** * Return an array of all accepted languages of the client * Expects RFC compilant Header !! * * The notation can be : * de,en-UK-US;q=0.5,fr-FR;q=0.2 * * @return array - list of accepted languages including quality */ public static function getBrowser() { if (self::$_browser !== null) { return self::$_browser; } $httplanguages = getenv('HTTP_ACCEPT_LANGUAGE'); if (empty($httplanguages) && array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) { $httplanguages = $_SERVER['HTTP_ACCEPT_LANGUAGE']; } $languages = array(); if (empty($httplanguages)) { return $languages; } $accepted = preg_split('/,\s*/', $httplanguages); foreach ($accepted as $accept) { $match = null; $result = preg_match('/^([a-z]{1,8}(?:[-_][a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', $accept, $match); if ($result < 1) { continue; } if (isset($match[2]) === true) { $quality = (float) $match[2]; } else { $quality = 1.0; } $countrys = explode('-', $match[1]); $region = array_shift($countrys); $country2 = explode('_', $region); $region = array_shift($country2); foreach ($countrys as $country) { $languages[$region . '_' . strtoupper($country)] = $quality; } foreach ($country2 as $country) { $languages[$region . '_' . strtoupper($country)] = $quality; } if ((isset($languages[$region]) === false) || ($languages[$region] < $quality)) { $languages[$region] = $quality; } } self::$_browser = $languages; return $languages; } /** * Sets a new locale * * @param string|Zend_Locale $locale (Optional) New locale to set * @return void */ public function setLocale($locale = null) { $locale = self::_prepareLocale($locale); if (isset(self::$_localeData[(string) $locale]) === false) { $region = substr((string) $locale, 0, 3); if (isset($region[2]) === true) { if (($region[2] === '_') or ($region[2] === '-')) { $region = substr($region, 0, 2); } } if (isset(self::$_localeData[(string) $region]) === true) { $this->_locale = $region; } else { $this->_locale = 'root'; } } else { $this->_locale = $locale; } } /** * Returns the language part of the locale * * @return string */ public function getLanguage() { $locale = explode('_', $this->_locale); return $locale[0]; } /** * Returns the region part of the locale if available * * @return string|false - Regionstring */ public function getRegion() { $locale = explode('_', $this->_locale); if (isset($locale[1]) === true) { return $locale[1]; } return false; } /** * Return the accepted charset of the client * * @return string */ public static function getHttpCharset() { $httpcharsets = getenv('HTTP_ACCEPT_CHARSET'); $charsets = array(); if ($httpcharsets === false) { return $charsets; } $accepted = preg_split('/,\s*/', $httpcharsets); foreach ($accepted as $accept) { if (empty($accept) === true) { continue; } if (strpos($accept, ';') !== false) { $quality = (float) substr($accept, (strpos($accept, '=') + 1)); $pos = substr($accept, 0, strpos($accept, ';')); $charsets[$pos] = $quality; } else { $quality = 1.0; $charsets[$accept] = $quality; } } return $charsets; } /** * Returns true if both locales are equal * * @param Zend_Locale $object Locale to check for equality * @return boolean */ public function equals(Zend_Locale $object) { if ($object->toString() === $this->toString()) { return true; } return false; } /** * Returns localized informations as array, supported are several * types of informations. * For detailed information about the types look into the documentation * * @param string $path (Optional) Type of information to return * @param string|Zend_Locale $locale (Optional) Locale|Language for which this informations should be returned * @param string $value (Optional) Value for detail list * @return array Array with the wished information in the given language */ public static function getTranslationList($path = null, $locale = null, $value = null) { #require_once 'Zend/Locale/Data.php'; $locale = self::findLocale($locale); $result = Zend_Locale_Data::getList($locale, $path, $value); if (empty($result) === true) { return false; } return $result; } /** * Returns an array with the name of all languages translated to the given language * * @param string|Zend_Locale $locale (Optional) Locale for language translation * @return array * @deprecated */ public static function getLanguageTranslationList($locale = null) { trigger_error("The method getLanguageTranslationList is deprecated. Use getTranslationList('language', $locale) instead", E_USER_NOTICE); return self::getTranslationList('language', $locale); } /** * Returns an array with the name of all scripts translated to the given language * * @param string|Zend_Locale $locale (Optional) Locale for script translation * @return array * @deprecated */ public static function getScriptTranslationList($locale = null) { trigger_error("The method getScriptTranslationList is deprecated. Use getTranslationList('script', $locale) instead", E_USER_NOTICE); return self::getTranslationList('script', $locale); } /** * Returns an array with the name of all countries translated to the given language * * @param string|Zend_Locale $locale (Optional) Locale for country translation * @return array * @deprecated */ public static function getCountryTranslationList($locale = null) { trigger_error("The method getCountryTranslationList is deprecated. Use getTranslationList('territory', $locale, 2) instead", E_USER_NOTICE); return self::getTranslationList('territory', $locale, 2); } /** * Returns an array with the name of all territories translated to the given language * All territories contains other countries. * * @param string|Zend_Locale $locale (Optional) Locale for territory translation * @return array * @deprecated */ public static function getTerritoryTranslationList($locale = null) { trigger_error("The method getTerritoryTranslationList is deprecated. Use getTranslationList('territory', $locale, 1) instead", E_USER_NOTICE); return self::getTranslationList('territory', $locale, 1); } /** * Returns a localized information string, supported are several types of informations. * For detailed information about the types look into the documentation * * @param string $value Name to get detailed information about * @param string $path (Optional) Type of information to return * @param string|Zend_Locale $locale (Optional) Locale|Language for which this informations should be returned * @return string|false The wished information in the given language */ public static function getTranslation($value = null, $path = null, $locale = null) { #require_once 'Zend/Locale/Data.php'; $locale = self::findLocale($locale); $result = Zend_Locale_Data::getContent($locale, $path, $value); if (empty($result) === true) { return false; } return $result; } /** * Returns the localized language name * * @param string $value Name to get detailed information about * @param string $locale (Optional) Locale for language translation * @return array * @deprecated */ public static function getLanguageTranslation($value, $locale = null) { trigger_error("The method getLanguageTranslation is deprecated. Use getTranslation($value, 'language', $locale) instead", E_USER_NOTICE); return self::getTranslation($value, 'language', $locale); } /** * Returns the localized script name * * @param string $value Name to get detailed information about * @param string $locale (Optional) locale for script translation * @return array * @deprecated */ public static function getScriptTranslation($value, $locale = null) { trigger_error("The method getScriptTranslation is deprecated. Use getTranslation($value, 'script', $locale) instead", E_USER_NOTICE); return self::getTranslation($value, 'script', $locale); } /** * Returns the localized country name * * @param string $value Name to get detailed information about * @param string|Zend_Locale $locale (Optional) Locale for country translation * @return array * @deprecated */ public static function getCountryTranslation($value, $locale = null) { trigger_error("The method getCountryTranslation is deprecated. Use getTranslation($value, 'country', $locale) instead", E_USER_NOTICE); return self::getTranslation($value, 'country', $locale); } /** * Returns the localized territory name * All territories contains other countries. * * @param string $value Name to get detailed information about * @param string|Zend_Locale $locale (Optional) Locale for territory translation * @return array * @deprecated */ public static function getTerritoryTranslation($value, $locale = null) { trigger_error("The method getTerritoryTranslation is deprecated. Use getTranslation($value, 'territory', $locale) instead", E_USER_NOTICE); return self::getTranslation($value, 'territory', $locale); } /** * Returns an array with translated yes strings * * @param string|Zend_Locale $locale (Optional) Locale for language translation (defaults to $this locale) * @return array */ public static function getQuestion($locale = null) { #require_once 'Zend/Locale/Data.php'; $locale = self::findLocale($locale); $quest = Zend_Locale_Data::getList($locale, 'question'); $yes = explode(':', $quest['yes']); $no = explode(':', $quest['no']); $quest['yes'] = $yes[0]; $quest['yesarray'] = $yes; $quest['no'] = $no[0]; $quest['noarray'] = $no; $quest['yesexpr'] = self::_prepareQuestionString($yes); $quest['noexpr'] = self::_prepareQuestionString($no); return $quest; } /** * Internal function for preparing the returned question regex string * * @param string $input Regex to parse * @return string */ private static function _prepareQuestionString($input) { $regex = ''; if (is_array($input) === true) { $regex = '^'; $start = true; foreach ($input as $row) { if ($start === false) { $regex .= '|'; } $start = false; $regex .= '('; $one = null; if (strlen($row) > 2) { $one = true; } foreach (str_split($row, 1) as $char) { $regex .= '[' . $char; $regex .= strtoupper($char) . ']'; if ($one === true) { $one = false; $regex .= '('; } } if ($one === false) { $regex .= ')'; } $regex .= '?)'; } } return $regex; } /** * Checks if a locale identifier is a real locale or not * Examples: * "en_XX" refers to "en", which returns true * "XX_yy" refers to "root", which returns false * * @param string|Zend_Locale $locale Locale to check for * @param boolean $strict (Optional) If true, no rerouting will be done when checking * @param boolean $compatible (DEPRECATED) Only for internal usage, brakes compatibility mode * @return boolean If the locale is known dependend on the settings */ public static function isLocale($locale, $strict = false, $compatible = true) { if (($locale instanceof Zend_Locale) || (is_string($locale) && array_key_exists($locale, self::$_localeData)) ) { return true; } if (($locale === null) || (!is_string($locale) and !is_array($locale))) { return false; } try { $locale = self::_prepareLocale($locale, $strict); } catch (Zend_Locale_Exception $e) { return false; } if (($compatible === true) and (self::$compatibilityMode === true)) { trigger_error('You are running Zend_Locale in compatibility mode... please migrate your scripts', E_USER_NOTICE); if (isset(self::$_localeData[$locale]) === true) { return $locale; } else if (!$strict) { $locale = explode('_', $locale); if (isset(self::$_localeData[$locale[0]]) === true) { return $locale[0]; } } } else { if (isset(self::$_localeData[$locale]) === true) { return true; } else if (!$strict) { $locale = explode('_', $locale); if (isset(self::$_localeData[$locale[0]]) === true) { return true; } } } return false; } /** * Finds the proper locale based on the input * Checks if it exists, degrades it when necessary * Detects registry locale and when all fails tries to detect a automatic locale * Returns the found locale as string * * @param string $locale * @throws Zend_Locale_Exception When the given locale is no locale or the autodetection fails * @return string */ public static function findLocale($locale = null) { if ($locale === null) { #require_once 'Zend/Registry.php'; if (Zend_Registry::isRegistered('Zend_Locale')) { $locale = Zend_Registry::get('Zend_Locale'); } } if ($locale === null) { $locale = new Zend_Locale(); } if (!Zend_Locale::isLocale($locale, true, false)) { if (!Zend_Locale::isLocale($locale, false, false)) { $locale = Zend_Locale::getLocaleToTerritory($locale); if (empty($locale)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("The locale '$locale' is no known locale"); } } else { $locale = new Zend_Locale($locale); } } $locale = self::_prepareLocale($locale); return $locale; } /** * Returns the expected locale for a given territory * * @param string $territory Territory for which the locale is being searched * @return string|null Locale string or null when no locale has been found */ public static function getLocaleToTerritory($territory) { $territory = strtoupper($territory); if (array_key_exists($territory, self::$_territoryData)) { return self::$_territoryData[$territory]; } return null; } /** * Returns a list of all known locales where the locale is the key * Only real locales are returned, the internal locales 'root', 'auto', 'browser' * and 'environment' are suppressed * * @return array List of all Locales */ public static function getLocaleList() { $list = self::$_localeData; unset($list['root']); unset($list['auto']); unset($list['browser']); unset($list['environment']); return $list; } /** * Returns the set cache * * @return Zend_Cache_Core The set cache */ public static function getCache() { #require_once 'Zend/Locale/Data.php'; return Zend_Locale_Data::getCache(); } /** * Sets a cache * * @param Zend_Cache_Core $cache Cache to set * @return void */ public static function setCache(Zend_Cache_Core $cache) { #require_once 'Zend/Locale/Data.php'; Zend_Locale_Data::setCache($cache); } /** * Returns true when a cache is set * * @return boolean */ public static function hasCache() { #require_once 'Zend/Locale/Data.php'; return Zend_Locale_Data::hasCache(); } /** * Removes any set cache * * @return void */ public static function removeCache() { #require_once 'Zend/Locale/Data.php'; Zend_Locale_Data::removeCache(); } /** * Clears all set cache data * * @param string $tag Tag to clear when the default tag name is not used * @return void */ public static function clearCache($tag = null) { #require_once 'Zend/Locale/Data.php'; Zend_Locale_Data::clearCache($tag); } /** * Disables the set cache * * @param boolean $flag True disables any set cache, default is false * @return void */ public static function disableCache($flag) { #require_once 'Zend/Locale/Data.php'; Zend_Locale_Data::disableCache($flag); } /** * Internal function, returns a single locale on detection * * @param string|Zend_Locale $locale (Optional) Locale to work on * @param boolean $strict (Optional) Strict preparation * @throws Zend_Locale_Exception When no locale is set which is only possible when the class was wrong extended * @return string */ private static function _prepareLocale($locale, $strict = false) { if ($locale instanceof Zend_Locale) { $locale = $locale->toString(); } if (is_array($locale)) { return ''; } if (empty(self::$_auto) === true) { self::$_browser = self::getBrowser(); self::$_environment = self::getEnvironment(); self::$_breakChain = true; self::$_auto = self::getBrowser() + self::getEnvironment() + self::getDefault(); } if (!$strict) { if ($locale === 'browser') { $locale = self::$_browser; } if ($locale === 'environment') { $locale = self::$_environment; } if ($locale === 'default') { $locale = self::$_default; } if (($locale === 'auto') or ($locale === null)) { $locale = self::$_auto; } if (is_array($locale) === true) { $locale = key($locale); } } // This can only happen when someone extends Zend_Locale and erases the default if ($locale === null) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception('Autodetection of Locale has been failed!'); } if (strpos($locale, '-') !== false) { $locale = strtr($locale, '-', '_'); } $parts = explode('_', $locale); if (!isset(self::$_localeData[$parts[0]])) { if ((count($parts) == 1) && array_key_exists($parts[0], self::$_territoryData)) { return self::$_territoryData[$parts[0]]; } return ''; } foreach($parts as $key => $value) { if ((strlen($value) < 2) || (strlen($value) > 3)) { unset($parts[$key]); } } $locale = implode('_', $parts); return (string) $locale; } /** * Search the locale automatically and return all used locales * ordered by quality * * Standard Searchorder is Browser, Environment, Default * * @param string $searchorder (Optional) Searchorder * @return array Returns an array of all detected locales */ public static function getOrder($order = null) { switch ($order) { case self::ENVIRONMENT: self::$_breakChain = true; $languages = self::getEnvironment() + self::getBrowser() + self::getDefault(); break; case self::ZFDEFAULT: self::$_breakChain = true; $languages = self::getDefault() + self::getEnvironment() + self::getBrowser(); break; default: self::$_breakChain = true; $languages = self::getBrowser() + self::getEnvironment() + self::getDefault(); break; } return $languages; } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Locale * @subpackage Data * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * include needed classes */ #require_once 'Zend/Locale.php'; /** * Locale data reader, handles the CLDR * * @category Zend * @package Zend_Locale * @subpackage Data * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Locale_Data { /** * Locale files * * @var ressource * @access private */ private static $_ldml = array(); /** * List of values which are collected * * @var array * @access private */ private static $_list = array(); /** * Internal cache for ldml values * * @var Zend_Cache_Core * @access private */ private static $_cache = null; /** * Internal value to remember if cache supports tags * * @var boolean */ private static $_cacheTags = false; /** * Internal option, cache disabled * * @var boolean * @access private */ private static $_cacheDisabled = false; /** * Read the content from locale * * Can be called like: * * test * content * content2 * * * * Case 1: _readFile('ar','/ldml/delimiter') -> returns [] = test * Case 1: _readFile('ar','/ldml/second[@type=myone]') -> returns [] = content * Case 2: _readFile('ar','/ldml/second','type') -> returns [myone] = content; [mysecond] = content2 * Case 3: _readFile('ar','/ldml/delimiter',,'right') -> returns [right] = test * Case 4: _readFile('ar','/ldml/third','type','myone') -> returns [myone] = mythird * * @param string $locale * @param string $path * @param string $attribute * @param string $value * @access private * @return array */ private static function _readFile($locale, $path, $attribute, $value, $temp) { // without attribute - read all values // with attribute - read only this value if (!empty(self::$_ldml[(string) $locale])) { $result = self::$_ldml[(string) $locale]->xpath($path); if (!empty($result)) { foreach ($result as &$found) { if (empty($value)) { if (empty($attribute)) { // Case 1 $temp[] = (string) $found; } else if (empty($temp[(string) $found[$attribute]])){ // Case 2 $temp[(string) $found[$attribute]] = (string) $found; } } else if (empty ($temp[$value])) { if (empty($attribute)) { // Case 3 $temp[$value] = (string) $found; } else { // Case 4 $temp[$value] = (string) $found[$attribute]; } } } } } return $temp; } /** * Find possible routing to other path or locale * * @param string $locale * @param string $path * @param string $attribute * @param string $value * @param array $temp * @throws Zend_Locale_Exception * @access private */ private static function _findRoute($locale, $path, $attribute, $value, &$temp) { // load locale file if not already in cache // needed for alias tag when referring to other locale if (empty(self::$_ldml[(string) $locale])) { $filename = dirname(__FILE__) . '/Data/' . $locale . '.xml'; if (!file_exists($filename)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Missing locale file '$filename' for '$locale' locale."); } self::$_ldml[(string) $locale] = simplexml_load_file($filename); } // search for 'alias' tag in the search path for redirection $search = ''; $tok = strtok($path, '/'); // parse the complete path if (!empty(self::$_ldml[(string) $locale])) { while ($tok !== false) { $search .= '/' . $tok; if (strpos($search, '[@') !== false) { while (strrpos($search, '[@') > strrpos($search, ']')) { $tok = strtok('/'); if (empty($tok)) { $search .= '/'; } $search = $search . '/' . $tok; } } $result = self::$_ldml[(string) $locale]->xpath($search . '/alias'); // alias found if (!empty($result)) { $source = $result[0]['source']; $newpath = $result[0]['path']; // new path - path //ldml is to ignore if ($newpath != '//ldml') { // other path - parse to make real path while (substr($newpath,0,3) == '../') { $newpath = substr($newpath, 3); $search = substr($search, 0, strrpos($search, '/')); } // truncate ../ to realpath otherwise problems with alias $path = $search . '/' . $newpath; while (($tok = strtok('/'))!== false) { $path = $path . '/' . $tok; } } // reroute to other locale if ($source != 'locale') { $locale = $source; } $temp = self::_getFile($locale, $path, $attribute, $value, $temp); return false; } $tok = strtok('/'); } } return true; } /** * Read the right LDML file * * @param string $locale * @param string $path * @param string $attribute * @param string $value * @access private */ private static function _getFile($locale, $path, $attribute = false, $value = false, $temp = array()) { $result = self::_findRoute($locale, $path, $attribute, $value, $temp); if ($result) { $temp = self::_readFile($locale, $path, $attribute, $value, $temp); } // parse required locales reversive // example: when given zh_Hans_CN // 1. -> zh_Hans_CN // 2. -> zh_Hans // 3. -> zh // 4. -> root if (($locale != 'root') && ($result)) { $locale = substr($locale, 0, -strlen(strrchr($locale, '_'))); if (!empty($locale)) { $temp = self::_getFile($locale, $path, $attribute, $value, $temp); } else { $temp = self::_getFile('root', $path, $attribute, $value, $temp); } } return $temp; } /** * Find the details for supplemental calendar datas * * @param string $locale Locale for Detaildata * @param array $list List to search * @return string Key for Detaildata */ private static function _calendarDetail($locale, $list) { $ret = "001"; foreach ($list as $key => $value) { if (strpos($locale, '_') !== false) { $locale = substr($locale, strpos($locale, '_') + 1); } if (strpos($key, $locale) !== false) { $ret = $key; break; } } return $ret; } /** * Internal function for checking the locale * * @param string|Zend_Locale $locale Locale to check * @return string */ private static function _checkLocale($locale) { if (empty($locale)) { $locale = new Zend_Locale(); } if (!(Zend_Locale::isLocale((string) $locale, null, false))) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Locale (" . (string) $locale . ") is a unknown locale"); } return (string) $locale; } /** * Read the LDML file, get a array of multipath defined value * * @param string $locale * @param string $path * @param string $value * @return array * @access public */ public static function getList($locale, $path, $value = false) { $locale = self::_checkLocale($locale); if (!isset(self::$_cache) && !self::$_cacheDisabled) { #require_once 'Zend/Cache.php'; self::$_cache = Zend_Cache::factory( 'Core', 'File', array('automatic_serialization' => true), array()); } $val = $value; if (is_array($value)) { $val = implode('_' , $value); } $val = urlencode($val); $id = strtr('Zend_LocaleL_' . $locale . '_' . $path . '_' . $val, array('-' => '_', '%' => '_', '+' => '_')); if (!self::$_cacheDisabled && ($result = self::$_cache->load($id))) { return unserialize($result); } $temp = array(); switch(strtolower($path)) { case 'language': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/languages/language', 'type'); break; case 'script': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/scripts/script', 'type'); break; case 'territory': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/territories/territory', 'type'); if ($value === 1) { foreach($temp as $key => $value) { if ((is_numeric($key) === false) and ($key != 'QO') and ($key != 'QU')) { unset($temp[$key]); } } } else if ($value === 2) { foreach($temp as $key => $value) { if (is_numeric($key) or ($key == 'QO') or ($key == 'QU')) { unset($temp[$key]); } } } break; case 'variant': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/variants/variant', 'type'); break; case 'key': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/keys/key', 'type'); break; case 'type': if (empty($type)) { $temp = self::_getFile($locale, '/ldml/localeDisplayNames/types/type', 'type'); } else { if (($value == 'calendar') or ($value == 'collation') or ($value == 'currency')) { $temp = self::_getFile($locale, '/ldml/localeDisplayNames/types/type[@key=\'' . $value . '\']', 'type'); } else { $temp = self::_getFile($locale, '/ldml/localeDisplayNames/types/type[@type=\'' . $value . '\']', 'type'); } } break; case 'layout': $temp = self::_getFile($locale, '/ldml/layout/orientation', 'lines', 'lines'); $temp += self::_getFile($locale, '/ldml/layout/orientation', 'characters', 'characters'); $temp += self::_getFile($locale, '/ldml/layout/inList', '', 'inList'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'currency\']', '', 'currency'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'dayWidth\']', '', 'dayWidth'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'fields\']', '', 'fields'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'keys\']', '', 'keys'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'languages\']', '', 'languages'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'long\']', '', 'long'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'measurementSystemNames\']', '', 'measurementSystemNames'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'monthWidth\']', '', 'monthWidth'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'quarterWidth\']', '', 'quarterWidth'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'scripts\']', '', 'scripts'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'territories\']', '', 'territories'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'types\']', '', 'types'); $temp += self::_getFile($locale, '/ldml/layout/inText[@type=\'variants\']', '', 'variants'); break; case 'characters': $temp = self::_getFile($locale, '/ldml/characters/exemplarCharacters', '', 'characters'); $temp += self::_getFile($locale, '/ldml/characters/exemplarCharacters[@type=\'auxiliary\']', '', 'auxiliary'); $temp += self::_getFile($locale, '/ldml/characters/exemplarCharacters[@type=\'currencySymbol\']', '', 'currencySymbol'); break; case 'delimiters': $temp = self::_getFile($locale, '/ldml/delimiters/quotationStart', '', 'quoteStart'); $temp += self::_getFile($locale, '/ldml/delimiters/quotationEnd', '', 'quoteEnd'); $temp += self::_getFile($locale, '/ldml/delimiters/alternateQuotationStart', '', 'quoteStartAlt'); $temp += self::_getFile($locale, '/ldml/delimiters/alternateQuotationEnd', '', 'quoteEndAlt'); break; case 'measurement': $temp = self::_getFile('supplementalData', '/supplementalData/measurementData/measurementSystem[@type=\'metric\']', 'territories', 'metric'); $temp += self::_getFile('supplementalData', '/supplementalData/measurementData/measurementSystem[@type=\'US\']', 'territories', 'US'); $temp += self::_getFile('supplementalData', '/supplementalData/measurementData/paperSize[@type=\'A4\']', 'territories', 'A4'); $temp += self::_getFile('supplementalData', '/supplementalData/measurementData/paperSize[@type=\'US-Letter\']', 'territories', 'US-Letter'); break; case 'months': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/default', 'choice', 'context'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/default', 'choice', 'default'); $temp['format']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/monthWidth[@type=\'abbreviated\']/month', 'type'); $temp['format']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/monthWidth[@type=\'narrow\']/month', 'type'); $temp['format']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/monthWidth[@type=\'wide\']/month', 'type'); $temp['stand-alone']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'stand-alone\']/monthWidth[@type=\'abbreviated\']/month', 'type'); $temp['stand-alone']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'stand-alone\']/monthWidth[@type=\'narrow\']/month', 'type'); $temp['stand-alone']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'stand-alone\']/monthWidth[@type=\'wide\']/month', 'type'); break; case 'month': if (empty($value)) { $value = array("gregorian", "format", "wide"); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/months/monthContext[@type=\'' . $value[1] . '\']/monthWidth[@type=\'' . $value[2] . '\']/month', 'type'); break; case 'days': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/default', 'choice', 'context'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/default', 'choice', 'default'); $temp['format']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/dayWidth[@type=\'abbreviated\']/day', 'type'); $temp['format']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/dayWidth[@type=\'narrow\']/day', 'type'); $temp['format']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/dayWidth[@type=\'wide\']/day', 'type'); $temp['stand-alone']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'stand-alone\']/dayWidth[@type=\'abbreviated\']/day', 'type'); $temp['stand-alone']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'stand-alone\']/dayWidth[@type=\'narrow\']/day', 'type'); $temp['stand-alone']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'stand-alone\']/dayWidth[@type=\'wide\']/day', 'type'); break; case 'day': if (empty($value)) { $value = array("gregorian", "format", "wide"); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/days/dayContext[@type=\'' . $value[1] . '\']/dayWidth[@type=\'' . $value[2] . '\']/day', 'type'); break; case 'week': $minDays = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/minDays', 'territories')); $firstDay = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/firstDay', 'territories')); $weekStart = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/weekendStart', 'territories')); $weekEnd = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/weekendEnd', 'territories')); $temp = self::_getFile('supplementalData', "/supplementalData/weekData/minDays[@territories='" . $minDays . "']", 'count', 'minDays'); $temp += self::_getFile('supplementalData', "/supplementalData/weekData/firstDay[@territories='" . $firstDay . "']", 'day', 'firstDay'); $temp += self::_getFile('supplementalData', "/supplementalData/weekData/weekendStart[@territories='" . $weekStart . "']", 'day', 'weekendStart'); $temp += self::_getFile('supplementalData', "/supplementalData/weekData/weekendEnd[@territories='" . $weekEnd . "']", 'day', 'weekendEnd'); break; case 'quarters': if (empty($value)) { $value = "gregorian"; } $temp['format']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'format\']/quarterWidth[@type=\'abbreviated\']/quarter', 'type'); $temp['format']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'format\']/quarterWidth[@type=\'narrow\']/quarter', 'type'); $temp['format']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'format\']/quarterWidth[@type=\'wide\']/quarter', 'type'); $temp['stand-alone']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'stand-alone\']/quarterWidth[@type=\'abbreviated\']/quarter', 'type'); $temp['stand-alone']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'stand-alone\']/quarterWidth[@type=\'narrow\']/quarter', 'type'); $temp['stand-alone']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'stand-alone\']/quarterWidth[@type=\'wide\']/quarter', 'type'); break; case 'quarter': if (empty($value)) { $value = array("gregorian", "format", "wide"); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/quarters/quarterContext[@type=\'' . $value[1] . '\']/quarterWidth[@type=\'' . $value[2] . '\']/quarter', 'type'); break; case 'eras': if (empty($value)) { $value = "gregorian"; } $temp['names'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/eras/eraNames/era', 'type'); $temp['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/eras/eraAbbr/era', 'type'); $temp['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/eras/eraNarrow/era', 'type'); break; case 'era': if (empty($value)) { $value = array("gregorian", "Abbr"); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/eras/era' . $value[1] . '/era', 'type'); break; case 'date': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'full\']/dateFormat/pattern', '', 'full'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'long\']/dateFormat/pattern', '', 'long'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'medium\']/dateFormat/pattern', '', 'medium'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'short\']/dateFormat/pattern', '', 'short'); break; case 'time': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'full\']/timeFormat/pattern', '', 'full'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'long\']/timeFormat/pattern', '', 'long'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'medium\']/timeFormat/pattern', '', 'medium'); $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'short\']/timeFormat/pattern', '', 'short'); break; case 'datetime': if (empty($value)) { $value = "gregorian"; } $timefull = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'full\']/timeFormat/pattern', '', 'full'); $timelong = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'long\']/timeFormat/pattern', '', 'long'); $timemedi = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'medium\']/timeFormat/pattern', '', 'medi'); $timeshor = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'short\']/timeFormat/pattern', '', 'shor'); $datefull = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'full\']/dateFormat/pattern', '', 'full'); $datelong = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'long\']/dateFormat/pattern', '', 'long'); $datemedi = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'medium\']/dateFormat/pattern', '', 'medi'); $dateshor = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'short\']/dateFormat/pattern', '', 'shor'); $full = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'full\']/dateTimeFormat/pattern', '', 'full'); $long = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'long\']/dateTimeFormat/pattern', '', 'long'); $medi = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'medium\']/dateTimeFormat/pattern', '', 'medi'); $shor = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'short\']/dateTimeFormat/pattern', '', 'shor'); $temp['full'] = str_replace(array('{0}', '{1}'), array($timefull['full'], $datefull['full']), $full['full']); $temp['long'] = str_replace(array('{0}', '{1}'), array($timelong['long'], $datelong['long']), $long['long']); $temp['medium'] = str_replace(array('{0}', '{1}'), array($timemedi['medi'], $datemedi['medi']), $medi['medi']); $temp['short'] = str_replace(array('{0}', '{1}'), array($timeshor['shor'], $dateshor['shor']), $shor['shor']); break; case 'dateitem': if (empty($value)) { $value = "gregorian"; } $_temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/availableFormats/dateFormatItem', 'id'); foreach($_temp as $key => $found) { $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/availableFormats/dateFormatItem[@id=\'' . $key . '\']', '', $key); } break; case 'dateinterval': if (empty($value)) { $value = "gregorian"; } $_temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/intervalFormats/intervalFormatItem', 'id'); foreach($_temp as $key => $found) { $temp[$key] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\'' . $key . '\']/greatestDifference', 'id'); } break; case 'field': if (empty($value)) { $value = "gregorian"; } $temp2 = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/fields/field', 'type'); foreach ($temp2 as $key => $keyvalue) { $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/fields/field[@type=\'' . $key . '\']/displayName', '', $key); } break; case 'relative': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/fields/field/relative', 'type'); break; case 'symbols': $temp = self::_getFile($locale, '/ldml/numbers/symbols/decimal', '', 'decimal'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/group', '', 'group'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/list', '', 'list'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/percentSign', '', 'percent'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/nativeZeroDigit', '', 'zero'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/patternDigit', '', 'pattern'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/plusSign', '', 'plus'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/minusSign', '', 'minus'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/exponential', '', 'exponent'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/perMille', '', 'mille'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/infinity', '', 'infinity'); $temp += self::_getFile($locale, '/ldml/numbers/symbols/nan', '', 'nan'); break; case 'nametocurrency': $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/displayName', '', $key); } break; case 'currencytoname': $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type'); foreach ($_temp as $key => $keyvalue) { $val = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/displayName', '', $key); if (!isset($val[$key])) { continue; } if (!isset($temp[$val[$key]])) { $temp[$val[$key]] = $key; } else { $temp[$val[$key]] .= " " . $key; } } break; case 'currencysymbol': $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/symbol', '', $key); } break; case 'question': $temp = self::_getFile($locale, '/ldml/posix/messages/yesstr', '', 'yes'); $temp += self::_getFile($locale, '/ldml/posix/messages/nostr', '', 'no'); break; case 'currencyfraction': $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info', 'iso4217'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $key . '\']', 'digits', $key); } break; case 'currencyrounding': $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info', 'iso4217'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $key . '\']', 'rounding', $key); } break; case 'currencytoregion': $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region', 'iso3166'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $key . '\']/currency', 'iso4217', $key); } break; case 'regiontocurrency': $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region', 'iso3166'); foreach ($_temp as $key => $keyvalue) { $val = self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $key . '\']/currency', 'iso4217', $key); if (!isset($val[$key])) { continue; } if (!isset($temp[$val[$key]])) { $temp[$val[$key]] = $key; } else { $temp[$val[$key]] .= " " . $key; } } break; case 'regiontoterritory': $_temp = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $key . '\']', 'contains', $key); } break; case 'territorytoregion': $_temp2 = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group', 'type'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $key . '\']', 'contains', $key); } foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'scripttolanguage': $_temp = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'scripts', $key); if (empty($temp[$key])) { unset($temp[$key]); } } break; case 'languagetoscript': $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'scripts', $key); } foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if (empty($found3)) { continue; } if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'territorytolanguage': $_temp = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'territories', $key); if (empty($temp[$key])) { unset($temp[$key]); } } break; case 'languagetoterritory': $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'territories', $key); } foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if (empty($found3)) { continue; } if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'timezonetowindows': $_temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/mapTimezones[@type=\'windows\']/mapZone', 'other'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/timezoneData/mapTimezones[@type=\'windows\']/mapZone[@other=\'' . $key . '\']', 'type', $key); } break; case 'windowstotimezone': $_temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/mapTimezones[@type=\'windows\']/mapZone', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/timezoneData/mapTimezones[@type=\'windows\']/mapZone[@type=\'' .$key . '\']', 'other', $key); } break; case 'territorytotimezone': $_temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/zoneFormatting/zoneItem', 'type'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/timezoneData/zoneFormatting/zoneItem[@type=\'' . $key . '\']', 'territory', $key); } break; case 'timezonetoterritory': $_temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/zoneFormatting/zoneItem', 'territory'); foreach ($_temp as $key => $found) { $temp += self::_getFile('supplementalData', '/supplementalData/timezoneData/zoneFormatting/zoneItem[@territory=\'' . $key . '\']', 'type', $key); } break; case 'citytotimezone': $_temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone', 'type'); foreach($_temp as $key => $found) { $temp += self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $key . '\']/exemplarCity', '', $key); } break; case 'timezonetocity': $_temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone', 'type'); $temp = array(); foreach($_temp as $key => $found) { $temp += self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $key . '\']/exemplarCity', '', $key); if (!empty($temp[$key])) { $temp[$temp[$key]] = $key; } unset($temp[$key]); } break; case 'phonetoterritory': $_temp = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory', 'territory'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $key . '\']/telephoneCountryCode', 'code', $key); } break; case 'territorytophone': $_temp = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory', 'territory'); foreach ($_temp as $key => $keyvalue) { $val = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $key . '\']/telephoneCountryCode', 'code', $key); if (!isset($val[$key])) { continue; } if (!isset($temp[$val[$key]])) { $temp[$val[$key]] = $key; } else { $temp[$val[$key]] .= " " . $key; } } break; case 'numerictoterritory': $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'type'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\'' . $key . '\']', 'numeric', $key); } break; case 'territorytonumeric': $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'numeric'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@numeric=\'' . $key . '\']', 'type', $key); } break; case 'alpha3toterritory': $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'type'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\'' . $key . '\']', 'alpha3', $key); } break; case 'territorytoalpha3': $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'alpha3'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@alpha3=\'' . $key . '\']', 'type', $key); } break; case 'postaltoterritory': $_temp = self::_getFile('postalCodeData', '/supplementalData/postalCodeData/postCodeRegex', 'territoryId'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('postalCodeData', '/supplementalData/postalCodeData/postCodeRegex[@territoryId=\'' . $key . '\']', 'territoryId'); } break; case 'numberingsystem': $_temp = self::_getFile('numberingSystems', '/supplementalData/numberingSystems/numberingSystem', 'id'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('numberingSystems', '/supplementalData/numberingSystems/numberingSystem[@id=\'' . $key . '\']', 'digits', $key); if (empty($temp[$key])) { unset($temp[$key]); } } break; case 'chartofallback': $_temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character', 'value'); foreach ($_temp as $key => $keyvalue) { $temp2 = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $key . '\']/substitute', '', $key); $temp[current($temp2)] = $key; } break; case 'fallbacktochar': $_temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character', 'value'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $key . '\']/substitute', '', $key); } break; case 'localeupgrade': $_temp = self::_getFile('likelySubtags', '/supplementalData/likelySubtags/likelySubtag', 'from'); foreach ($_temp as $key => $keyvalue) { $temp += self::_getFile('likelySubtags', '/supplementalData/likelySubtags/likelySubtag[@from=\'' . $key . '\']', 'to', $key); } break; case 'unit': $_temp = self::_getFile($locale, '/ldml/units/unit', 'type'); foreach($_temp as $key => $keyvalue) { $_temp2 = self::_getFile($locale, '/ldml/units/unit[@type=\'' . $key . '\']/unitPattern', 'count'); $temp[$key] = $_temp2; } break; default : #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown list ($path) for parsing locale data."); break; } if (isset(self::$_cache)) { if (self::$_cacheTags) { self::$_cache->save( serialize($temp), $id, array('Zend_Locale')); } else { self::$_cache->save( serialize($temp), $id); } } return $temp; } /** * Read the LDML file, get a single path defined value * * @param string $locale * @param string $path * @param string $value * @return string * @access public */ public static function getContent($locale, $path, $value = false) { $locale = self::_checkLocale($locale); if (!isset(self::$_cache) && !self::$_cacheDisabled) { #require_once 'Zend/Cache.php'; self::$_cache = Zend_Cache::factory( 'Core', 'File', array('automatic_serialization' => true), array()); } $val = $value; if (is_array($value)) { $val = implode('_' , $value); } $val = urlencode($val); $id = strtr('Zend_LocaleC_' . $locale . '_' . $path . '_' . $val, array('-' => '_', '%' => '_', '+' => '_')); if (!self::$_cacheDisabled && ($result = self::$_cache->load($id))) { return unserialize($result); } switch(strtolower($path)) { case 'language': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/languages/language[@type=\'' . $value . '\']', 'type'); break; case 'script': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/scripts/script[@type=\'' . $value . '\']', 'type'); break; case 'country': case 'territory': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/territories/territory[@type=\'' . $value . '\']', 'type'); break; case 'variant': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/variants/variant[@type=\'' . $value . '\']', 'type'); break; case 'key': $temp = self::_getFile($locale, '/ldml/localeDisplayNames/keys/key[@type=\'' . $value . '\']', 'type'); break; case 'defaultcalendar': $temp = self::_getFile($locale, '/ldml/dates/calendars/default', 'choice', 'default'); break; case 'monthcontext': if (empty ($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/default', 'choice', 'context'); break; case 'defaultmonth': if (empty ($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/default', 'choice', 'default'); break; case 'month': if (!is_array($value)) { $temp = $value; $value = array("gregorian", "format", "wide", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/months/monthContext[@type=\'' . $value[1] . '\']/monthWidth[@type=\'' . $value[2] . '\']/month[@type=\'' . $value[3] . '\']', 'type'); break; case 'daycontext': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/default', 'choice', 'context'); break; case 'defaultday': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/default', 'choice', 'default'); break; case 'day': if (!is_array($value)) { $temp = $value; $value = array("gregorian", "format", "wide", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/days/dayContext[@type=\'' . $value[1] . '\']/dayWidth[@type=\'' . $value[2] . '\']/day[@type=\'' . $value[3] . '\']', 'type'); break; case 'quarter': if (!is_array($value)) { $temp = $value; $value = array("gregorian", "format", "wide", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/quarters/quarterContext[@type=\'' . $value[1] . '\']/quarterWidth[@type=\'' . $value[2] . '\']/quarter[@type=\'' . $value[3] . '\']', 'type'); break; case 'am': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/am', '', 'am'); break; case 'pm': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/pm', '', 'pm'); break; case 'era': if (!is_array($value)) { $temp = $value; $value = array("gregorian", "Abbr", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/eras/era' . $value[1] . '/era[@type=\'' . $value[2] . '\']', 'type'); break; case 'defaultdate': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/default', 'choice', 'default'); break; case 'date': if (empty($value)) { $value = array("gregorian", "medium"); } if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateFormats/dateFormatLength[@type=\'' . $value[1] . '\']/dateFormat/pattern', '', 'pattern'); break; case 'defaulttime': if (empty($value)) { $value = "gregorian"; } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/default', 'choice', 'default'); break; case 'time': if (empty($value)) { $value = array("gregorian", "medium"); } if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/timeFormats/timeFormatLength[@type=\'' . $value[1] . '\']/timeFormat/pattern', '', 'pattern'); break; case 'datetime': if (empty($value)) { $value = array("gregorian", "medium"); } if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp); } $date = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateFormats/dateFormatLength[@type=\'' . $value[1] . '\']/dateFormat/pattern', '', 'pattern'); $time = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/timeFormats/timeFormatLength[@type=\'' . $value[1] . '\']/timeFormat/pattern', '', 'pattern'); $datetime = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'' . $value[1] . '\']/dateTimeFormat/pattern', '', 'pattern'); $temp = str_replace(array('{0}', '{1}'), array(current($time), current($date)), current($datetime)); break; case 'dateitem': if (empty($value)) { $value = array("gregorian", "yyMMdd"); } if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateTimeFormats/availableFormats/dateFormatItem[@id=\'' . $value[1] . '\']', ''); break; case 'dateinterval': if (empty($value)) { $value = array("gregorian", "yMd", "y"); } if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp, $temp[0]); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\'' . $value[1] . '\']/greatestDifference[@id=\'' . $value[2] . '\']', ''); break; case 'field': if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/fields/field[@type=\'' . $value[1] . '\']/displayName', '', $value[1]); break; case 'relative': if (!is_array($value)) { $temp = $value; $value = array("gregorian", $temp); } $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/fields/field/relative[@type=\'' . $value[1] . '\']', '', $value[1]); break; case 'decimalnumber': $temp = self::_getFile($locale, '/ldml/numbers/decimalFormats/decimalFormatLength/decimalFormat/pattern', '', 'default'); break; case 'scientificnumber': $temp = self::_getFile($locale, '/ldml/numbers/scientificFormats/scientificFormatLength/scientificFormat/pattern', '', 'default'); break; case 'percentnumber': $temp = self::_getFile($locale, '/ldml/numbers/percentFormats/percentFormatLength/percentFormat/pattern', '', 'default'); break; case 'currencynumber': $temp = self::_getFile($locale, '/ldml/numbers/currencyFormats/currencyFormatLength/currencyFormat/pattern', '', 'default'); break; case 'nametocurrency': $temp = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $value . '\']/displayName', '', $value); break; case 'currencytoname': $temp = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $value . '\']/displayName', '', $value); $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type'); $temp = array(); foreach ($_temp as $key => $keyvalue) { $val = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/displayName', '', $key); if (!isset($val[$key]) or ($val[$key] != $value)) { continue; } if (!isset($temp[$val[$key]])) { $temp[$val[$key]] = $key; } else { $temp[$val[$key]] .= " " . $key; } } break; case 'currencysymbol': $temp = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $value . '\']/symbol', '', $value); break; case 'question': $temp = self::_getFile($locale, '/ldml/posix/messages/' . $value . 'str', '', $value); break; case 'currencyfraction': if (empty($value)) { $value = "DEFAULT"; } $temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $value . '\']', 'digits', 'digits'); break; case 'currencyrounding': if (empty($value)) { $value = "DEFAULT"; } $temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $value . '\']', 'rounding', 'rounding'); break; case 'currencytoregion': $temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $value . '\']/currency', 'iso4217', $value); break; case 'regiontocurrency': $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region', 'iso3166'); $temp = array(); foreach ($_temp as $key => $keyvalue) { $val = self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $key . '\']/currency', 'iso4217', $key); if (!isset($val[$key]) or ($val[$key] != $value)) { continue; } if (!isset($temp[$val[$key]])) { $temp[$val[$key]] = $key; } else { $temp[$val[$key]] .= " " . $key; } } break; case 'regiontoterritory': $temp = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $value . '\']', 'contains', $value); break; case 'territorytoregion': $_temp2 = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group', 'type'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $key . '\']', 'contains', $key); } $temp = array(); foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if ($found3 !== $value) { continue; } if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'scripttolanguage': $temp = self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $value . '\']', 'scripts', $value); break; case 'languagetoscript': $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'scripts', $key); } $temp = array(); foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if ($found3 !== $value) { continue; } if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'territorytolanguage': $temp = self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $value . '\']', 'territories', $value); break; case 'languagetoterritory': $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'territories', $key); } $temp = array(); foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if ($found3 !== $value) { continue; } if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'timezonetowindows': $temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/mapTimezones[@type=\'windows\']/mapZone[@other=\''.$value.'\']', 'type', $value); break; case 'windowstotimezone': $temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/mapTimezones[@type=\'windows\']/mapZone[@type=\''.$value.'\']', 'other', $value); break; case 'territorytotimezone': $temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/zoneFormatting/zoneItem[@type=\'' . $value . '\']', 'territory', $value); break; case 'timezonetoterritory': $temp = self::_getFile('supplementalData', '/supplementalData/timezoneData/zoneFormatting/zoneItem[@territory=\'' . $value . '\']', 'type', $value); break; case 'citytotimezone': $temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $value . '\']/exemplarCity', '', $value); break; case 'timezonetocity': $_temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone', 'type'); $temp = array(); foreach($_temp as $key => $found) { $temp += self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $key . '\']/exemplarCity', '', $key); if (!empty($temp[$key])) { if ($temp[$key] == $value) { $temp[$temp[$key]] = $key; } } unset($temp[$key]); } break; case 'phonetoterritory': $temp = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $value . '\']/telephoneCountryCode', 'code', $value); break; case 'territorytophone': $_temp2 = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory', 'territory'); $_temp = array(); foreach ($_temp2 as $key => $found) { $_temp += self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $key . '\']/telephoneCountryCode', 'code', $key); } $temp = array(); foreach($_temp as $key => $found) { $_temp3 = explode(" ", $found); foreach($_temp3 as $found3) { if ($found3 !== $value) { continue; } if (!isset($temp[$found3])) { $temp[$found3] = (string) $key; } else { $temp[$found3] .= " " . $key; } } } break; case 'numerictoterritory': $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\''.$value.'\']', 'numeric', $value); break; case 'territorytonumeric': $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@numeric=\''.$value.'\']', 'type', $value); break; case 'alpha3toterritory': $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\''.$value.'\']', 'alpha3', $value); break; case 'territorytoalpha3': $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@alpha3=\''.$value.'\']', 'type', $value); break; case 'postaltoterritory': $temp = self::_getFile('postalCodeData', '/supplementalData/postalCodeData/postCodeRegex[@territoryId=\'' . $value . '\']', 'territoryId'); break; case 'numberingsystem': $temp = self::_getFile('numberingSystems', '/supplementalData/numberingSystems/numberingSystem[@id=\'' . strtolower($value) . '\']', 'digits', $value); break; case 'chartofallback': $_temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character', 'value'); foreach ($_temp as $key => $keyvalue) { $temp2 = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $key . '\']/substitute', '', $key); if (current($temp2) == $value) { $temp = $key; } } break; $temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $value . '\']/substitute', '', $value); break; case 'fallbacktochar': $temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $value . '\']/substitute', ''); break; case 'localeupgrade': $temp = self::_getFile('likelySubtags', '/supplementalData/likelySubtags/likelySubtag[@from=\'' . $value . '\']', 'to', $value); break; case 'unit': $temp = self::_getFile($locale, '/ldml/units/unit[@type=\'' . $value[0] . '\']/unitPattern[@count=\'' . $value[1] . '\']', ''); break; default : #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown detail ($path) for parsing locale data."); break; } if (is_array($temp)) { $temp = current($temp); } if (isset(self::$_cache)) { if (self::$_cacheTags) { self::$_cache->save( serialize($temp), $id, array('Zend_Locale')); } else { self::$_cache->save( serialize($temp), $id); } } return $temp; } /** * Returns the set cache * * @return Zend_Cache_Core The set cache */ public static function getCache() { return self::$_cache; } /** * Set a cache for Zend_Locale_Data * * @param Zend_Cache_Core $cache A cache frontend */ public static function setCache(Zend_Cache_Core $cache) { self::$_cache = $cache; self::_getTagSupportForCache(); } /** * Returns true when a cache is set * * @return boolean */ public static function hasCache() { if (self::$_cache !== null) { return true; } return false; } /** * Removes any set cache * * @return void */ public static function removeCache() { self::$_cache = null; } /** * Clears all set cache data * * @return void */ public static function clearCache() { if (self::$_cacheTags) { self::$_cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('Zend_Locale')); } else { self::$_cache->clean(Zend_Cache::CLEANING_MODE_ALL); } } /** * Disables the cache * * @param unknown_type $flag */ public static function disableCache($flag) { self::$_cacheDisabled = (boolean) $flag; } /** * Internal method to check if the given cache supports tags * * @param Zend_Cache $cache */ private static function _getTagSupportForCache() { $backend = self::$_cache->getBackend(); if ($backend instanceof Zend_Cache_Backend_ExtendedInterface) { $cacheOptions = $backend->getCapabilities(); self::$_cacheTags = $cacheOptions['tags']; } else { self::$_cacheTags = false; } return self::$_cacheTags; } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Locale * @subpackage Format * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * include needed classes */ #require_once 'Zend/Locale/Data.php'; /** * @category Zend * @package Zend_Locale * @subpackage Format * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Locale_Format { const STANDARD = 'auto'; private static $_options = array('date_format' => null, 'number_format' => null, 'format_type' => 'iso', 'fix_date' => false, 'locale' => null, 'cache' => null, 'disableCache' => false, 'precision' => null); /** * Sets class wide options, if no option was given, the actual set options will be returned * The 'precision' option of a value is used to truncate or stretch extra digits. -1 means not to touch the extra digits. * The 'locale' option helps when parsing numbers and dates using separators and month names. * The date format 'format_type' option selects between CLDR/ISO date format specifier tokens and PHP's date() tokens. * The 'fix_date' option enables or disables heuristics that attempt to correct invalid dates. * The 'number_format' option can be used to specify a default number format string * The 'date_format' option can be used to specify a default date format string, but beware of using getDate(), * checkDateFormat() and getTime() after using setOptions() with a 'format'. To use these four methods * with the default date format for a locale, use array('date_format' => null, 'locale' => $locale) for their options. * * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false, * locale = Zend_Locale | locale string, precision = whole number between -1 and 30 * @throws Zend_Locale_Exception * @return Options array if no option was given */ public static function setOptions(array $options = array()) { self::$_options = self::_checkOptions($options) + self::$_options; return self::$_options; } /** * Internal function for checking the options array of proper input values * See {@link setOptions()} for details. * * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false, * locale = Zend_Locale | locale string, precision = whole number between -1 and 30 * @throws Zend_Locale_Exception * @return Options array if no option was given */ private static function _checkOptions(array $options = array()) { if (count($options) == 0) { return self::$_options; } foreach ($options as $name => $value) { $name = strtolower($name); if ($name !== 'locale') { if (gettype($value) === 'string') { $value = strtolower($value); } } switch($name) { case 'number_format' : if ($value == Zend_Locale_Format::STANDARD) { $locale = self::$_options['locale']; if (isset($options['locale'])) { $locale = $options['locale']; } $options['number_format'] = Zend_Locale_Data::getContent($locale, 'decimalnumber'); } else if ((gettype($value) !== 'string') and ($value !== NULL)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown number format type '" . gettype($value) . "'. " . "Format '$value' must be a valid number format string."); } break; case 'date_format' : if ($value == Zend_Locale_Format::STANDARD) { $locale = self::$_options['locale']; if (isset($options['locale'])) { $locale = $options['locale']; } $options['date_format'] = Zend_Locale_Format::getDateFormat($locale); } else if ((gettype($value) !== 'string') and ($value !== NULL)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown dateformat type '" . gettype($value) . "'. " . "Format '$value' must be a valid ISO or PHP date format string."); } else { if (((isset($options['format_type']) === true) and ($options['format_type'] == 'php')) or ((isset($options['format_type']) === false) and (self::$_options['format_type'] == 'php'))) { $options['date_format'] = Zend_Locale_Format::convertPhpToIsoFormat($value); } } break; case 'format_type' : if (($value != 'php') && ($value != 'iso')) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown date format type '$value'. Only 'iso' and 'php'" . " are supported."); } break; case 'fix_date' : if (($value !== true) && ($value !== false)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Enabling correction of dates must be either true or false" . "(fix_date='$value')."); } break; case 'locale' : $options['locale'] = Zend_Locale::findLocale($value); break; case 'cache' : if ($value instanceof Zend_Cache_Core) { Zend_Locale_Data::setCache($value); } break; case 'disablecache' : Zend_Locale_Data::disableCache($value); break; case 'precision' : if ($value === NULL) { $value = -1; } if (($value < -1) || ($value > 30)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("'$value' precision is not a whole number less than 30."); } break; default: #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown option: '$name' = '$value'"); break; } } return $options; } /** * Changes the numbers/digits within a given string from one script to another * 'Decimal' representated the stardard numbers 0-9, if a script does not exist * an exception will be thrown. * * Examples for conversion from Arabic to Latin numerals: * convertNumerals('١١٠ Tests', 'Arab'); -> returns '100 Tests' * Example for conversion from Latin to Arabic numerals: * convertNumerals('100 Tests', 'Latn', 'Arab'); -> returns '١١٠ Tests' * * @param string $input String to convert * @param string $from Script to parse, see {@link Zend_Locale::getScriptList()} for details. * @param string $to OPTIONAL Script to convert to * @return string Returns the converted input * @throws Zend_Locale_Exception */ public static function convertNumerals($input, $from, $to = null) { if (!self::_getUniCodeSupport()) { trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE); } $from = strtolower($from); $source = Zend_Locale_Data::getContent('en', 'numberingsystem', $from); if (empty($source)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown script '$from'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9."); } if ($to !== null) { $to = strtolower($to); $target = Zend_Locale_Data::getContent('en', 'numberingsystem', $to); if (empty($target)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown script '$to'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9."); } } else { $target = '0123456789'; } for ($x = 0; $x < 10; ++$x) { $asource[$x] = "/" . iconv_substr($source, $x, 1, 'UTF-8') . "/u"; $atarget[$x] = iconv_substr($target, $x, 1, 'UTF-8'); } return preg_replace($asource, $atarget, $input); } /** * Returns the normalized number from a localized one * Parsing depends on given locale (grouping and decimal) * * Examples for input: * '2345.4356,1234' = 23455456.1234 * '+23,3452.123' = 233452.123 * '12343 ' = 12343 * '-9456' = -9456 * '0' = 0 * * @param string $input Input string to parse for numbers * @param array $options Options: locale, precision. See {@link setOptions()} for details. * @return string Returns the extracted number * @throws Zend_Locale_Exception */ public static function getNumber($input, array $options = array()) { $options = self::_checkOptions($options) + self::$_options; if (!is_string($input)) { return $input; } if (!self::isNumber($input, $options)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception('No localized value in ' . $input . ' found, or the given number does not match the localized format'); } // Get correct signs for this locale $symbols = Zend_Locale_Data::getList($options['locale'],'symbols'); // Change locale input to be default number if ((strpos($input, $symbols['minus']) !== false) || (strpos($input, '-') !== false)) { $input = strtr($input, array($symbols['minus'] => '', '-' => '')); $input = '-' . $input; } $input = str_replace($symbols['group'],'', $input); if (strpos($input, $symbols['decimal']) !== false) { if ($symbols['decimal'] != '.') { $input = str_replace($symbols['decimal'], ".", $input); } $pre = substr($input, strpos($input, '.') + 1); if ($options['precision'] === null) { $options['precision'] = strlen($pre); } if (strlen($pre) >= $options['precision']) { $input = substr($input, 0, strlen($input) - strlen($pre) + $options['precision']); } if (($options['precision'] == 0) && ($input[strlen($input) - 1] == '.')) { $input = substr($input, 0, -1); } } return $input; } /** * Returns a locale formatted number depending on the given options. * The seperation and fraction sign is used from the set locale. * ##0.# -> 12345.12345 -> 12345.12345 * ##0.00 -> 12345.12345 -> 12345.12 * ##,##0.00 -> 12345.12345 -> 12,345.12 * * @param string $input Localized number string * @param array $options Options: number_format, locale, precision. See {@link setOptions()} for details. * @return string locale formatted number * @throws Zend_Locale_Exception */ public static function toNumber($value, array $options = array()) { // load class within method for speed #require_once 'Zend/Locale/Math.php'; $value = Zend_Locale_Math::normalize($value); $value = Zend_Locale_Math::floatalize($value); $options = self::_checkOptions($options) + self::$_options; $options['locale'] = (string) $options['locale']; // Get correct signs for this locale $symbols = Zend_Locale_Data::getList($options['locale'], 'symbols'); $oenc = iconv_get_encoding('internal_encoding'); iconv_set_encoding('internal_encoding', 'UTF-8'); // Get format $format = $options['number_format']; if ($format === null) { $format = Zend_Locale_Data::getContent($options['locale'], 'decimalnumber'); $format = self::_seperateFormat($format, $value, $options['precision']); if ($options['precision'] !== null) { $value = Zend_Locale_Math::normalize(Zend_Locale_Math::round($value, $options['precision'])); } } else { // seperate negative format pattern when available $format = self::_seperateFormat($format, $value, $options['precision']); if (strpos($format, '.')) { if (is_numeric($options['precision'])) { $value = Zend_Locale_Math::round($value, $options['precision']); } else { if (substr($format, iconv_strpos($format, '.') + 1, 3) == '###') { $options['precision'] = null; } else { $options['precision'] = iconv_strlen(iconv_substr($format, iconv_strpos($format, '.') + 1, iconv_strrpos($format, '0') - iconv_strpos($format, '.'))); $format = iconv_substr($format, 0, iconv_strpos($format, '.') + 1) . '###' . iconv_substr($format, iconv_strrpos($format, '0') + 1); } } } else { $value = Zend_Locale_Math::round($value, 0); $options['precision'] = 0; } $value = Zend_Locale_Math::normalize($value); } if (iconv_strpos($format, '0') === false) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception('Wrong format... missing 0'); } // get number parts $pos = iconv_strpos($value, '.'); if ($pos !== false) { if ($options['precision'] === null) { $precstr = iconv_substr($value, $pos + 1); } else { $precstr = iconv_substr($value, $pos + 1, $options['precision']); if (iconv_strlen($precstr) < $options['precision']) { $precstr = $precstr . str_pad("0", ($options['precision'] - iconv_strlen($precstr)), "0"); } } } else { if ($options['precision'] > 0) { $precstr = str_pad("0", ($options['precision']), "0"); } } if ($options['precision'] === null) { if (isset($precstr)) { $options['precision'] = iconv_strlen($precstr); } else { $options['precision'] = 0; } } // get fraction and format lengths if (strpos($value, '.') !== false) { $number = substr((string) $value, 0, strpos($value, '.')); } else { $number = $value; } $prec = call_user_func(Zend_Locale_Math::$sub, $value, $number, $options['precision']); $prec = Zend_Locale_Math::floatalize($prec); $prec = Zend_Locale_Math::normalize($prec); if (iconv_strpos($prec, '-') !== false) { $prec = iconv_substr($prec, 1); } if (($prec == 0) and ($options['precision'] > 0)) { $prec = "0.0"; } if (($options['precision'] + 2) > iconv_strlen($prec)) { $prec = str_pad((string) $prec, $options['precision'] + 2, "0", STR_PAD_RIGHT); } if (iconv_strpos($number, '-') !== false) { $number = iconv_substr($number, 1); } $group = iconv_strrpos($format, ','); $group2 = iconv_strpos ($format, ','); $point = iconv_strpos ($format, '0'); // Add fraction $rest = ""; if (iconv_strpos($format, '.')) { $rest = iconv_substr($format, iconv_strpos($format, '.') + 1); $length = iconv_strlen($rest); for($x = 0; $x < $length; ++$x) { if (($rest[0] == '0') || ($rest[0] == '#')) { $rest = iconv_substr($rest, 1); } } $format = iconv_substr($format, 0, iconv_strlen($format) - iconv_strlen($rest)); } if ($options['precision'] == '0') { if (iconv_strrpos($format, '-') != 0) { $format = iconv_substr($format, 0, $point) . iconv_substr($format, iconv_strrpos($format, '#') + 2); } else { $format = iconv_substr($format, 0, $point); } } else { $format = iconv_substr($format, 0, $point) . $symbols['decimal'] . iconv_substr($prec, 2); } $format .= $rest; // Add seperation if ($group == 0) { // no seperation $format = $number . iconv_substr($format, $point); } else if ($group == $group2) { // only 1 seperation $seperation = ($point - $group); for ($x = iconv_strlen($number); $x > $seperation; $x -= $seperation) { if (iconv_substr($number, 0, $x - $seperation) !== "") { $number = iconv_substr($number, 0, $x - $seperation) . $symbols['group'] . iconv_substr($number, $x - $seperation); } } $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point); } else { // 2 seperations if (iconv_strlen($number) > ($point - $group)) { $seperation = ($point - $group); $number = iconv_substr($number, 0, iconv_strlen($number) - $seperation) . $symbols['group'] . iconv_substr($number, iconv_strlen($number) - $seperation); if ((iconv_strlen($number) - 1) > ($point - $group + 1)) { $seperation2 = ($group - $group2 - 1); for ($x = iconv_strlen($number) - $seperation2 - 2; $x > $seperation2; $x -= $seperation2) { $number = iconv_substr($number, 0, $x - $seperation2) . $symbols['group'] . iconv_substr($number, $x - $seperation2); } } } $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point); } // set negative sign if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $options['precision']) < 0) { if (iconv_strpos($format, '-') === false) { $format = $symbols['minus'] . $format; } else { $format = str_replace('-', $symbols['minus'], $format); } } iconv_set_encoding('internal_encoding', $oenc); return (string) $format; } private static function _seperateFormat($format, $value, $precision) { if (iconv_strpos($format, ';') !== false) { if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $precision) < 0) { $tmpformat = iconv_substr($format, iconv_strpos($format, ';') + 1); if ($tmpformat[0] == '(') { $format = iconv_substr($format, 0, iconv_strpos($format, ';')); } else { $format = $tmpformat; } } else { $format = iconv_substr($format, 0, iconv_strpos($format, ';')); } } return $format; } /** * Checks if the input contains a normalized or localized number * * @param string $input Localized number string * @param array $options Options: locale. See {@link setOptions()} for details. * @return boolean Returns true if a number was found */ public static function isNumber($input, array $options = array()) { if (!self::_getUniCodeSupport()) { trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE); } $options = self::_checkOptions($options) + self::$_options; // Get correct signs for this locale $symbols = Zend_Locale_Data::getList($options['locale'],'symbols'); $regexs = Zend_Locale_Format::_getRegexForType('decimalnumber', $options); $regexs = array_merge($regexs, Zend_Locale_Format::_getRegexForType('scientificnumber', $options)); if (!empty($input) && ($input[0] == $symbols['decimal'])) { $input = 0 . $input; } foreach ($regexs as $regex) { preg_match($regex, $input, $found); if (isset($found[0])) { return true; } } return false; } /** * Internal method to convert cldr number syntax into regex * * @param string $type * @return string */ private static function _getRegexForType($type, $options) { $decimal = Zend_Locale_Data::getContent($options['locale'], $type); $decimal = preg_replace('/[^#0,;\.\-Ee]/u', '',$decimal); $patterns = explode(';', $decimal); if (count($patterns) == 1) { $patterns[1] = '-' . $patterns[0]; } $symbols = Zend_Locale_Data::getList($options['locale'],'symbols'); foreach($patterns as $pkey => $pattern) { $regex[$pkey] = '/^'; $rest = 0; $end = null; if (strpos($pattern, '.') !== false) { $end = substr($pattern, strpos($pattern, '.') + 1); $pattern = substr($pattern, 0, -strlen($end) - 1); } if (strpos($pattern, ',') !== false) { $parts = explode(',', $pattern); $count = count($parts); foreach($parts as $key => $part) { switch ($part) { case '#': case '-#': if ($part[0] == '-') { $regex[$pkey] .= '[' . $symbols['minus'] . '-]{0,1}'; } else { $regex[$pkey] .= '[' . $symbols['plus'] . '+]{0,1}'; } if (($parts[$key + 1]) == '##0') { $regex[$pkey] .= '[0-9]{1,3}'; } else if (($parts[$key + 1]) == '##') { $regex[$pkey] .= '[0-9]{1,2}'; } else { throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 1):"' . $pattern . '"'); } break; case '##': if ($parts[$key + 1] == '##0') { $regex[$pkey] .= '(\\' . $symbols['group'] . '{0,1}[0-9]{2})*'; } else { throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 2):"' . $pattern . '"'); } break; case '##0': if ($parts[$key - 1] == '##') { $regex[$pkey] .= '[0-9]'; } else if (($parts[$key - 1] == '#') || ($parts[$key - 1] == '-#')) { $regex[$pkey] .= '(\\' . $symbols['group'] . '{0,1}[0-9]{3})*'; } else { throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 3):"' . $pattern . '"'); } break; case '#0': if ($key == 0) { $regex[$pkey] .= '[0-9]*'; } else { throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 4):"' . $pattern . '"'); } break; } } } if (strpos($pattern, 'E') !== false) { if (($pattern == '#E0') || ($pattern == '#E00')) { $regex[$pkey] .= '[' . $symbols['plus']. '+]{0,1}[0-9]{1,}(\\' . $symbols['decimal'] . '[0-9]{1,})*[eE][' . $symbols['plus']. '+]{0,1}[0-9]{1,}'; } else if (($pattern == '-#E0') || ($pattern == '-#E00')) { $regex[$pkey] .= '[' . $symbols['minus']. '-]{0,1}[0-9]{1,}(\\' . $symbols['decimal'] . '[0-9]{1,})*[eE][' . $symbols['minus']. '-]{0,1}[0-9]{1,}'; } else { throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 5):"' . $pattern . '"'); } } if (!empty($end)) { if ($end == '###') { $regex[$pkey] .= '(\\' . $symbols['decimal'] . '{1}[0-9]{1,}){0,1}'; } else if ($end == '###-') { $regex[$pkey] .= '(\\' . $symbols['decimal'] . '{1}[0-9]{1,}){0,1}[' . $symbols['minus']. '-]'; } else { throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 6):"' . $pattern . '"'); } } $regex[$pkey] .= '$/u'; } return $regex; } /** * Alias for getNumber * * @param string $value Number to localize * @param array $options Options: locale, precision. See {@link setOptions()} for details. * @return float */ public static function getFloat($input, array $options = array()) { return floatval(self::getNumber($input, $options)); } /** * Returns a locale formatted integer number * Alias for toNumber() * * @param string $value Number to normalize * @param array $options Options: locale, precision. See {@link setOptions()} for details. * @return string Locale formatted number */ public static function toFloat($value, array $options = array()) { $options['number_format'] = Zend_Locale_Format::STANDARD; return self::toNumber($value, $options); } /** * Returns if a float was found * Alias for isNumber() * * @param string $input Localized number string * @param array $options Options: locale. See {@link setOptions()} for details. * @return boolean Returns true if a number was found */ public static function isFloat($value, array $options = array()) { return self::isNumber($value, $options); } /** * Returns the first found integer from an string * Parsing depends on given locale (grouping and decimal) * * Examples for input: * ' 2345.4356,1234' = 23455456 * '+23,3452.123' = 233452 * ' 12343 ' = 12343 * '-9456km' = -9456 * '0' = 0 * '(-){0,1}(\d+(\.){0,1})*(\,){0,1})\d+' * * @param string $input Input string to parse for numbers * @param array $options Options: locale. See {@link setOptions()} for details. * @return integer Returns the extracted number */ public static function getInteger($input, array $options = array()) { $options['precision'] = 0; return intval(self::getFloat($input, $options)); } /** * Returns a localized number * * @param string $value Number to normalize * @param array $options Options: locale. See {@link setOptions()} for details. * @return string Locale formatted number */ public static function toInteger($value, array $options = array()) { $options['precision'] = 0; $options['number_format'] = Zend_Locale_Format::STANDARD; return self::toNumber($value, $options); } /** * Returns if a integer was found * * @param string $input Localized number string * @param array $options Options: locale. See {@link setOptions()} for details. * @return boolean Returns true if a integer was found */ public static function isInteger($value, array $options = array()) { if (!self::isNumber($value, $options)) { return false; } if (self::getInteger($value, $options) == self::getFloat($value, $options)) { return true; } return false; } /** * Converts a format string from PHP's date format to ISO format * Remember that Zend Date always returns localized string, so a month name which returns the english * month in php's date() will return the translated month name with this function... use 'en' as locale * if you are in need of the original english names * * The conversion has the following restrictions: * 'a', 'A' - Meridiem is not explicit upper/lowercase, you have to upper/lowercase the translated value yourself * * @param string $format Format string in PHP's date format * @return string Format string in ISO format */ public static function convertPhpToIsoFormat($format) { if ($format === null) { return null; } $convert = array('d' => 'dd' , 'D' => 'EE' , 'j' => 'd' , 'l' => 'EEEE', 'N' => 'eee' , 'S' => 'SS' , 'w' => 'e' , 'z' => 'D' , 'W' => 'ww' , 'F' => 'MMMM', 'm' => 'MM' , 'M' => 'MMM' , 'n' => 'M' , 't' => 'ddd' , 'L' => 'l' , 'o' => 'YYYY', 'Y' => 'yyyy', 'y' => 'yy' , 'a' => 'a' , 'A' => 'a' , 'B' => 'B' , 'g' => 'h' , 'G' => 'H' , 'h' => 'hh' , 'H' => 'HH' , 'i' => 'mm' , 's' => 'ss' , 'e' => 'zzzz', 'I' => 'I' , 'O' => 'Z' , 'P' => 'ZZZZ', 'T' => 'z' , 'Z' => 'X' , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ', 'r' => 'r' , 'U' => 'U'); $values = str_split($format); foreach ($values as $key => $value) { if (isset($convert[$value]) === true) { $values[$key] = $convert[$value]; } } return join($values); } /** * Parse date and split in named array fields * * @param string $date Date string to parse * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format */ private static function _parseDate($date, $options) { if (!self::_getUniCodeSupport()) { trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE); } $options = self::_checkOptions($options) + self::$_options; $test = array('h', 'H', 'm', 's', 'y', 'Y', 'M', 'd', 'D', 'E', 'S', 'l', 'B', 'I', 'X', 'r', 'U', 'G', 'w', 'e', 'a', 'A', 'Z', 'z', 'v'); $format = $options['date_format']; $number = $date; // working copy $result['date_format'] = $format; // save the format used to normalize $number (convenience) $result['locale'] = $options['locale']; // save the locale used to normalize $number (convenience) $oenc = iconv_get_encoding('internal_encoding'); iconv_set_encoding('internal_encoding', 'UTF-8'); $day = iconv_strpos($format, 'd'); $month = iconv_strpos($format, 'M'); $year = iconv_strpos($format, 'y'); $hour = iconv_strpos($format, 'H'); $min = iconv_strpos($format, 'm'); $sec = iconv_strpos($format, 's'); $am = null; if ($hour === false) { $hour = iconv_strpos($format, 'h'); } if ($year === false) { $year = iconv_strpos($format, 'Y'); } if ($day === false) { $day = iconv_strpos($format, 'E'); if ($day === false) { $day = iconv_strpos($format, 'D'); } } if ($day !== false) { $parse[$day] = 'd'; if (!empty($options['locale']) && ($options['locale'] !== 'root') && (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) { // erase day string $daylist = Zend_Locale_Data::getList($options['locale'], 'day'); foreach($daylist as $key => $name) { if (iconv_strpos($number, $name) !== false) { $number = str_replace($name, "EEEE", $number); break; } } } } $position = false; if ($month !== false) { $parse[$month] = 'M'; if (!empty($options['locale']) && ($options['locale'] !== 'root') && (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) { // prepare to convert month name to their numeric equivalents, if requested, // and we have a $options['locale'] $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'], 'month')); if ($position === false) { $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'], 'month', array('gregorian', 'format', 'abbreviated'))); } } } if ($year !== false) { $parse[$year] = 'y'; } if ($hour !== false) { $parse[$hour] = 'H'; } if ($min !== false) { $parse[$min] = 'm'; } if ($sec !== false) { $parse[$sec] = 's'; } if (empty($parse)) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unknown date format, neither date nor time in '" . $format . "' found"); } ksort($parse); // get daytime if (iconv_strpos($format, 'a') !== false) { if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'am'))) !== false) { $am = true; } else if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'pm'))) !== false) { $am = false; } } // split number parts $split = false; preg_match_all('/\d+/u', $number, $splitted); if (count($splitted[0]) == 0) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("No date part in '$date' found."); } if (count($splitted[0]) == 1) { $split = 0; } $cnt = 0; foreach($parse as $key => $value) { switch($value) { case 'd': if ($split === false) { if (count($splitted[0]) > $cnt) { $result['day'] = $splitted[0][$cnt]; } } else { $result['day'] = iconv_substr($splitted[0][0], $split, 2); $split += 2; } ++$cnt; break; case 'M': if ($split === false) { if (count($splitted[0]) > $cnt) { $result['month'] = $splitted[0][$cnt]; } } else { $result['month'] = iconv_substr($splitted[0][0], $split, 2); $split += 2; } ++$cnt; break; case 'y': $length = 2; if ((iconv_substr($format, $year, 4) == 'yyyy') || (iconv_substr($format, $year, 4) == 'YYYY')) { $length = 4; } if ($split === false) { if (count($splitted[0]) > $cnt) { $result['year'] = $splitted[0][$cnt]; } } else { $result['year'] = iconv_substr($splitted[0][0], $split, $length); $split += $length; } ++$cnt; break; case 'H': if ($split === false) { if (count($splitted[0]) > $cnt) { $result['hour'] = $splitted[0][$cnt]; } } else { $result['hour'] = iconv_substr($splitted[0][0], $split, 2); $split += 2; } ++$cnt; break; case 'm': if ($split === false) { if (count($splitted[0]) > $cnt) { $result['minute'] = $splitted[0][$cnt]; } } else { $result['minute'] = iconv_substr($splitted[0][0], $split, 2); $split += 2; } ++$cnt; break; case 's': if ($split === false) { if (count($splitted[0]) > $cnt) { $result['second'] = $splitted[0][$cnt]; } } else { $result['second'] = iconv_substr($splitted[0][0], $split, 2); $split += 2; } ++$cnt; break; } } // AM/PM correction if ($hour !== false) { if (($am === true) and ($result['hour'] == 12)){ $result['hour'] = 0; } else if (($am === false) and ($result['hour'] != 12)) { $result['hour'] += 12; } } if ($options['fix_date'] === true) { $result['fixed'] = 0; // nothing has been "fixed" by swapping date parts around (yet) } if ($day !== false) { // fix false month if (isset($result['day']) and isset($result['month'])) { if (($position !== false) and ((iconv_strpos($date, $result['day']) === false) or (isset($result['year']) and (iconv_strpos($date, $result['year']) === false)))) { if ($options['fix_date'] !== true) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format . "' (false month, $position, $month)"); } $temp = $result['day']; $result['day'] = $result['month']; $result['month'] = $temp; $result['fixed'] = 1; } } // fix switched values d <> y if (isset($result['day']) and isset($result['year'])) { if ($result['day'] > 31) { if ($options['fix_date'] !== true) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format . "' (d <> y)"); } $temp = $result['year']; $result['year'] = $result['day']; $result['day'] = $temp; $result['fixed'] = 2; } } // fix switched values M <> y if (isset($result['month']) and isset($result['year'])) { if ($result['month'] > 31) { if ($options['fix_date'] !== true) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format . "' (M <> y)"); } $temp = $result['year']; $result['year'] = $result['month']; $result['month'] = $temp; $result['fixed'] = 3; } } // fix switched values M <> d if (isset($result['month']) and isset($result['day'])) { if ($result['month'] > 12) { if ($options['fix_date'] !== true || $result['month'] > 31) { iconv_set_encoding('internal_encoding', $oenc); #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format . "' (M <> d)"); } $temp = $result['day']; $result['day'] = $result['month']; $result['month'] = $temp; $result['fixed'] = 4; } } } if (isset($result['year'])) { if (((iconv_strlen($result['year']) == 2) && ($result['year'] < 10)) || (((iconv_strpos($format, 'yy') !== false) && (iconv_strpos($format, 'yyyy') === false)) || ((iconv_strpos($format, 'YY') !== false) && (iconv_strpos($format, 'YYYY') === false)))) { if (($result['year'] >= 0) && ($result['year'] < 100)) { if ($result['year'] < 70) { $result['year'] = (int) $result['year'] + 100; } $result['year'] = (int) $result['year'] + 1900; } } } iconv_set_encoding('internal_encoding', $oenc); return $result; } /** * Search $number for a month name found in $monthlist, and replace if found. * * @param string $number Date string (modified) * @param array $monthlist List of month names * * @return int|false Position of replaced string (false if nothing replaced) */ protected static function _replaceMonth(&$number, $monthlist) { // If $locale was invalid, $monthlist will default to a "root" identity // mapping for each month number from 1 to 12. // If no $locale was given, or $locale was invalid, do not use this identity mapping to normalize. // Otherwise, translate locale aware month names in $number to their numeric equivalents. $position = false; if ($monthlist && $monthlist[1] != 1) { foreach($monthlist as $key => $name) { if (($position = iconv_strpos($number, $name, 0, 'UTF-8')) !== false) { $number = str_ireplace($name, $key, $number); return $position; } } } return false; } /** * Returns the default date format for $locale. * * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT') * @return string format * @throws Zend_Locale_Exception throws an exception when locale data is broken */ public static function getDateFormat($locale = null) { $format = Zend_Locale_Data::getContent($locale, 'date'); if (empty($format)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("failed to receive data from locale $locale"); } return $format; } /** * Returns an array with the normalized date from an locale date * a input of 10.01.2006 without a $locale would return: * array ('day' => 10, 'month' => 1, 'year' => 2006) * The 'locale' option is only used to convert human readable day * and month names to their numeric equivalents. * The 'format' option allows specification of self-defined date formats, * when not using the default format for the 'locale'. * * @param string $date Date string * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format */ public static function getDate($date, array $options = array()) { $options = self::_checkOptions($options) + self::$_options; if (empty($options['date_format'])) { $options['format_type'] = 'iso'; $options['date_format'] = self::getDateFormat($options['locale']); } return self::_parseDate($date, $options); } /** * Returns if the given datestring contains all date parts from the given format. * If no format is given, the default date format from the locale is used * If you want to check if the date is a proper date you should use Zend_Date::isDate() * * @param string $date Date string * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details. * @return boolean */ public static function checkDateFormat($date, array $options = array()) { try { $date = self::getDate($date, $options); } catch (Exception $e) { return false; } if (empty($options['date_format'])) { $options['format_type'] = 'iso'; $options['date_format'] = self::getDateFormat($options['locale']); } $options = self::_checkOptions($options) + self::$_options; // day expected but not parsed if ((iconv_strpos($options['date_format'], 'd', 0, 'UTF-8') !== false) and (!isset($date['day']) or ($date['day'] === ""))) { return false; } // month expected but not parsed if ((iconv_strpos($options['date_format'], 'M', 0, 'UTF-8') !== false) and (!isset($date['month']) or ($date['month'] === ""))) { return false; } // year expected but not parsed if (((iconv_strpos($options['date_format'], 'Y', 0, 'UTF-8') !== false) or (iconv_strpos($options['date_format'], 'y', 0, 'UTF-8') !== false)) and (!isset($date['year']) or ($date['year'] === ""))) { return false; } // second expected but not parsed if ((iconv_strpos($options['date_format'], 's', 0, 'UTF-8') !== false) and (!isset($date['second']) or ($date['second'] === ""))) { return false; } // minute expected but not parsed if ((iconv_strpos($options['date_format'], 'm', 0, 'UTF-8') !== false) and (!isset($date['minute']) or ($date['minute'] === ""))) { return false; } // hour expected but not parsed if (((iconv_strpos($options['date_format'], 'H', 0, 'UTF-8') !== false) or (iconv_strpos($options['date_format'], 'h', 0, 'UTF-8') !== false)) and (!isset($date['hour']) or ($date['hour'] === ""))) { return false; } return true; } /** * Returns the default time format for $locale. * * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT') * @return string format */ public static function getTimeFormat($locale = null) { $format = Zend_Locale_Data::getContent($locale, 'time'); if (empty($format)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("failed to receive data from locale $locale"); } return $format; } /** * Returns an array with 'hour', 'minute', and 'second' elements extracted from $time * according to the order described in $format. For a format of 'H:m:s', and * an input of 11:20:55, getTime() would return: * array ('hour' => 11, 'minute' => 20, 'second' => 55) * The optional $locale parameter may be used to help extract times from strings * containing both a time and a day or month name. * * @param string $time Time string * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format */ public static function getTime($time, array $options = array()) { $options = self::_checkOptions($options) + self::$_options; if (empty($options['date_format'])) { $options['format_type'] = 'iso'; $options['date_format'] = self::getTimeFormat($options['locale']); } return self::_parseDate($time, $options); } /** * Returns the default datetime format for $locale. * * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT') * @return string format */ public static function getDateTimeFormat($locale = null) { $format = Zend_Locale_Data::getContent($locale, 'datetime'); if (empty($format)) { #require_once 'Zend/Locale/Exception.php'; throw new Zend_Locale_Exception("failed to receive data from locale $locale"); } return $format; } /** * Returns an array with 'year', 'month', 'day', 'hour', 'minute', and 'second' elements * extracted from $datetime according to the order described in $format. For a format of 'd.M.y H:m:s', * and an input of 10.05.1985 11:20:55, getDateTime() would return: * array ('year' => 1985, 'month' => 5, 'day' => 10, 'hour' => 11, 'minute' => 20, 'second' => 55) * The optional $locale parameter may be used to help extract times from strings * containing both a time and a day or month name. * * @param string $datetime DateTime string * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format */ public static function getDateTime($datetime, array $options = array()) { $options = self::_checkOptions($options) + self::$_options; if (empty($options['date_format'])) { $options['format_type'] = 'iso'; $options['date_format'] = self::getDateTimeFormat($options['locale']); } return self::_parseDate($datetime, $options); } /** * Internal method to detect of Unicode supports UTF8 * which should be enabled within vanilla php installations * * @return boolean */ protected static function _getUniCodeSupport() { return (@preg_match('/\pL/u', 'a')) ? true : false; } } /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Locale * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * Utility class for proxying math function to bcmath functions, if present, * otherwise to PHP builtin math operators, with limited detection of overflow conditions. * Sampling of PHP environments and platforms suggests that at least 80% to 90% support bcmath. * Thus, this file should be as light as possible. * * @category Zend * @package Zend_Locale * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Locale_Math { // support unit testing without using bcmath functions public static $_bcmathDisabled = false; public static $add = array('Zend_Locale_Math', 'Add'); public static $sub = array('Zend_Locale_Math', 'Sub'); public static $pow = array('Zend_Locale_Math', 'Pow'); public static $mul = array('Zend_Locale_Math', 'Mul'); public static $div = array('Zend_Locale_Math', 'Div'); public static $comp = array('Zend_Locale_Math', 'Comp'); public static $sqrt = array('Zend_Locale_Math', 'Sqrt'); public static $mod = array('Zend_Locale_Math', 'Mod'); public static $scale = 'bcscale'; public static function isBcmathDisabled() { return self::$_bcmathDisabled; } /** * Surprisingly, the results of this implementation of round() * prove better than the native PHP round(). For example, try: * round(639.795, 2); * round(267.835, 2); * round(0.302515, 5); * round(0.36665, 4); * then try: * Zend_Locale_Math::round('639.795', 2); */ public static function round($op1, $precision = 0) { if (self::$_bcmathDisabled) { $op1 = round($op1, $precision); if (strpos((string) $op1, 'E') === false) { return self::normalize(round($op1, $precision)); } } if (strpos($op1, 'E') !== false) { $op1 = self::floatalize($op1); } $op1 = trim(self::normalize($op1)); $length = strlen($op1); if (($decPos = strpos($op1, '.')) === false) { $op1 .= '.0'; $decPos = $length; $length += 2; } if ($precision < 0 && abs($precision) > $decPos) { return '0'; } $digitsBeforeDot = $length - ($decPos + 1); if ($precision >= ($length - ($decPos + 1))) { return $op1; } if ($precision === 0) { $triggerPos = 1; $roundPos = -1; } elseif ($precision > 0) { $triggerPos = $precision + 1; $roundPos = $precision; } else { $triggerPos = $precision; $roundPos = $precision -1; } $triggerDigit = $op1[$triggerPos + $decPos]; if ($precision < 0) { // zero fill digits to the left of the decimal place $op1 = substr($op1, 0, $decPos + $precision) . str_pad('', abs($precision), '0'); } if ($triggerDigit >= '5') { if ($roundPos + $decPos == -1) { return str_pad('1', $decPos + 1, '0'); } $roundUp = str_pad('', $length, '0'); $roundUp[$decPos] = '.'; $roundUp[$roundPos + $decPos] = '1'; if ($op1 > 0) { if (self::$_bcmathDisabled) { return Zend_Locale_Math_PhpMath::Add($op1, $roundUp, $precision); } return self::Add($op1, $roundUp, $precision); } else { if (self::$_bcmathDisabled) { return Zend_Locale_Math_PhpMath::Sub($op1, $roundUp, $precision); } return self::Sub($op1, $roundUp, $precision); } } elseif ($precision >= 0) { return substr($op1, 0, $decPos + ($precision ? $precision + 1: 0)); } return (string) $op1; } /** * Convert a scientific notation to float * Additionally fixed a problem with PHP <= 5.2.x with big integers * * @param string $value */ public static function floatalize($value) { $value = strtoupper($value); if (strpos($value, 'E') === false) { return $value; } $number = substr($value, 0, strpos($value, 'E')); if (strpos($number, '.') !== false) { $post = strlen(substr($number, strpos($number, '.') + 1)); $mantis = substr($value, strpos($value, 'E') + 1); if ($mantis < 0) { $post += abs((int) $mantis); } $value = number_format($value, $post, '.', ''); } else { $value = number_format($value, 0, '.', ''); } return $value; } /** * Normalizes an input to standard english notation * Fixes a problem of BCMath with setLocale which is PHP related * * @param integer $value Value to normalize * @return string Normalized string without BCMath problems */ public static function normalize($value) { $convert = localeconv(); $value = str_replace($convert['thousands_sep'], "",(string) $value); $value = str_replace($convert['positive_sign'], "", $value); $value = str_replace($convert['decimal_point'], ".",$value); if (!empty($convert['negative_sign']) and (strpos($value, $convert['negative_sign']))) { $value = str_replace($convert['negative_sign'], "", $value); $value = "-" . $value; } return $value; } /** * Localizes an input from standard english notation * Fixes a problem of BCMath with setLocale which is PHP related * * @param integer $value Value to normalize * @return string Normalized string without BCMath problems */ public static function localize($value) { $convert = localeconv(); $value = str_replace(".", $convert['decimal_point'], (string) $value); if (!empty($convert['negative_sign']) and (strpos($value, "-"))) { $value = str_replace("-", $convert['negative_sign'], $value); } return $value; } /** * Changes exponential numbers to plain string numbers * Fixes a problem of BCMath with numbers containing exponents * * @param integer $value Value to erase the exponent * @param integer $scale (Optional) Scale to use * @return string */ public static function exponent($value, $scale = null) { if (!extension_loaded('bcmath')) { return $value; } $split = explode('e', $value); if (count($split) == 1) { $split = explode('E', $value); } if (count($split) > 1) { $value = bcmul($split[0], bcpow(10, $split[1], $scale), $scale); } return $value; } /** * BCAdd - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @param integer $scale * @return string */ public static function Add($op1, $op2, $scale = null) { $op1 = self::exponent($op1, $scale); $op2 = self::exponent($op2, $scale); return bcadd($op1, $op2, $scale); } /** * BCSub - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @param integer $scale * @return string */ public static function Sub($op1, $op2, $scale = null) { $op1 = self::exponent($op1, $scale); $op2 = self::exponent($op2, $scale); return bcsub($op1, $op2, $scale); } /** * BCPow - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @param integer $scale * @return string */ public static function Pow($op1, $op2, $scale = null) { $op1 = self::exponent($op1, $scale); $op2 = self::exponent($op2, $scale); return bcpow($op1, $op2, $scale); } /** * BCMul - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @param integer $scale * @return string */ public static function Mul($op1, $op2, $scale = null) { $op1 = self::exponent($op1, $scale); $op2 = self::exponent($op2, $scale); return bcmul($op1, $op2, $scale); } /** * BCDiv - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @param integer $scale * @return string */ public static function Div($op1, $op2, $scale = null) { $op1 = self::exponent($op1, $scale); $op2 = self::exponent($op2, $scale); return bcdiv($op1, $op2, $scale); } /** * BCSqrt - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param integer $scale * @return string */ public static function Sqrt($op1, $scale = null) { $op1 = self::exponent($op1, $scale); return bcsqrt($op1, $scale); } /** * BCMod - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @return string */ public static function Mod($op1, $op2) { $op1 = self::exponent($op1); $op2 = self::exponent($op2); return bcmod($op1, $op2); } /** * BCComp - fixes a problem of BCMath and exponential numbers * * @param string $op1 * @param string $op2 * @param integer $scale * @return string */ public static function Comp($op1, $op2, $scale = null) { $op1 = self::exponent($op1, $scale); $op2 = self::exponent($op2, $scale); return bccomp($op1, $op2, $scale); } } if (!extension_loaded('bcmath') || (defined('TESTS_ZEND_LOCALE_BCMATH_ENABLED') && !TESTS_ZEND_LOCALE_BCMATH_ENABLED) ) { require_once 'Zend/Locale/Math/PhpMath.php'; Zend_Locale_Math_PhpMath::disable(); }