PageRenderTime 59ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/core/Mage/Bundle/Model/Product/Type.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 1010 lines | 608 code | 127 blank | 275 comment | 119 complexity | f7111b8cd2b7975f9f93392ab5c6fa69 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Bundle
  23. * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Bundle Type Model
  28. *
  29. * @category Mage
  30. * @package Mage_Bundle
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Bundle_Model_Product_Type extends Mage_Catalog_Model_Product_Type_Abstract
  34. {
  35. /**
  36. * Product is composite
  37. *
  38. * @var bool
  39. */
  40. protected $_isComposite = true;
  41. /**
  42. * Cache key for Options Collection
  43. *
  44. * @var string
  45. */
  46. protected $_keyOptionsCollection = '_cache_instance_options_collection';
  47. /**
  48. * Cache key for Selections Collection
  49. *
  50. * @var string
  51. */
  52. protected $_keySelectionsCollection = '_cache_instance_selections_collection';
  53. /**
  54. * Cache key for used Selections
  55. *
  56. * @var string
  57. */
  58. protected $_keyUsedSelections = '_cache_instance_used_selections';
  59. /**
  60. * Cache key for used selections ids
  61. *
  62. * @var string
  63. */
  64. protected $_keyUsedSelectionsIds = '_cache_instance_used_selections_ids';
  65. /**
  66. * Cache key for used options
  67. *
  68. * @var string
  69. */
  70. protected $_keyUsedOptions = '_cache_instance_used_options';
  71. /**
  72. * Cache key for used options ids
  73. *
  74. * @var string
  75. */
  76. protected $_keyUsedOptionsIds = '_cache_instance_used_options_ids';
  77. /**
  78. * Product is configurable
  79. *
  80. * @var bool
  81. */
  82. protected $_canConfigure = true;
  83. /**
  84. * Return relation info about used products
  85. *
  86. * @return Varien_Object Object with information data
  87. */
  88. public function getRelationInfo()
  89. {
  90. $info = new Varien_Object();
  91. $info->setTable('bundle/selection')
  92. ->setParentFieldName('parent_product_id')
  93. ->setChildFieldName('product_id');
  94. return $info;
  95. }
  96. /**
  97. * Retrieve Required children ids
  98. * Return grouped array, ex array(
  99. * group => array(ids)
  100. * )
  101. *
  102. * @param int $parentId
  103. * @param bool $required
  104. * @return array
  105. */
  106. public function getChildrenIds($parentId, $required = true)
  107. {
  108. return Mage::getResourceSingleton('bundle/selection')
  109. ->getChildrenIds($parentId, $required);
  110. }
  111. /**
  112. * Retrieve parent ids array by requered child
  113. *
  114. * @param int|array $childId
  115. * @return array
  116. */
  117. public function getParentIdsByChild($childId)
  118. {
  119. return Mage::getResourceSingleton('bundle/selection')
  120. ->getParentIdsByChild($childId);
  121. }
  122. /**
  123. * Return product sku based on sku_type attribute
  124. *
  125. * @param Mage_Catalog_Model_Product $product
  126. * @return string
  127. */
  128. public function getSku($product = null)
  129. {
  130. $sku = parent::getSku($product);
  131. if ($this->getProduct($product)->getData('sku_type')) {
  132. return $sku;
  133. } else {
  134. $skuParts = array($sku);
  135. if ($this->getProduct($product)->hasCustomOptions()) {
  136. $customOption = $this->getProduct($product)->getCustomOption('bundle_selection_ids');
  137. $selectionIds = unserialize($customOption->getValue());
  138. if (!empty($selectionIds)) {
  139. $selections = $this->getSelectionsByIds($selectionIds, $product);
  140. foreach ($selections->getItems() as $selection) {
  141. $skuParts[] = $selection->getSku();
  142. }
  143. }
  144. }
  145. return implode('-', $skuParts);
  146. }
  147. }
  148. /**
  149. * Return product weight based on weight_type attribute
  150. *
  151. * @param Mage_Catalog_Model_Product $product
  152. * @return decimal
  153. */
  154. public function getWeight($product = null)
  155. {
  156. if ($this->getProduct($product)->getData('weight_type')) {
  157. return $this->getProduct($product)->getData('weight');
  158. } else {
  159. $weight = 0;
  160. if ($this->getProduct($product)->hasCustomOptions()) {
  161. $customOption = $this->getProduct($product)->getCustomOption('bundle_selection_ids');
  162. $selectionIds = unserialize($customOption->getValue());
  163. $selections = $this->getSelectionsByIds($selectionIds, $product);
  164. foreach ($selections->getItems() as $selection) {
  165. $weight += $selection->getWeight();
  166. }
  167. }
  168. return $weight;
  169. }
  170. }
  171. /**
  172. * Check is virtual product
  173. *
  174. * @param Mage_Catalog_Model_Product $product
  175. * @return bool
  176. */
  177. public function isVirtual($product = null)
  178. {
  179. if ($this->getProduct($product)->hasCustomOptions()) {
  180. $customOption = $this->getProduct($product)->getCustomOption('bundle_selection_ids');
  181. $selectionIds = unserialize($customOption->getValue());
  182. $selections = $this->getSelectionsByIds($selectionIds, $product);
  183. $virtualCount = 0;
  184. foreach ($selections->getItems() as $selection) {
  185. if ($selection->isVirtual()) {
  186. $virtualCount++;
  187. }
  188. }
  189. if ($virtualCount == count($selections)) {
  190. return true;
  191. }
  192. }
  193. return false;
  194. }
  195. /**
  196. * Before save type related data
  197. *
  198. * @param Mage_Catalog_Model_Product $product
  199. */
  200. public function beforeSave($product = null)
  201. {
  202. parent::beforeSave($product);
  203. $product = $this->getProduct($product);
  204. // If bundle product has dynamic weight, than delete weight attribute
  205. if (!$product->getData('weight_type') && $product->hasData('weight')) {
  206. $product->setData('weight', false);
  207. }
  208. $product->canAffectOptions(false);
  209. if ($product->getCanSaveBundleSelections()) {
  210. $product->canAffectOptions(true);
  211. $selections = $product->getBundleSelectionsData();
  212. if ($selections) {
  213. if (!empty($selections)) {
  214. $options = $product->getBundleOptionsData();
  215. if ($options) {
  216. foreach ($options as $option) {
  217. if (empty($option['delete']) || 1 != (int)$option['delete']) {
  218. $product->setTypeHasOptions(true);
  219. if (1 == (int)$option['required']) {
  220. $product->setTypeHasRequiredOptions(true);
  221. break;
  222. }
  223. }
  224. }
  225. }
  226. }
  227. }
  228. }
  229. }
  230. /**
  231. * Save type related data
  232. *
  233. * @param Mage_Catalog_Model_Product $product
  234. * @return Mage_Bundle_Model_Product_Type
  235. */
  236. public function save($product = null)
  237. {
  238. parent::save($product);
  239. $product = $this->getProduct($product);
  240. /* @var $resource Mage_Bundle_Model_Mysql4_Bundle */
  241. $resource = Mage::getResourceModel('bundle/bundle');
  242. $options = $product->getBundleOptionsData();
  243. if ($options) {
  244. $product->setIsRelationsChanged(true);
  245. foreach ($options as $key => $option) {
  246. if (isset($option['option_id']) && $option['option_id'] == '') {
  247. unset($option['option_id']);
  248. }
  249. $optionModel = Mage::getModel('bundle/option')
  250. ->setData($option)
  251. ->setParentId($product->getId())
  252. ->setStoreId($product->getStoreId());
  253. $optionModel->isDeleted((bool)$option['delete']);
  254. $optionModel->save();
  255. $options[$key]['option_id'] = $optionModel->getOptionId();
  256. }
  257. $usedProductIds = array();
  258. $excludeSelectionIds = array();
  259. $selections = $product->getBundleSelectionsData();
  260. if ($selections) {
  261. foreach ($selections as $index => $group) {
  262. foreach ($group as $key => $selection) {
  263. if (isset($selection['selection_id']) && $selection['selection_id'] == '') {
  264. unset($selection['selection_id']);
  265. }
  266. if (!isset($selection['is_default'])) {
  267. $selection['is_default'] = 0;
  268. }
  269. $selectionModel = Mage::getModel('bundle/selection')
  270. ->setData($selection)
  271. ->setOptionId($options[$index]['option_id'])
  272. ->setParentProductId($product->getId());
  273. $selectionModel->isDeleted((bool)$selection['delete']);
  274. $selectionModel->save();
  275. $selection['selection_id'] = $selectionModel->getSelectionId();
  276. if ($selectionModel->getSelectionId()) {
  277. $excludeSelectionIds[] = $selectionModel->getSelectionId();
  278. $usedProductIds[] = $selectionModel->getProductId();
  279. }
  280. }
  281. }
  282. $resource->dropAllUnneededSelections($product->getId(), $excludeSelectionIds);
  283. $resource->saveProductRelations($product->getId(), array_unique($usedProductIds));
  284. }
  285. if ($product->getData('price_type') != $product->getOrigData('price_type')) {
  286. $resource->dropAllQuoteChildItems($product->getId());
  287. }
  288. }
  289. return $this;
  290. }
  291. /**
  292. * Retrieve bundle options items
  293. *
  294. * @param Mage_Catalog_Model_Product $product
  295. * @return array
  296. */
  297. public function getOptions($product = null)
  298. {
  299. return $this->getOptionsCollection($product)->getItems();
  300. }
  301. /**
  302. * Retrieve bundle options ids
  303. *
  304. * @param Mage_Catalog_Model_Product $product
  305. * @return array
  306. */
  307. public function getOptionsIds($product = null)
  308. {
  309. return $this->getOptionsCollection($product)->getAllIds();
  310. }
  311. /**
  312. * Retrieve bundle option collection
  313. *
  314. * @param Mage_Catalog_Model_Product $product
  315. * @return Mage_Bundle_Model_Mysql4_Option_Collection
  316. */
  317. public function getOptionsCollection($product = null)
  318. {
  319. if (!$this->getProduct($product)->hasData($this->_keyOptionsCollection)) {
  320. $optionsCollection = Mage::getModel('bundle/option')->getResourceCollection()
  321. ->setProductIdFilter($this->getProduct($product)->getId())
  322. ->setPositionOrder();
  323. $storeId = $this->getStoreFilter($product);
  324. if ($storeId instanceof Mage_Core_Model_Store) {
  325. $storeId = $storeId->getId();
  326. }
  327. $optionsCollection->joinValues($storeId);
  328. $this->getProduct($product)->setData($this->_keyOptionsCollection, $optionsCollection);
  329. }
  330. return $this->getProduct($product)->getData($this->_keyOptionsCollection);
  331. }
  332. /**
  333. * Retrive bundle selections collection based on used options
  334. *
  335. * @param array $optionIds
  336. * @param Mage_Catalog_Model_Product $product
  337. * @return Mage_Bundle_Model_Mysql4_Selection_Collection
  338. */
  339. public function getSelectionsCollection($optionIds, $product = null)
  340. {
  341. $keyOptionIds = (is_array($optionIds) ? implode('_', $optionIds) : '');
  342. $key = $this->_keySelectionsCollection . $keyOptionIds;
  343. if (!$this->getProduct($product)->hasData($key)) {
  344. $storeId = $this->getProduct($product)->getStoreId();
  345. $selectionsCollection = Mage::getResourceModel('bundle/selection_collection')
  346. ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
  347. ->setFlag('require_stock_items', true)
  348. ->setFlag('product_children', true)
  349. ->setPositionOrder()
  350. ->addStoreFilter($this->getStoreFilter($product))
  351. ->setStoreId($storeId)
  352. ->addFilterByRequiredOptions()
  353. ->setOptionIdsFilter($optionIds);
  354. if (!Mage::helper('catalog')->isPriceGlobal() && $storeId) {
  355. $websiteId = Mage::app()->getStore($storeId)->getWebsiteId();
  356. $selectionsCollection->joinPrices($websiteId);
  357. }
  358. $this->getProduct($product)->setData($key, $selectionsCollection);
  359. }
  360. return $this->getProduct($product)->getData($key);
  361. }
  362. /**
  363. * Method is needed for specific actions to change given quote options values
  364. * according current product type logic
  365. * Example: the cataloginventory validation of decimal qty can change qty to int,
  366. * so need to change quote item qty option value too.
  367. *
  368. * @param array $options
  369. * @param Varien_Object $option
  370. * @param mixed $value
  371. * @param Mage_Catalog_Model_Product $product
  372. * @return Mage_Bundle_Model_Product_Type
  373. */
  374. public function updateQtyOption($options, Varien_Object $option, $value, $product = null)
  375. {
  376. $optionProduct = $option->getProduct($product);
  377. $optionUpdateFlag = $option->getHasQtyOptionUpdate();
  378. $optionCollection = $this->getOptionsCollection($product);
  379. $selections = $this->getSelectionsCollection($optionCollection->getAllIds(), $product);
  380. foreach ($selections as $selection) {
  381. if ($selection->getProductId() == $optionProduct->getId()) {
  382. foreach ($options as &$option) {
  383. if ($option->getCode() == 'selection_qty_'.$selection->getSelectionId()) {
  384. if ($optionUpdateFlag) {
  385. $option->setValue(intval($option->getValue()));
  386. }
  387. else {
  388. $option->setValue($value);
  389. }
  390. }
  391. }
  392. }
  393. }
  394. return $this;
  395. }
  396. /**
  397. * Prepare Quote Item Quantity
  398. *
  399. * @param mixed $qty
  400. * @param Mage_Catalog_Model_Product $product
  401. * @return int
  402. */
  403. public function prepareQuoteItemQty($qty, $product = null)
  404. {
  405. return intval($qty);
  406. }
  407. /**
  408. * Checking if we can sale this bundle
  409. *
  410. * @param Mage_Catalog_Model_Product $product
  411. * @return bool
  412. */
  413. public function isSalable($product = null)
  414. {
  415. $salable = parent::isSalable($product);
  416. if (!is_null($salable)) {
  417. return $salable;
  418. }
  419. $optionCollection = $this->getOptionsCollection($product);
  420. if (!count($optionCollection->getItems())) {
  421. return false;
  422. }
  423. $requiredOptionIds = array();
  424. foreach ($optionCollection->getItems() as $option) {
  425. if ($option->getRequired()) {
  426. $requiredOptionIds[$option->getId()] = 0;
  427. }
  428. }
  429. $selectionCollection = $this->getSelectionsCollection($optionCollection->getAllIds(), $product);
  430. if (!count($selectionCollection->getItems())) {
  431. return false;
  432. }
  433. $salableSelectionCount = 0;
  434. foreach ($selectionCollection as $selection) {
  435. if ($selection->isSalable()) {
  436. $requiredOptionIds[$selection->getOptionId()] = 1;
  437. $salableSelectionCount++;
  438. }
  439. }
  440. return (array_sum($requiredOptionIds) == count($requiredOptionIds) && $salableSelectionCount);
  441. }
  442. /**
  443. * Prepare product and its configuration to be added to some products list.
  444. * Perform standard preparation process and then prepare of bundle selections options.
  445. *
  446. * @param Varien_Object $buyRequest
  447. * @param Mage_Catalog_Model_Product $product
  448. * @param string $processMode
  449. * @return array|string
  450. */
  451. protected function _prepareProduct(Varien_Object $buyRequest, $product, $processMode)
  452. {
  453. $result = parent::_prepareProduct($buyRequest, $product, $processMode);
  454. if (is_string($result)) {
  455. return $result;
  456. }
  457. $selections = array();
  458. $product = $this->getProduct($product);
  459. $isStrictProcessMode = $this->_isStrictProcessMode($processMode);
  460. $_appendAllSelections = (bool)$product->getSkipCheckRequiredOption();
  461. $options = $buyRequest->getBundleOption();
  462. if (is_array($options)) {
  463. $options = array_filter($options, 'intval');
  464. $qtys = $buyRequest->getBundleOptionQty();
  465. foreach ($options as $_optionId => $_selections) {
  466. if (empty($_selections)) {
  467. unset($options[$_optionId]);
  468. }
  469. }
  470. $optionIds = array_keys($options);
  471. if (empty($optionIds) && $isStrictProcessMode) {
  472. return Mage::helper('bundle')->__('Please select options for product.');
  473. }
  474. //$optionsCollection = $this->getOptionsByIds($optionIds, $product);
  475. $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product);
  476. $optionsCollection = $this->getOptionsCollection($product);
  477. if (!$this->getProduct($product)->getSkipCheckRequiredOption() && $isStrictProcessMode) {
  478. foreach ($optionsCollection->getItems() as $option) {
  479. if ($option->getRequired() && !isset($options[$option->getId()])) {
  480. return Mage::helper('bundle')->__('Required options are not selected.');
  481. }
  482. }
  483. }
  484. $selectionIds = array();
  485. foreach ($options as $optionId => $selectionId) {
  486. if (!is_array($selectionId)) {
  487. if ($selectionId != '') {
  488. $selectionIds[] = (int)$selectionId;
  489. }
  490. } else {
  491. foreach ($selectionId as $id) {
  492. if ($id != '') {
  493. $selectionIds[] = (int)$id;
  494. }
  495. }
  496. }
  497. }
  498. $selections = $this->getSelectionsByIds($selectionIds, $product);
  499. /**
  500. * checking if selections that where added are still on sale
  501. */
  502. foreach ($selections->getItems() as $key => $selection) {
  503. if (!$selection->isSalable()) {
  504. $_option = $optionsCollection->getItemById($selection->getOptionId());
  505. if (is_array($options[$_option->getId()]) && count($options[$_option->getId()]) > 1){
  506. $moreSelections = true;
  507. } else {
  508. $moreSelections = false;
  509. }
  510. if ($_option->getRequired() &&
  511. (!$_option->isMultiSelection() || ($_option->isMultiSelection() && !$moreSelections))
  512. ) {
  513. return Mage::helper('bundle')->__('Selected required options are not available.');
  514. }
  515. }
  516. }
  517. $optionsCollection->appendSelections($selections, false, $_appendAllSelections);
  518. $selections = $selections->getItems();
  519. } else {
  520. $product->setOptionsValidationFail(true);
  521. $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product);
  522. $optionCollection = $product->getTypeInstance(true)->getOptionsCollection($product);
  523. $optionIds = $product->getTypeInstance(true)->getOptionsIds($product);
  524. $selectionIds = array();
  525. $selectionCollection = $product->getTypeInstance(true)
  526. ->getSelectionsCollection(
  527. $optionIds,
  528. $product
  529. );
  530. $options = $optionCollection->appendSelections($selectionCollection, false, $_appendAllSelections);
  531. foreach ($options as $option) {
  532. if ($option->getRequired() && count($option->getSelections()) == 1) {
  533. $selections = array_merge($selections, $option->getSelections());
  534. } else {
  535. $selections = array();
  536. break;
  537. }
  538. }
  539. }
  540. if (count($selections) > 0 || !$isStrictProcessMode) {
  541. $uniqueKey = array($product->getId());
  542. $selectionIds = array();
  543. /*
  544. * shaking selection array :) by option position
  545. */
  546. usort($selections, array($this, 'shakeSelections'));
  547. foreach ($selections as $selection) {
  548. if ($selection->getSelectionCanChangeQty() && isset($qtys[$selection->getOptionId()])) {
  549. $qty = (float)$qtys[$selection->getOptionId()] > 0 ? $qtys[$selection->getOptionId()] : 1;
  550. } else {
  551. $qty = (float)$selection->getSelectionQty() ? $selection->getSelectionQty() : 1;
  552. }
  553. $qty = (float)$qty;
  554. $product->addCustomOption('selection_qty_' . $selection->getSelectionId(), $qty, $selection);
  555. $selection->addCustomOption('selection_id', $selection->getSelectionId());
  556. $beforeQty = 0;
  557. if ($customOption = $product->getCustomOption('product_qty_' . $selection->getId())) {
  558. $beforeQty = (float)$customOption->getValue();
  559. }
  560. $product->addCustomOption('product_qty_' . $selection->getId(), $qty + $beforeQty, $selection);
  561. /*
  562. * creating extra attributes that will be converted
  563. * to product options in order item
  564. * for selection (not for all bundle)
  565. */
  566. $price = $product->getPriceModel()->getSelectionPrice($product, $selection, $qty);
  567. $attributes = array(
  568. 'price' => Mage::app()->getStore()->convertPrice($price),
  569. 'qty' => $qty,
  570. 'option_label' => $selection->getOption()->getTitle(),
  571. 'option_id' => $selection->getOption()->getId()
  572. );
  573. //if (!$product->getPriceType()) {
  574. $_result = $selection->getTypeInstance(true)->prepareForCart($buyRequest, $selection);
  575. if (is_string($_result) && !is_array($_result)) {
  576. return $_result;
  577. }
  578. if (!isset($_result[0])) {
  579. return Mage::helper('checkout')->__('Cannot add item to the shopping cart.');
  580. }
  581. $result[] = $_result[0]->setParentProductId($product->getId())
  582. ->addCustomOption('bundle_option_ids', serialize(array_map('intval', $optionIds)))
  583. ->addCustomOption('bundle_selection_attributes', serialize($attributes));
  584. //}
  585. if ($isStrictProcessMode) {
  586. $_result[0]->setCartQty($qty);
  587. }
  588. $selectionIds[] = $_result[0]->getSelectionId();
  589. $uniqueKey[] = $_result[0]->getSelectionId();
  590. $uniqueKey[] = $qty;
  591. }
  592. /**
  593. * "unique" key for bundle selection and add it to selections and bundle for selections
  594. */
  595. $uniqueKey = implode('_', $uniqueKey);
  596. foreach ($result as $item) {
  597. $item->addCustomOption('bundle_identity', $uniqueKey);
  598. }
  599. $product->addCustomOption('bundle_option_ids', serialize(array_map('intval',$optionIds)));
  600. $product->addCustomOption('bundle_selection_ids', serialize($selectionIds));
  601. return $result;
  602. }
  603. return $this->getSpecifyOptionMessage();
  604. }
  605. /**
  606. * Retrieve message for specify option(s)
  607. *
  608. * @return string
  609. */
  610. public function getSpecifyOptionMessage()
  611. {
  612. return Mage::helper('bundle')->__('Please specify product option(s).');
  613. }
  614. /**
  615. * Retrieve bundle selections collection based on ids
  616. *
  617. * @param array $selectionIds
  618. * @param Mage_Catalog_Model_Product $product
  619. * @return Mage_Bundle_Model_Mysql4_Selection_Collection
  620. */
  621. public function getSelectionsByIds($selectionIds, $product = null)
  622. {
  623. sort($selectionIds);
  624. $usedSelections = $this->getProduct($product)->getData($this->_keyUsedSelections);
  625. $usedSelectionsIds = $this->getProduct($product)->getData($this->_keyUsedSelectionsIds);
  626. if (!$usedSelections || serialize($usedSelectionsIds) != serialize($selectionIds)) {
  627. $storeId = $this->getProduct($product)->getStoreId();
  628. $usedSelections = Mage::getResourceModel('bundle/selection_collection')
  629. ->addAttributeToSelect('*')
  630. ->setFlag('require_stock_items', true)
  631. ->addStoreFilter($this->getStoreFilter($product))
  632. ->setStoreId($storeId)
  633. ->setPositionOrder()
  634. ->addFilterByRequiredOptions()
  635. ->setSelectionIdsFilter($selectionIds);
  636. if (!Mage::helper('catalog')->isPriceGlobal() && $storeId) {
  637. $websiteId = Mage::app()->getStore($storeId)->getWebsiteId();
  638. $usedSelections->joinPrices($websiteId);
  639. }
  640. $this->getProduct($product)->setData($this->_keyUsedSelections, $usedSelections);
  641. $this->getProduct($product)->setData($this->_keyUsedSelectionsIds, $selectionIds);
  642. }
  643. return $usedSelections;
  644. }
  645. /**
  646. * Retrieve bundle options collection based on ids
  647. *
  648. * @param array $optionIds
  649. * @param Mage_Catalog_Model_Product $product
  650. * @return Mage_Bundle_Model_Mysql4_Option_Collection
  651. */
  652. public function getOptionsByIds($optionIds, $product = null)
  653. {
  654. sort($optionIds);
  655. $usedOptions = $this->getProduct($product)->getData($this->_keyUsedOptions);
  656. $usedOptionsIds = $this->getProduct($product)->getData($this->_keyUsedOptionsIds);
  657. if (!$usedOptions || serialize($usedOptionsIds) != serialize($optionIds)) {
  658. $usedOptions = Mage::getModel('bundle/option')->getResourceCollection()
  659. ->setProductIdFilter($this->getProduct($product)->getId())
  660. ->setPositionOrder()
  661. ->joinValues(Mage::app()->getStore()->getId())
  662. ->setIdFilter($optionIds);
  663. $this->getProduct($product)->setData($this->_keyUsedOptions, $usedOptions);
  664. $this->getProduct($product)->setData($this->_keyUsedOptionsIds, $optionIds);
  665. }
  666. return $usedOptions;
  667. }
  668. /**
  669. * Prepare additional options/information for order item which will be
  670. * created from this product
  671. *
  672. * @param Mage_Catalog_Model_Product $product
  673. * @return array
  674. */
  675. public function getOrderOptions($product = null)
  676. {
  677. $optionArr = parent::getOrderOptions($product);
  678. $bundleOptions = array();
  679. $product = $this->getProduct($product);
  680. if ($product->hasCustomOptions()) {
  681. $customOption = $product->getCustomOption('bundle_option_ids');
  682. $optionIds = unserialize($customOption->getValue());
  683. $options = $this->getOptionsByIds($optionIds, $product);
  684. $customOption = $product->getCustomOption('bundle_selection_ids');
  685. $selectionIds = unserialize($customOption->getValue());
  686. $selections = $this->getSelectionsByIds($selectionIds, $product);
  687. foreach ($selections->getItems() as $selection) {
  688. if ($selection->isSalable()) {
  689. $selectionQty = $product->getCustomOption('selection_qty_' . $selection->getSelectionId());
  690. if ($selectionQty) {
  691. $price = $product->getPriceModel()->getSelectionPrice(
  692. $product,
  693. $selection,
  694. $selectionQty->getValue()
  695. );
  696. $option = $options->getItemById($selection->getOptionId());
  697. if (!isset($bundleOptions[$option->getId()])) {
  698. $bundleOptions[$option->getId()] = array(
  699. 'option_id' => $option->getId(),
  700. 'label' => $option->getTitle(),
  701. 'value' => array()
  702. );
  703. }
  704. $bundleOptions[$option->getId()]['value'][] = array(
  705. 'title' => $selection->getName(),
  706. 'qty' => $selectionQty->getValue(),
  707. 'price' => Mage::app()->getStore()->convertPrice($price)
  708. );
  709. }
  710. }
  711. }
  712. }
  713. $optionArr['bundle_options'] = $bundleOptions;
  714. /**
  715. * Product Prices calculations save
  716. */
  717. if ($product->getPriceType()) {
  718. $optionArr['product_calculations'] = self::CALCULATE_PARENT;
  719. } else {
  720. $optionArr['product_calculations'] = self::CALCULATE_CHILD;
  721. }
  722. $optionArr['shipment_type'] = $product->getShipmentType();
  723. return $optionArr;
  724. }
  725. public function shakeSelections($a, $b)
  726. {
  727. $aPosition = ($a->getOption()->getPosition()+1)*($a->getPosition()+1);
  728. $bPosition = ($b->getOption()->getPosition()+1)*($b->getPosition()+1);
  729. if ($aPosition == $bPosition) {
  730. if ($a->getSelectionId() == $b->getSelectionId()) {
  731. return 0;
  732. }
  733. return ($a->getSelectionId() < $b->getSelectionId()) ? -1 : 1;
  734. }
  735. return ($aPosition < $bPosition) ? -1 : 1;
  736. }
  737. /**
  738. * Return true if product has options
  739. *
  740. * @param Mage_Catalog_Model_Product $product
  741. * @return bool
  742. */
  743. public function hasOptions($product = null)
  744. {
  745. $product = $this->getProduct($product);
  746. $this->setStoreFilter($product->getStoreId(), $product);
  747. $optionIds = $this->getOptionsCollection($product)->getAllIds();
  748. $collection = $this->getSelectionsCollection($optionIds, $product);
  749. if (count($collection) > 0 || $product->getOptions()) {
  750. return true;
  751. }
  752. return false;
  753. }
  754. /**
  755. * Allow for updates of chidren qty's
  756. *
  757. * @param Mage_Catalog_Model_Product $product
  758. * @return boolean true
  759. */
  760. public function getForceChildItemQtyChanges($product = null)
  761. {
  762. return true;
  763. }
  764. /**
  765. * Retrieve additional searchable data from type instance
  766. * Using based on product id and store_id data
  767. *
  768. * @param Mage_Catalog_Model_Product $product
  769. * @return array
  770. */
  771. public function getSearchableData($product = null)
  772. {
  773. $searchData = parent::getSearchableData($product);
  774. $product = $this->getProduct($product);
  775. $optionSearchData = Mage::getSingleton('bundle/option')
  776. ->getSearchableData($product->getId(), $product->getStoreId());
  777. if ($optionSearchData) {
  778. $searchData = array_merge($searchData, $optionSearchData);
  779. }
  780. return $searchData;
  781. }
  782. /**
  783. * Check if product can be bought
  784. *
  785. * @param Mage_Catalog_Model_Product $product
  786. * @return Mage_Bundle_Model_Product_Type
  787. * @throws Mage_Core_Exception
  788. */
  789. public function checkProductBuyState($product = null)
  790. {
  791. parent::checkProductBuyState($product);
  792. $product = $this->getProduct($product);
  793. $productOptionIds = $this->getOptionsIds($product);
  794. $productSelections = $this->getSelectionsCollection($productOptionIds, $product);
  795. $selectionIds = $product->getCustomOption('bundle_selection_ids');
  796. $selectionIds = unserialize($selectionIds->getValue());
  797. $buyRequest = $product->getCustomOption('info_buyRequest');
  798. $buyRequest = new Varien_Object(unserialize($buyRequest->getValue()));
  799. $bundleOption = $buyRequest->getBundleOption();
  800. if (empty($bundleOption)) {
  801. Mage::throwException($this->getSpecifyOptionMessage());
  802. }
  803. foreach ($selectionIds as $selectionId) {
  804. /* @var $selection Mage_Bundle_Model_Selection */
  805. $selection = $productSelections->getItemById($selectionId);
  806. if (!$selection || !$selection->isSalable()) {
  807. Mage::throwException(
  808. Mage::helper('bundle')->__('Selected required options are not available.')
  809. );
  810. }
  811. }
  812. $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product);
  813. $optionsCollection = $this->getOptionsCollection($product);
  814. foreach ($optionsCollection->getItems() as $option) {
  815. if ($option->getRequired() && empty($bundleOption[$option->getId()])) {
  816. Mage::throwException(
  817. Mage::helper('bundle')->__('Required options are not selected.')
  818. );
  819. }
  820. }
  821. return $this;
  822. }
  823. /**
  824. * Retrieve products divided into groups required to purchase
  825. * At least one product in each group has to be purchased
  826. *
  827. * @param Mage_Catalog_Model_Product $product
  828. * @return array
  829. */
  830. public function getProductsToPurchaseByReqGroups($product = null)
  831. {
  832. $product = $this->getProduct($product);
  833. $groups = array();
  834. $allProducts = array();
  835. $hasRequiredOptions = false;
  836. foreach ($this->getOptions($product) as $option) {
  837. $groupProducts = array();
  838. foreach ($this->getSelectionsCollection(array($option->getId()), $product) as $childProduct) {
  839. $groupProducts[] = $childProduct;
  840. $allProducts[] = $childProduct;
  841. }
  842. if ($option->getRequired()) {
  843. $groups[] = $groupProducts;
  844. $hasRequiredOptions = true;
  845. }
  846. }
  847. if (!$hasRequiredOptions) {
  848. $groups = array($allProducts);
  849. }
  850. return $groups;
  851. }
  852. /**
  853. * Prepare selected options for bundle product
  854. *
  855. * @param Mage_Catalog_Model_Product $product
  856. * @param Varien_Object $buyRequest
  857. * @return array
  858. */
  859. public function processBuyRequest($product, $buyRequest)
  860. {
  861. $option = $buyRequest->getBundleOption();
  862. $optionQty = $buyRequest->getBundleOptionQty();
  863. $option = (is_array($option)) ? array_filter($option, 'intval') : array();
  864. $optionQty = (is_array($optionQty)) ? array_filter($optionQty, 'intval') : array();
  865. $options = array(
  866. 'bundle_option' => $option,
  867. 'bundle_option_qty' => $optionQty
  868. );
  869. return $options;
  870. }
  871. /**
  872. * Check if product can be configured
  873. *
  874. * @param Mage_Catalog_Model_Product $product
  875. * @return bool
  876. */
  877. public function canConfigure($product = null)
  878. {
  879. return $product instanceof Mage_Catalog_Model_Product
  880. && $product->isAvailable()
  881. && parent::canConfigure();
  882. }
  883. }