├── .gitignore ├── README.markdown ├── app ├── code │ └── community │ │ └── OrganicInternet │ │ └── SimpleConfigurableProducts │ │ ├── Adminhtml │ │ └── Block │ │ │ └── Catalog │ │ │ └── Product │ │ │ └── Edit │ │ │ └── Tab │ │ │ └── Super │ │ │ └── Config │ │ │ └── Grid.php │ │ ├── Catalog │ │ ├── Block │ │ │ └── Product │ │ │ │ ├── Price.php │ │ │ │ └── View │ │ │ │ ├── Attributes.php │ │ │ │ ├── Media.php │ │ │ │ └── Type │ │ │ │ └── Configurable.php │ │ └── Model │ │ │ ├── Product.php │ │ │ ├── Product │ │ │ └── Type │ │ │ │ ├── Configurable.php │ │ │ │ ├── Configurable │ │ │ │ └── Price.php │ │ │ │ ├── Simple.php │ │ │ │ └── Virtual.php │ │ │ └── Resource │ │ │ └── Product │ │ │ ├── Collection.php │ │ │ └── Indexer │ │ │ ├── Price.php │ │ │ └── Price │ │ │ └── Configurable.php │ │ ├── CatalogIndex │ │ └── Model │ │ │ └── Data │ │ │ └── Configurable.php │ │ ├── CatalogInventory │ │ └── Model │ │ │ └── Resource │ │ │ └── Indexer │ │ │ └── Stock │ │ │ └── Configurable.php │ │ ├── CatalogRule │ │ └── Model │ │ │ └── Resource │ │ │ └── Rule.php │ │ ├── Checkout │ │ └── Block │ │ │ └── Cart │ │ │ └── Item │ │ │ └── Renderer.php │ │ ├── Helper │ │ └── Data.php │ │ ├── Rss │ │ └── Block │ │ │ ├── Catalog │ │ │ ├── Category.php │ │ │ ├── New.php │ │ │ ├── Special.php │ │ │ └── Tag.php │ │ │ └── Wishlist.php │ │ ├── controllers │ │ ├── AjaxController.php │ │ └── Checkout │ │ │ └── CartController.php │ │ └── etc │ │ ├── config.xml │ │ └── system.xml ├── design │ └── frontend │ │ └── base │ │ └── default │ │ ├── layout │ │ └── scp.xml │ │ └── template │ │ ├── catalog │ │ └── product │ │ │ └── view │ │ │ └── scpavailability.phtml │ │ └── scp │ │ ├── catalog │ │ └── product │ │ │ └── view │ │ │ ├── options │ │ │ └── scpwrapper.phtml │ │ │ ├── scpajaxoptions.phtml │ │ │ └── scpoptions.phtml │ │ ├── page │ │ └── scpcontentonly.phtml │ │ └── sales │ │ └── reorder │ │ └── scpsidebar.phtml ├── etc │ └── modules │ │ └── OrganicInternet_SimpleConfigurableProducts.xml └── locale │ ├── de_DE │ └── OrganicInternet_SimpleConfigurableProducts.csv │ ├── en_US │ └── OrganicInternet_SimpleConfigurableProducts.csv │ └── fr_FR │ └── OrganicInternet_SimpleConfigurableProducts.csv ├── composer.json ├── modman ├── releasedVersions ├── OrganicInternet_SimpleConfigurableProducts-0.1.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.2.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.3.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.4.1.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.4.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.5.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.6-dev.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.6.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.7.1.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.7.2.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.7.3.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.7.4.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.7.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.8.0.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.8.2.MCMv1.tgz ├── OrganicInternet_SimpleConfigurableProducts-0.8.2.MCMv2.tgz └── readme └── skin └── frontend └── base └── default ├── images └── scp │ └── scp-ajax-loader.gif └── js └── scp └── scp_product_extension.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Simple Configurable Products Extension For Magento 2 | ================================================== 3 | 4 | *This documentation applies to SCP versions 0.7 onwards. 5 | The documentation for SCP v0.6 and earlier can be seen [here](http://github.com/organicinternet/magento-configurable-simple/blob/34bda60fe4f0ab75d28135748528c08d2e134834/README.markdown)* 6 | 7 | This extension changes the way that the pricing of configurable products works in Magento. 8 | With this extension enabled, a configurable product's own price is never used. Instead, the price used is that of the matching associated product. 9 | 10 | This gives site owners direct control to set the price of every configuration of a product, while still giving users the flexibility they usually get with configurable products. 11 | (There's no more having to set rules such as: +20% for blue, -£10 for small, +15% for leather. You just price the underlying small-blue-leather product at £199.99 and that's what the user pays.) 12 | 13 | 14 | This change has two effects on the behaviour of a Magento site: 15 | 16 | * When an attempt is made to add a configurable product to the basket/cart, the matching associated simple product is added instead. 17 | * Configurable product prices are shown with "Price from:" followed by the lowest price that this product can be configured to. (Once configurable options have been chosen by the user and the specific product price is known, the 'Price from:' text disappears) 18 | 19 | 20 | 21 | Installation 22 | ------------ 23 | 24 | Installation of SCP is the same as for most extensions, that is via your Magento Connect Manager using the extension key found on the MagentoCommerce site. 25 | Important: Once installed you must refresh all caches and reindex all data (under System->Cache Management and System->Index Management). You will then also need to logout then login of Admin (the SCP Admin options will not be displayed otherwise). 26 | 27 | There are also some SCP configuration options under System->Configuration->SCP Config, and it's likely you will want to change these from their default values. What each option does should hopefully be self-explanatory. 28 | 29 | 30 | 31 | Uninstallation 32 | ------------ 33 | Uninstallation of SCP is the same as for most extensions, that is via your Magento Connect Manager. 34 | Once SCP is uninstalled you must go into Admin and refresh cache and reindex all data (under System->Cache Management and System->Index Management) 35 | 36 | 37 | 38 | Some key things to be aware of 39 | ------------------------------ 40 | * SCP does not allow you to have some configurable products using the SCP logic and some others using the default Magento logic on the same site. If SCP is installed all configurable products will use SCP logic. 41 | * Do not assign any custom options, tier prices, or apply price rules directly to the Configurable Product when SCP is installed. Although SCP will not use them if you do (as SCP only adds the associated products to the cart), they may well still be displayed in various places by the core Magento code and so can be very confusing for your customers. In addition, SCP is not tested for these cases so it's possible that you'll see odd behaviour or errors. If you assign options/prices/rules directly to the associated simple products instead then they'll work just fine. 42 | 43 | 44 | Main Features 45 | ------------- 46 | 47 | * SCP fully supports special prices, catalog price rules, tier prices, custom options etc. (see notes about use of custom options) 48 | * In addition it can optionally change the product's image, associated image gallery, name and description to match the associated product when a user has made their selection of a product's configurable options. (so if a user has chosen a silver phone they can see it in silver before they buy it) 49 | * There's the option to show whether the configurable product or associated product name and image are shown in the cart 50 | * There's the option to show price ranges for the remaining choices in the configurable product option drop downs on the product page 51 | * It now uses the new Magento 1.4 indexers to perform most calculations behind the scenes, so doesn't slow down your site. 52 | * There's no theme setup needed. Just install the extension like any other, refresh your caches, and away you go. 53 | 54 | 55 | 56 | Functionality in detail 57 | ----------------------- 58 | In Magento a Configurable Product has one or several Associated Products. These Associated Products are just Simple Products that you've chosen to 'associate' with the Configurable Product. 59 | A Configurable Product also has a set of Configurable Options (say Colour, Size, Material), and a combination of each of these options maps onto a specific Associated Product. 60 | For example, if the configurable options are {Colour, Size, Material} the choices {red, small, steel} will map onto one associated simple product, and the choices {orange, medium, plastic} will map onto another, etc. 61 | 62 | With vanilla Magento, when working with configurable products any pricing that has been directly assigned to the associated simple products in the Magento admin interface is ignored. The configurable product price is actually calculated from the price assigned directly to the configurable product itself, plus any modifiers that can be set per configurable option. (eg +10% for green, +€99 for titanium, etc) 63 | 64 | SCP's original goal was to allow site owners to have more direct control over product pricing by changing the way pricing for Magento Configurable Products work, such that the usual rules for Configurable Product pricing (described above) are ignored, and the price that's directly assigned to the associated simple products is used instead. The mechanism SCP uses to achieve this is actually to add the simple product to the cart, instead of adding the configurable product. This approach has a number of benefits, and some limitations. 65 | 66 | The main benefit is that it allows site owners to not only directly choose the price that each combination of configurable options will result in, but it also allows them to assign completely different tier prices, custom options, special prices (aka offer prices) etc on the same basis. So for example if the products in question are tables, a {small, oak} table could have a 'buy 2 for only £129.99 each' offer, whereas the {large, pine} version of the same table may have a Custom Option which allows the customer to specify what kind of finish or dye they require, or have a 10% discount this week. At the time of writing this kind of flexibility is not present with standard Magento Configurable Products. 67 | 68 | The main downside to this flexibility is only when you don't need it. If you just want flexibility around pricing, but want to have the same set of custom options, discounts, etc for every associated product there currently isn't an easy way to do this with SCP. At the moment you have to manually assign the same values to each associated product. It's something I'll look into handling better in future versions of SCP. 69 | 70 | 71 | Notes 72 | ----- 73 | 74 | v0.7 of SCP is a significant rewrite. Magento 1.3 and earlier are no longer supported. 75 | 76 | * Magento doesn't normally allow Simple Products which have compulsory Custom Options to be associated to a configurable product, as Magento isn't normally able to display these Custom Option to the user. (so it could never be selected despite being compulsory) 77 | SCP *does* allow this association, as it is able to show these custom options to the user. However, if you uninstall SCP then later save any Configurable Products that have associated products that have compulsory Custom Options they'll no longer be associated to the Configurable Product and will need re-associating if you later install SCP. (without SCP installed you can't re-associate them while there are still compulsory custom options on the simple product) 78 | 79 | * SCP uses a JavaScript file called scp_product_extension.js. This needs to be loaded after the Magento product.js file, and SCP is written such that it will be. However in some cases the new Magento 'Merge JavaScript Files' option may cause it to be loaded earlier, which will stop SCP from working. If you are seeing JavaScript errors, or if you are seeing the Configurable Product being added to the cart instead of the Associated Products, turn off the 'Merge JavaScript Files' option in Admin->Configuration->Developer 80 | 81 | * Some of SCP's JavaScript is dependent on the DOM structure of the Product Page (just as the core Magento product.js is). If you have a customised theme you may find you have JavaScript errors or that some product properties don't update even if enabled in Admin, and so you may need to modify some of the JS in product_extension.js to match your modified theme. 82 | 83 | 84 | 85 | ## Feature Aspirations 86 | * Investigate whether it's possible to allow custom options to be set on the Configurable Product (for when they need to be the same across all associated products). 87 | * Backordering enhancements. Currently only in-stock associated products are shown even if allow backordering is enabled. This is inline with default Magento behaviour, but it's something that could possibly be enhanced by SCP. 88 | * Possibly allow SCP logic and Magento logic for Configurable Options to run side-by-side. 89 | 90 | 91 | ## Lightboxes 92 | 93 | If you're using a 3rd party lightbox to display your product images rather than the built-in Magento one, it is likely this will not work with SCP without some additional work on your part. This is not an SCP bug as such; it's not possible to SCP to be compatible with all the possible 3rd party extensions. 94 | To fix, it's often just a matter of editing the showFullImageDiv function in the scp_product_extension.js file: Change evalScripts to true if it's not already, and possibly you'll also need to remove the code which exists in a few places which looks like: 95 | product_zoom = new Product.Zoom('image', 'track', 'handle', 'zoom_in', 'zoom_out', 'track_hint'); 96 | Depending on your choice of lightbox it may be much more complex than this, but it's very likely that it's this function that you'll have to update to support your Lightbox. 97 | 98 | 99 | ## Bugs / Issues 100 | 101 | #### Open Bugs 102 | * See [here](http://github.com/organicinternet/magento-configurable-simple/issues) also see [here](https://github.com/organicinternet/magento-configurable-simple/issues/closed) for any issues that either have been fixed, are unfixable, are not SCP related, etc. 103 | * When SCP dynamically upates various parts of the product page (description, attributes, product name etc) for a matching associated product, it only works if the configurable product also has the same property present. So for example, if you have no description on your configurable product, but you do on one of your associated products, it will not be displayed when this associated product is selected by the user's choice of configurable options. This is because if there's not already a description on the page, SCP doesn't know which part of the page to update to show the associated product description. (or name, or extended attributes, etc) 104 | 105 | #### Magento (i.e. not SCP) Bugs/Limitations 106 | * Selecting custom options does not affect the displayed tier price on the product page. 107 | 108 | #### Reporting Bugs 109 | Please report and/or fix bugs [here](http://github.com/organicinternet/magento-configurable-simple/issues) 110 | 111 | When reporting bugs, please specify: 112 | 113 | * How to reproduce it, in as much detail as you can 114 | * Which version of Magento you are using 115 | * Which version of SCP you are using 116 | * Whether you're seeing any JavaScript errors, what they are and when they occur (install firebug for firefox, and look at the script console) 117 | * Whether there are any errors output to your error log (turn it on in admin) or output into the page's html. 118 | * Which other extensions you have installed 119 | * If possible, whether the problem goes away when SCP is uninstalled 120 | 121 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Adminhtml/Block/Catalog/Product/Edit/Tab/Super/Config/Grid.php: -------------------------------------------------------------------------------- 1 | getNode('global/catalog/product/type/configurable/allow_product_types')->children() as $type) { 16 | $allowProductTypes[] = $type->getName(); 17 | } 18 | 19 | $product = $this->_getProduct(); 20 | $collection = $product->getCollection() 21 | ->addAttributeToSelect('name') 22 | ->addAttributeToSelect('sku') 23 | ->addAttributeToSelect('attribute_set_id') 24 | ->addAttributeToSelect('type_id') 25 | ->addAttributeToSelect('price') 26 | ->addFieldToFilter('attribute_set_id',$product->getAttributeSetId()) 27 | ->addFieldToFilter('type_id', $allowProductTypes); 28 | //->addFilterByRequiredOptions(); 29 | 30 | Mage::getModel('cataloginventory/stock_item')->addCatalogInventoryToProductCollection($collection); 31 | 32 | foreach ($product->getTypeInstance(true)->getUsedProductAttributes($product) as $attribute) { 33 | $collection->addAttributeToSelect($attribute->getAttributeCode()); 34 | $collection->addAttributeToFilter($attribute->getAttributeCode(), array('nin'=>array(null))); 35 | } 36 | 37 | $this->setCollection($collection); 38 | 39 | if ($this->isReadonly()) { 40 | $collection->addFieldToFilter('entity_id', array('in' => $this->_getSelectedProducts())); 41 | } 42 | 43 | Mage_Adminhtml_Block_Widget_Grid::_prepareCollection(); 44 | return $this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Block/Product/Price.php: -------------------------------------------------------------------------------- 1 | '; 10 | if ($this->getTemplate() == 'catalog/product/price.phtml') { 11 | $product = $this->getProduct(); 12 | if (is_object($product) && $product->isConfigurable()) { 13 | $extraHtml = ''; 17 | 18 | if ($product->getMaxPossibleFinalPrice() != $product->getFinalPrice()) { 19 | $extraHtml .= $this->__('Price From:'); 20 | } 21 | $extraHtml .= ''; 22 | $priceHtml = parent::_toHtml(); 23 | #manually insert extra html needed by the extension into the normal price html 24 | return substr_replace($priceHtml, $extraHtml, strpos($priceHtml, $htmlToInsertAfter)+strlen($htmlToInsertAfter),0); 25 | } 26 | } 27 | return parent::_toHtml(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Block/Product/View/Attributes.php: -------------------------------------------------------------------------------- 1 | _product = $product; 10 | return $this; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Block/Product/View/Media.php: -------------------------------------------------------------------------------- 1 | $this->getProduct()->getId()); 11 | $params = array( 12 | 'id'=>$this->getProduct()->getId(), 13 | 'pid'=>$this->getProduct()->getCpid() 14 | ); 15 | if ($image) { 16 | $params['image'] = $image->getValueId(); 17 | return $this->getUrl('*/*/gallery', $params); 18 | } 19 | return $this->getUrl('*/*/gallery', $params); 20 | } 21 | 22 | 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Block/Product/View/Type/Configurable.php: -------------------------------------------------------------------------------- 1 | getAllowProducts() as $product) { 14 | $productId = $product->getId(); 15 | $childProducts[$productId] = array( 16 | "price" => $this->_registerJsPrice($this->_convertPrice($product->getPrice())), 17 | "finalPrice" => $this->_registerJsPrice($this->_convertPrice($product->getFinalPrice())), 18 | "sku" => $product->getSku(), 19 | ); 20 | 21 | if (Mage::getStoreConfig('SCP_options/product_page/change_name')) { 22 | $childProducts[$productId]["productName"] = $product->getName(); 23 | } 24 | if (Mage::getStoreConfig('SCP_options/product_page/change_description')) { 25 | $childProducts[$productId]["description"] = $this->helper('catalog/output')->productAttribute($product, $product->getDescription(), 'description'); 26 | } 27 | if (Mage::getStoreConfig('SCP_options/product_page/change_short_description')) { 28 | $childProducts[$productId]["shortDescription"] = $this->helper('catalog/output')->productAttribute($product, nl2br($product->getShortDescription()), 'short_description'); 29 | } 30 | 31 | if (Mage::getStoreConfig('SCP_options/product_page/change_attributes')) { 32 | $childBlock = $this->getLayout()->createBlock('catalog/product_view_attributes'); 33 | $childProducts[$productId]["productAttributes"] = $childBlock->setTemplate('catalog/product/view/attributes.phtml') 34 | ->setProduct($product) 35 | ->toHtml(); 36 | } 37 | 38 | $bChangeStock = Mage::getStoreConfig('SCP_options/product_page/change_stock'); 39 | if ($bChangeStock) { 40 | // Stock status HTML 41 | $oStockBlock = $this->getLayout()->createBlock('catalog/product_view_type_simple')->setTemplate('catalog/product/view/scpavailability.phtml'); 42 | $childProducts[$productId]["stockStatus"] = $oStockBlock->setProduct($product)->toHtml(); 43 | 44 | // Add to cart button 45 | $oAddToCartBlock = $this->getLayout()->createBlock('catalog/product_view_type_simple')->setTemplate('catalog/product/view/addtocart.phtml'); 46 | $childProducts[$productId]["addToCart"] = $oAddToCartBlock->setProduct($product)->toHtml(); 47 | } 48 | 49 | $bShowProductAlerts = Mage::getStoreConfig(Mage_ProductAlert_Model_Observer::XML_PATH_STOCK_ALLOW); 50 | if ($bShowProductAlerts && !$product->isAvailable()) { 51 | $oAlertBlock = $this->getLayout()->createBlock('productalert/product_view') 52 | ->setTemplate('productalert/product/view.phtml') 53 | ->setSignupUrl(Mage::helper('productalert')->setProduct($product)->getSaveUrl('stock'));; 54 | $childProducts[$productId]["alertHtml"] = $oAlertBlock->toHtml(); 55 | } 56 | 57 | #if image changing is enabled.. 58 | if (Mage::getStoreConfig('SCP_options/product_page/change_image')) { 59 | #but dont bother if fancy image changing is enabled 60 | if (!Mage::getStoreConfig('SCP_options/product_page/change_image_fancy')) { 61 | #If image is not placeholder... 62 | if($product->getImage()!=='no_selection') { 63 | $productMag = Mage::getModel('catalog/product')->load($productId); 64 | foreach($productMag->getMediaGalleryImages() as $image) 65 | { 66 | $childProducts[$productId]["imageUrl"][] = (string)Mage::helper('catalog/image')->init($product, 'image', $image->getFile()); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | //Remove any existing option prices. 74 | //Removing holes out of existing arrays is not nice, 75 | //but it keeps the extension's code separate so if Varien's getJsonConfig 76 | //is added to, things should still work. 77 | if (is_array($config['attributes'])) { 78 | foreach ($config['attributes'] as $attributeID => &$info) { 79 | if (is_array($info['options'])) { 80 | foreach ($info['options'] as &$option) { 81 | unset($option['price']); 82 | } 83 | unset($option); //clear foreach var ref 84 | } 85 | } 86 | unset($info); //clear foreach var ref 87 | } 88 | 89 | $p = $this->getProduct(); 90 | $config['childProducts'] = $childProducts; 91 | if ($p->getMaxPossibleFinalPrice() != $p->getFinalPrice()) { 92 | $config['priceFromLabel'] = $this->__('Price From:'); 93 | } else { 94 | $config['priceFromLabel'] = $this->__(''); 95 | } 96 | $config['ajaxBaseUrl'] = Mage::getUrl('oi/ajax/'); 97 | $config['productName'] = $p->getName(); 98 | $config['description'] = $this->helper('catalog/output')->productAttribute($p, $p->getDescription(), 'description'); 99 | $config['shortDescription'] = $this->helper('catalog/output')->productAttribute($p, nl2br($p->getShortDescription()), 'short_description'); 100 | 101 | if (Mage::getStoreConfig('SCP_options/product_page/change_image')) { 102 | foreach($p->getMediaGalleryImages() as $image) 103 | { 104 | $config["imageUrl"][] = (string)Mage::helper('catalog/image')->init($p, 'image', $image->getFile()); 105 | } 106 | } 107 | 108 | $childBlock = $this->getLayout()->createBlock('catalog/product_view_attributes'); 109 | $config["productAttributes"] = $childBlock->setTemplate('catalog/product/view/attributes.phtml') 110 | ->setProduct($this->getProduct()) 111 | ->toHtml(); 112 | 113 | // Prevent Issue 6 start 114 | /* 115 | $bShowProductAlerts = Mage::getStoreConfig(Mage_ProductAlert_Model_Observer::XML_PATH_STOCK_ALLOW); 116 | if ($bShowProductAlerts && !Mage::registry('child_product')->isAvailable()) { 117 | $oAlertBlock = $this->getLayout()->createBlock('productalert/product_view') 118 | ->setTemplate('productalert/product/view.phtml') 119 | ->setSignupUrl(Mage::helper('productalert')->setProduct(Mage::registry('child_product'))->getSaveUrl('stock'));; 120 | $config["alertHtml"] = $oAlertBlock->toHtml(); 121 | } 122 | */ 123 | // Prevent Issue 6 end 124 | 125 | if (Mage::getStoreConfig('SCP_options/product_page/change_image')) { 126 | if (Mage::getStoreConfig('SCP_options/product_page/change_image_fancy')) { 127 | $childBlock = $this->getLayout()->createBlock('catalog/product_view_media'); 128 | $config["imageZoomer"] = $childBlock->setTemplate('catalog/product/view/media.phtml') 129 | ->setProduct($this->getProduct()) 130 | ->toHtml(); 131 | } 132 | } 133 | 134 | if (Mage::getStoreConfig('SCP_options/product_page/show_price_ranges_in_options')) { 135 | $config['showPriceRangesInOptions'] = true; 136 | $config['rangeToLabel'] = $this->__('to'); 137 | } 138 | return Zend_Json::encode($config); 139 | //parent getJsonConfig uses the following instead, but it seems to just break inline translate of this json? 140 | //return Mage::helper('core')->jsonEncode($config); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Product.php: -------------------------------------------------------------------------------- 1 | getPriceModel(), 'getMaxPossibleFinalPrice'))) { 8 | return $this->getPriceModel()->getMaxPossibleFinalPrice($this); 9 | } else { 10 | #return $this->_getData('minimal_price'); 11 | return parent::getMaxPrice(); 12 | } 13 | } 14 | 15 | public function isVisibleInSiteVisibility() 16 | { 17 | #Force visible any simple products which have a parent conf product. 18 | #this will only apply to products which have been added to the cart 19 | if(is_callable(array($this->getTypeInstance(), 'hasConfigurableProductParentId')) 20 | && $this->getTypeInstance()->hasConfigurableProductParentId()) { 21 | return true; 22 | } else { 23 | return parent::isVisibleInSiteVisibility(); 24 | } 25 | } 26 | 27 | 28 | public function getProductUrl($useSid = null) 29 | { 30 | if(is_callable(array($this->getTypeInstance(), 'hasConfigurableProductParentId')) 31 | && $this->getTypeInstance()->hasConfigurableProductParentId()) { 32 | 33 | $confProdId = $this->getTypeInstance()->getConfigurableProductParentId(); 34 | return Mage::getModel('catalog/product')->load($confProdId)->getProductUrl(); 35 | 36 | } else { 37 | return parent::getProductUrl($useSid); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Product/Type/Configurable.php: -------------------------------------------------------------------------------- 1 | getProduct($product)->hasData($this->_usedProducts)) { 12 | if (is_null($requiredAttributeIds) 13 | and is_null($this->getProduct($product)->getData($this->_configurableAttributes))) { 14 | // If used products load before attributes, we will load attributes. 15 | $this->getConfigurableAttributes($product); 16 | // After attributes loading products loaded too. 17 | Varien_Profiler::stop('CONFIGURABLE:'.__METHOD__); 18 | return $this->getProduct($product)->getData($this->_usedProducts); 19 | } 20 | 21 | $usedProducts = array(); 22 | $collection = $this->getUsedProductCollection($product) 23 | ->addAttributeToSelect('*'); 24 | // ->addFilterByRequiredOptions(); 25 | 26 | if (is_array($requiredAttributeIds)) { 27 | foreach ($requiredAttributeIds as $attributeId) { 28 | $attribute = $this->getAttributeById($attributeId, $product); 29 | if (!is_null($attribute)) 30 | $collection->addAttributeToFilter($attribute->getAttributeCode(), array('notnull'=>1)); 31 | } 32 | } 33 | 34 | foreach ($collection as $item) { 35 | if ($item->getStatus() == 2) 36 | continue; // Hide disabled products from dropdowns; 37 | $usedProducts[] = $item; 38 | } 39 | 40 | $this->getProduct($product)->setData($this->_usedProducts, $usedProducts); 41 | } 42 | Varien_Profiler::stop('CONFIGURABLE:'.__METHOD__); 43 | return $this->getProduct($product)->getData($this->_usedProducts); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Product/Type/Configurable/Price.php: -------------------------------------------------------------------------------- 1 | getPrice($product); 13 | } 14 | 15 | public function getMaxPossibleFinalPrice($product) { 16 | #Indexer calculates max_price, so if this value's been loaded, use it 17 | $price = $product->getMaxPrice(); 18 | if ($price !== null) { 19 | return $price; 20 | } 21 | 22 | $childProduct = $this->getChildProductWithHighestPrice($product, "finalPrice"); 23 | #If there aren't any salable child products we return the highest price 24 | #of all child products, including any ones not currently salable. 25 | 26 | if (!$childProduct) { 27 | $childProduct = $this->getChildProductWithHighestPrice($product, "finalPrice", false); 28 | } 29 | 30 | if ($childProduct) { 31 | return $childProduct->getFinalPrice(); 32 | } 33 | return false; 34 | } 35 | 36 | #If there aren't any salable child products we return the lowest price 37 | #of all child products, including any ones not currently salable. 38 | public function getFinalPrice($qty=null, $product) 39 | { 40 | /* 41 | #calculatedFinalPrice seems not to be set in this version (1.4.0.1) 42 | if (is_null($qty) && !is_null($product->getCalculatedFinalPrice())) { 43 | #Doesn't usually get this far as Product.php checks first. 44 | #Mage::log("returning calculatedFinalPrice for product: " . $product->getId()); 45 | return $product->getCalculatedFinalPrice(); 46 | } 47 | */ 48 | 49 | #check if it's a 'Wishlist buy request', if so return the price of the particular option added to the wishlist 50 | $buyRequest = $product->getCustomOption('info_buyRequest'); 51 | if ($buyRequest) { 52 | $simpleProduct = $product->getCustomOption('info_buyRequest')->getItem()->getOptionByCode('simple_product'); 53 | if($simpleProduct) { 54 | $options = $simpleProduct->getData(); 55 | } else { 56 | $options = $product->getCustomOption('info_buyRequest')->getItem()->getData(); 57 | } 58 | $productId = $options["product_id"]; 59 | $childProduct = Mage::getModel('catalog/product')->load($productId); 60 | return $childProduct->getPrice(); 61 | } 62 | 63 | $childProduct = $this->getChildProductWithLowestPrice($product, "finalPrice"); 64 | if (!$childProduct) { 65 | $childProduct = $this->getChildProductWithLowestPrice($product, "finalPrice", false); 66 | } 67 | 68 | if ($childProduct) { 69 | $fp = $childProduct->getFinalPrice(); 70 | } else { 71 | return false; 72 | } 73 | 74 | if(Mage::app()->getStore()->isAdmin()) { 75 | $productAdmin = Mage::getModel('catalog/product')->loadByAttribute('sku',$product->getSku()); 76 | if($productAdmin->getSpecialPrice()) { 77 | $fp = $productAdmin->getSpecialPrice(); 78 | } else { 79 | $fp = $productAdmin->getPrice(); 80 | } 81 | $product->setFinalPrice($fp); 82 | return $fp; 83 | } else { 84 | $product->setFinalPrice($fp); 85 | return $fp; 86 | } 87 | } 88 | 89 | public function getPrice($product) 90 | { 91 | #Just return indexed_price, if it's been fetched already 92 | #(which it will have been for collections, but not on product page) 93 | $price = $product->getIndexedPrice(); 94 | if ($price !== null) { 95 | return $price; 96 | } 97 | 98 | $childProduct = $this->getChildProductWithLowestPrice($product, "finalPrice"); 99 | #If there aren't any salable child products we return the lowest price 100 | #of all child products, including any ones not currently salable. 101 | if (!$childProduct) { 102 | $childProduct = $this->getChildProductWithLowestPrice($product, "finalPrice", false); 103 | } 104 | 105 | if ($childProduct) { 106 | return $childProduct->getPrice(); 107 | } 108 | 109 | return false; 110 | } 111 | 112 | public function getChildProducts($product, $checkSalable=true) 113 | { 114 | static $childrenCache = array(); 115 | $cacheKey = $product->getId() . ':' . $checkSalable; 116 | 117 | if (isset($childrenCache[$cacheKey])) { 118 | return $childrenCache[$cacheKey]; 119 | } 120 | 121 | $childProducts = $product->getTypeInstance(true)->getUsedProductCollection($product); 122 | $childProducts->addAttributeToSelect(array('price', 'special_price', 'status', 'special_from_date', 'special_to_date')); 123 | 124 | if ($checkSalable) { 125 | $salableChildProducts = array(); 126 | foreach($childProducts as $childProduct) { 127 | if($childProduct->isSalable()) { 128 | $salableChildProducts[] = $childProduct; 129 | } 130 | } 131 | $childProducts = $salableChildProducts; 132 | } 133 | 134 | $childrenCache[$cacheKey] = $childProducts; 135 | return $childProducts; 136 | } 137 | 138 | /* 139 | public function getLowestChildPrice($product, $priceType, $checkSalable=true) 140 | { 141 | $childProduct = $this->getChildProductWithLowestPrice($product, $priceType, $checkSalable); 142 | if ($childProduct) { 143 | if ($priceType == "finalPrice") { 144 | $childPrice = $childProduct->getFinalPrice(); 145 | } else { 146 | $childPrice = $childProduct->getPrice(); 147 | } 148 | } else { 149 | $childPrice = false; 150 | } 151 | return $childPrice; 152 | } 153 | */ 154 | #Could no doubt add highest/lowest as param to save 2 near-identical functions 155 | public function getChildProductWithHighestPrice($product, $priceType, $checkSalable=true) 156 | { 157 | $childProducts = $this->getChildProducts($product, $checkSalable); 158 | if (count($childProducts) == 0) { #If config product has no children 159 | return false; 160 | } 161 | $maxPrice = 0; 162 | $maxProd = false; 163 | foreach($childProducts as $childProduct) { 164 | if ($priceType == "finalPrice") { 165 | $thisPrice = $childProduct->getFinalPrice(); 166 | } else { 167 | $thisPrice = $childProduct->getPrice(); 168 | } 169 | if($thisPrice > $maxPrice) { 170 | $maxPrice = $thisPrice; 171 | $maxProd = $childProduct; 172 | } 173 | } 174 | return $maxProd; 175 | } 176 | 177 | public function getChildProductWithLowestPrice($product, $priceType, $checkSalable=true) 178 | { 179 | $childProducts = $this->getChildProducts($product, $checkSalable); 180 | if (count($childProducts) == 0) { #If config product has no children 181 | return false; 182 | } 183 | $minPrice = PHP_INT_MAX; 184 | $minProd = false; 185 | foreach($childProducts as $childProduct) { 186 | if ($priceType == "finalPrice") { 187 | $thisPrice = $childProduct->getFinalPrice(); 188 | } else { 189 | $thisPrice = $childProduct->getPrice(); 190 | } 191 | if($thisPrice < $minPrice) { 192 | $minPrice = $thisPrice; 193 | $minProd = $childProduct; 194 | } 195 | } 196 | return $minProd; 197 | } 198 | 199 | //Force tier pricing to be empty for configurable products: 200 | public function getTierPrice($qty=null, $product) 201 | { 202 | return array(); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Product/Type/Simple.php: -------------------------------------------------------------------------------- 1 | getProduct()->getCustomOption('cpid'); 10 | if ($cpid) { 11 | return $cpid; 12 | } 13 | 14 | $br = $this->getProduct()->getCustomOption('info_buyRequest'); 15 | if ($br) { 16 | $brData = unserialize($br->getValue()); 17 | if(!empty($brData['cpid'])) { 18 | return $brData['cpid']; 19 | } 20 | } 21 | 22 | return false; 23 | } 24 | 25 | public function prepareForCart(Varien_Object $buyRequest, $product = null) 26 | { 27 | $product = $this->getProduct($product); 28 | parent::prepareForCart($buyRequest, $product); 29 | if ($buyRequest->getcpid()) { 30 | $product->addCustomOption('cpid', $buyRequest->getcpid()); 31 | } 32 | return array($product); 33 | } 34 | 35 | public function hasConfigurableProductParentId() 36 | { 37 | $cpid = $this->getCpid(); 38 | //Mage::log("cpid: ". $cpid); 39 | return !empty($cpid); 40 | } 41 | 42 | public function getConfigurableProductParentId() 43 | { 44 | return $this->getCpid(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Product/Type/Virtual.php: -------------------------------------------------------------------------------- 1 | getProduct($product); 8 | parent::prepareForCart($buyRequest, $product); 9 | if ($buyRequest->getcpid()) { 10 | $product->addCustomOption('cpid', $buyRequest->getcpid()); 11 | } 12 | return array($product); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Resource/Product/Collection.php: -------------------------------------------------------------------------------- 1 | addPriceData(); 9 | * 10 | * @see Mage_Catalog_Model_Resource_Product_Collection::_productLimitationJoinPrice() 11 | */ 12 | protected function _productLimitationPrice($joinLeft = false) 13 | { 14 | $filters = $this->_productLimitationFilters; 15 | if (empty($filters['use_price_index'])) { 16 | return $this; 17 | } 18 | 19 | $helper = Mage::getResourceHelper('core'); 20 | $connection = $this->getConnection(); 21 | $select = $this->getSelect(); 22 | $joinCond = join(' AND ', array( 23 | 'price_index.entity_id = e.entity_id', 24 | $connection->quoteInto('price_index.website_id = ?', $filters['website_id']), 25 | $connection->quoteInto('price_index.customer_group_id = ?', $filters['customer_group_id']) 26 | )); 27 | 28 | $fromPart = $select->getPart(Zend_Db_Select::FROM); 29 | if (!isset($fromPart['price_index'])) { 30 | $least = $connection->getLeastSql(array('price_index.min_price', 'price_index.tier_price')); 31 | $minimalExpr = $connection->getCheckSql('price_index.tier_price IS NOT NULL', 32 | $least, 'price_index.min_price'); 33 | $indexedExpr = new Zend_Db_Expr('price_index.price'); 34 | $colls = array('indexed_price'=>$indexedExpr,'price', 'tax_class_id', 'final_price', 35 | 'minimal_price'=>$minimalExpr , 'min_price', 'max_price', 'tier_price'); 36 | $tableName = array('price_index' => $this->getTable('catalog/product_index_price')); 37 | if ($joinLeft) { 38 | $select->joinLeft($tableName, $joinCond, $colls); 39 | } else { 40 | $select->join($tableName, $joinCond, $colls); 41 | } 42 | // Set additional field filters 43 | foreach ($this->_priceDataFieldFilters as $filterData) { 44 | $select->where(call_user_func_array('sprintf', $filterData)); 45 | } 46 | 47 | } else { 48 | $fromPart['price_index']['joinCondition'] = $joinCond; 49 | $select->setPart(Zend_Db_Select::FROM, $fromPart); 50 | } 51 | //Clean duplicated fields 52 | $helper->prepareColumnsList($select); 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Added exception handling to addItem, otherwise in some cases will throw an exception 59 | * 60 | * @param \Varien_Object $object 61 | */ 62 | public function addItem(\Varien_Object $object) 63 | { 64 | try { 65 | return parent::addItem($object); 66 | } catch (Exception $e) { 67 | 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Resource/Product/Indexer/Price.php: -------------------------------------------------------------------------------- 1 | child_product type_id 11 | */ 12 | private function getChildIdsByParent($parentId) 13 | { 14 | $read = $this->_getReadAdapter(); 15 | $select = $read->select() 16 | ->from(array('p' => $this->getTable('catalog/product')), array('entity_id')) 17 | ->join( 18 | array('pr' => $this->getTable('catalog/product_relation')), 19 | 'pr.child_id=p.entity_id', 20 | array('p.type_id')) 21 | ->where('pr.parent_id=?', $parentId); 22 | return $read->fetchPairs($select); 23 | } 24 | 25 | /** 26 | * Get product type from product id 27 | * 28 | * catalog_product_entity has the product type. Not exactly sure why 29 | * we're using a join here, but it works. 30 | * 31 | * @param integer $id 32 | * @return string Product type 33 | */ 34 | private function getProductTypeById($id) 35 | { 36 | $read = $this->_getReadAdapter(); 37 | $select = $read->select() 38 | ->from(array('pr' => $this->getTable('catalog/product_relation')), array('parent_id')) 39 | ->join( 40 | array('p' => $this->getTable('catalog/product')), 41 | 'pr.parent_id=p.entity_id', 42 | array('p.type_id')) 43 | ->where('pr.parent_id=?', $id); 44 | $data = $read->fetchRow($select); 45 | return $data['type_id']; 46 | } 47 | 48 | 49 | /** 50 | * Modified to pull in all sibling associated products' tier prices and 51 | * to reindex child tier prices when a parent is saved. 52 | * 53 | * Process product save. 54 | * Method is responsible for index support 55 | * when product was saved and changed attribute(s) has an effect on price. 56 | * 57 | * @param Mage_Index_Model_Event $event 58 | * @return Mage_Catalog_Model_Resource_Product_Indexer_Price 59 | */ 60 | public function catalogProductSave(Mage_Index_Model_Event $event) 61 | { 62 | $productId = $event->getEntityPk(); 63 | $data = $event->getNewData(); 64 | 65 | /** 66 | * Check if price attribute values were updated 67 | */ 68 | if (!isset($data['reindex_price'])) { 69 | return $this; 70 | } 71 | 72 | $this->clearTemporaryIndexTable(); 73 | $this->_prepareWebsiteDateTable(); 74 | 75 | $indexer = $this->_getIndexer($data['product_type_id']); 76 | $processIds = array($productId); 77 | if ($indexer->getIsComposite()) { 78 | if ($this->getProductTypeById($productId) == 'configurable') { 79 | $children = $this->getChildIdsByParent($productId); 80 | $processIds = array_merge($processIds, array_keys($children)); 81 | //Ignore tier and group price data for actual configurable product 82 | $tierPriceIds = array_keys($children); 83 | } else { 84 | $tierPriceIds = $productId; 85 | } 86 | $this->_copyRelationIndexData($productId); 87 | $this->_prepareTierPriceIndex($tierPriceIds); 88 | $this->_prepareGroupPriceIndex($tierPriceIds); 89 | $indexer->reindexEntity($productId); 90 | } else { 91 | $parentIds = $this->getProductParentsByChild($productId); 92 | if ($parentIds) { 93 | $processIds = array_merge($processIds, array_keys($parentIds)); 94 | $siblingIds = array(); 95 | foreach (array_keys($parentIds) as $parentId) { 96 | $childIds = $this->getChildIdsByParent($parentId); 97 | $siblingIds = array_merge($siblingIds, array_keys($childIds)); 98 | } 99 | if(count($siblingIds)>0) { 100 | $processIds = array_unique(array_merge($processIds, $siblingIds)); 101 | } 102 | $this->_copyRelationIndexData(array_keys($parentIds), $productId); 103 | $this->_prepareTierPriceIndex($processIds); 104 | $this->_prepareGroupPriceIndex($processIds); 105 | $indexer->reindexEntity($productId); 106 | 107 | $parentByType = array(); 108 | foreach ($parentIds as $parentId => $parentType) { 109 | $parentByType[$parentType][$parentId] = $parentId; 110 | } 111 | 112 | foreach ($parentByType as $parentType => $entityIds) { 113 | $this->_getIndexer($parentType)->reindexEntity($entityIds); 114 | } 115 | } else { 116 | $this->_prepareTierPriceIndex($productId); 117 | $this->_prepareGroupPriceIndex($productId); 118 | $indexer->reindexEntity($productId); 119 | } 120 | } 121 | 122 | $this->_copyIndexDataToMainTable($processIds); 123 | 124 | return $this; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Catalog/Model/Resource/Product/Indexer/Price/Configurable.php: -------------------------------------------------------------------------------- 1 | _prepareDefaultFinalPriceTable(); 24 | 25 | $write = $this->_getWriteAdapter(); 26 | $select = $write->select() 27 | ->from( 28 | array('e' => $this->getTable('catalog/product')), 29 | array()) 30 | ->joinLeft( 31 | array('l' => $this->getTable('catalog/product_super_link')), 32 | 'l.parent_id = e.entity_id', 33 | array()) 34 | ->join( 35 | array('ce' => $this->getTable('catalog/product')), 36 | 'ce.entity_id = l.product_id', 37 | array()) 38 | ->join( 39 | array('pi' => $this->getIdxTable()), 40 | 'ce.entity_id = pi.entity_id', 41 | array()) 42 | ->join( 43 | array('cw' => $this->getTable('core/website')), 44 | 'pi.website_id = cw.website_id', 45 | array()) 46 | ->join( 47 | array('csg' => $this->getTable('core/store_group')), 48 | 'csg.website_id = cw.website_id AND cw.default_group_id = csg.group_id', 49 | array()) 50 | ->join( 51 | array('cs' => $this->getTable('core/store')), 52 | 'csg.default_store_id = cs.store_id AND cs.store_id != 0', 53 | array()) 54 | ->join( 55 | array('cis' => $this->getTable('cataloginventory/stock')), 56 | '', 57 | array()) 58 | ->joinLeft( 59 | array('cisi' => $this->getTable('cataloginventory/stock_item')), 60 | 'cisi.stock_id = cis.stock_id AND cisi.product_id = ce.entity_id', 61 | array()) 62 | ->where('e.type_id=?', $this->getTypeId()); ## is this one needed? 63 | 64 | 65 | $productStatusExpr = $this->_addAttributeToSelect($select, 'status', 'ce.entity_id', 'cs.store_id'); 66 | 67 | if ($this->_isManageStock()) { 68 | $stockStatusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0,' . ' 1, cisi.is_in_stock)'); 69 | } else { 70 | $stockStatusExpr = new Zend_Db_Expr('IF(cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1,' . 'cisi.is_in_stock, 1)'); 71 | } 72 | $isInStockExpr = new Zend_Db_Expr("IF({$stockStatusExpr}, 1, 0)"); 73 | 74 | $isValidChildProductExpr = new Zend_Db_Expr("{$productStatusExpr}"); 75 | 76 | $select->columns(array( 77 | 'entity_id' => new Zend_Db_Expr('e.entity_id'), 78 | 'customer_group_id' => new Zend_Db_Expr('pi.customer_group_id'), 79 | 'website_id' => new Zend_Db_Expr('cw.website_id'), 80 | 'tax_class_id' => new Zend_Db_Expr('pi.tax_class_id'), 81 | 'orig_price' => new Zend_Db_Expr('pi.price'), 82 | 'price' => new Zend_Db_Expr('pi.final_price'), 83 | 'min_price' => new Zend_Db_Expr('pi.final_price'), 84 | 'max_price' => new Zend_Db_Expr('pi.final_price'), 85 | 'tier_price' => new Zend_Db_Expr('pi.tier_price'), 86 | 'base_tier' => new Zend_Db_Expr('pi.tier_price'), 87 | 'group_price' => new Zend_Db_Expr('pi.group_price'), 88 | 'base_group_price' => new Zend_Db_Expr('pi.group_price'), 89 | )); 90 | 91 | 92 | 93 | if (!is_null($entityIds)) { 94 | $select->where('e.entity_id IN(?)', $entityIds); 95 | } 96 | 97 | #Inner select order needs to be: 98 | #1st) If it's in stock come first (out of stock product prices aren't used if not-all products are out of stock) 99 | #2nd) Finalprice 100 | #3rd) $price, in case all finalPrices are NULL. (this gives the lowest price for all associated products when they're all out of stock) 101 | $sortExpr = new Zend_Db_Expr("${isInStockExpr} DESC, pi.final_price ASC, pi.price ASC"); 102 | $select->order($sortExpr); 103 | 104 | /** 105 | * Add additional external limitation 106 | */ 107 | Mage::dispatchEvent('prepare_catalog_product_index_select', array( 108 | 'select' => $select, 109 | 'entity_field' => new Zend_Db_Expr('e.entity_id'), 110 | 'website_field' => new Zend_Db_Expr('cw.website_id'), 111 | 'store_field' => new Zend_Db_Expr('cs.store_id') 112 | )); 113 | 114 | 115 | #This uses the fact that mysql's 'group by' picks the first row, and the subselect is ordered as we want it 116 | #Bit hacky, but lots of people do it :) 117 | $outerSelect = $write->select() 118 | ->from(array("inner" => $select), 'entity_id') 119 | ->group(array('inner.entity_id', 'inner.customer_group_id', 'inner.website_id')); 120 | 121 | $outerSelect->columns(array( 122 | 'customer_group_id', 123 | 'website_id', 124 | 'tax_class_id', 125 | 'orig_price', 126 | 'price', 127 | 'min_price', 128 | 'max_price' => new Zend_Db_Expr('MAX(inner.max_price)'), 129 | 'tier_price', 130 | 'base_tier', 131 | 'group_price', 132 | 'base_group_price', 133 | #'child_entity_id' 134 | )); 135 | # Mage::log("SCP Price inner query: " . $select->__toString()); 136 | # Mage::log("SCP Price outer query: " . $outerSelect->__toString()); 137 | $query = $outerSelect->insertFromSelect($this->_getDefaultFinalPriceTable()); 138 | # Mage::log("===================SCP Price outer query: " . $outerSelect->__toString()); 139 | $write->query($query); 140 | 141 | /** 142 | * Add possibility modify prices from external events 143 | */ 144 | $select = $write->select() 145 | ->join(array('wd' => $this->_getWebsiteDateTable()), 146 | 'i.website_id = wd.website_id', 147 | array()); 148 | Mage::dispatchEvent('prepare_catalog_product_price_index_table', array( 149 | 'index_table' => array('i' => $this->_getDefaultFinalPriceTable()), 150 | 'select' => $select, 151 | 'entity_id' => 'i.entity_id', 152 | 'customer_group_id' => 'i.customer_group_id', 153 | 'website_id' => 'i.website_id', 154 | 'website_date' => 'wd.website_date', 155 | 'update_fields' => array('price', 'min_price', 'max_price') 156 | )); 157 | 158 | return $this; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/CatalogIndex/Model/Data/Configurable.php: -------------------------------------------------------------------------------- 1 | true, 7 | Mage_CatalogIndex_Model_Retreiver::CHILDREN_FOR_PRICES=>true, 8 | Mage_CatalogIndex_Model_Retreiver::CHILDREN_FOR_ATTRIBUTES=>true, 9 | ); 10 | 11 | public function getFinalPrice($product, $store, $group) 12 | { 13 | return false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/CatalogInventory/Model/Resource/Indexer/Stock/Configurable.php: -------------------------------------------------------------------------------- 1 | _getWriteAdapter(); 25 | $idxTable = $usePrimaryTable ? $this->getMainTable() : $this->getIdxTable(); 26 | $select = $adapter->select() 27 | ->from(array('e' => $this->getTable('catalog/product')), array('entity_id')); 28 | $this->_addWebsiteJoinToSelect($select, true); 29 | $select->columns('cw.website_id') 30 | ->join( 31 | array('cis' => $this->getTable('cataloginventory/stock')), 32 | '', 33 | array('stock_id')) 34 | ->joinLeft( 35 | array('l' => $this->getTable('catalog/product_super_link')), 36 | 'l.parent_id = e.entity_id', 37 | array()) 38 | ->join( 39 | array('le' => $this->getTable('catalog/product')), 40 | 'le.entity_id = l.product_id', 41 | array()) 42 | ->joinLeft( 43 | array('cisi' => $this->getTable('cataloginventory/stock_item')), 44 | 'cisi.stock_id = cis.stock_id AND cisi.product_id = le.entity_id', 45 | array()) 46 | ->joinLeft( 47 | array('i' => $idxTable), 48 | 'i.product_id = le.entity_id AND cw.website_id = i.website_id AND cis.stock_id = i.stock_id', 49 | array()) 50 | ->columns(array('qty' => new Zend_Db_Expr('0'))) 51 | ->where('cw.website_id != 0') 52 | ->where('e.type_id = ?', $this->getTypeId()) 53 | ->group(array('e.entity_id', 'cw.website_id', 'cis.stock_id')); 54 | 55 | $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'le.entity_id'); 56 | 57 | $psExpr = $this->_addAttributeToSelect($select, 'status', 'le.entity_id', 'cs.store_id'); 58 | $psCond = $adapter->quoteInto($psExpr . '=?', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); 59 | 60 | if ($this->_isManageStock()) { 61 | $statusExpr = $adapter->getCheckSql('cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0', 62 | 1, 'cisi.is_in_stock'); 63 | } else { 64 | $statusExpr = $adapter->getCheckSql('cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1', 65 | 'cisi.is_in_stock', 1); 66 | } 67 | 68 | $stockStatusExpr = new Zend_Db_Expr("MAX(LEAST(IF({$psCond}, 1, 0), {$statusExpr}))"); 69 | 70 | $select->columns(array( 71 | 'status' => $stockStatusExpr 72 | )); 73 | 74 | if (!is_null($entityIds)) { 75 | $select->where('e.entity_id IN(?)', $entityIds); 76 | } 77 | return $select; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/CatalogRule/Model/Resource/Rule.php: -------------------------------------------------------------------------------- 1 | getIsActive()) { 15 | return $this; 16 | } 17 | 18 | $ruleId = $rule->getId(); 19 | $productId = $product->getId(); 20 | 21 | $write = $this->_getWriteAdapter(); 22 | $write->beginTransaction(); 23 | 24 | $write->delete($this->getTable('catalogrule/rule_product'), array( 25 | $write->quoteInto('rule_id=?', $ruleId), 26 | $write->quoteInto('product_id=?', $productId), 27 | )); 28 | 29 | if (!$rule->getConditions()->validate($product)) { 30 | /* 31 | $write->delete($this->getTable('catalogrule/rule_product_price'), array( 32 | # $write->quoteInto('rule_id=?', $ruleId), 33 | $write->quoteInto('product_id=?', $productId), 34 | )); 35 | */ 36 | $write->commit(); 37 | return $this; 38 | } 39 | 40 | $customerGroupIds = $rule->getCustomerGroupIds(); 41 | 42 | $fromTime = strtotime($rule->getFromDate()); 43 | $toTime = strtotime($rule->getToDate()); 44 | $toTime = $toTime ? $toTime+self::SECONDS_IN_DAY-1 : 0; 45 | 46 | $sortOrder = (int)$rule->getSortOrder(); 47 | $actionOperator = $rule->getSimpleAction(); 48 | $actionAmount = $rule->getDiscountAmount(); 49 | $actionStop = $rule->getStopRulesProcessing(); 50 | 51 | $rows = array(); 52 | $header = 'replace into '.$this->getTable('catalogrule/rule_product').' ( 53 | rule_id, 54 | from_time, 55 | to_time, 56 | website_id, 57 | customer_group_id, 58 | product_id, 59 | action_operator, 60 | action_amount, 61 | action_stop, 62 | sort_order 63 | ) values '; 64 | try { 65 | foreach ($websiteIds as $websiteId) { 66 | foreach ($customerGroupIds as $customerGroupId) { 67 | $rows[] = "( 68 | '$ruleId', 69 | '$fromTime', 70 | '$toTime', 71 | '$websiteId', 72 | '$customerGroupId', 73 | '$productId', 74 | '$actionOperator', 75 | '$actionAmount', 76 | '$actionStop', 77 | '$sortOrder' 78 | )"; 79 | if (sizeof($rows)==100) { 80 | $sql = $header.join(',', $rows); 81 | $write->query($sql); 82 | $rows = array(); 83 | } 84 | } 85 | } 86 | 87 | if (!empty($rows)) { 88 | $sql = $header.join(',', $rows); 89 | $write->query($sql); 90 | } 91 | } catch (Exception $e) { 92 | $write->rollback(); 93 | throw $e; 94 | 95 | } 96 | $this->applyAllRulesForDateRange(null, null, $product); 97 | $write->commit(); 98 | return $this; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Checkout/Block/Cart/Item/Renderer.php: -------------------------------------------------------------------------------- 1 | getItem()->getOptionByCode('cpid')) { 8 | return $this->getItem()->getOptionByCode('cpid')->getValue(); 9 | } 10 | #No idea why in 1.5 the stuff in buyRequest isn't auto-decoded from info_buyRequest 11 | #but then it's Magento we're talking about, so I've not a clue what's *meant* to happen. 12 | try { 13 | $buyRequest = $this->getItem()->getOptionByCode('info_buyRequest'); 14 | if ($buyRequest) { 15 | $buyRequest = unserialize($buyRequest->getValue()); 16 | } 17 | if(!empty($buyRequest['cpid'])) { 18 | return $buyRequest['cpid']; 19 | } 20 | } catch (Exception $e) { 21 | } 22 | return null; 23 | } 24 | 25 | protected function getConfigurableProductParent() 26 | { 27 | return Mage::getModel('catalog/product') 28 | ->setStoreId(Mage::app()->getStore()->getId()) 29 | ->load($this->getConfigurableProductParentId()); 30 | } 31 | 32 | public function getProduct() 33 | { 34 | return Mage::getModel('catalog/product') 35 | ->setStoreId(Mage::app()->getStore()->getId()) 36 | ->load($this->getItem()->getProductId()); 37 | } 38 | 39 | public function getProductName() 40 | { 41 | if (Mage::getStoreConfig('SCP_options/cart/show_configurable_product_name') 42 | && $this->getConfigurableProductParentId()) { 43 | return $this->getConfigurableProductParent()->getName(); 44 | } else { 45 | return parent::getProductName(); 46 | } 47 | } 48 | 49 | 50 | /* Bit of a hack this - assumes configurable parent is always linkable */ 51 | public function hasProductUrl() 52 | { 53 | if ($this->getConfigurableProductParentId()) { 54 | return true; 55 | } else { 56 | return parent::hasProductUrl(); 57 | } 58 | } 59 | 60 | public function getProductUrl() 61 | { 62 | if ($this->getConfigurableProductParentId()) { 63 | return $this->getConfigurableProductParent()->getProductUrl(); 64 | } else { 65 | return parent::getProductUrl(); 66 | #return $this->getProduct()->getProductUrl(); 67 | } 68 | } 69 | 70 | public function getOptionList() 71 | { 72 | $options = false; 73 | if (Mage::getStoreConfig('SCP_options/cart/show_custom_options')) { 74 | $options = parent::getOptionList(); 75 | } 76 | 77 | if (Mage::getStoreConfig('SCP_options/cart/show_config_product_options')) { 78 | if ($this->getConfigurableProductParentId()) { 79 | $attributes = $this->getConfigurableProductParent() 80 | ->getTypeInstance() 81 | ->getUsedProductAttributes(); 82 | foreach($attributes as $attribute) { 83 | $options[] = array( 84 | 'label' => $attribute->getStoreLabel(), 85 | 'value' => $this->getProduct()->getAttributeText($attribute->getAttributeCode()), 86 | 'option_id' => $attribute->getId(), 87 | ); 88 | } 89 | } 90 | } 91 | return $options; 92 | } 93 | 94 | /* 95 | Logic is: 96 | If not SCP product, use normal thumbnail behaviour 97 | If is SCP product, and admin value is set to use configurable image, do so 98 | If is SCP product, and admin value is set to use simple image, do so, 99 | but 'fail back' to configurable image if simple image is placeholder 100 | If logic says to use it, but configurable product image is placeholder, then 101 | just display placeholder 102 | 103 | */ 104 | public function getProductThumbnail() 105 | { 106 | #If product not added via SCP, use default behaviour 107 | if (!$this->getConfigurableProductParentId()) { 108 | return parent::getProductThumbnail(); 109 | } 110 | 111 | 112 | #If showing simple product image 113 | if (!Mage::getStoreConfig('SCP_options/cart/show_configurable_product_image')) { 114 | $product = $this->getProduct(); 115 | #if product image is not a thumbnail 116 | if($product->getData('thumbnail') && ($product->getData('thumbnail') != 'no_selection')) { 117 | return $this->helper('catalog/image')->init($product, 'thumbnail'); 118 | } 119 | } 120 | 121 | #If simple prod thumbnail image is placeholder, or we're not using simple product image 122 | #show configurable product image 123 | $product = $this->getConfigurableProductParent(); 124 | return $this->helper('catalog/image')->init($product, 'thumbnail'); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /app/code/community/OrganicInternet/SimpleConfigurableProducts/Helper/Data.php: -------------------------------------------------------------------------------- 1 | getRequest()->getParam('cid'); 9 | $storeId = $this->_getStoreId(); 10 | $rssObj = Mage::getModel('rss/rss'); 11 | if ($categoryId) { 12 | $category = Mage::getModel('catalog/category')->load($categoryId); 13 | if ($category && $category->getId()) { 14 | $layer = Mage::getSingleton('catalog/layer')->setStore($storeId); 15 | //want to load all products no matter anchor or not 16 | $category->setIsAnchor(true); 17 | $newurl = $category->getUrl(); 18 | $title = $category->getName(); 19 | $data = array('title' => $title, 20 | 'description' => $title, 21 | 'link' => $newurl, 22 | 'charset' => 'UTF-8', 23 | ); 24 | //echo "
"; 25 | //print_r($data); 26 | $rssObj->_addHeader($data); 27 | 28 | $_collection = $category->getCollection(); 29 | $_collection->addAttributeToSelect('url_key') 30 | ->addAttributeToSelect('name') 31 | ->addAttributeToSelect('is_anchor') 32 | ->addAttributeToFilter('is_active',1) 33 | ->addIdFilter($category->getChildren()) 34 | ->load() 35 | ; 36 | $productCollection = Mage::getModel('catalog/product')->getCollection(); 37 | 38 | $currentyCateogry = $layer->setCurrentCategory($category); 39 | $layer->prepareProductCollection($productCollection); 40 | $productCollection->addCountToCategories($_collection); 41 | 42 | /*if ($_collection->count()) { 43 | foreach ($_collection as $_category){ 44 | $data = array( 45 | 'title' => $_category->getName(), 46 | 'link' => $_category->getCategoryUrl(), 47 | 'description' => $this->helper('rss')->__('Total Products: %s', $_category->getProductCount()), 48 | ); 49 | 50 | $rssObj->_addEntry($data); 51 | } 52 | } 53 | */ 54 | $category->getProductCollection()->setStoreId($storeId); 55 | /* 56 | only load latest 50 products 57 | */ 58 | $_productCollection = $currentyCateogry 59 | ->getProductCollection() 60 | ->addAttributeToSort('updated_at','desc') 61 | ->setCurPage(1) 62 | ->setPageSize(50) 63 | ; 64 | //echo "
".$_productCollection->getSelect(); 65 | if ($_productCollection->getSize()>0) { 66 | foreach ($_productCollection as $_product) { 67 | $final_price = $_product->getFinalPrice(); 68 | $description = '
'.$product->getDescription().
13 | ($product->isConfigurable() ? ' Preis ab: ' : ' Preis:'). 14 | Mage::helper('core')->currency($product->getFinalPrice()).' '. 15 | ' | '.
16 | '
__('Availability:') ?> __('In stock') ?>
6 | 7 |__('Availability:') ?> __('Out of stock') ?>
8 | 9 | -------------------------------------------------------------------------------- /app/design/frontend/base/default/template/scp/catalog/product/view/options/scpwrapper.phtml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/design/frontend/base/default/template/scp/catalog/product/view/scpajaxoptions.phtml: -------------------------------------------------------------------------------- 1 | 7 | 13 | getOptions())): ?> 14 |