diff --git a/.htaccess b/.htaccess index 546f18e6f3..dadffa268d 100644 --- a/.htaccess +++ b/.htaccess @@ -32,7 +32,7 @@ ## adjust memory limit # php_value memory_limit 64M - php_value memory_limit 128M + php_value memory_limit 256M php_value max_execution_time 18000 ############################################ @@ -176,4 +176,3 @@ ## http://developer.yahoo.com/performance/rules.html#etags #FileETag none - diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 1e9d20981c..95fea45354 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,11 +1,564 @@ +==== 1.5.0.0-alpha1 ==== + +=== Major Highlights === +Balance Response, Partial Authorization Transactions, Authorization Reversals Support for MasterCard and Discover +3D Secure authentication for Authorize.net payment method +PayflowLink payment method +Authorize.Net SIM payment method +Improved Import/Export functionality +Ability to order composite products from backend and some extra functionality +- reconfigure already added products on front end +- adding preconfigured products in wish-list +Alternative media storage +- Database +- CDN +Order status management +- ability to add new status and assign to some state +- from now on statuses stored in appropriate DB table instead of configuration file +Ability to edit order addresses +- this functionality admin has link to edit address for order view page +Magento Mobile included in base packaging + +=== Improvements === +Upgraded Zend Framework to 1.11.0 +Implemented new process of hashing parameters in Ogone payment method +- implemented an advanced hashing method that invokes all transaction parameters for building security hash +- updated fields sort order in system configuration +- added the new parameter which designates whether to use the old or advanced hashing method +- made SHA-IN and SHA-OUT sys config parameter titles corresponding to parameter titles on merchant site in Ogone +- major refactoring of the Ogone helper: simplified public interface of hash validation, added support for SHA-1, SHA-256 and SHA-512 algorithms (not selectable in system config) +- optimized performance of debugging and building redirect form: removed 2 excessive calls (one from template, another from debugging - it invoked form building even if debugging was disabled) +- since the form is built from a block, prevented injecting SID parameter to URLs when building form +- added HTML escaping in the template hidden fields +- added new system configuration parameter - hashing algorithm +- verified/fixed all API hashing parameters in accordance to documentation v.5.0 + +=== Fixes === +Fixed Cannot open menu configuration-general-design +Fixed Added backend design exception model +Fixed The options in the action dropdown for export are incorrectly labeled in the Sales section +Fixed Checking if address exists was added before save address'es attributes (to prevent foreign key error in case of two users were logged in under one account in the same time). +Fixed URL rewrite algorithm was changed: fix for permanent link for old URLs. +Fixed Incorrect number of used card is shown after you have returned to 'Shopping Cart' in partial authorization (Authorize.Net) +Fixed In one page checkout incorrect information for declined card is shown for partial authorization(Authorize.Net) +Fixed Bundle products shoved without options +Fixed Instructions on the Payflow Link Configuration Menu +Fixed rate model checks rate to existens in rule before delete action +Fixed store name 'Demo Store' was changes to variable with real store name var store.getFrontendName() +Fixed Place order does not work with free shipping +Fixed possibility to find product in advanced search with from-to price is 0 was fixed +Fixed FPT with prices included tax problem +Fixed Instable work of back-end notification +- For now pop-up window doesn't go to our side. Flash availability check is removed +- little refactoring +- method Mage_AdminNotification_Helper_Data::isReadablePopupObject() marked as deprecated +Fixed Onepage checkout - Shipping address issues +- added resetting property to save billing address in address book +- added saving of new shipping address +- simplified condition in order preparation routine +Fixed Backordered Item Status on Orders +- saving current ordered items number for stock item and calculating backorder qty according to it +Fixed The product category is empty after moving category with products to another one +Fixed Magento Connect -> If substitute channel for package extension, MCM will send authorization data to the fake URL +Fixed Image Label is not Uploading properly +Fixed Category Tree -> Changing category color +Fixed With enabled "Inline Translation" its impossible to finish purchase +- Added checking for escaped html end tag +Fixed #0024559: Special Price to Date can not set Use Default Value +Fixed CMS -> Manage Pages: It's possible to save New Page with capital letters in URL key +- corrected js validation; +- added server-side url key validation. +Fixed Makeup of subcategories dropdown menu at front-end glitter with category fields bar +- removed property "z-index: 1" for #nav li.level-top +Revert changes from rev #83486 +Fixed Tier prices are not recalculated in bundle product configuration with different currency +Fixed On Multi store installation, 'specials' rss feed includes specials from other stores +Fixed Subscribe to Order Status - translation problem +Fixed There is a spelling error with the translation +Fixed Single Coupon applying for each shipping location rather than whole order +- applying cart fixed rules for first shipping address order only +- store which quote address cart fixed rule was applied for in SalesRule_Model_Validator +Added method getDefaultCountry and constant XML_PATH_DEFAULT_COUNTRY into Mage_Core_Helper_Data +Added more abstract system config backend model for uploading files: +- removed duplicated logic from system config backend image model. +Fixed Grand total doubles when processing multi-shipping checkout and ordinary checkout +- cleaning address information when checkout type changes from multi-shipping to onepage +Fixed Problem with admin roles +Checkout page IE6/7 CSS bug fixed. +Fixed Edit product->"Inventory" tab - "Qty Increments" error contain mistake +Fixed No field for "Search Query" +Fixed Character "b" is added to Review +Fixed Retain the selected tab on editing CMS page +Fixed Invoices Tax class not displayed +Fixed Full tax summary on invoice +Fixed working with partial authorizations on first card submit +Fixed CMS can't create Hierarchy Node Link widget in IE8 +Fixed The Wrong / not exist Url should be redirect to 404 page +Fixed Interface Locale needs additional country +Fixed The product category is empty after moving category with products to another one +Fixed 'Gateway error: A valid amount is required' appears during create Credit Memo for order, which uses Authorize.net +Fixed #19807: Product with visibility- Nowhere display on the fron-end in 'Last ordered items' block, if order create on back-end +Fixed Displaying Out of Stock Products on the front-end +- added price data for consider item stock status for wish-list and compare products items collections, in reorder for product collection which sales order item collection based on. +Fixed active tabs in store view scope while disabled PayPal methods +- Added functionality that disables corresponding methods in store view scope. +- Fixed related bug: in website view scope Express Checkout PE checkbox appears improperly checked after page load. +Fixed Active tabs in store view scope while disabled PayPal methods +Fixed Bundle price wrong when static qty above 1 +Fixed No "Suspected Fraud" status for hacked orders +Fixed PayPal API Certificate uses settings from the default configuration, instead of the website +Fixed Don't show (-) in totals when shipping title and shiping method empty in a configuration +Fixed Product with price 0.00 possibility purchase through Shortcut PayPal button +Fixed Archived orders not displayed in customer's orders list +Fixed Fatal error on magento compilation +Fixed Mage::app() call is not overriding cache/var directories +Fixed After switching "Manage Stock" option, product prices index does not invalidate +Fixed Transfer Cart Line: dropdown with shipping Rates is absent on PayPal side +Fixed Can't choose Group at the creation new Order by admin +Fixed PayPal API Certificate uses settings from the default configuration, instead of the website +- added forgotten file from rev 84979 +Fixed Bug in Error Message display for Send to Friend (Mage_Sendfriend_ProductController) +Fixed Admin order creation JS error message +Fixed Problems with category sort order +Fixed Button "Credit Memo" after refund partial per invoice is enabled +Fixed Report don't show order with status "Canceled" +Fixed Wrong quantity checks architecture in inventory observer +Fixed On page 404, link "go back" does not work +Fixed Category editing "Use Parent Category Settings" inconsistent behavior (Google Chrome) +Fixed Set products per Page +Fixed product review filter by customer does not work +- type is administrator when customer_id is NULL and store_id is admin store id +Fixed Product review filter by customer does not work +Fixed WYSIWYG Editor disabling issue +Fixed Quantity increment for Group Product Issue +Fixed #15780: Add configuration option to ignore SID on frontend +- Changed fieldset scope from global to website, because the field has website scope. +Fixed Message "The product has required options" appears twice in the back-end order for items with mandatory custom options +Fixed Meta description can be more than 255 chars +Fixed When creating a new customer from the backend in "Manage Customers", the welcome email is empty +Fixed Image Label is not Uploading properly +- Slightly changed logic in adding image algorithms due to possible existence of added pictures +Fixed JavaScript Calendar Date Range +- Also little fix to maintain corporate standards +Fixed Gift message displaying conditions not properly work on frontend and backend: +Fixed flat catalog tables do not contain varchar values for store view level +Fixed Add check "Use Default" for dependent form elements (in the admin), because if field "Use Default" it should be always disabled. +Fixed #21084: "can not" -> "cannot" text changes (found only one occurrence of "can not" and changed it) +Fixed New added required attribute should be filled in by customer before checkout +Fixed PayPal Billing Agreement presents in payment methods when no BA are created during admin order creation +Fixed Free Shipping Banner appears to be hard coded into the template file - replaced hardcoded callouts with CNS blocks. Two CMS blocks should be added to RR install. +Fixed Empty order status field +- Configuration mistake +Fixed Cart Rule discount with Fixed amount for a whole Cart is not applied for OnePageCheckout +Fixed Coupon with "Apply fixed amount discount for whole cart" does not apply to bundle products with dynamic price +Fixed Frontend additional attributes issue with price attributes. +Fixed Unable to translate "Submit Invoice" button +Fixed Undefined index after clicking on Print Shipment +Fixed Customer cannot be confirmed from the admin +Fixed Inline Translations don't work if you have more than one store +Fixed Invitation link has a session ID parameter +Fixed Magento creates order even if response from PaypalUk is empty +- response validation has been added +Fixed Displaying Out of Stock Products on the front-end +Fixed Incorrect billing/shipping address transfer from magento to PayPal (WPP Payflow Edition, WPP Payflow Edition EC ) +Fixed User cannot be associated with webservice role if he was selected in the Role Users of Role inforamation Page +Fixed CSS Merger Cache Ignores Hostname and HTTPS +- removed "beta" mark on CSS merger feature in system configuration, because known issue with different host names for different store views is solved +- split merged CSS storage into 2 parts: "css" and "css_secure" +- included "port" and "base host name" parameters into merger hash generation algorithm as parameters +Fixed: Default country setting not affect country select field default value on frontend +Fixed Credit card data Iframe for PayflowLink is displayed on Order Review step for all payment methods +Fixed The Wrong / not exist Url should be redirect to 404 page +- Added section availability in preDispatch +Fixed Report > Products Ordered ignores Store view switcher +- reforming $storeIds checks +Fixed Report > Products Ordered ignores Store view switcher +Fixed Cannot create or save after editing customer's Address, "Please enter the street" error appears on frontend +Fixed Magento allows admin to create category/product url rewrite for a store that doesn't have this category/product +- Showing websites that only associated to current category or product. +Fixed Magento allows admin to create category/product url rewrite for a store that doesn't have this category/product +- Fixed coding standards +Fixed Not all session data destroyed on logout +Fixed Inline Translation - Pages View Issue +- move cache types list in config +Fixed Shop By index range is build based on Excl. Tax value, but filter products in catalog based on Incl tax value. filter works incorrect +- it was problem with facets calculation, when we use Solr +Fixed Security issue - processing of disallowed actions with orders through direct URL +- wrong Credit Memo ACL resource name +Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat +Fixed Different order amount in Google checkout and Magento orders +Fixed Bundle Product w/o required option calculates wrong fixed minimal price +Fixed Admin unable to uninstall payment method without editing config +- getMethodInstance method was rewrited +- Added instance check in some payment models according to this +Fixed lastInsertId invokes when no insert where proceed +Fixed Changing the root category for a store doesn't work correctly +Fixed Unable to translate notice messages, errors and success messages +Fixed Problems with newsletter template preview on newsletter queue edit page +Fixed Tax of shipping method Flat Rate is not passed to the order while Google Checkout +Fixed Product Flat Data reindex +- disable flat data usage during reindex process +Fixed Catalog Rule does not work properly when condition uses Contains +Fixed Wrong prices (currency) for shipping price via UPS XML rates +Fixed Unable to translate product edit/create page +Fixed During product save operation, Magento disables keys for catalog_product_index_* tables. +Fixed FPT with prices included tax problem +- Added 'Catalog Prices' option check +Fixed Google Analytics e-commerce tracking not working +Fixed Empty bundle selections are shown as item options +Fixed Subtotal for Bundle product with quantity 2 calculates as for one +Fixed Remove initSessionLayoutMessages() from ProductController +Fixed Incorrect value of field "Custom Layout Update" causes fatal error +Fixed Duplicate of a product creates it with no SKU value and is saved +Fixed Field "Meta Description" should be has limit of 255 characters +Fixed Row subtotal is not displayed for Downloadable product in Backend +Fixed Bad styling of product options displayed in wishlist +Fixed Need to show item options of customer shopping cart at backend +Fixed Wishlist item configuration is not saved at backend +Fixed "OK" button instead of "Ok" must be on product configuration popups in backend +Fixed Products wishlist items are not sorted by added_at +Fixed Mage_Core_Model_Template doesn't properly restore old design context +Fixed StoreView value not in FlatCatalog for multiple-select type attribute +Fixed Rule condition "is one of" disappeared for category_ids attribute +- Added "is one of" and "is not one of" to multiselect type conditions +Fixed Security issue - the way to get URL-path of Admin side through Front-end URL +Fixed Pictures does not appears on the additional information tab on front end for product attribute with Catalog Input Type for Store Owner= Text Area +Fixed Add method which was accidentally removed +- Deprecated methods Mage_Catalog_Model_Product::loadParentProductIds, Mage_Catalog_Model_Resource_Eav_Mysql4_Product::getParentProductIds +Fixed Can't create refund online for Google Chekout +Fixed After pressing 'Cancel Payment' link nothing happens in Payflow Link payment method +Fixed class Mage_Core_Model_Store has problem with _processConfigValue and processSubst +- Added "@deprecated after 1.4.2.0" mark for processSubst() in Mage_Core_Model_Store +Fixed Category tree is missing for product, assigned to root category +Fixed Unable to translate "Delete Image" checkbox +Fixed wrong XML paths in isAllowed() method for system->Admin roles controllers. +Fixed Magento falls into the white screen when saving URL rewrite for a product on the Default Store View +- Changed exception message +Fixed Different shipping amount for creditmemo from order page and invoice page +Fixed Tax rates with zip ranges doesn't match to addresses with asterisk ( * ) as zip code value +Fixed Fedex doesn't react to overrided in website scope BaseCurrency value. +Fixed Export Customers. Map billing or shipping street in the mapping interface. They won't be exported +Fixed Advanced Import Profiles doesn't work +Fixed Add New Customer Form: checkbox "Send Welcome Email" is not disabled if "Associate to Website"="Admin" +Fixed JS validation prevent submit form +Fixed Payment action: Ogone Default Operation is not working at all +Fixed There are no ability to create several Refunds to Order completed using Partial Authorization +Fixed Incorrect shipping tax calculation on invoice creditmemos with included tax +Fixed URL key wasn't used while product save +Fixed Inline Translations don't work if you have more than one store +Fixed Tax Report Shows Wrong Tax Percent After Changing Tax Rate +- added grouping by tax percent in report collections +- modified unique key in the tax report aggregation table to allow generating report with grouping by tax percents +- data in the tax report aggregation table is truncated and lifetime statistics must be re-generated after upgrade +Fixed make increment_id fields unique in sales tables +Fixed Added items to the Wishlist in the "Manage Shopping Cart" are not shown +Fixed Orders: More than one filter to the same field is not possible +- Function items was changed. +Fixed Invoices: More than one filter to the same field is not possible +- Function items was changed. +Fixed Report counts configurable products twice +Fixed Tax rate with ZIP XXXXX* doesn't match to customer zip XXXXX +Fixed Method Mage_Wishlist_Block_Links::addWishlistLink removed +- Added "@deprecated after 1.4.2.0" +Fixed "Subscribed to Newsletter" success e-mail couldn't be sent if you changed customer's subscription in admin. +Fixed Problems with newsletter template preview on newsletter queue edit page +Fixed Dashboard reports bug +- Added discount to Mage_Reports_Model_Mysql4_Order_Collection:::addSumAvgTotals() +Fixed Google base timeout +- Timeout is 60 seconds now. +Fixed Bundle product is not shipped correctly +Fixed Unable to translate product edit/create page +Fixed Dataflow customers export optimization +- customer groups are storing in memory instead of DB queries +Fixed Price layer navigation does not count product with zero price +Fixed Import Product doesn't work +- added empty file checking +Fixed Price Indexer does not apply configurable options surcharges for customer groups different to "NOT LOGGED IN" +Fixed Category Update Not Reflected in Left Nav +Fixed Notify Stock RSS Includes Products without stock +Fixed FPT is not shown in the order review page (for website) +Fixed SQL Upgrades have wrong implementation +Fixed Email to a friend error, existing Order Send Email error +- Added ability to access to /admin/sales_order/email/ action +Fixed Total Refunded Report shows Offline Refunded orders like Online Refunded +Fixed UPS XML Shipping method doesn't work, if country of shipping origin is not USA +- Added Mage::log() for errors @ Mage_Usa_Model_Shipping_Carrier_Ups::_parseXmlResponse() + + +==== 1.5.x-devel-88903 ==== + +=== Major Highlights === +Balance Response, Partial Authorization Transactions, Authorization Reversals Support for MasterCard and Discover +3D Secure authentication for Authorize.net payment method +PayflowLink payment method +Improved Import/Export functionality +Ability to order composite products from backend and some extra functionality +- reconfigure already added products on front end +- adding preconfigured products in wish-list +Alternative media storage +- Database +- CDN +Order status management +- ability to add new status and assign to some state +- from now on statuses stored in appropriate DB table instead of configuration file +Ability to edit order addresses +- this functionality admin has link to edit address for order view page + +=== Improvements === +Upgraded Zend Framework to 1.11.0 +Implemented new process of hashing parameters in Ogone payment method +- implemented an advanced hashing method that invokes all transaction parameters for building security hash +- updated fields sort order in system configuration +- added the new parameter which designates whether to use the old or advanced hashing method +- made SHA-IN and SHA-OUT sys config parameter titles corresponding to parameter titles on merchant site in Ogone +- major refactoring of the Ogone helper: simplified public interface of hash validation, added support for SHA-1, SHA-256 and SHA-512 algorithms (not selectable in system config) +- optimized performance of debugging and building redirect form: removed 2 excessive calls (one from template, another from debugging - it invoked form building even if debugging was disabled) +- since the form is built from a block, prevented injecting SID parameter to URLs when building form +- added HTML escaping in the template hidden fields +- added new system configuration parameter - hashing algorithm +- verified/fixed all API hashing parameters in accordance to documentation v.5.0 + +=== Fixes === +Fixed Onepage checkout - Shipping address issues +- added resetting property to save billing address in address book +- added saving of new shipping address +- simplified condition in order preparation routine +Fixed Backordered Item Status on Orders +- saving current ordered items number for stock item and calculating backorder qty according to it +Fixed The product category is empty after moving category with products to another one +Fixed Magento Connect -> If substitute channel for package extension, MCM will send authorization data to the fake URL +Fixed Image Label is not Uploading properly +Fixed Category Tree -> Changing category color +Fixed With enabled "Inline Translation" its impossible to finish purchase +- Added checking for escaped html end tag +Fixed #0024559: Special Price to Date can not set Use Default Value +Fixed CMS -> Manage Pages: It's possible to save New Page with capital letters in URL key +- corrected js validation; +- added server-side url key validation. +Fixed Makeup of subcategories dropdown menu at front-end glitter with category fields bar +- removed property "z-index: 1" for #nav li.level-top +Revert changes from rev #83486 +Fixed Tier prices are not recalculated in bundle product configuration with different currency +Fixed On Multi store installation, 'specials' rss feed includes specials from other stores +Fixed Subscribe to Order Status - translation problem +Fixed There is a spelling error with the translation +Fixed Single Coupon applying for each shipping location rather than whole order +- applying cart fixed rules for first shipping address order only +- store which quote address cart fixed rule was applied for in SalesRule_Model_Validator +Added method getDefaultCountry and constant XML_PATH_DEFAULT_COUNTRY into Mage_Core_Helper_Data +Added more abstract system config backend model for uploading files: +- removed duplicated logic from system config backend image model. +Fixed Grand total doubles when processing multi-shipping checkout and ordinary checkout +- cleaning address information when checkout type changes from multi-shipping to onepage +Fixed Problem with admin roles +Checkout page IE6/7 CSS bug fixed. +Fixed Edit product->"Inventory" tab - "Qty Increments" error contain mistake +Fixed No field for "Search Query" +Fixed Character "b" is added to Review +Fixed Retain the selected tab on editing CMS page +Fixed Invoices Tax class not displayed +Fixed Full tax summary on invoice +Fixed working with partial authorizations on first card submit +Fixed CMS can't create Hierarchy Node Link widget in IE8 +Fixed The Wrong / not exist Url should be redirect to 404 page +Fixed Interface Locale needs additional country +Fixed The product category is empty after moving category with products to another one +Fixed 'Gateway error: A valid amount is required' appears during create Credit Memo for order, which uses Authorize.net +Fixed #19807: Product with visibility- Nowhere display on the fron-end in 'Last ordered items' block, if order create on back-end +Fixed Displaying Out of Stock Products on the front-end +- added price data for consider item stock status for wish-list and compare products items collections, in reorder for product collection which sales order item collection based on. +Fixed active tabs in store view scope while disabled PayPal methods +- Added functionality that disables corresponding methods in store view scope. +- Fixed related bug: in website view scope Express Checkout PE checkbox appears improperly checked after page load. +Fixed Active tabs in store view scope while disabled PayPal methods +Fixed Bundle price wrong when static qty above 1 +Fixed No "Suspected Fraud" status for hacked orders +Fixed PayPal API Certificate uses settings from the default configuration, instead of the website +Fixed Don't show (-) in totals when shipping title and shiping method empty in a configuration +Fixed Product with price 0.00 possibility purchase through Shortcut PayPal button +Fixed Archived orders not displayed in customer's orders list +Fixed Fatal error on magento compilation +Fixed Mage::app() call is not overriding cache/var directories +Fixed After switching "Manage Stock" option, product prices index does not invalidate +Fixed Transfer Cart Line: dropdown with shipping Rates is absent on PayPal side +Fixed Can't choose Group at the creation new Order by admin +Fixed PayPal API Certificate uses settings from the default configuration, instead of the website +- added forgotten file from rev 84979 +Fixed Bug in Error Message display for Send to Friend (Mage_Sendfriend_ProductController) +Fixed Admin order creation JS error message +Fixed Problems with category sort order +Fixed Button "Credit Memo" after refund partial per invoice is enabled +Fixed Report don't show order with status "Canceled" +Fixed Wrong quantity checks architecture in inventory observer +Fixed On page 404, link "go back" does not work +Fixed Category editing "Use Parent Category Settings" inconsistent behavior (Google Chrome) +Fixed Set products per Page +Fixed product review filter by customer does not work +- type is administrator when customer_id is NULL and store_id is admin store id +Fixed Product review filter by customer does not work +Fixed WYSIWYG Editor disabling issue +Fixed Quantity increment for Group Product Issue +Fixed #15780: Add configuration option to ignore SID on frontend +- Changed fieldset scope from global to website, because the field has website scope. +Fixed Message "The product has required options" appears twice in the back-end order for items with mandatory custom options +Fixed Meta description can be more than 255 chars +Fixed When creating a new customer from the backend in "Manage Customers", the welcome email is empty +Fixed Image Label is not Uploading properly +- Slightly changed logic in adding image algorithms due to possible existence of added pictures +Fixed JavaScript Calendar Date Range +- Also little fix to maintain corporate standards +Fixed Gift message displaying conditions not properly work on frontend and backend: +Fixed flat catalog tables do not contain varchar values for store view level +Fixed Add check "Use Default" for dependent form elements (in the admin), because if field "Use Default" it should be always disabled. +Fixed #21084: "can not" -> "cannot" text changes (found only one occurrence of "can not" and changed it) +Fixed New added required attribute should be filled in by customer before checkout +Fixed PayPal Billing Agreement presents in payment methods when no BA are created during admin order creation +Fixed Free Shipping Banner appears to be hard coded into the template file - replaced hardcoded callouts with CNS blocks. Two CMS blocks should be added to RR install. +Fixed Empty order status field +- Configuration mistake +Fixed Cart Rule discount with Fixed amount for a whole Cart is not applied for OnePageCheckout +Fixed Coupon with "Apply fixed amount discount for whole cart" does not apply to bundle products with dynamic price +Fixed Frontend additional attributes issue with price attributes. +Fixed Unable to translate "Submit Invoice" button +Fixed Undefined index after clicking on Print Shipment +Fixed Customer cannot be confirmed from the admin +Fixed Inline Translations don't work if you have more than one store +Fixed Invitation link has a session ID parameter +Fixed Magento creates order even if response from PaypalUk is empty +- response validation has been added +Fixed Displaying Out of Stock Products on the front-end +Fixed Incorrect billing/shipping address transfer from magento to PayPal (WPP Payflow Edition, WPP Payflow Edition EC ) +Fixed User cannot be associated with webservice role if he was selected in the Role Users of Role inforamation Page +Fixed CSS Merger Cache Ignores Hostname and HTTPS +- removed "beta" mark on CSS merger feature in system configuration, because known issue with different host names for different store views is solved +- split merged CSS storage into 2 parts: "css" and "css_secure" +- included "port" and "base host name" parameters into merger hash generation algorithm as parameters +Fixed: Default country setting not affect country select field default value on frontend +Fixed Credit card data Iframe for PayflowLink is displayed on Order Review step for all payment methods +Fixed The Wrong / not exist Url should be redirect to 404 page +- Added section availability in preDispatch +Fixed Report > Products Ordered ignores Store view switcher +- reforming $storeIds checks +Fixed Report > Products Ordered ignores Store view switcher +Fixed Cannot create or save after editing customer's Address, "Please enter the street" error appears on frontend +Fixed Magento allows admin to create category/product url rewrite for a store that doesn't have this category/product +- Showing websites that only associated to current category or product. +Fixed Magento allows admin to create category/product url rewrite for a store that doesn't have this category/product +- Fixed coding standards +Fixed Not all session data destroyed on logout +Fixed Inline Translation - Pages View Issue +- move cache types list in config +Fixed Shop By index range is build based on Excl. Tax value, but filter products in catalog based on Incl tax value. filter works incorrect +- it was problem with facets calculation, when we use Solr +Fixed Security issue - processing of disallowed actions with orders through direct URL +- wrong Credit Memo ACL resource name +Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Flat +Fixed Different order amount in Google checkout and Magento orders +Fixed Bundle Product w/o required option calculates wrong fixed minimal price +Fixed Admin unable to uninstall payment method without editing config +- getMethodInstance method was rewrited +- Added instance check in some payment models according to this +Fixed lastInsertId invokes when no insert where proceed +Fixed Changing the root category for a store doesn't work correctly +Fixed Unable to translate notice messages, errors and success messages +Fixed Problems with newsletter template preview on newsletter queue edit page +Fixed Tax of shipping method Flat Rate is not passed to the order while Google Checkout +Fixed Product Flat Data reindex +- disable flat data usage during reindex process +Fixed Catalog Rule does not work properly when condition uses Contains +Fixed Wrong prices (currency) for shipping price via UPS XML rates +Fixed Unable to translate product edit/create page +Fixed During product save operation, Magento disables keys for catalog_product_index_* tables. +Fixed FPT with prices included tax problem +- Added 'Catalog Prices' option check +Fixed Google Analytics e-commerce tracking not working +Fixed Empty bundle selections are shown as item options +Fixed Subtotal for Bundle product with quantity 2 calculates as for one +Fixed Remove initSessionLayoutMessages() from ProductController +Fixed Incorrect value of field "Custom Layout Update" causes fatal error +Fixed Duplicate of a product creates it with no SKU value and is saved +Fixed Field "Meta Description" should be has limit of 255 characters +Fixed Row subtotal is not displayed for Downloadable product in Backend +Fixed Bad styling of product options displayed in wishlist +Fixed Need to show item options of customer shopping cart at backend +Fixed Wishlist item configuration is not saved at backend +Fixed "OK" button instead of "Ok" must be on product configuration popups in backend +Fixed Products wishlist items are not sorted by added_at +Fixed Mage_Core_Model_Template doesn't properly restore old design context +Fixed StoreView value not in FlatCatalog for multiple-select type attribute +Fixed Rule condition "is one of" disappeared for category_ids attribute +- Added "is one of" and "is not one of" to multiselect type conditions +Fixed Security issue - the way to get URL-path of Admin side through Front-end URL +Fixed Pictures does not appears on the additional information tab on front end for product attribute with Catalog Input Type for Store Owner= Text Area +Fixed Add method which was accidentally removed +- Deprecated methods Mage_Catalog_Model_Product::loadParentProductIds, Mage_Catalog_Model_Resource_Eav_Mysql4_Product::getParentProductIds +Fixed Can't create refund online for Google Chekout +Fixed After pressing 'Cancel Payment' link nothing happens in Payflow Link payment method +Fixed class Mage_Core_Model_Store has problem with _processConfigValue and processSubst +- Added "@deprecated after 1.4.2.0" mark for processSubst() in Mage_Core_Model_Store +Fixed Category tree is missing for product, assigned to root category +Fixed Unable to translate "Delete Image" checkbox +Fixed wrong XML paths in isAllowed() method for system->Admin roles controllers. +Fixed Magento falls into the white screen when saving URL rewrite for a product on the Default Store View +- Changed exception message +Fixed Different shipping amount for creditmemo from order page and invoice page +Fixed Tax rates with zip ranges doesn't match to addresses with asterisk ( * ) as zip code value +Fixed Fedex doesn't react to overrided in website scope BaseCurrency value. +Fixed Export Customers. Map billing or shipping street in the mapping interface. They won't be exported +Fixed Advanced Import Profiles doesn't work +Fixed Add New Customer Form: checkbox "Send Welcome Email" is not disabled if "Associate to Website"="Admin" +Fixed JS validation prevent submit form +Fixed Payment action: Ogone Default Operation is not working at all +Fixed There are no ability to create several Refunds to Order completed using Partial Authorization +Fixed Incorrect shipping tax calculation on invoice creditmemos with included tax +Fixed URL key wasn't used while product save +Fixed Inline Translations don't work if you have more than one store +Fixed Tax Report Shows Wrong Tax Percent After Changing Tax Rate +- added grouping by tax percent in report collections +- modified unique key in the tax report aggregation table to allow generating report with grouping by tax percents +- data in the tax report aggregation table is truncated and lifetime statistics must be re-generated after upgrade +Fixed make increment_id fields unique in sales tables +Fixed Added items to the Wishlist in the "Manage Shopping Cart" are not shown +Fixed Orders: More than one filter to the same field is not possible +- Function items was changed. +Fixed Invoices: More than one filter to the same field is not possible +- Function items was changed. +Fixed Report counts configurable products twice +Fixed Tax rate with ZIP XXXXX* doesn't match to customer zip XXXXX +Fixed Method Mage_Wishlist_Block_Links::addWishlistLink removed +- Added "@deprecated after 1.4.2.0" +Fixed "Subscribed to Newsletter" success e-mail couldn't be sent if you changed customer's subscription in admin. +Fixed Problems with newsletter template preview on newsletter queue edit page +Fixed Dashboard reports bug +- Added discount to Mage_Reports_Model_Mysql4_Order_Collection:::addSumAvgTotals() +Fixed Google base timeout +- Timeout is 60 seconds now. +Fixed Bundle product is not shipped correctly +Fixed Unable to translate product edit/create page +Fixed Dataflow customers export optimization +- customer groups are storing in memory instead of DB queries +Fixed Price layer navigation does not count product with zero price +Fixed Import Product doesn't work +- added empty file checking +Fixed Price Indexer does not apply configurable options surcharges for customer groups different to "NOT LOGGED IN" +Fixed Category Update Not Reflected in Left Nav +Fixed Notify Stock RSS Includes Products without stock +Fixed FPT is not shown in the order review page (for website) +Fixed SQL Upgrades have wrong implementation +Fixed Email to a friend error, existing Order Send Email error +- Added ability to access to /admin/sales_order/email/ action +Fixed Total Refunded Report shows Offline Refunded orders like Online Refunded +Fixed UPS XML Shipping method doesn't work, if country of shipping origin is not USA +- Added Mage::log() for errors @ Mage_Usa_Model_Shipping_Carrier_Ups::_parseXmlResponse() + + ==== 1.4.2.0 ==== === Major Highlights === * Starting from this release we are including TheFind extension. -* New Magento Connect Manager excluded from this version. == Upgrade Notes == -* Those who installed Magento version 1.4.2.0-beta1 or 1.4.2.0-rc1 through Magento Connect should reinstall it manually, because this version contains old version of Magento Connect Manager +* Those who installed Magento version 1.4.2.0-beta1 or 1.4.2.0-rc1 through Magento Connect should reinstall it manually, because its version number is the same as of the current release and no automatic update will be available for it. == Improvements == * Added "Switch/Maestro" card type support to centinel 3DS validator. Added comment about maestro and 3d-secure to paypal system config @@ -308,7 +861,7 @@ * Fixed Related products are not saved when you attach them to a product * Fixed Added qty to bundle unit price calculation to quote totals recalculating. * Fixed Saving category cause: 'Exception' with message 'File was not uploaded.' in /home/vadim.kusakin/dev/qa/2759/lib/Varien/File/Uploader.php:139 -* Fixed Fixed MAGE-638 Magento Connect -> MCM -> If agreement checkbox is unchecked, "Continue" button should be disabled +* Fixed Fixed Magento Connect -> MCM -> If agreement checkbox is unchecked, "Continue" button should be disabled * Fixed Set the same column font size as in cells in PDF documents printing * Fixed Home page appears instead of predefined 404 page * Fixed Removed converting of "is one of" and "is not one of" values in decimal. @@ -375,7 +928,7 @@ * Fixed #23461: Wrong attribute value in catalog link widget XML * Fixed getting complete state for orders with zero grand total when processed * Added Store id param to billing agreement entity. Also fixed store setting in billing agreement payment method. -* Fixed fatal error in payment method list fetching (MAGE-500) +* Fixed fatal error in payment method list fetching * Fixed #22575: Trace error during using filter "Products" on Tags page * Fixed Grids with settings remain active while disabled PayPal methods * Fixed configuration merger fatal error when store/website resource structure is inconsistent @@ -808,7 +1361,7 @@ * Fixed Related products are not saved when you attach them to a product * Fixed Added qty to bundle unit price calculation to quote totals recalculating. * Fixed Saving category cause: 'Exception' with message 'File was not uploaded.' in /home/vadim.kusakin/dev/qa/2759/lib/Varien/File/Uploader.php:139 -* Fixed Fixed MAGE-638 Magento Connect -> MCM -> If agreement checkbox is unchecked, "Continue" button should be disabled +* Fixed Fixed Magento Connect -> MCM -> If agreement checkbox is unchecked, "Continue" button should be disabled * Fixed Set the same column font size as in cells in PDF documents printing * Fixed Home page appears instead of predefined 404 page * Fixed Removed converting of "is one of" and "is not one of" values in decimal. @@ -895,7 +1448,6 @@ * Fixed #23461: Wrong attribute value in catalog link widget XML * Fixed getting complete state for orders with zero grand total when processed * Added Store id param to billing agreement entity. Also fixed store setting in billing agreement payment method. -* Fixed fatal error in payment method list fetching (MAGE-500) * Fixed #22575: Trace error during using filter "Products" on Tags page * Fixed Grids with settings remain active while disabled PayPal methods * Fixed configuration merger fatal error when store/website resource structure is inconsistent diff --git a/app/Mage.php b/app/Mage.php index 2157104377..9760351883 100644 --- a/app/Mage.php +++ b/app/Mage.php @@ -151,11 +151,11 @@ public static function getVersionInfo() { return array( 'major' => '1', - 'minor' => '4', - 'revision' => '2', + 'minor' => '5', + 'revision' => '0', 'patch' => '0', - 'stability' => '', - 'number' => '', + 'stability' => 'alpha', + 'number' => '1', ); } @@ -574,6 +574,37 @@ public static function app($code = '', $type = 'store', $options = array()) return self::$_app; } + /** + * @static + * @param string $code + * @param string $type + * @param array $options + * @param string|array $modules + */ + public static function init($code = '', $type = 'store', $options = array(), $modules = array()) + { + try { + self::setRoot(); + self::$_app = new Mage_Core_Model_App(); + self::$_config = new Mage_Core_Model_Config(); + + if (!empty($modules)) { + self::$_app->initSpecified($code, $type, $options, $modules); + } else { + self::$_app->init($code, $type, $options); + } + } catch (Mage_Core_Model_Session_Exception $e) { + header('Location: ' . self::getBaseUrl()); + die; + } catch (Mage_Core_Model_Store_Exception $e) { + require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php'); + die; + } catch (Exception $e) { + self::printException($e); + die; + } + } + /** * Front end main entry point * @@ -586,9 +617,9 @@ public static function run($code = '', $type = 'store', $options=array()) try { Varien_Profiler::start('mage'); self::setRoot(); - self::$_app = new Mage_Core_Model_App(); - self::$_events = new Varien_Event_Collection(); - self::$_config = new Mage_Core_Model_Config(); + self::$_app = new Mage_Core_Model_App(); + self::$_events = new Varien_Event_Collection(); + self::$_config = new Mage_Core_Model_Config(); self::$_app->run(array( 'scope_code' => $code, 'scope_type' => $type, diff --git a/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes.php b/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes.php index 17c55520bf..2e5f82c6a0 100644 --- a/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes.php +++ b/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes/Edit/Form.php b/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes/Edit/Form.php index 43ab782e8f..6de0d1e2a0 100644 --- a/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes/Edit/Form.php +++ b/app/code/community/Find/Feed/Block/Adminhtml/Edit/Codes/Edit/Form.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Block/Adminhtml/List/Codes.php b/app/code/community/Find/Feed/Block/Adminhtml/List/Codes.php index 0e14a48980..8745273213 100644 --- a/app/code/community/Find/Feed/Block/Adminhtml/List/Codes.php +++ b/app/code/community/Find/Feed/Block/Adminhtml/List/Codes.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Block/Adminhtml/List/Codes/Grid.php b/app/code/community/Find/Feed/Block/Adminhtml/List/Codes/Grid.php index 6a71ccc901..36d15417fe 100644 --- a/app/code/community/Find/Feed/Block/Adminhtml/List/Codes/Grid.php +++ b/app/code/community/Find/Feed/Block/Adminhtml/List/Codes/Grid.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Block/Adminhtml/List/Items.php b/app/code/community/Find/Feed/Block/Adminhtml/List/Items.php index 80ec99cfe1..bc4ffe395a 100644 --- a/app/code/community/Find/Feed/Block/Adminhtml/List/Items.php +++ b/app/code/community/Find/Feed/Block/Adminhtml/List/Items.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Block/Adminhtml/List/Items/Grid.php b/app/code/community/Find/Feed/Block/Adminhtml/List/Items/Grid.php index 84cdb1e067..7835014686 100644 --- a/app/code/community/Find/Feed/Block/Adminhtml/List/Items/Grid.php +++ b/app/code/community/Find/Feed/Block/Adminhtml/List/Items/Grid.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Helper/Data.php b/app/code/community/Find/Feed/Helper/Data.php index a63f1a395d..9547bb5e4c 100755 --- a/app/code/community/Find/Feed/Helper/Data.php +++ b/app/code/community/Find/Feed/Helper/Data.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Frequency.php b/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Frequency.php index 611a353ff3..b52827bd4d 100644 --- a/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Frequency.php +++ b/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Frequency.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Hours.php b/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Hours.php index 9c172f8386..fc17461a80 100644 --- a/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Hours.php +++ b/app/code/community/Find/Feed/Model/Adminhtml/System/Source/Cron/Hours.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Codes.php b/app/code/community/Find/Feed/Model/Codes.php index bf5a2c480a..2db7603ac7 100644 --- a/app/code/community/Find/Feed/Model/Codes.php +++ b/app/code/community/Find/Feed/Model/Codes.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Import.php b/app/code/community/Find/Feed/Model/Import.php index 45de9d1665..3a517f94e4 100755 --- a/app/code/community/Find/Feed/Model/Import.php +++ b/app/code/community/Find/Feed/Model/Import.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Mysql4/Codes.php b/app/code/community/Find/Feed/Model/Mysql4/Codes.php index aa381ab8e8..27d5d4934a 100644 --- a/app/code/community/Find/Feed/Model/Mysql4/Codes.php +++ b/app/code/community/Find/Feed/Model/Mysql4/Codes.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Mysql4/Codes/Collection.php b/app/code/community/Find/Feed/Model/Mysql4/Codes/Collection.php index 2c1ae1a25d..02ba7b82d8 100644 --- a/app/code/community/Find/Feed/Model/Mysql4/Codes/Collection.php +++ b/app/code/community/Find/Feed/Model/Mysql4/Codes/Collection.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Mysql4/Setup.php b/app/code/community/Find/Feed/Model/Mysql4/Setup.php index c7561edcee..eebc496a01 100755 --- a/app/code/community/Find/Feed/Model/Mysql4/Setup.php +++ b/app/code/community/Find/Feed/Model/Mysql4/Setup.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/Model/Observer.php b/app/code/community/Find/Feed/Model/Observer.php index 43c287b065..d17342c035 100644 --- a/app/code/community/Find/Feed/Model/Observer.php +++ b/app/code/community/Find/Feed/Model/Observer.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/controllers/Adminhtml/Codes/GridController.php b/app/code/community/Find/Feed/controllers/Adminhtml/Codes/GridController.php index 1bf761087a..3ffa939add 100644 --- a/app/code/community/Find/Feed/controllers/Adminhtml/Codes/GridController.php +++ b/app/code/community/Find/Feed/controllers/Adminhtml/Codes/GridController.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/controllers/Adminhtml/Items/GridController.php b/app/code/community/Find/Feed/controllers/Adminhtml/Items/GridController.php index 6bb4b7977e..ced97cdf5a 100644 --- a/app/code/community/Find/Feed/controllers/Adminhtml/Items/GridController.php +++ b/app/code/community/Find/Feed/controllers/Adminhtml/Items/GridController.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/etc/adminhtml.xml b/app/code/community/Find/Feed/etc/adminhtml.xml index 462f7f809a..2c1d0da2e4 100644 --- a/app/code/community/Find/Feed/etc/adminhtml.xml +++ b/app/code/community/Find/Feed/etc/adminhtml.xml @@ -19,8 +19,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/app/code/community/Find/Feed/etc/config.xml b/app/code/community/Find/Feed/etc/config.xml index fbbfbc7d10..3e46029213 100755 --- a/app/code/community/Find/Feed/etc/config.xml +++ b/app/code/community/Find/Feed/etc/config.xml @@ -19,8 +19,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/app/code/community/Find/Feed/etc/system.xml b/app/code/community/Find/Feed/etc/system.xml index 7b9f0aa53c..61993b7323 100755 --- a/app/code/community/Find/Feed/etc/system.xml +++ b/app/code/community/Find/Feed/etc/system.xml @@ -19,8 +19,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-install-0.0.1.php b/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-install-0.0.1.php index 860059b23f..29b8858bfd 100755 --- a/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-install-0.0.1.php +++ b/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-install-0.0.1.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-upgrade-0.0.1-0.0.2.php b/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-upgrade-0.0.1-0.0.2.php index 34726cbc26..f502e21823 100644 --- a/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-upgrade-0.0.1-0.0.2.php +++ b/app/code/community/Find/Feed/sql/find_feed_setup/mysql4-upgrade-0.0.1-0.0.2.php @@ -18,8 +18,8 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Find - * @package Find_Feed + * @category + * @package _home * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/AdminNotification/Helper/Data.php b/app/code/core/Mage/AdminNotification/Helper/Data.php index 4ca99e64ee..1da9d2eec6 100644 --- a/app/code/core/Mage/AdminNotification/Helper/Data.php +++ b/app/code/core/Mage/AdminNotification/Helper/Data.php @@ -111,6 +111,7 @@ public function getPopupObjectUrl($withExt = false) /** * Check is readable Popup Notification Object + * @deprecated after 1.4.2.0 * * @return bool */ diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Attributes.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Attributes.php index 788d718ebf..988e131aba 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Attributes.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Attributes.php @@ -140,7 +140,7 @@ protected function _prepareForm() { } } if ($element = $form->getElement('custom_use_parent_settings')) { - $element->setOnclick('onCustomUseParentChanged(this)'); + $element->setData('onchange', 'onCustomUseParentChanged(this)'); } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Configure.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Configure.php new file mode 100644 index 0000000000..4ba173b8a2 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Configure.php @@ -0,0 +1,74 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Configure extends Mage_Adminhtml_Block_Widget +{ + protected $_product; + + /** + * Set template + */ + protected function _construct() + { + $this->setTemplate('catalog/product/composite/configure.phtml'); + } + + /** + * Retrieve product object + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + if (!$this->_product) { + if (Mage::registry('current_product')) { + $this->_product = Mage::registry('current_product'); + } else { + $this->_product = Mage::getSingleton('catalog/product'); + } + } + return $this->_product; + } + + /** + * Set product object + * + * @param Mage_Catalog_Model_Product $product + * @return Mage_Adminhtml_Block_Catalog_Product_Composite_Configure + */ + public function setProduct(Mage_Catalog_Model_Product $product = null) + { + $this->_product = $product; + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Error.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Error.php new file mode 100644 index 0000000000..b48d75ed60 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Error.php @@ -0,0 +1,47 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Error extends Mage_Core_Block_Template +{ + /** + * Returns error message to show what kind of error happened during retrieving of product + * configuration controls + * + * @return string + */ + public function _toHtml() + { + $message = Mage::registry('composite_configure_result_error_message'); + return Mage::helper('core')->jsonEncode(array('error' => true, 'message' => $message)); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset.php new file mode 100644 index 0000000000..0ccff0f5b2 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset.php @@ -0,0 +1,62 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset extends Mage_Core_Block_Text_List +{ + /** + * + * Iterates through fieldsets and fetches complete html + * + * @return string + */ + protected function _toHtml() + { + $children = $this->getSortedChildren(); + $total = count($children); + $i = 0; + $this->setText(''); + foreach ($children as $name) { + $block = $this->getLayout()->getBlock($name); + if (!$block) { + Mage::throwException(Mage::helper('core')->__('Invalid block: %s', $name)); + } + + $i++; + $block->setIsLastFieldset($i == $total); + + $this->addText($block->toHtml()); + } + + return parent::_toHtml(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Configurable.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Configurable.php new file mode 100644 index 0000000000..2790626950 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Configurable.php @@ -0,0 +1,76 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Configurable extends Mage_Catalog_Block_Product_View_Type_Configurable +{ + /** + * Retrieve product + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + if (!$this->hasData('product')) { + $this->setData('product', Mage::registry('product')); + } + $product = $this->getData('product'); + if (is_null($product->getTypeInstance(true)->getStoreFilter($product))) { + $product->getTypeInstance(true)->setStoreFilter(Mage::app()->getStore($product->getStoreId()), $product); + } + + return $product; + } + + /** + * Retrieve current store + * + * @return Mage_Core_Model_Store + */ + public function getCurrentStore() + { + return Mage::app()->getStore($this->getProduct()->getStoreId()); + } + + /** + * Returns additional values for js config, con be overriden by descedants + * + * @return array + */ + protected function _getAdditionalConfig() + { + $result = parent::_getAdditionalConfig(); + $result['disablePriceReload'] = true; // There's no field for price at popup + $result['stablePrices'] = true; // We don't want to recalc prices displayed in OPTIONs of SELECT + return $result; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Customoptions.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Customoptions.php new file mode 100644 index 0000000000..5f989689ac --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Customoptions.php @@ -0,0 +1,37 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Customoptions extends Mage_Adminhtml_Block_Catalog_Product_Composite_Configure +{ + +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Grouped.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Grouped.php new file mode 100644 index 0000000000..0e9b5ad3e5 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Grouped.php @@ -0,0 +1,134 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Grouped extends Mage_Catalog_Block_Product_View_Type_Grouped +{ + /** + * Redefine default price block + * Set current customer to tax calculation + */ + protected function _construct() + { + parent::_construct(); + + $this->_block = 'adminhtml/catalog_product_price'; + $this->_useLinkForAsLowAs = false; + + $taxCalculation = Mage::getSingleton('tax/calculation'); + if (!$taxCalculation->getCustomer() && Mage::registry('current_customer')) { + $taxCalculation->setCustomer(Mage::registry('current_customer')); + } + } + + /** + * Retrieve product + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + if (!$this->hasData('product')) { + $this->setData('product', Mage::registry('product')); + } + $product = $this->getData('product'); + if (is_null($product->getTypeInstance(true)->getStoreFilter($product))) { + $product->getTypeInstance(true)->setStoreFilter(Mage::app()->getStore($product->getStoreId()), $product); + } + + return $product; + } + + /** + * Retrieve array of associated products + * + * @return array + */ + public function getAssociatedProducts() + { + $product = $this->getProduct(); + $result = $product->getTypeInstance(true) + ->getAssociatedProducts($product); + + $storeId = $product->getStoreId(); + foreach ($result as $item) { + $item->setStoreId($storeId); + } + + return $result; + } + + + /** + * Set preconfigured values to grouped associated products + * + * @return Mage_Catalog_Block_Product_View_Type_Grouped + */ + public function setPreconfiguredValue() { + $configValues = $this->getProduct()->getPreconfiguredValues()->getSuperGroup(); + if (is_array($configValues)) { + $associatedProducts = $this->getAssociatedProducts(); + foreach ($associatedProducts as $item) { + if (isset($configValues[$item->getId()])) { + $item->setQty($configValues[$item->getId()]); + } + } + } + return $this; + } + + /** + * Check whether the price can be shown for the specified product + * + * @param Mage_Catalog_Model_Product $product + * @return bool + */ + public function getCanShowProductPrice($product) + { + return true; + } + + /** + * Checks whether block is last fieldset in popup + * + * @return bool + */ + public function getIsLastFieldset() + { + $isLast = $this->getData('is_last_fieldset'); + if (!$isLast) { + $options = $this->getProduct()->getOptions(); + return !$options || !count($options); + } + return $isLast; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Options.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Options.php new file mode 100644 index 0000000000..387eba7afd --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Options.php @@ -0,0 +1,73 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Options extends Mage_Catalog_Block_Product_View_Options +{ + /** + * Constructor for our block with options + * + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->addOptionRenderer( + 'default', + 'catalog/product_view_options_type_default', + 'catalog/product/composite/fieldset/options/type/default.phtml' + ); + } + + /** + * Get option html block + * + * @param Mage_Catalog_Model_Product_Option $option + * + * @return string + */ + public function getOptionHtml(Mage_Catalog_Model_Product_Option $option) + { + $renderer = $this->getOptionRender( + $this->getGroupOfOption($option->getType()) + ); + if (is_null($renderer['renderer'])) { + $renderer['renderer'] = $this->getLayout()->createBlock($renderer['block']) + ->setTemplate($renderer['template']) + ->setSkipJsReloadPrice(1); + } + return $renderer['renderer'] + ->setProduct($this->getProduct()) + ->setOption($option) + ->toHtml(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Qty.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Qty.php new file mode 100644 index 0000000000..acd63a5ddc --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Qty.php @@ -0,0 +1,75 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Qty extends Mage_Core_Block_Template +{ + /** + * Constructor for our block with options + * + * @return void + */ + public function _construct() + { + parent::_construct(); + $this->setIsLastFieldset(true); + } + + /** + * Retrieve product + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + if (!$this->hasData('product')) { + $this->setData('product', Mage::registry('product')); + } + $product = $this->getData('product'); + + return $product; + } + + /** + * Return selected qty + * + * @return int + */ + public function getQtyValue() + { + $qty = $this->getProduct()->getPreconfiguredValues()->getQty(); + if (!$qty) { + $qty = 1; + } + return $qty; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Simple.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Simple.php new file mode 100644 index 0000000000..280f6764b6 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Simple.php @@ -0,0 +1,37 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Simple extends Mage_Adminhtml_Block_Catalog_Product_Composite_Configure +{ + +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Virtual.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Virtual.php new file mode 100644 index 0000000000..f457d69271 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Fieldset/Virtual.php @@ -0,0 +1,37 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Virtual extends Mage_Adminhtml_Block_Catalog_Product_Composite_Configure +{ + +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Update/Result.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Update/Result.php new file mode 100644 index 0000000000..c76abb1dd2 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Composite/Update/Result.php @@ -0,0 +1,50 @@ + + */ +class Mage_Adminhtml_Block_Catalog_Product_Composite_Update_Result extends Mage_Core_Block_Template +{ + /** + * Forms script response + * + * @return string + */ + public function _toHtml() + { + $updateResult = Mage::registry('composite_update_result'); + $resultJson = Mage::helper('core')->jsonEncode($updateResult); + $jsVarname = $updateResult->getJsVarName(); + return Mage::helper('adminhtml/js')->getScript(sprintf('var %s = %s', $jsVarname, $resultJson)); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Action/Attribute.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Action/Attribute.php index ecc9f19484..e35fdc9f66 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Action/Attribute.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Action/Attribute.php @@ -65,7 +65,7 @@ protected function _prepareLayout() } /** - * Retrive selected products for update + * Retrieve selected products for update * * @return unknown */ @@ -75,7 +75,7 @@ public function getProducts() } /** - * Retrive block attributes update helper + * Retrieve block attributes update helper * * @return Mage_Adminhtml_Helper_Catalog_Product_Edit_Action_Attribute */ @@ -83,25 +83,54 @@ protected function _getHelper() { return $this->helper('adminhtml/catalog_product_edit_action_attribute'); } - + + /** + * Retrieve back button html code + * + * @return string + */ public function getBackButtonHtml() { return $this->getChildHtml('back_button'); } - public function getCancelButtonHtml() + /** + * Retrieve cancel button html code + * + * @return string + */ + public function getCancelButtonHtml() { return $this->getChildHtml('reset_button'); } + /** + * Retrieve save button html code + * + * @return string + */ public function getSaveButtonHtml() { return $this->getChildHtml('save_button'); } + /** + * Get save url + * + * @return string + */ public function getSaveUrl() { return $this->getUrl('*/*/save', array('store'=>Mage::helper('adminhtml/catalog_product_edit_action_attribute')->getSelectedStoreId())); } - + + /** + * Get validation url + * + * @return string + */ + public function getValidationUrl() + { + return $this->getUrl('*/*/validate', array('_current'=>true)); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Categories.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Categories.php index 6e1445752c..cd00fb72ea 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Categories.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Categories.php @@ -212,8 +212,14 @@ public function getLoadTreeUrl($expanded=null) public function getSelectedCategoriesPathIds($rootId = false) { $ids = array(); - $collection = Mage::getModel('catalog/category')->getCollection() - ->addFieldToFilter('entity_id', array('in'=>$this->getCategoryIds())); + $collection = Mage::getModel('catalog/category')->getCollection(); + + if ($rootId) { + $collection->addFieldToFilter('parent_id', $rootId); + } else { + $collection->addFieldToFilter('entity_id', array('in'=>$this->getCategoryIds())); + } + foreach ($collection as $item) { if ($rootId && !in_array($rootId, $item->getPathIds())) { continue; diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php index bc9226957e..2dd161144d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Crosssell.php @@ -46,6 +46,9 @@ public function __construct() if ($this->_getProduct()->getId()) { $this->setDefaultFilter(array('in_products'=>1)); } + if ($this->isReadonly()) { + $this->setFilterVisibility(false); + } } /** diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php index 000fc79ac5..f567bea99a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Related.php @@ -46,6 +46,9 @@ public function __construct() if ($this->_getProduct()->getId()) { $this->setDefaultFilter(array('in_products' => 1)); } + if ($this->isReadonly()) { + $this->setFilterVisibility(false); + } } /** diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php index f4e75c23dc..b7b5361580 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Upsell.php @@ -47,6 +47,9 @@ public function __construct() if ($this->_getProduct()->getId()) { $this->setDefaultFilter(array('in_products'=>1)); } + if ($this->isReadonly()) { + $this->setFilterVisibility(false); + } } /** diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tabs.php index 8806f1dfe4..c0f6a35421 100644 --- a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tabs.php @@ -72,16 +72,16 @@ protected function _prepareLayout() $this->addTab('group_'.$group->getId(), array( 'label' => Mage::helper('catalog')->__($group->getAttributeGroupName()), - 'content' => $this->getLayout()->createBlock($this->getAttributeTabBlock()) - ->setGroup($group) - ->setGroupAttributes($attributes) - ->toHtml(), + 'content' => $this->_translateHtml($this->getLayout()->createBlock($this->getAttributeTabBlock()) + ->setGroup($group) + ->setGroupAttributes($attributes) + ->toHtml()), )); } $this->addTab('inventory', array( 'label' => Mage::helper('catalog')->__('Inventory'), - 'content' => $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_inventory')->toHtml(), + 'content' => $this->_translateHtml($this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_inventory')->toHtml()), )); @@ -91,7 +91,7 @@ protected function _prepareLayout() if (!Mage::app()->isSingleStoreMode()) { $this->addTab('websites', array( 'label' => Mage::helper('catalog')->__('Websites'), - 'content' => $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_websites')->toHtml(), + 'content' => $this->_translateHtml($this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_websites')->toHtml()), )); } @@ -130,7 +130,7 @@ protected function _prepareLayout() if ($alertPriceAllow || $alertStockAllow) { $this->addTab('productalert', array( 'label' => Mage::helper('catalog')->__('Product Alerts'), - 'content' => $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_alerts', 'admin.alerts.products')->toHtml() + 'content' => $this->_translateHtml($this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_alerts', 'admin.alerts.products')->toHtml()) )); } @@ -175,7 +175,7 @@ protected function _prepareLayout() else { $this->addTab('set', array( 'label' => Mage::helper('catalog')->__('Settings'), - 'content' => $this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_settings')->toHtml(), + 'content' => $this->_translateHtml($this->getLayout()->createBlock('adminhtml/catalog_product_edit_tab_settings')->toHtml()), 'active' => true )); } @@ -213,4 +213,16 @@ public function setAttributeTabBlock($attributeTabBlock) $this->_attributeTabBlock = $attributeTabBlock; return $this; } + + /** + * Translate html content + * + * @param string $html + * @return string + */ + protected function _translateHtml($html) + { + Mage::getSingleton('core/translate_inline')->processResponseBody($html); + return $html; + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Price.php b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Price.php new file mode 100644 index 0000000000..0edc93057a --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Catalog/Product/Price.php @@ -0,0 +1,37 @@ +_updateButton('save', 'label', Mage::helper('cms')->__('Save Page')); $this->_addButton('saveandcontinue', array( 'label' => Mage::helper('adminhtml')->__('Save and Continue Edit'), - 'onclick' => 'saveAndContinueEdit()', + 'onclick' => 'saveAndContinueEdit(\''.$this->_getSaveAndContinueUrl().'\')', 'class' => 'save', ), -100); } else { @@ -58,19 +58,6 @@ public function __construct() $this->_removeButton('delete'); } - $this->_formScripts[] = " - function toggleEditor() { - if (tinyMCE.getInstanceById('page_content') == null) { - tinyMCE.execCommand('mceAddControl', false, 'page_content'); - } else { - tinyMCE.execCommand('mceRemoveControl', false, 'page_content'); - } - } - - function saveAndContinueEdit(){ - editForm.submit($('edit_form').action+'back/edit/'); - } - "; } /** @@ -98,4 +85,58 @@ protected function _isAllowedAction($action) { return Mage::getSingleton('admin/session')->isAllowed('cms/page/' . $action); } + + /** + * Getter of url for "Save and Continue" button + * tab_id will be replaced by desired by JS later + * + * @return string + */ + protected function _getSaveAndContinueUrl() + { + return $this->getUrl('*/*/save', array( + '_current' => true, + 'back' => 'edit', + 'active_tab' => '{{tab_id}}' + )); + } + + /** + * @see Mage_Adminhtml_Block_Widget_Container::_prepareLayout() + */ + protected function _prepareLayout() + { + $tabsBlock = $this->getLayout()->getBlock('cms_page_edit_tabs'); + if ($tabsBlock) { + $tabsBlockJsObject = $tabsBlock->getJsObjectName(); + $tabsBlockPrefix = $tabsBlock->getId() . '_'; + } else { + $tabsBlockJsObject = 'page_tabsJsTabs'; + $tabsBlockPrefix = 'page_tabs_'; + } + + $this->_formScripts[] = " + function toggleEditor() { + if (tinyMCE.getInstanceById('page_content') == null) { + tinyMCE.execCommand('mceAddControl', false, 'page_content'); + } else { + tinyMCE.execCommand('mceRemoveControl', false, 'page_content'); + } + } + + function saveAndContinueEdit(urlTemplate) { + var tabsIdValue = " . $tabsBlockJsObject . ".activeTab.id; + var tabsBlockPrefix = '" . $tabsBlockPrefix . "'; + if (tabsIdValue.startsWith(tabsBlockPrefix)) { + tabsIdValue = tabsIdValue.substr(tabsBlockPrefix.length) + } + var template = new Template(urlTemplate, /(^|.|\\r|\\n)({{(\w+)}})/); + var url = template.evaluate({tab_id:tabsIdValue}); + editForm.submit(url); + } + "; + return parent::_prepareLayout(); + } + + } diff --git a/app/code/core/Mage/Adminhtml/Block/Customer/Edit.php b/app/code/core/Mage/Adminhtml/Block/Customer/Edit.php index bf2a4e9676..a02c5816ba 100644 --- a/app/code/core/Mage/Adminhtml/Block/Customer/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Customer/Edit.php @@ -82,6 +82,18 @@ public function getHeaderText() } } + /** + * Prepare form html. Add block for configurable product modification interface + * + * @return string + */ + public function getFormHtml() + { + $html = parent::getFormHtml(); + $html .= $this->getLayout()->createBlock('adminhtml/catalog_product_composite_configure')->toHtml(); + return $html; + } + public function getValidationUrl() { return $this->getUrl('*/*/validate', array('_current'=>true)); @@ -93,7 +105,7 @@ protected function _prepareLayout() $this->_addButton('save_and_continue', array( 'label' => Mage::helper('customer')->__('Save and Continue Edit'), 'onclick' => 'saveAndContinueEdit(\''.$this->_getSaveAndContinueUrl().'\')', - 'class' => 'save' + 'class' => 'save' ), 10); } diff --git a/app/code/core/Mage/Adminhtml/Block/Customer/Edit/Tab/Account.php b/app/code/core/Mage/Adminhtml/Block/Customer/Edit/Tab/Account.php index 24c4f243ee..74a3bcfad9 100644 --- a/app/code/core/Mage/Adminhtml/Block/Customer/Edit/Tab/Account.php +++ b/app/code/core/Mage/Adminhtml/Block/Customer/Edit/Tab/Account.php @@ -114,6 +114,7 @@ public function initForm() 'name' => 'sendemail', 'label' => Mage::helper('customer')->__('Send Welcome Email after Confirmation') )); + $customer->setData('sendemail', '1'); } } } @@ -138,6 +139,7 @@ public function initForm() 'name' => 'sendemail', 'id' => 'sendemail', )); + $customer->setData('sendemail', '1'); if (!Mage::app()->isSingleStoreMode()) { $fieldset->addField('sendemail_store_id', 'select', array( 'label' => $this->helper('customer')->__('Send From'), @@ -148,16 +150,19 @@ public function initForm() } // make sendemail and sendmail_store_id disabled, if website_id has empty value - $sendEmail = $form->getElement('sendemail_store_id'); + $isSingleMode = Mage::app()->isSingleStoreMode(); + $sendEmailId = $isSingleMode ? 'sendemail' : 'sendemail_store_id'; + $sendEmail = $form->getElement($sendEmailId); + + $prefix = $form->getHtmlIdPrefix(); if ($sendEmail) { - $prefix = $form->getHtmlIdPrefix(); $sendEmail->setAfterElementHtml( ''; + } +} diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php new file mode 100644 index 0000000000..8c686ce5a5 --- /dev/null +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php @@ -0,0 +1,59 @@ + + */ +class Mage_Bundle_Block_Adminhtml_Catalog_Product_Composite_Fieldset_Options_Type_Multi + extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Multi +{ + /** + * Set template + * + * @return void + */ + protected function _construct() + { + $this->setTemplate('bundle/product/composite/fieldset/options/type/multi.phtml'); + } + + /** + * @param string $elementId + * @param string $containerId + * @return string + */ + public function setValidationContainer($elementId, $containerId) + { + return ''; + } +} diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Radio.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Radio.php new file mode 100644 index 0000000000..ab4707f31e --- /dev/null +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Radio.php @@ -0,0 +1,59 @@ + + */ +class Mage_Bundle_Block_Adminhtml_Catalog_Product_Composite_Fieldset_Options_Type_Radio + extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Radio +{ + /** + * Set template + * + * @return void + */ + protected function _construct() + { + $this->setTemplate('bundle/product/composite/fieldset/options/type/radio.phtml'); + } + + /** + * @param string $elementId + * @param string $containerId + * @return string + */ + public function setValidationContainer($elementId, $containerId) + { + return ''; + } +} diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Select.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Select.php new file mode 100644 index 0000000000..9472d6f5b3 --- /dev/null +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Select.php @@ -0,0 +1,59 @@ + + */ +class Mage_Bundle_Block_Adminhtml_Catalog_Product_Composite_Fieldset_Options_Type_Select + extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Select +{ + /** + * Set template + * + * @return void + */ + protected function _construct() + { + $this->setTemplate('bundle/product/composite/fieldset/options/type/select.phtml'); + } + + /** + * @param string $elementId + * @param string $containerId + * @return string + */ + public function setValidationContainer($elementId, $containerId) + { + return ''; + } +} diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php index ca0a54345e..0bdbe33e1e 100644 --- a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php @@ -43,7 +43,7 @@ public function __construct() public function getTabUrl() { - return $this->getUrl('bundle/product_edit/form', array('_current' => true)); + return $this->getUrl('*/bundle_product_edit/form', array('_current' => true)); } public function getTabClass() diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Search/Grid.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Search/Grid.php index 63a3bd555e..debb0f4ed0 100644 --- a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Search/Grid.php +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Search/Grid.php @@ -152,7 +152,7 @@ protected function _prepareColumns() public function getGridUrl() { - return $this->getUrl('bundle/selection/grid', array('index' => $this->getIndex(), 'productss' => implode(',', $this->_getProducts()))); + return $this->getUrl('*/bundle_selection/grid', array('index' => $this->getIndex(), 'productss' => implode(',', $this->_getProducts()))); } protected function _getSelectedProducts() diff --git a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Selection.php b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Selection.php index 01fa16624f..2a70755980 100644 --- a/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Selection.php +++ b/app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle/Option/Selection.php @@ -132,7 +132,7 @@ public function getQtyTypeSelectHtml() */ public function getSelectionSearchUrl() { - return $this->getUrl('bundle/selection/search'); + return $this->getUrl('*/bundle_selection/search'); } /** diff --git a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle.php b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle.php index 0782f3ab2b..8ad914b6c2 100644 --- a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle.php +++ b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle.php @@ -35,22 +35,25 @@ class Mage_Bundle_Block_Catalog_Product_View_Type_Bundle extends Mage_Catalog_Block_Product_View_Abstract { protected $_optionRenderers = array(); - protected $_options = null; + protected $_options = null; public function getOptions() { if (!$this->_options) { - $this->getProduct()->getTypeInstance(true)->setStoreFilter($this->getProduct()->getStoreId(), $this->getProduct()); + $product = $this->getProduct(); + $typeInstance = $product->getTypeInstance(true); + $typeInstance->setStoreFilter($product->getStoreId(), $product); - $optionCollection = $this->getProduct()->getTypeInstance(true)->getOptionsCollection($this->getProduct()); + $optionCollection = $typeInstance->getOptionsCollection($product); - $selectionCollection = $this->getProduct()->getTypeInstance(true)->getSelectionsCollection( - $this->getProduct()->getTypeInstance(true)->getOptionsIds($this->getProduct()), - $this->getProduct() + $selectionCollection = $typeInstance->getSelectionsCollection( + $typeInstance->getOptionsIds($product), + $product ); $this->_options = $optionCollection->appendSelections($selectionCollection, false, false); } + return $this->_options; } @@ -66,64 +69,95 @@ public function hasOptions() public function getJsonConfig() { Mage::app()->getLocale()->getJsPriceFormat(); - $store = Mage::app()->getStore(); $optionsArray = $this->getOptions(); - $options = array(); - $selected = array(); + $options = array(); + $selected = array(); + $currentProduct = $this->getProduct(); + $coreHelper = Mage::helper('core'); + + if ($preconfiguredFlag = $currentProduct->hasPreconfiguredValues()) { + $preconfiguredValues = $currentProduct->getPreconfiguredValues(); + $defaultValues = array(); + } foreach ($optionsArray as $_option) { if (!$_option->getSelections()) { continue; } + + $optionId = $_option->getId(); $option = array ( 'selections' => array(), - 'title' => $_option->getTitle(), - 'isMulti' => ($_option->getType() == 'multi' || $_option->getType() == 'checkbox') + 'title' => $_option->getTitle(), + 'isMulti' => in_array($_option->getType(), array('multi', 'checkbox')) ); $selectionCount = count($_option->getSelections()); foreach ($_option->getSelections() as $_selection) { - $_qty = !($_selection->getSelectionQty()*1)?'1':$_selection->getSelectionQty()*1; + $selectionId = $_selection->getSelectionId(); + $_qty = !($_selection->getSelectionQty()*1) ? '1' : $_selection->getSelectionQty()*1; + // recalculate currency + $tierPrices = $_selection->getTierPrice(); + foreach ($tierPrices as &$tierPriceInfo) { + $tierPriceInfo['price'] = $coreHelper->currency($tierPriceInfo['price'], false, false); + } + unset($tierPriceInfo); // break the reference with the last element + $selection = array ( - 'qty' => $_qty, + 'qty' => $_qty, 'customQty' => $_selection->getSelectionCanChangeQty(), - 'price' => Mage::helper('core')->currency($_selection->getFinalPrice(), false, false), - 'priceValue' => Mage::helper('core')->currency($_selection->getSelectionPriceValue(), false, false), + 'price' => $coreHelper->currency($_selection->getFinalPrice(), false, false), + 'priceValue' => $coreHelper->currency($_selection->getSelectionPriceValue(), false, false), 'priceType' => $_selection->getSelectionPriceType(), - 'tierPrice' => $_selection->getTierPrice(), - 'name' => $_selection->getName(), + 'tierPrice' => $tierPrices, + 'name' => $_selection->getName(), 'plusDisposition' => 0, - 'minusDisposition' => 0, + 'minusDisposition' => 0 ); + $responseObject = new Varien_Object(); - $args = array('response_object'=>$responseObject, 'selection'=>$_selection); + $args = array('response_object' => $responseObject, 'selection' => $_selection); Mage::dispatchEvent('bundle_product_view_config', $args); if (is_array($responseObject->getAdditionalOptions())) { foreach ($responseObject->getAdditionalOptions() as $o=>$v) { $selection[$o] = $v; } } - $option['selections'][$_selection->getSelectionId()] = $selection; + $option['selections'][$selectionId] = $selection; - if (($_selection->getIsDefault() || ($selectionCount == 1 && $_option->getRequired())) && $_selection->isSalable()) { - $selected[$_option->getId()][] = $_selection->getSelectionId(); + if (($_selection->getIsDefault() || ($selectionCount == 1 && $_option->getRequired())) + && $_selection->isSalable() + ) { + $selected[$optionId][] = $selectionId; + } + } + $options[$optionId] = $option; + + // Add attribute default value (if set) + if ($preconfiguredFlag) { + $configValue = $preconfiguredValues->getData('bundle_option/' . $optionId); + if ($configValue) { + $defaultValues[$optionId] = $configValue; } } - $options[$_option->getId()] = $option; } $config = array( - 'options' => $options, - 'selected' => $selected, - 'bundleId' => $this->getProduct()->getId(), - 'priceFormat' => Mage::app()->getLocale()->getJsPriceFormat(), - 'basePrice' => Mage::helper('core')->currency($this->getProduct()->getPrice(), false, false), - 'priceType' => $this->getProduct()->getPriceType(), - 'specialPrice' => $this->getProduct()->getSpecialPrice() + 'options' => $options, + 'selected' => $selected, + 'bundleId' => $currentProduct->getId(), + 'priceFormat' => Mage::app()->getLocale()->getJsPriceFormat(), + 'basePrice' => $coreHelper->currency($currentProduct->getPrice(), false, false), + 'priceType' => $currentProduct->getPriceType(), + 'specialPrice' => $currentProduct->getSpecialPrice() ); - return Mage::helper('core')->jsonEncode($config); + if ($preconfiguredFlag && !empty($defaultValues)) { + $config['defaultValues'] = $defaultValues; + } + + return $coreHelper->jsonEncode($config); } public function addRenderer($type, $block) diff --git a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php index 03ade6607e..8e2e85f49b 100644 --- a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php +++ b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php @@ -34,6 +34,128 @@ */ class Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option extends Mage_Bundle_Block_Catalog_Product_Price { + /** + * Store preconfigured options + * + * @var int|array|string + */ + protected $_selectedOptions = null; + + /** + * Show if option has a single selection + * + * @var bool + */ + protected $_showSingle = null; + + /** + * Check if option has a single selection + * + * @return bool + */ + protected function _showSingle() + { + if (is_null($this->_showSingle)) { + $_option = $this->getOption(); + $_selections = $_option->getSelections(); + + $this->_showSingle = (count($_selections) == 1 && $_option->getRequired()); + } + + return $this->_showSingle; + } + + /** + * Retrieve default values for template + * + * @return array + */ + protected function _getDefaultValues() + { + $_option = $this->getOption(); + $_default = $_option->getDefaultSelection(); + $_selections = $_option->getSelections(); + $selectedOptions = $this->_getSelectedOptions(); + + if ($_default && empty($selectedOptions)) { + $_defaultQty = $_default->getSelectionQty()*1; + $_canChangeQty = $_default->getSelectionCanChangeQty(); + } elseif (!$this->_showSingle() || $this->getProduct()->hasPreconfiguredValues()) { + $_defaultQty = $this->_getSelectedQty(); + $_canChangeQty = (bool)$_defaultQty; + } else { + $_defaultQty = $_selections[0]->getSelectionQty()*1; + $_canChangeQty = $_selections[0]->getSelectionCanChangeQty(); + } + + return array($_defaultQty, $_canChangeQty); + } + + /** + * Collect selected options + * + * @return void + */ + protected function _getSelectedOptions() + { + if (is_null($this->_selectedOptions)) { + $this->_selectedOptions = array(); + $option = $this->getOption(); + + if ($this->getProduct()->hasPreconfiguredValues()) { + $configValue = $this->getProduct()->getPreconfiguredValues() + ->getData('bundle_option/' . $option->getId()); + if ($configValue) { + $this->_selectedOptions = $configValue; + } elseif (!$option->getRequired()) { + $this->_selectedOptions = 'None'; + } + } + } + + return $this->_selectedOptions; + } + + /** + * Define if selection is selected + * + * @param Mage_Catalog_Model_Product $selection + * @return bool + */ + protected function _isSelected($selection) + { + $selectedOptions = $this->_getSelectedOptions(); + if (is_numeric($selectedOptions)) { + return ($selection->getSelectionId() == $this->_getSelectedOptions()); + } elseif (is_array($selectedOptions) && !empty($selectedOptions)) { + return in_array($selection->getSelectionId(), $this->_getSelectedOptions()); + } elseif ($selectedOptions == 'None') { + return false; + } else { + return ($selection->getIsDefault() && $selection->isSaleable()); + } + } + + /** + * Retrieve selected option qty + * + * @return int + */ + protected function _getSelectedQty() + { + if ($this->getProduct()->hasPreconfiguredValues()) { + $selectedQty = (int)$this->getProduct()->getPreconfiguredValues() + ->getData('bundle_option_qty/' . $this->getOption()->getId()); + if ($selectedQty < 0) { + $selectedQty = 0; + } + } else { + $selectedQty = 0; + } + + return $selectedQty; + } + public function getProduct() { if (!$this->hasData('product')) { @@ -46,17 +168,24 @@ public function getSelectionQtyTitlePrice($_selection, $includeContainer = true) { $price = $this->getProduct()->getPriceModel()->getSelectionPreFinalPrice($this->getProduct(), $_selection); return $_selection->getSelectionQty()*1 . ' x ' . $_selection->getName() . '   ' . - ($includeContainer ? '':'') . '+' . - $this->formatPriceString($price, $includeContainer) . ($includeContainer ? '':''); + ($includeContainer ? '' : '') . '+' . + $this->formatPriceString($price, $includeContainer) . ($includeContainer ? '' : ''); } public function getSelectionTitlePrice($_selection, $includeContainer = true) { $price = $this->getProduct()->getPriceModel()->getSelectionPreFinalPrice($this->getProduct(), $_selection, 1); - return $_selection->getName() . '   ' . ($includeContainer ? '':'') . '+' . - $this->formatPriceString($price, $includeContainer) . ($includeContainer ? '':''); + return $_selection->getName() . '   ' . ($includeContainer ? '' : '') . '+' . + $this->formatPriceString($price, $includeContainer) . ($includeContainer ? '' : ''); } + /** + * Set JS validation container for element + * + * @param int $elementId + * @param int $containerId + * @return string + */ public function setValidationContainer($elementId, $containerId) { return ''; } + /** + * Format price string + * + * @param float $price + * @param bool $includeContainer + * @return string + */ public function formatPriceString($price, $includeContainer = true) { - $priceTax = Mage::helper('tax')->getPrice($this->getProduct(), $price); - $priceIncTax = Mage::helper('tax')->getPrice($this->getProduct(), $price, true); + $taxHelper = Mage::helper('tax'); + $coreHelper = $this->helper('core'); + $product = $this->getProduct(); - if (Mage::helper('tax')->displayBothPrices() && $priceTax != $priceIncTax) { - $formated = Mage::helper('core')->currency($priceTax, true, $includeContainer); - $formated .= ' (+'.Mage::helper('core')->currency($priceIncTax, true, $includeContainer).' '.Mage::helper('tax')->__('Incl. Tax').')'; - } else { - $formated = $this->helper('core')->currency($priceTax, true, $includeContainer); + $priceTax = $taxHelper->getPrice($product, $price); + $priceIncTax = $taxHelper->getPrice($product, $price, true); + + $formated = $coreHelper->currencyByStore($priceTax, $product->getStore(), true, $includeContainer); + if ($taxHelper->displayBothPrices() && $priceTax != $priceIncTax) { + $formated .= + ' (+' . + $coreHelper->currencyByStore($priceIncTax, $product->getStore(), true, $includeContainer) . + ' ' . $this->__('Incl. Tax') . ')'; } return $formated; diff --git a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Checkbox.php b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Checkbox.php index 9efd724f09..6362a6a3ae 100644 --- a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Checkbox.php +++ b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Checkbox.php @@ -35,7 +35,12 @@ class Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Checkbox extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option { - public function _construct() + /** + * Set template + * + * @return void + */ + protected function _construct() { $this->setTemplate('bundle/catalog/product/view/type/bundle/option/checkbox.phtml'); } diff --git a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Multi.php b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Multi.php index fa87910640..b833e0bd01 100644 --- a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Multi.php +++ b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Multi.php @@ -35,7 +35,12 @@ class Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Multi extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option { - public function _construct() + /** + * Set template + * + * @return void + */ + protected function _construct() { $this->setTemplate('bundle/catalog/product/view/type/bundle/option/multi.phtml'); } diff --git a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Radio.php b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Radio.php index da729300ce..7a1b5b34b0 100644 --- a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Radio.php +++ b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Radio.php @@ -35,7 +35,12 @@ class Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Radio extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option { - public function _construct() + /** + * Set template + * + * @return void + */ + protected function _construct() { $this->setTemplate('bundle/catalog/product/view/type/bundle/option/radio.phtml'); } diff --git a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Select.php b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Select.php index f9e844185c..bcbd891541 100644 --- a/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Select.php +++ b/app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option/Select.php @@ -35,7 +35,12 @@ class Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Select extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option { - public function _construct() + /** + * Set template + * + * @return void + */ + protected function _construct() { $this->setTemplate('bundle/catalog/product/view/type/bundle/option/select.phtml'); } diff --git a/app/code/core/Mage/Bundle/Block/Checkout/Cart/Item/Renderer.php b/app/code/core/Mage/Bundle/Block/Checkout/Cart/Item/Renderer.php index b491e37ca1..bee28046b6 100644 --- a/app/code/core/Mage/Bundle/Block/Checkout/Cart/Item/Renderer.php +++ b/app/code/core/Mage/Bundle/Block/Checkout/Cart/Item/Renderer.php @@ -43,50 +43,7 @@ class Mage_Bundle_Block_Checkout_Cart_Item_Renderer extends Mage_Checkout_Block_ */ protected function _getBundleOptions($useCache = true) { - $options = array(); - - /** - * @var Mage_Bundle_Model_Product_Type - */ - $typeInstance = $this->getProduct()->getTypeInstance(true); - - // get bundle options - $optionsQuoteItemOption = $this->getItem()->getOptionByCode('bundle_option_ids'); - $bundleOptionsIds = unserialize($optionsQuoteItemOption->getValue()); - if ($bundleOptionsIds) { - /** - * @var Mage_Bundle_Model_Mysql4_Option_Collection - */ - $optionsCollection = $typeInstance->getOptionsByIds($bundleOptionsIds, $this->getProduct()); - - // get and add bundle selections collection - $selectionsQuoteItemOption = $this->getItem()->getOptionByCode('bundle_selection_ids'); - - $selectionsCollection = $typeInstance->getSelectionsByIds( - unserialize($selectionsQuoteItemOption->getValue()), - $this->getProduct() - ); - - $bundleOptions = $optionsCollection->appendSelections($selectionsCollection, true); - foreach ($bundleOptions as $bundleOption) { - if ($bundleOption->getSelections()) { - $option = array( - 'label' => $bundleOption->getTitle(), - 'value' => array() - ); - - $bundleSelections = $bundleOption->getSelections(); - - foreach ($bundleSelections as $bundleSelection) { - $option['value'][] = $this->_getSelectionQty($bundleSelection->getSelectionId()).' x '. $this->htmlEscape($bundleSelection->getName()). ' ' .Mage::helper('core')->currency($this->_getSelectionFinalPrice($bundleSelection)); - } - - $options[] = $option; - } - } - } - - return $options; + return Mage::helper('bundle/catalog_product_configuration')->getBundleOptions($this->getItem()); } /** @@ -97,12 +54,7 @@ protected function _getBundleOptions($useCache = true) */ protected function _getSelectionFinalPrice($selectionProduct) { - $bundleProduct = $this->getProduct(); - return $bundleProduct->getPriceModel()->getSelectionFinalPrice( - $bundleProduct, $selectionProduct, - $this->getQty(), - $this->_getSelectionQty($selectionProduct->getSelectionId()) - ); + return Mage::helper('bundle/catalog_product_configuration')->getSelectionFinalPrice($this->getItem(), $selectionProduct); } /** @@ -113,10 +65,7 @@ protected function _getSelectionFinalPrice($selectionProduct) */ protected function _getSelectionQty($selectionId) { - if ($selectionQty = $this->getProduct()->getCustomOption('selection_qty_' . $selectionId)) { - return $selectionQty->getValue(); - } - return 0; + return Mage::helper('bundle/catalog_product_configuration')->getSelectionQty($this->getProduct(), $selectionId); } /** @@ -127,14 +76,7 @@ protected function _getSelectionQty($selectionId) */ public function getOptionList() { - $item = $this->getItem(); - $optionList = $item->getBlockOptionList(); - if ($optionList === null) { - $optionList = array_merge($this->_getBundleOptions(), parent::getOptionList()); - $item->setBlockOptionList($optionList); - } - - return $optionList; + return Mage::helper('bundle/catalog_product_configuration')->getOptions($this->getItem()); } /** diff --git a/app/code/core/Mage/Bundle/Helper/Catalog/Product/Configuration.php b/app/code/core/Mage/Bundle/Helper/Catalog/Product/Configuration.php new file mode 100644 index 0000000000..6f5abc3ae2 --- /dev/null +++ b/app/code/core/Mage/Bundle/Helper/Catalog/Product/Configuration.php @@ -0,0 +1,145 @@ + + */ +class Mage_Bundle_Helper_Catalog_Product_Configuration extends Mage_Core_Helper_Abstract + implements Mage_Catalog_Helper_Product_Configuration_Interface +{ + /** + * Get selection quantity + * + * @param Mage_Catalog_Model_Product $product + * @param int $selectionId + * @return decimal + */ + public function getSelectionQty($product, $selectionId) + { + $selectionQty = $product->getCustomOption('selection_qty_' . $selectionId); + if ($selectionQty) { + return $selectionQty->getValue(); + } + return 0; + } + + /** + * Obtain final price of selection in a bundle product + * + * @param Mage_Catalog_Model_Product_Configuration_Item_Interface $item + * @param Mage_Catalog_Model_Product $selectionProduct + * @return decimal + */ + public function getSelectionFinalPrice(Mage_Catalog_Model_Product_Configuration_Item_Interface $item, $selectionProduct) + { + return $item->getProduct()->getPriceModel()->getSelectionFinalPrice( + $item->getProduct(), $selectionProduct, + $item->getQty() * 1, + $this->getSelectionQty($item->getProduct(), $selectionProduct->getSelectionId()) + ); + } + + /** + * Get bundled selections (slections-products collection) + * + * Returns array of options objects. + * Each option object will contain array of selections objects + * + * @return array + */ + public function getBundleOptions(Mage_Catalog_Model_Product_Configuration_Item_Interface $item) + { + $options = array(); + $product = $item->getProduct(); + + /** + * @var Mage_Bundle_Model_Product_Type + */ + $typeInstance = $product->getTypeInstance(true); + + // get bundle options + $optionsQuoteItemOption = $item->getOptionByCode('bundle_option_ids'); + $bundleOptionsIds = unserialize($optionsQuoteItemOption->getValue()); + if ($bundleOptionsIds) { + /** + * @var Mage_Bundle_Model_Mysql4_Option_Collection + */ + $optionsCollection = $typeInstance->getOptionsByIds($bundleOptionsIds, $product); + + // get and add bundle selections collection + $selectionsQuoteItemOption = $item->getOptionByCode('bundle_selection_ids'); + + $selectionsCollection = $typeInstance->getSelectionsByIds( + unserialize($selectionsQuoteItemOption->getValue()), + $product + ); + + $bundleOptions = $optionsCollection->appendSelections($selectionsCollection, true); + foreach ($bundleOptions as $bundleOption) { + if ($bundleOption->getSelections()) { + $option = array( + 'label' => $bundleOption->getTitle(), + 'value' => array() + ); + + $bundleSelections = $bundleOption->getSelections(); + + foreach ($bundleSelections as $bundleSelection) { + $qty = $this->getSelectionQty($product, $bundleSelection->getSelectionId()) * 1; + if ($qty) { + $option['value'][] = $qty . ' x ' . $this->escapeHtml($bundleSelection->getName()) + . ' ' . Mage::helper('core')->currency($this->getSelectionFinalPrice($item, $bundleSelection)); + } + } + + if ($option['value']) { + $options[] = $option; + } + } + } + } + + return $options; + } + + /** + * Retrieves product options list + * + * @param Mage_Catalog_Model_Product_Configuration_Item_Interface $item + * @return array + */ + public function getOptions(Mage_Catalog_Model_Product_Configuration_Item_Interface $item) + { + return array_merge( + $this->getBundleOptions($item), + Mage::helper('catalog/product_configuration')->getCustomOptions($item) + ); + } +} diff --git a/app/code/core/Mage/Bundle/Model/Mysql4/Option/Collection.php b/app/code/core/Mage/Bundle/Model/Mysql4/Option/Collection.php index 0496b002d2..eed2d0aec3 100644 --- a/app/code/core/Mage/Bundle/Model/Mysql4/Option/Collection.php +++ b/app/code/core/Mage/Bundle/Model/Mysql4/Option/Collection.php @@ -79,7 +79,7 @@ public function setPositionOrder() /** * Append selection to options * stripBefore - indicates to reload - * appendAll - indecates do we need to filter by saleable and required custom options + * appendAll - indicates do we need to filter by saleable and required custom options * * @param Mage_Bundle_Model_Mysql4_Selection_Collection $selectionsCollection * @param bool $stripBefore @@ -93,9 +93,9 @@ public function appendSelections($selectionsCollection, $stripBefore = false, $a } if (!$this->_selectionsAppended) { - foreach ($selectionsCollection->getItems() as $key=>$_selection) { + foreach ($selectionsCollection->getItems() as $key => $_selection) { if ($_option = $this->getItemById($_selection->getOptionId())) { - if ((!$appendAll && $_selection->isSalable() && !$_selection->getRequiredOptions()) || $appendAll) { + if ($appendAll || ($_selection->isSalable() && !$_selection->getRequiredOptions())) { $_selection->setOption($_option); $_option->addSelection($_selection); } else { @@ -105,6 +105,7 @@ public function appendSelections($selectionsCollection, $stripBefore = false, $a } $this->_selectionsAppended = true; } + return $this->getItems(); } diff --git a/app/code/core/Mage/Bundle/Model/Product/Price.php b/app/code/core/Mage/Bundle/Model/Product/Price.php index 5d02916273..33bb85d6c5 100644 --- a/app/code/core/Mage/Bundle/Model/Product/Price.php +++ b/app/code/core/Mage/Bundle/Model/Product/Price.php @@ -171,7 +171,7 @@ public function getPrices($product, $which = null) } $qty = $selection->getSelectionQty(); - if ($selection->getSelectionCanChangeQty() && $option->isMultiSelection()) { + if ($selection->getSelectionCanChangeQty() && !$option->isMultiSelection()) { $qty = min(1, $qty); } @@ -185,6 +185,7 @@ public function getPrices($product, $which = null) $minimalPrice += $selMinPrice; $minPriceFounded = true; } elseif (true !== $minPriceFounded) { + $selMinPrice += $minimalPrice; $minPriceFounded = false === $minPriceFounded ? $selMinPrice : min($minPriceFounded, $selMinPrice); } diff --git a/app/code/core/Mage/Bundle/Model/Product/Type.php b/app/code/core/Mage/Bundle/Model/Product/Type.php index f190c50374..69d79dd913 100644 --- a/app/code/core/Mage/Bundle/Model/Product/Type.php +++ b/app/code/core/Mage/Bundle/Model/Product/Type.php @@ -350,8 +350,14 @@ public function getOptionsCollection($product = null) if (!$this->getProduct($product)->hasData($this->_keyOptionsCollection)) { $optionsCollection = Mage::getModel('bundle/option')->getResourceCollection() ->setProductIdFilter($this->getProduct($product)->getId()) - ->setPositionOrder() - ->joinValues($this->getStoreFilter($product)); + ->setPositionOrder(); + + $storeId = $this->getStoreFilter($product); + if ($storeId instanceof Mage_Core_Model_Store) { + $storeId = $storeId->getId(); + } + + $optionsCollection->joinValues($storeId); $this->getProduct($product)->setData($this->_keyOptionsCollection, $optionsCollection); } return $this->getProduct($product)->getData($this->_keyOptionsCollection); @@ -485,32 +491,30 @@ public function isSalable($product = null) } /** - * Initialize product(s) for add to cart process + * Prepare product and its configuration to be added to some products list. + * Perform standard preparation process and then prepare of bundle selections options. * - * @param Varien_Object $buyRequest + * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product - * @return unknown + * @param string $processMode + * @return array|string */ - public function prepareForCart(Varien_Object $buyRequest, $product = null) + protected function _prepareProduct(Varien_Object $buyRequest, $product, $processMode) { - $result = parent::prepareForCart($buyRequest, $product); + $result = parent::_prepareProduct($buyRequest, $product, $processMode); if (is_string($result)) { return $result; } $selections = array(); - $product = $this->getProduct($product); + $isStrictProcessMode = $this->_isStrictProcessMode($processMode); + $_appendAllSelections = (bool)$product->getSkipCheckRequiredOption(); - $_appendAllSelections = false; - if ($product->getSkipCheckRequiredOption()) { - $_appendAllSelections = true; - } - - $options = array_filter($buyRequest->getBundleOption(), 'intval'); - - if ($options) { + $options = $buyRequest->getBundleOption(); + if (is_array($options)) { + $options = array_filter($options, 'intval'); $qtys = $buyRequest->getBundleOptionQty(); foreach ($options as $_optionId => $_selections) { if (empty($_selections)) { @@ -519,14 +523,14 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) } $optionIds = array_keys($options); - if (empty($optionIds)) { + if (empty($optionIds) && $isStrictProcessMode) { return Mage::helper('bundle')->__('Please select options for product.'); } //$optionsCollection = $this->getOptionsByIds($optionIds, $product); $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product); $optionsCollection = $this->getOptionsCollection($product); - if (!$this->getProduct($product)->getSkipCheckRequiredOption()) { + if (!$this->getProduct($product)->getSkipCheckRequiredOption() && $isStrictProcessMode) { foreach ($optionsCollection->getItems() as $option) { if ($option->getRequired() && !isset($options[$option->getId()])) { return Mage::helper('bundle')->__('Required options are not selected.'); @@ -562,7 +566,9 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) } else { $moreSelections = false; } - if ($_option->getRequired() && (!$_option->isMultiSelection() || ($_option->isMultiSelection() && !$moreSelections))) { + if ($_option->getRequired() && + (!$_option->isMultiSelection() || ($_option->isMultiSelection() && !$moreSelections)) + ) { return Mage::helper('bundle')->__('Selected required options are not available.'); } } @@ -572,6 +578,7 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) $selections = $selections->getItems(); } else { + $product->setOptionsValidationFail(true); $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product); $optionCollection = $product->getTypeInstance(true)->getOptionsCollection($product); @@ -581,7 +588,7 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) $selectionCollection = $product->getTypeInstance(true) ->getSelectionsCollection( - $product->getTypeInstance(true)->getOptionsIds($product), + $optionIds, $product ); @@ -596,30 +603,30 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) } } } - if (count($selections) > 0) { + if (count($selections) > 0 || !$isStrictProcessMode) { $uniqueKey = array($product->getId()); $selectionIds = array(); /* * shaking selection array :) by option position */ - usort($selections, array($this, "shakeSelections")); + usort($selections, array($this, 'shakeSelections')); foreach ($selections as $selection) { if ($selection->getSelectionCanChangeQty() && isset($qtys[$selection->getOptionId()])) { - $qty = $qtys[$selection->getOptionId()] > 0 ? $qtys[$selection->getOptionId()] : 1; + $qty = (float)$qtys[$selection->getOptionId()] > 0 ? $qtys[$selection->getOptionId()] : 1; } else { - $qty = $selection->getSelectionQty() ? $selection->getSelectionQty() : 1; + $qty = (float)$selection->getSelectionQty() ? $selection->getSelectionQty() : 1; } $product->addCustomOption('selection_qty_' . $selection->getSelectionId(), $qty, $selection); $selection->addCustomOption('selection_id', $selection->getSelectionId()); + $beforeQty = 0; if ($customOption = $product->getCustomOption('product_qty_' . $selection->getId())) { - $customOption->setValue($customOption->getValue() + $qty); - } else { - $product->addCustomOption('product_qty_' . $selection->getId(), $qty, $selection); + $beforeQty = $customOption->getValue(); } + $product->addCustomOption('product_qty_' . $selection->getId(), $qty + $beforeQty, $selection); /* * creating extra attributes that will be converted @@ -628,10 +635,10 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) */ $price = $product->getPriceModel()->getSelectionPrice($product, $selection, $qty); $attributes = array( - 'price' => Mage::app()->getStore()->convertPrice($price), - 'qty' => $qty, - 'option_label' => $selection->getOption()->getTitle(), - 'option_id' => $selection->getOption()->getId() + 'price' => Mage::app()->getStore()->convertPrice($price), + 'qty' => $qty, + 'option_label' => $selection->getOption()->getTitle(), + 'option_id' => $selection->getOption()->getId() ); //if (!$product->getPriceType()) { @@ -645,11 +652,14 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) } $result[] = $_result[0]->setParentProductId($product->getId()) - ->addCustomOption('bundle_option_ids', serialize($optionIds)) - ->addCustomOption('bundle_selection_attributes', serialize($attributes)) - ->setCartQty($qty); + ->addCustomOption('bundle_option_ids', serialize(array_map('intval', $optionIds))) + ->addCustomOption('bundle_selection_attributes', serialize($attributes)); //} + if ($isStrictProcessMode) { + $_result[0]->setCartQty($qty); + } + $selectionIds[] = $_result[0]->getSelectionId(); $uniqueKey[] = $_result[0]->getSelectionId(); $uniqueKey[] = $qty; @@ -661,11 +671,12 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) foreach ($result as $item) { $item->addCustomOption('bundle_identity', $uniqueKey); } - $product->addCustomOption('bundle_option_ids', serialize($optionIds)); + $product->addCustomOption('bundle_option_ids', serialize(array_map('intval',$optionIds))); $product->addCustomOption('bundle_selection_ids', serialize($selectionIds)); return $result; } + return $this->getSpecifyOptionMessage(); } @@ -879,12 +890,19 @@ public function getSearchableData($product = null) public function checkProductBuyState($product = null) { parent::checkProductBuyState($product); - $product = $this->getProduct($product); - $productOptionIds = $this->getOptionsIds($product); - $productSelections = $this->getSelectionsCollection($productOptionIds, $product); + $product = $this->getProduct($product); + $productOptionIds = $this->getOptionsIds($product); + $productSelections = $this->getSelectionsCollection($productOptionIds, $product); + $selectionIds = $product->getCustomOption('bundle_selection_ids'); + $selectionIds = unserialize($selectionIds->getValue()); + $buyRequest = $product->getCustomOption('info_buyRequest'); + $buyRequest = new Varien_Object(unserialize($buyRequest->getValue())); + $bundleOption = $buyRequest->getBundleOption(); + + if (empty($bundleOption)) { + Mage::throwException($this->getSpecifyOptionMessage()); + } - $selectionIds = $product->getCustomOption('bundle_selection_ids'); - $selectionIds = unserialize($selectionIds->getValue()); foreach ($selectionIds as $selectionId) { /* @var $selection Mage_Bundle_Model_Selection */ $selection = $productSelections->getItemById($selectionId); @@ -895,6 +913,16 @@ public function checkProductBuyState($product = null) } } + $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product); + $optionsCollection = $this->getOptionsCollection($product); + foreach ($optionsCollection->getItems() as $option) { + if ($option->getRequired() && empty($bundleOption[$option->getId()])) { + Mage::throwException( + Mage::helper('bundle')->__('Required options are not selected.') + ); + } + } + return $this; } @@ -902,7 +930,7 @@ public function checkProductBuyState($product = null) * Retrieve products divided into groups required to purchase * At least one product in each group has to be purchased * - * @param Mage_Catalog_Model_Product $product + * @param Mage_Catalog_Model_Product $product * @return array */ public function getProductsToPurchaseByReqGroups($product = null) @@ -927,4 +955,27 @@ public function getProductsToPurchaseByReqGroups($product = null) } return $groups; } + + /** + * Prepare selected options for bundle product + * + * @param Mage_Catalog_Model_Product $product + * @param Varien_Object $buyRequest + * @return array + */ + public function processBuyRequest($product, $buyRequest) + { + $option = $buyRequest->getBundleOption(); + $optionQty = $buyRequest->getBundleOptionQty(); + + $option = (is_array($option)) ? array_filter($option, 'intval') : array(); + $optionQty = (is_array($optionQty)) ? array_filter($optionQty, 'intval') : array(); + + $options = array( + 'bundle_option' => $option, + 'bundle_option_qty' => $optionQty + ); + + return $options; + } } diff --git a/app/code/core/Mage/Bundle/controllers/Adminhtml/Bundle/Product/EditController.php b/app/code/core/Mage/Bundle/controllers/Adminhtml/Bundle/Product/EditController.php new file mode 100644 index 0000000000..86b3b159d1 --- /dev/null +++ b/app/code/core/Mage/Bundle/controllers/Adminhtml/Bundle/Product/EditController.php @@ -0,0 +1,52 @@ + + */ +class Mage_Bundle_Adminhtml_Bundle_Product_EditController extends Mage_Adminhtml_Catalog_ProductController +{ + protected function _construct() + { + $this->setUsedModuleName('Mage_Bundle'); + } + + public function formAction() + { + $product = $this->_initProduct(); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('bundle/adminhtml_catalog_product_edit_tab_bundle', 'admin.product.bundle.items') + ->setProductId($product->getId()) + ->toHtml() + ); + } +} diff --git a/app/code/core/Mage/Bundle/controllers/Adminhtml/Bundle/SelectionController.php b/app/code/core/Mage/Bundle/controllers/Adminhtml/Bundle/SelectionController.php new file mode 100644 index 0000000000..cf7fe9e85f --- /dev/null +++ b/app/code/core/Mage/Bundle/controllers/Adminhtml/Bundle/SelectionController.php @@ -0,0 +1,62 @@ + + */ +class Mage_Bundle_Adminhtml_Bundle_SelectionController extends Mage_Adminhtml_Controller_Action +{ + protected function _construct() + { + $this->setUsedModuleName('Mage_Bundle'); + } + + public function searchAction() + { + return $this->getResponse()->setBody( + $this->getLayout() + ->createBlock('bundle/adminhtml_catalog_product_edit_tab_bundle_option_search') + ->setIndex($this->getRequest()->getParam('index')) + ->setFirstShow(true) + ->toHtml() + ); + } + + public function gridAction() + { + return $this->getResponse()->setBody( + $this->getLayout() + ->createBlock('bundle/adminhtml_catalog_product_edit_tab_bundle_option_search_grid') + ->setIndex($this->getRequest()->getParam('index')) + ->toHtml() + ); + } + +} diff --git a/app/code/core/Mage/Bundle/controllers/Product/EditController.php b/app/code/core/Mage/Bundle/controllers/Product/EditController.php index d2492d319e..b172bdfd5f 100644 --- a/app/code/core/Mage/Bundle/controllers/Product/EditController.php +++ b/app/code/core/Mage/Bundle/controllers/Product/EditController.php @@ -24,7 +24,7 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -require_once 'Mage/Adminhtml/controllers/Catalog/ProductController.php'; +require_once 'Mage/Bundle/controllers/Adminhtml/Bundle/Product/EditController.php'; /** * Adminhtml bundle product edit @@ -32,21 +32,9 @@ * @category Mage * @package Mage_Bundle * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_Bundle_Adminhtml_Product_EditController is used */ -class Mage_Bundle_Product_EditController extends Mage_Adminhtml_Catalog_ProductController +class Mage_Bundle_Product_EditController extends Mage_Bundle_Adminhtml_Bundle_Product_EditController { - protected function _construct() - { - $this->setUsedModuleName('Mage_Bundle'); - } - public function formAction() - { - $product = $this->_initProduct(); - $this->getResponse()->setBody( - $this->getLayout()->createBlock('bundle/adminhtml_catalog_product_edit_tab_bundle', 'admin.product.bundle.items') - ->setProductId($product->getId()) - ->toHtml() - ); - } } diff --git a/app/code/core/Mage/Bundle/controllers/SelectionController.php b/app/code/core/Mage/Bundle/controllers/SelectionController.php index 2d0470f81c..60184a3813 100644 --- a/app/code/core/Mage/Bundle/controllers/SelectionController.php +++ b/app/code/core/Mage/Bundle/controllers/SelectionController.php @@ -24,39 +24,17 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +require_once 'Mage/Bundle/controllers/Adminhtml/Bundle/SelectionController.php'; + /** * Adminhtml selection grid controller * * @category Mage * @package Mage_Bundle * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_Bundle_Adminhtml_Bundle_SelectionController is used */ -class Mage_Bundle_SelectionController extends Mage_Adminhtml_Controller_Action +class Mage_Bundle_SelectionController extends Mage_Bundle_Adminhtml_Bundle_SelectionController { - protected function _construct() - { - $this->setUsedModuleName('Mage_Bundle'); - } - - public function searchAction() - { - return $this->getResponse()->setBody( - $this->getLayout() - ->createBlock('bundle/adminhtml_catalog_product_edit_tab_bundle_option_search') - ->setIndex($this->getRequest()->getParam('index')) - ->setFirstShow(true) - ->toHtml() - ); - } - - public function gridAction() - { - return $this->getResponse()->setBody( - $this->getLayout() - ->createBlock('bundle/adminhtml_catalog_product_edit_tab_bundle_option_search_grid') - ->setIndex($this->getRequest()->getParam('index')) - ->toHtml() - ); - } } diff --git a/app/code/core/Mage/Bundle/etc/config.xml b/app/code/core/Mage/Bundle/etc/config.xml index b325647f13..675c5cdc2f 100644 --- a/app/code/core/Mage/Bundle/etc/config.xml +++ b/app/code/core/Mage/Bundle/etc/config.xml @@ -231,13 +231,13 @@ - - admin + - Mage_Bundle - bundle + + Mage_Bundle_Adminhtml + - + @@ -257,6 +257,15 @@ + + + + + + + + + diff --git a/app/code/core/Mage/Catalog/Block/Navigation.php b/app/code/core/Mage/Catalog/Block/Navigation.php index 17e423862c..dfc16c57ef 100644 --- a/app/code/core/Mage/Catalog/Block/Navigation.php +++ b/app/code/core/Mage/Catalog/Block/Navigation.php @@ -36,6 +36,13 @@ class Mage_Catalog_Block_Navigation extends Mage_Core_Block_Template { protected $_categoryInstance = null; + /** + * Current category key + * + * @var string + */ + protected $_currentCategoryKey; + /** * Array of level position counters * @@ -65,7 +72,8 @@ public function getCacheKeyInfo() Mage::getDesign()->getTheme('template'), Mage::getSingleton('customer/session')->getCustomerGroupId(), 'template' => $this->getTemplate(), - 'name' => $this->getNameInLayout() + 'name' => $this->getNameInLayout(), + $this->getCurrenCategoryPath() ); $cacheId = $shortCacheId; @@ -73,19 +81,29 @@ public function getCacheKeyInfo() $shortCacheId = implode('|', $shortCacheId); $shortCacheId = md5($shortCacheId); - $cacheId['category_path'] = $this->getCurrenCategoryKey(); + $cacheId['category_path'] = $this->getCurrenCategoryPath(); $cacheId['short_cache_id'] = $shortCacheId; return $cacheId; } - public function getCurrenCategoryKey() + /** + * Get current category key + * + * @return mixed + */ + public function getCurrenCategoryPath() { - if ($category = Mage::registry('current_category')) { - return $category->getPath(); - } else { - return Mage::app()->getStore()->getRootCategoryId(); + if (!$this->_currentCategoryKey) { + if ($category = Mage::registry('current_category')) { + $this->_currentCategoryKey = $category->getPath(); + } else { + $storeId = Mage::app()->getStore()->getId(); + $this->_currentCategoryKey = Mage::app()->getStore($storeId)->getRootCategoryId(); + } } + + return $this->_currentCategoryKey; } /** diff --git a/app/code/core/Mage/Catalog/Block/Product/Abstract.php b/app/code/core/Mage/Catalog/Block/Product/Abstract.php index e0033b619a..b67116ad59 100644 --- a/app/code/core/Mage/Catalog/Block/Product/Abstract.php +++ b/app/code/core/Mage/Catalog/Block/Product/Abstract.php @@ -35,9 +35,20 @@ abstract class Mage_Catalog_Block_Product_Abstract extends Mage_Core_Block_Template { protected $_priceBlock = array(); + + /** + * Default price block + * + * @var string + */ + protected $_block = 'catalog/product_price'; + protected $_priceBlockDefaultTemplate = 'catalog/product/price.phtml'; + protected $_tierPriceDefaultTemplate = 'catalog/product/view/tierprices.phtml'; + protected $_priceBlockTypes = array(); + /** * Flag which allow/disallow to use link for as low as price * @@ -85,6 +96,28 @@ public function getAddToCartUrl($product, $additional = array()) return $this->helper('checkout/cart')->getAddUrl($product, $additional); } + /** + * Retrieves url for form submitting: + * some objects can use setSubmitRouteData() to set route and params for form submitting, + * otherwise default url will be used + * + * @param Mage_Catalog_Model_Product $product + * @param array $additional + * @return string + */ + public function getSubmitUrl($product, $additional = array()) + { + $submitRouteData = $this->getData('submit_route_data'); + if ($submitRouteData) { + $route = $submitRouteData['route']; + $params = isset($submitRouteData['params']) ? $submitRouteData['params'] : array(); + $submitUrl = $this->getUrl($route, array_merge($params, $additional)); + } else { + $submitUrl = $this->getAddToCartUrl($product, $additional); + } + return $submitUrl; + } + /** * Enter description here... * @@ -118,7 +151,7 @@ public function getMinimalQty($product) protected function _getPriceBlock($productTypeId) { if (!isset($this->_priceBlock[$productTypeId])) { - $block = 'catalog/product_price'; + $block = $this->_block; if (isset($this->_priceBlockTypes[$productTypeId])) { if ($this->_priceBlockTypes[$productTypeId]['block'] != '') { $block = $this->_priceBlockTypes[$productTypeId]['block']; diff --git a/app/code/core/Mage/Catalog/Block/Product/Compare/List.php b/app/code/core/Mage/Catalog/Block/Product/Compare/List.php index 987610ccd9..43dc1e9940 100644 --- a/app/code/core/Mage/Catalog/Block/Product/Compare/List.php +++ b/app/code/core/Mage/Catalog/Block/Product/Compare/List.php @@ -151,8 +151,7 @@ public function getProductAttributeValue($product, $attribute) if ($attribute->getSourceModel() || in_array($attribute->getFrontendInput(), array('select','boolean','multiselect'))) { //$value = $attribute->getSource()->getOptionText($product->getData($attribute->getAttributeCode())); $value = $attribute->getFrontend()->getValue($product); - } - else { + } else { $value = $product->getData($attribute->getAttributeCode()); } return ((string)$value == '') ? Mage::helper('catalog')->__('No') : $value; diff --git a/app/code/core/Mage/Catalog/Block/Product/View.php b/app/code/core/Mage/Catalog/Block/Product/View.php index 25dcf923a0..c9a1052d88 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View.php +++ b/app/code/core/Mage/Catalog/Block/Product/View.php @@ -60,7 +60,7 @@ protected function _prepareLayout() if ($description) { $headBlock->setDescription( ($description) ); } else { - $headBlock->setDescription($product->getDescription()); + $headBlock->setDescription(Mage::helper('core/string')->substr($product->getDescription(), 0, 255)); } if ($this->helper('catalog/product')->canUseCanonicalTag()) { $params = array('_ignore_category'=>true); @@ -189,4 +189,41 @@ public function hasRequiredOptions() { return $this->getProduct()->getTypeInstance(true)->hasRequiredOptions($this->getProduct()); } + + /** + * Define if setting of product options must be shown instantly. + * Used in case when options are usually hidden and shown only when user + * presses some button or link. In editing mode we better show these options + * instantly. + * + * @return bool + */ + public function isStartCustomization() + { + return $this->getProduct()->getConfigureMode() || Mage::app()->getRequest()->getParam('startcustomization'); + } + + /** + * Get default qty - either as preconfigured, or as 1. + * Also restricts it by minimal qty. + * + * @param null|Mage_Catalog_Model_Product + * + * @return int|float + */ + public function getProductDefaultQty($product = null) + { + if (!$product) { + $product = $this->getProduct(); + } + + $qty = $this->getMinimalQty($product); + $config = $product->getPreconfiguredValues(); + $configQty = $config->getQty(); + if ($configQty > $qty) { + $qty = $configQty; + } + + return $qty; + } } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Abstract.php b/app/code/core/Mage/Catalog/Block/Product/View/Abstract.php index c4893f8835..4d3e8dce95 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Abstract.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Abstract.php @@ -47,5 +47,4 @@ public function getProduct() return $product; } - } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Attributes.php b/app/code/core/Mage/Catalog/Block/Product/View/Attributes.php index 1c0bea03c2..ae18dafd24 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Attributes.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Attributes.php @@ -63,16 +63,13 @@ public function getAdditionalData(array $excludeAttr = array()) if (!$product->hasData($attribute->getAttributeCode())) { $value = Mage::helper('catalog')->__('N/A'); - } - elseif ((string)$value == '') { + } elseif ((string)$value == '') { $value = Mage::helper('catalog')->__('No'); + } elseif ($attribute->getFrontendInput() == 'price' && is_string($value)) { + $value = Mage::app()->getStore()->convertPrice($value, true); } - // TODO this is temporary skipping eco taxes if (is_string($value) && strlen($value)) { - if ($attribute->getFrontendInput() == 'price') { - $value = Mage::app()->getStore()->convertPrice($value,true); - } $data[$attribute->getAttributeCode()] = array( 'label' => $attribute->getStoreLabel(), 'value' => $value, diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options.php b/app/code/core/Mage/Catalog/Block/Product/View/Options.php index 7491b26521..34ee619277 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options.php @@ -56,7 +56,7 @@ public function __construct() public function getProduct() { if (!$this->_product) { - if (Mage::registry('product')) { + if (Mage::registry('current_product')) { $this->_product = Mage::registry('current_product'); } else { $this->_product = Mage::getSingleton('catalog/product'); diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options/Abstract.php b/app/code/core/Mage/Catalog/Block/Product/View/Options/Abstract.php index 5e9b34e1e3..26a678355f 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options/Abstract.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options/Abstract.php @@ -114,23 +114,28 @@ protected function _formatPrice($value, $flag=true) if ($value['pricing_value'] == 0) { return ''; } + + $taxHelper = Mage::helper('tax'); + $store = $this->getProduct()->getStore(); + $sign = '+'; if ($value['pricing_value'] < 0) { $sign = '-'; $value['pricing_value'] = 0 - $value['pricing_value']; } + $priceStr = $sign; $_priceInclTax = $this->getPrice($value['pricing_value'], true); $_priceExclTax = $this->getPrice($value['pricing_value']); - if (Mage::helper('tax')->displayPriceIncludingTax()) { - $priceStr .= $this->helper('core')->currency($_priceInclTax, true, $flag); - } elseif (Mage::helper('tax')->displayPriceExcludingTax()) { - $priceStr .= $this->helper('core')->currency($_priceExclTax, true, $flag); - } elseif (Mage::helper('tax')->displayBothPrices()) { - $priceStr .= $this->helper('core')->currency($_priceExclTax, true, $flag); + if ($taxHelper->displayPriceIncludingTax()) { + $priceStr .= $this->helper('core')->currencyByStore($_priceInclTax, $store, true, $flag); + } elseif ($taxHelper->displayPriceExcludingTax()) { + $priceStr .= $this->helper('core')->currencyByStore($_priceExclTax, $store, true, $flag); + } elseif ($taxHelper->displayBothPrices()) { + $priceStr .= $this->helper('core')->currencyByStore($_priceExclTax, $store, true, $flag); if ($_priceInclTax != $_priceExclTax) { $priceStr .= ' ('.$sign.$this->helper('core') - ->currency($_priceInclTax, true, $flag).' '.$this->__('Incl. Tax').')'; + ->currencyByStore($_priceInclTax, $store, true, $flag).' '.$this->__('Incl. Tax').')'; } } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php index 08a8112047..2bd8e39e09 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Date.php @@ -81,16 +81,27 @@ public function getDateHtml() */ public function getCalendarDateHtml() { - // $require = $this->getOption()->getIsRequire() ? ' required-entry' : ''; + $option = $this->getOption(); + $value = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId() . '/date'); + + //$require = $this->getOption()->getIsRequire() ? ' required-entry' : ''; $require = ''; + + $yearStart = Mage::getSingleton('catalog/product_option_type_date')->getYearStart(); + $yearEnd = Mage::getSingleton('catalog/product_option_type_date')->getYearEnd(); + $calendar = $this->getLayout() ->createBlock('core/html_date') ->setId('options_'.$this->getOption()->getId().'_date') ->setName('options['.$this->getOption()->getId().'][date]') ->setClass('product-custom-option datetime-picker input-text' . $require) ->setImage($this->getSkinUrl('images/calendar.gif')) - ->setExtraParams('onchange="opConfig.reloadPrice()"') - ->setFormat(Mage::app()->getLocale()->getDateStrFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT)); + ->setFormat(Mage::app()->getLocale()->getDateStrFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT)) + ->setValue($value) + ->setYearsRange('[' . $yearStart . ', ' . $yearEnd . ']'); + if (!$this->getSkipJsReloadPrice()) { + $calendar->setExtraParams('onchange="opConfig.reloadPrice()"'); + } return $calendar->getHtml(); } @@ -102,23 +113,23 @@ public function getCalendarDateHtml() */ public function getDropDownsDateHtml() { - $_fieldsSeparator = ' '; - $_fieldsOrder = Mage::getSingleton('catalog/product_option_type_date')->getConfigData('date_fields_order'); - $_fieldsOrder = str_replace(',', $_fieldsSeparator, $_fieldsOrder); + $fieldsSeparator = ' '; + $fieldsOrder = Mage::getSingleton('catalog/product_option_type_date')->getConfigData('date_fields_order'); + $fieldsOrder = str_replace(',', $fieldsSeparator, $fieldsOrder); $monthsHtml = $this->_getSelectFromToHtml('month', 1, 12); $daysHtml = $this->_getSelectFromToHtml('day', 1, 31); - $_yearStart = Mage::getSingleton('catalog/product_option_type_date')->getYearStart(); - $_yearEnd = Mage::getSingleton('catalog/product_option_type_date')->getYearEnd(); - $yearsHtml = $this->_getSelectFromToHtml('year', $_yearStart, $_yearEnd); + $yearStart = Mage::getSingleton('catalog/product_option_type_date')->getYearStart(); + $yearEnd = Mage::getSingleton('catalog/product_option_type_date')->getYearEnd(); + $yearsHtml = $this->_getSelectFromToHtml('year', $yearStart, $yearEnd); - $_translations = array( + $translations = array( 'd' => $daysHtml, 'm' => $monthsHtml, 'y' => $yearsHtml ); - return strtr($_fieldsOrder, $_translations); + return strtr($fieldsOrder, $translations); } /** @@ -178,16 +189,29 @@ protected function _getSelectFromToHtml($name, $from, $to, $value = null) */ protected function _getHtmlSelect($name, $value = null) { + $option = $this->getOption(); + // $require = $this->getOption()->getIsRequire() ? ' required-entry' : ''; $require = ''; $select = $this->getLayout()->createBlock('core/html_select') ->setId('options_' . $this->getOption()->getId() . '_' . $name) ->setClass('product-custom-option datetime-picker' . $require) - ->setExtraParams('style="width:auto;" onchange="opConfig.reloadPrice()"') - ->setName('options[' . $this->getOption()->getId() . '][' . $name . ']'); + ->setExtraParams() + ->setName('options[' . $option->getId() . '][' . $name . ']'); + + $extraParams = 'style="width:auto"'; + if (!$this->getSkipJsReloadPrice()) { + $extraParams .= ' onchange="opConfig.reloadPrice()"'; + } + $select->setExtraParams($extraParams); + + if (is_null($value)) { + $value = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId() . '/' . $name); + } if (!is_null($value)) { $select->setValue($value); } + return $select; } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/File.php b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/File.php index 479cce6c68..0422a5ae30 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/File.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/File.php @@ -35,5 +35,19 @@ class Mage_Catalog_Block_Product_View_Options_Type_File extends Mage_Catalog_Block_Product_View_Options_Abstract { - + /** + * Returns info of file + * + * @return string + */ + public function getFileInfo() + { + $info = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $this->getOption()->getId()); + if (empty($info)) { + $info = new Varien_Object(); + } else if (is_array($info)) { + $info = new Varien_Object($info); + } + return $info; + } } diff --git a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Select.php index 382103a54b..e9aff1a67d 100644 --- a/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Select.php +++ b/app/code/core/Mage/Catalog/Block/Product/View/Options/Type/Select.php @@ -35,10 +35,15 @@ class Mage_Catalog_Block_Product_View_Options_Type_Select extends Mage_Catalog_Block_Product_View_Options_Abstract { - + /** + * Return html for control element + * + * @return string + */ public function getValuesHtml() { $_option = $this->getOption(); + $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $_option->getId()); if ($_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DROP_DOWN || $_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_MULTIPLE) { @@ -69,7 +74,14 @@ public function getValuesHtml() if ($_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_MULTIPLE) { $extraParams = ' multiple="multiple"'; } - $select->setExtraParams('onchange="opConfig.reloadPrice()"'.$extraParams); + if (!$this->getSkipJsReloadPrice()) { + $extraParams .= ' onchange="opConfig.reloadPrice()"'; + } + $select->setExtraParams($extraParams); + + if ($configValue) { + $select->setValue($configValue); + } return $select->getHtml(); } @@ -85,7 +97,7 @@ public function getValuesHtml() $type = 'radio'; $class = 'radio'; if (!$_option->getIsRequire()) { - $selectHtml .= '
  • '; + $selectHtml .= '
  • getSkipJsReloadPrice() ? '' : ' onclick="opConfig.reloadPrice()"') . ' value="" checked="checked" />
  • '; } break; case Mage_Catalog_Model_Product_Option::OPTION_TYPE_CHECKBOX: @@ -97,12 +109,21 @@ public function getValuesHtml() $count = 1; foreach ($_option->getValues() as $_value) { $count++; + $priceStr = $this->_formatPrice(array( 'is_percent' => ($_value->getPriceType() == 'percent') ? true : false, 'pricing_value' => $_value->getPrice(true) )); + + $htmlValue = $_value->getOptionTypeId(); + if ($arraySign) { + $checked = (is_array($configValue) && in_array($htmlValue, $configValue)) ? 'checked' : ''; + } else { + $checked = $configValue == $htmlValue ? 'checked' : ''; + } + $selectHtml .= '
  • ' . - '' . + 'getSkipJsReloadPrice() ? '' : ' onclick="opConfig.reloadPrice()"') . ' name="options['.$_option->getId().']'.$arraySign.'" id="options_'.$_option->getId().'_'.$count.'" value="' . $htmlValue . '" ' . $checked . ' />' . ''; if ($_option->getIsRequire()) { $selectHtml .= ''; diff --git a/app/code/core/Mage/Core/Block/Messages.php b/app/code/core/Mage/Core/Block/Messages.php index dfa739a748..a2e7d85956 100644 --- a/app/code/core/Mage/Core/Block/Messages.php +++ b/app/code/core/Mage/Core/Block/Messages.php @@ -54,6 +54,13 @@ class Mage_Core_Block_Messages extends Mage_Core_Block_Template */ protected $_messagesSecondLevelTagName = 'li'; + /** + * Store content wrapper html tag name for messages html output + * + * @var string + */ + protected $_messagesContentWrapperTagName = 'span'; + /** * Flag which require message text escape * @@ -231,7 +238,9 @@ public function getGroupedHtml() foreach ( $messages as $message ) { $html.= '<' . $this->_messagesSecondLevelTagName . '>'; + $html.= '<' . $this->_messagesContentWrapperTagName . '>'; $html.= ($this->_escapeMessageFlag) ? $this->htmlEscape($message->getText()) : $message->getText(); + $html.= '_messagesContentWrapperTagName . '>'; $html.= '_messagesSecondLevelTagName . '>'; } $html .= '_messagesFirstLevelTagName . '>'; diff --git a/app/code/core/Mage/Core/Controller/Varien/Action.php b/app/code/core/Mage/Core/Controller/Varien/Action.php index 0db5053290..a87e828031 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Action.php +++ b/app/code/core/Mage/Core/Controller/Varien/Action.php @@ -32,7 +32,7 @@ * * @category Mage * @package Mage_Core - * @author Magento Core Team + * @author Magento Core Team */ abstract class Mage_Core_Controller_Varien_Action { @@ -607,22 +607,44 @@ protected function _forward($action, $controller = null, $module = null, array $ ->setDispatched(false); } + /** + * Inits layout messages by message storage(s), loading and adding messages to layout messages block + * + * @param string|array $messagesStorage + * @return Mage_Core_Controller_Varien_Action + */ protected function _initLayoutMessages($messagesStorage) { - if ($storage = Mage::getSingleton($messagesStorage)) { - $this->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); - $this->getLayout()->getMessagesBlock()->setEscapeMessageFlag( - $storage->getEscapeMessages(true) - ); + if (!is_array($messagesStorage)) { + $messagesStorage = array($messagesStorage); } - else { - Mage::throwException( - Mage::helper('core')->__('Invalid messages storage "%s" for layout messages initialization', (string)$messagesStorage) - ); + foreach ($messagesStorage as $storageName) { + $storage = Mage::getSingleton($storageName); + if ($storage) { + $block = $this->getLayout()->getMessagesBlock(); + $block->addMessages($storage->getMessages(true)); + $block->setEscapeMessageFlag($storage->getEscapeMessages(true)); + } + else { + Mage::throwException( + Mage::helper('core')->__('Invalid messages storage "%s" for layout messages initialization', (string) $storageName) + ); + } } return $this; } + /** + * Inits layout messages by message storage(s), loading and adding messages to layout messages block + * + * @param string|array $messagesStorage + * @return Mage_Core_Controller_Varien_Action + */ + public function initLayoutMessages($messagesStorage) + { + return $this->_initLayoutMessages($messagesStorage); + } + /** * Set redirect url into response * @@ -954,4 +976,66 @@ protected function _filterDateTime($array, $dateFields) } return $array; } + + /** + * Declare headers and content file in responce for file download + * + * @param string $fileName + * @param string|array $content set to null to avoid starting output, $contentLength should be set explicitly in + * that case + * @param string $contentType + * @param int $contentLength explicit content length, if strlen($content) isn't applicable + * @return Mage_Core_Controller_Varien_Action + */ + protected function _prepareDownloadResponse($fileName, $content, $contentType = 'application/octet-stream', $contentLength = null) + { + $session = Mage::getSingleton('admin/session'); + if ($session->isFirstPageAfterLogin()) { + $this->_redirect($session->getUser()->getStartupPageUrl()); + return $this; + } + + $isFile = false; + $file = null; + if (is_array($content)) { + if (!isset($content['type']) || !isset($content['value'])) { + return $this; + } + if ($content['type'] == 'filename') { + $isFile = true; + $file = $content['value']; + $contentLength = filesize($file); + } + } + + $this->getResponse() + ->setHttpResponseCode(200) + ->setHeader('Pragma', 'public', true) + ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) + ->setHeader('Content-type', $contentType, true) + ->setHeader('Content-Length', is_null($contentLength) ? strlen($content) : $contentLength) + ->setHeader('Content-Disposition', 'attachment; filename="'.$fileName.'"') + ->setHeader('Last-Modified', date('r')); + + if (!is_null($content)) { + if ($isFile) { + $this->getResponse()->clearBody(); + $this->getResponse()->sendHeaders(); + + $ioAdapter = new Varien_Io_File(); + $ioAdapter->open(array('path' => $ioAdapter->dirname($file))); + $ioAdapter->streamOpen($file, 'r'); + while ($buffer = $ioAdapter->streamRead()) { + print $buffer; + } + $ioAdapter->streamClose(); + if (!empty($content['rm'])) { + $ioAdapter->rm($file); + } + } else { + $this->getResponse()->setBody($content); + } + } + return $this; + } } diff --git a/app/code/core/Mage/Core/Helper/Data.php b/app/code/core/Mage/Core/Helper/Data.php index c9079b9fdc..cf949d653b 100644 --- a/app/code/core/Mage/Core/Helper/Data.php +++ b/app/code/core/Mage/Core/Helper/Data.php @@ -31,6 +31,8 @@ */ class Mage_Core_Helper_Data extends Mage_Core_Helper_Abstract { + const XML_PATH_DEFAULT_COUNTRY = 'general/country/default'; + /** * @var Mage_Core_Model_Encryption */ @@ -62,14 +64,33 @@ public function getEncryptor() * @param bool $includeContainer * @return mixed */ - public static function currency($value, $format=true, $includeContainer = true) + public static function currency($value, $format = true, $includeContainer = true) + { + return self::currencyByStore($value, null, $format, $includeContainer); + } + + /** + * Convert and format price value for specified store + * + * @param float $value + * @param int|Mage_Core_Model_Store $store + * @param bool $format + * @param bool $includeContainer + * @return mixed + */ + public static function currencyByStore($value, $store = null, $format = true, $includeContainer = true) { try { - $value = Mage::app()->getStore()->convertPrice($value, $format, $includeContainer); + if (!($store instanceof Mage_Core_Model_Store)) { + $store = Mage::app()->getStore($store); + } + + $value = $store->convertPrice($value, $format, $includeContainer); } catch (Exception $e){ $value = $e->getMessage(); } + return $value; } @@ -697,4 +718,15 @@ public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = fa } return false; } + + /** + * Return default country code + * + * @param Mage_Core_Model_Store|string|int $store + * @return string + */ + public function getDefaultCountry($store = null) + { + return Mage::getStoreConfig(self::XML_PATH_DEFAULT_COUNTRY, $store); + } } diff --git a/app/code/core/Mage/Core/Helper/File/Storage.php b/app/code/core/Mage/Core/Helper/File/Storage.php new file mode 100644 index 0000000000..3576b42333 --- /dev/null +++ b/app/code/core/Mage/Core/Helper/File/Storage.php @@ -0,0 +1,128 @@ + + */ +class Mage_Core_Helper_File_Storage extends Mage_Core_Helper_Abstract +{ + /** + * Current storage code + * + * @var int + */ + protected $_currentStorage = null; + + /** + * Return saved storage code + * + * @return int + */ + public function getCurrentStorage() + { + if (is_null($this->_currentStorage)) { + $this->_currentStorage = (int) Mage::app() + ->getConfig()->getNode(Mage_Core_Model_File_Storage::XML_PATH_STORAGE_MEDIA); + } + + return $this->_currentStorage; + } + + /** + * Retrieve file system storage model + * + * @return Mage_Core_Model_File_Storage_File + */ + public function getStorageFileModel() + { + return Mage::getSingleton('core/file_storage_file'); + } + + /** + * Check if storage is internal + * + * @param int|null $storage + * @return bool + */ + public function isInternalStorage($storage = null) + { + $storage = ($storage) ? (int) $storage : $this->getCurrentStorage(); + + return $storage == Mage_Core_Model_File_Storage::STORAGE_MEDIA_FILE_SYSTEM; + } + + /** + * Retrieve storage model + * + * @param int|null $storage + * @param array $params + * @return Mage_Core_Model_Abstract|bool + */ + public function getStorageModel($storage = null, $params = array()) + { + return Mage::getSingleton('core/file_storage')->getStorageModel($storage, $params); + } + + /** + * Check if needed to copy file from storage to file system and + * if file exists in the storage + * + * @param string $filename + * @return bool|int + */ + public function processStorageFile($filename) + { + if ($this->isInternalStorage()) { + return false; + } + + $dbHelper = Mage::helper('core/file_storage_database'); + + $relativePath = $dbHelper->getMediaRelativePath($filename); + $file = $this->getStorageModel()->loadByFilename($relativePath); + + if (!$file->getId()) { + return false; + } + + return $this->saveFileToFileSystem($file); + } + + /** + * Save file to file system + * + * @param Mage_Core_Model_File_Storage_Database $file + * @return bool|int + */ + public function saveFileToFileSystem($file) + { + return $this->getStorageFileModel()->saveFile($file, true); + } +} diff --git a/app/code/core/Mage/Core/Helper/File/Storage/Database.php b/app/code/core/Mage/Core/Helper/File/Storage/Database.php new file mode 100644 index 0000000000..47daea099a --- /dev/null +++ b/app/code/core/Mage/Core/Helper/File/Storage/Database.php @@ -0,0 +1,294 @@ + + */ +class Mage_Core_Helper_File_Storage_Database extends Mage_Core_Helper_Abstract +{ + /** + * Database storage model + * @var null|Mage_Core_Model_File_Storage_Database + */ + protected $_databaseModel = null; + + /** + * Storage resource model + * @var null|Mage_Core_Model_Mysql4_File_Storage_Database + */ + protected $_resourceModel = null; + + /** + * Db usage flag + * + * @var bool + */ + protected $_useDb = null; + + /** + * Check if we use DB storage + * + * @return bool + */ + public function checkDbUsage() + { + if (is_null($this->_useDb)) { + $currentStorage = (int) Mage::app() + ->getConfig()->getNode(Mage_Core_Model_File_Storage::XML_PATH_STORAGE_MEDIA); + $this->_useDb = ($currentStorage == Mage_Core_Model_File_Storage::STORAGE_MEDIA_DATABASE); + } + + return $this->_useDb; + } + + /** + * Get database storage model + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function getStorageDatabaseModel() + { + if (is_null($this->_databaseModel)) { + $this->_databaseModel = Mage::getModel('core/file_storage_database'); + } + + return $this->_databaseModel; + } + + /** + * Get file storage model + * + * @return Mage_Core_Model_File_Storage_File + */ + public function getStorageFileModel() + { + return Mage::getSingleton('core/file_storage_file'); + } + + /** + * Get storage model + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function getResourceStorageModel() + { + if (is_null($this->_resourceModel)) { + $this->_resourceModel = $this->getStorageDatabaseModel()->getResource(); + } + return $this->_resourceModel; + } + + /** + * Save file in DB storage + * + * @param string $filename + */ + public function saveFile($filename) + { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel()->saveFile($this->_removeAbsPathFromFileName($filename)); + } + } + + /** + * Rename file in DB storage + * + * @param string $oldName + * @param string $newName + */ + public function renameFile($oldName, $newName) { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel() + ->renameFile($this->_removeAbsPathFromFileName($oldName), $this->_removeAbsPathFromFileName($newName)); + } + } + + /** + * Copy file in DB storage + * + * @param string $oldName + * @param string $newName + */ + public function copyFile($oldName, $newName) { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel() + ->copyFile($this->_removeAbsPathFromFileName($oldName), $this->_removeAbsPathFromFileName($newName)); + } + } + + /** + * Check whether file exists in DB + * + * @param string $filename can be both full path or partial (like in DB) + * @return bool|null + */ + public function fileExists($filename) + { + if ($this->checkDbUsage()) { + return $this->getStorageDatabaseModel()->fileExists($this->_removeAbsPathFromFileName($filename)); + } else { + return null; + } + } + + /** + * Get unique name for passed file in case this file already exists + * + * @param string $directory - can be both full path or partial (like in DB) + * @param string $filename - not just a filename. Can have directory chunks. return will have this form + * @return string + */ + public function getUniqueFilename($directory, $filename) + { + if ($this->checkDbUsage()) { + $directory = $this->_removeAbsPathFromFileName($directory); + if($this->fileExists($directory . $filename)) { + $index = 1; + $extension = strrchr($filename, '.'); + $filenameWoExtension = substr($filename, 0, -1 * strlen($extension)); + while($this->fileExists($directory . $filenameWoExtension . '_' . $index . $extension)) { + $index ++; + } + $filename = $filenameWoExtension . '_' . $index . $extension; + } + } + return $filename; + } + + /** + * Save database file to file system + * + * @param string $filename + * @return bool|int + */ + public function saveFileToFilesystem($filename) { + if ($this->checkDbUsage()) { + $file = Mage::getModel('core/file_storage_database')->loadByFilename($this->_removeAbsPathFromFileName($filename)); + if (!$file->getId()) { + return false; + } + + return $this->getStorageFileModel()->saveFile($file, true); + } + } + + /** + * Return relative uri for media content by full path + * + * @param string $fullPath + * @return string + */ + public function getMediaRelativePath($fullPath) + { + $relativePath = ltrim(str_replace(Mage::getBaseDir('media'), '', $fullPath), DS); + return str_replace(DS, '/', $relativePath); + } + + /** + * Deletes from DB files, which belong to one folder + * + * @param string $folderName + */ + public function deleteFolder($folderName) + { + if ($this->checkDbUsage()) { + $this->getResourceStorageModel()->deleteFolder($this->_removeAbsPathFromFileName($folderName)); + } + } + + /** + * Deletes from DB files, which belong to one folder + * + * @param string $filename + */ + public function deleteFile($filename) + { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel()->deleteFile($this->_removeAbsPathFromFileName($filename)); + } + } + + /** + * Saves uploaded by Varien_File_Uploader file to DB with existence tests + * + * param $result should be result from Varien_File_Uploader::save() method + * Checks in DB, whether uploaded file exists ($result['file']) + * If yes, renames file on FS (!!!!!) + * Saves file with unique name into DB + * If passed file exists returns new name, file was renamed to (in the same context) + * Otherwise returns $result['file'] + * + * @param array $result + * @return string + */ + public function saveUploadedFile($result = array()) + { + if ($this->checkDbUsage()) { + $path = str_replace('/', DS, $result['path']); + if (!in_array(substr($result['file'], 0, 1), array('/', DS)) + && !in_array(substr($result['path'], -1), array('/', DS))) { + $path = $path . '/'; + } + $uniqueResultFile = $this->getUniqueFilename($path, $result['file']); + if ($uniqueResultFile !== $result['file']) { + $ioObject = new Varien_Io_File(); + $ioObject->open(array('path' => $path)); + $ioObject->mv($path . $result['file'], $path . $uniqueResultFile); + } + $this->saveFile($path . $uniqueResultFile); + + return $uniqueResultFile; + } else { + return $result['file']; + } + } + + /** + * Convert full filepath to local (as used by model) + * If not - returns just a filename + * + * @param string $filename + * @return string + */ + protected function _removeAbsPathFromFileName($filename) + { + return $this->getMediaRelativePath($filename); + } + + /** + * Return Media base dir + * + * @return string + */ + public function getMediaBaseDir() + { + return rtrim(Mage::getBaseDir('media'), DS); + } +} diff --git a/app/code/core/Mage/Core/Helper/Js.php b/app/code/core/Mage/Core/Helper/Js.php index 2840f5312e..97a0828eb5 100644 --- a/app/code/core/Mage/Core/Helper/Js.php +++ b/app/code/core/Mage/Core/Helper/Js.php @@ -126,8 +126,8 @@ protected function _getTranslateData() 'Please select an option.' => $this->__('Please select an option.'), 'This is a required field.' => $this->__('This is a required field.'), 'Please enter a valid number in this field.' => $this->__('Please enter a valid number in this field.'), - 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.' => - $this->__('Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.'), + 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.' => + $this->__('Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.'), 'Please use letters only (a-z) in this field.' => $this->__('Please use letters only (a-z) in this field.'), 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => $this->__('Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.'), @@ -205,6 +205,10 @@ protected function _getTranslateData() 'Please enter a number lower than 100.' => $this->__('Please enter a number lower than 100.'), 'Please enter issue number or start date for switch/solo card type.' => $this->__('Please enter issue number or start date for switch/solo card type.'), + 'Please enter a valid day (1-%d).' => $this->__('Please enter a valid day (1-%d).'), + 'Please enter a valid month (1-12).' => $this->__('Please enter a valid month (1-12).'), + 'Please enter a valid year (1900-%d).' => $this->__('Please enter a valid year (1900-%d).'), + 'Please enter a valid full date' => $this->__('Please enter a valid full date') ); foreach ($this->_translateData as $key=>$value) { if ($key == $value) { diff --git a/app/code/core/Mage/Core/Helper/Translate.php b/app/code/core/Mage/Core/Helper/Translate.php new file mode 100644 index 0000000000..022d6c733c --- /dev/null +++ b/app/code/core/Mage/Core/Helper/Translate.php @@ -0,0 +1,54 @@ + + */ +class Mage_Core_Helper_Translate extends Mage_Core_Helper_Abstract +{ + /** + * Save transalation data to database for specific area + * + * @param array $translate + * @param string $area + * @param string $return_type + * @return string + */ + public function apply($translate, $area, $returnType = 'json') + { + try { + if ($area) { + Mage::getDesign()->setArea($area); + } + Mage::getModel('core/translate_inline')->processAjaxPost($translate); + return $returnType == 'json' ? "{success:true}" : true; + } catch (Exception $e) { + return $returnType == 'json' ? "{error:true,message:'" . $e->getMessage() . "'}" : false; + } + } +} diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php index 661303dcb0..c996950ed5 100644 --- a/app/code/core/Mage/Core/Model/App.php +++ b/app/code/core/Mage/Core/Model/App.php @@ -238,12 +238,12 @@ public function __construct() /** * Initialize application without request processing * - * @param string|array $code - * @param string $type - * @param string $etcDir + * @param string|array $code + * @param string $type + * @param string|array $options * @return Mage_Core_Model_App */ - public function init($code, $type=null, $options=array()) + public function init($code, $type = null, $options = array()) { $this->_initEnvironment(); if (is_string($options)) { @@ -252,6 +252,7 @@ public function init($code, $type=null, $options=array()) Varien_Profiler::start('mage::app::init::config'); $this->_config = Mage::getConfig(); + $this->_config->setOptions($options); $this->_initBaseConfig(); $this->_initCache(); $this->_config->init($options); @@ -265,22 +266,13 @@ public function init($code, $type=null, $options=array()) } /** - * Run application. Run process responsible for request processing and sending response. - * List of suppported parametes: - * scope_code - code of default scope (website/store_group/store code) - * scope_type - type of default scope (website/group/store) - * options - configuration options - * - * @param array $params application run parameters + * Common logic for all run types * + * @param string|array $options * @return Mage_Core_Model_App */ - public function run($params) + public function baseInit($options) { - $scopeCode = isset($params['scope_code']) ? $params['scope_code'] : ''; - $scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store'; - $options = isset($params['options']) ? $params['options'] : array(); - $this->_initEnvironment(); $this->_config = Mage::getConfig(); @@ -289,6 +281,48 @@ public function run($params) $this->_initBaseConfig(); $this->_initCache(); + return $this; + } + + /** + * Run light version of application with specified modules support + * + * @see Mage_Core_Model_App->run() + * + * @param string|array $scopeCode + * @param string $scopeType + * @param string|array $options + * @param string|array $modules + * @return Mage_Core_Model_App + */ + public function initSpecified($scopeCode, $scopeType = null, $options = array(), $modules = array()) + { + $this->baseInit($options); + + if (!empty($modules)) { + $this->_config->addAllowedModules($modules); + } + $this->_initModules(); + $this->_initCurrentStore($scopeCode, $scopeType); + + return $this; + } + + /** + * Run application. Run process responsible for request processing and sending response. + * List of supported parameters: + * scope_code - code of default scope (website/store_group/store code) + * scope_type - type of default scope (website/group/store) + * options - configuration options + * + * @param array $params application run parameters + * @return Mage_Core_Model_App + */ + public function run($params) + { + $options = isset($params['options']) ? $params['options'] : array(); + $this->baseInit($options); + if ($this->_cache->processRequest()) { $this->getResponse()->sendResponse(); } else { @@ -296,6 +330,8 @@ public function run($params) $this->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS); if ($this->_config->isLocalConfigLoaded()) { + $scopeCode = isset($params['scope_code']) ? $params['scope_code'] : ''; + $scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store'; $this->_initCurrentStore($scopeCode, $scopeType); $this->_initRequest(); Mage_Core_Model_Resource_Setup::applyAllDataUpdates(); diff --git a/app/code/core/Mage/Core/Model/Config.php b/app/code/core/Mage/Core/Model/Config.php index 82de1bea74..f8c4b60bd1 100644 --- a/app/code/core/Mage/Core/Model/Config.php +++ b/app/code/core/Mage/Core/Model/Config.php @@ -181,6 +181,14 @@ class Mage_Core_Model_Config extends Mage_Core_Model_Config_Base */ private $_moduleNamespaces = null; + /** + * Modules allowed to load + * If empty - all modules are allowed + * + * @var array + */ + protected $_allowedModules = array(); + /** * Class construct * @@ -679,6 +687,40 @@ protected function _getDeclaredModuleFiles() ); } + /** + * Add module(s) to allowed list + * + * @param strung|array $module + * @return Mage_Core_Model_Config + */ + public function addAllowedModules($module) + { + if (is_array($module)) { + foreach ($module as $moduleName) { + $this->addAllowedModules($moduleName); + } + } elseif (!in_array($module, $this->_allowedModules)) { + $this->_allowedModules[] = $module; + } + + return $this; + } + + /** + * Define if module is allowed + * + * @param string $moduleName + * @return bool + */ + protected function _isAllowedModule($moduleName) + { + if (empty($this->_allowedModules)) { + return true; + } else { + return in_array($moduleName, $this->_allowedModules); + } + } + /** * Load declared modules configuration * @@ -706,6 +748,10 @@ protected function _loadDeclaredModules($mergeConfig = null) $moduleDepends = array(); foreach ($unsortedConfig->getNode('modules')->children() as $moduleName => $moduleNode) { + if (!$this->_isAllowedModule($moduleName)) { + continue; + } + $depends = array(); if ($moduleNode->depends) { foreach ($moduleNode->depends->children() as $depend) { @@ -719,7 +765,7 @@ protected function _loadDeclaredModules($mergeConfig = null) ); } - // check and sort module dependens + // check and sort module dependence $moduleDepends = $this->_sortModuleDepends($moduleDepends); // create sorted config @@ -843,7 +889,7 @@ public function determineOmittedNamespace($name, $asFullModuleName = false) */ public function loadModulesConfiguration($fileName, $mergeToObject = null, $mergeModel=null) { - $disableLocalModules = !$this->_canUseLocalModules(); + $disableLocalModules = !$this->_canUseLocalModules(); if ($mergeToObject === null) { $mergeToObject = clone $this->_prototype; diff --git a/app/code/core/Mage/Core/Model/Date.php b/app/code/core/Mage/Core/Model/Date.php index b93d6a40c7..fa5cb19578 100644 --- a/app/code/core/Mage/Core/Model/Date.php +++ b/app/code/core/Mage/Core/Model/Date.php @@ -69,7 +69,7 @@ protected function _getConfigTimezone() /** * Calculates timezone offset * - * @var string $timezone + * @param string $timezone * @return int offset between timezone and gmt */ public function calculateOffset($timezone = null) @@ -96,8 +96,8 @@ public function calculateOffset($timezone = null) /** * Forms GMT date * - * @param string $format - * @param int || string $input date in current timezone + * @param string $format + * @param int|string $input date in current timezone * @return string */ public function gmtDate($format = null, $input = null) @@ -114,8 +114,9 @@ public function gmtDate($format = null, $input = null) * Converts input date into date with timezone offset * Input date must be in GMT timezone * - * @param string $format - * @param int || string $input date in GMT timezone + * @param string $format + * @param int|string $input date in GMT timezone + * @return string */ public function date($format = null, $input = null) { @@ -130,7 +131,8 @@ public function date($format = null, $input = null) /** * Forms GMT timestamp * - * @param int || string $input date in current timezone + * @param int|string $input date in current timezone + * @return int */ public function gmtTimestamp($input = null) { @@ -154,7 +156,8 @@ public function gmtTimestamp($input = null) * Converts input date into timestamp with timezone offset * Input date must be in GMT timezone * - * @param int || string $input date in GMT timezone + * @param int|string $input date in GMT timezone + * @return int */ public function timestamp($input = null) { @@ -176,7 +179,7 @@ public function timestamp($input = null) /** * Get current timezone offset in seconds/minutes/hours * - * @param string $type + * @param string $type * @return int */ public function getGmtOffset($type = 'seconds') diff --git a/app/code/core/Mage/Core/Model/Design/Package.php b/app/code/core/Mage/Core/Model/Design/Package.php index 74b2d0391d..44dcfaad5e 100644 --- a/app/code/core/Mage/Core/Model/Design/Package.php +++ b/app/code/core/Mage/Core/Model/Design/Package.php @@ -553,32 +553,52 @@ private function _listDirectories($path, $fullPath = false) */ protected function _checkUserAgentAgainstRegexps($regexpsConfigPath) { - if (!empty($_SERVER['HTTP_USER_AGENT'])) { - if (!empty(self::$_customThemeTypeCache[$regexpsConfigPath])) { - return self::$_customThemeTypeCache[$regexpsConfigPath]; + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + if (!empty(self::$_customThemeTypeCache[$regexpsConfigPath])) { + return self::$_customThemeTypeCache[$regexpsConfigPath]; + } + + $configValueSerialized = Mage::getStoreConfig($regexpsConfigPath, $this->getStore()); + + if (!$configValueSerialized) { + return false; + } + + $regexps = @unserialize($configValueSerialized); + + if (empty($regexps)) { + return false; + } + + return self::getPackageByUserAgent($regexps, $regexpsConfigPath); + } + + /** + * Return package name based on design exception rules + * + * @param array $rules - design exception rules + * @param string $regexpsConfigPath + */ + public static function getPackageByUserAgent(array $rules, $regexpsConfigPath = 'path_mock') + { + foreach ($rules as $rule) { + if (!empty(self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']])) { + self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; + return $rule['value']; } - $configValueSerialized = Mage::getStoreConfig($regexpsConfigPath, $this->getStore()); - if ($configValueSerialized) { - $regexps = @unserialize($configValueSerialized); - if (!empty($regexps)) { - foreach ($regexps as $rule) { - if (!empty(self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']])) { - self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; - return $rule['value']; - } - $regexp = $rule['regexp']; - if (false === strpos($regexp, '/', 0)) { - $regexp = '/' . $regexp . '/'; - } - if (@preg_match($regexp, $_SERVER['HTTP_USER_AGENT'])) { - self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']] = true; - self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; - return $rule['value']; - } - } - } + + $regexp = '/' . trim($rule['regexp'], '/') . '/'; + + if (@preg_match($regexp, $_SERVER['HTTP_USER_AGENT'])) { + self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']] = true; + self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; + return $rule['value']; } } + return false; } @@ -595,8 +615,8 @@ public function getMergedJsUrl($files) if (!$targetDir) { return ''; } - if (Mage::helper('core')->mergeFiles($files, $targetDir . DS . $targetFilename, false, null, 'js')) { - return Mage::getBaseUrl('media') . 'js/' . $targetFilename; + if ($this->_mergeFiles($files, $targetDir . DS . $targetFilename, false, null, 'js')) { + return Mage::getBaseUrl('media', Mage::app()->getRequest()->isSecure()) . 'js/' . $targetFilename; } return ''; } @@ -607,18 +627,64 @@ public function getMergedJsUrl($files) * @param $files * @return string */ - public function getMergedCssUrl($files) - { - $targetFilename = md5(implode(',', $files)) . '.css'; - $targetDir = $this->_initMergerDir('css'); + public function getMergedCssUrl($files) + { + // secure or unsecure + $isSecure = Mage::app()->getRequest()->isSecure(); + $mergerDir = $isSecure ? 'css_secure' : 'css'; + $targetDir = $this->_initMergerDir($mergerDir); if (!$targetDir) { return ''; } - if (Mage::helper('core')->mergeFiles($files, $targetDir . DS . $targetFilename, false, array($this, 'beforeMergeCss'), 'css')) { - return Mage::getBaseUrl('media') . 'css/' . $targetFilename; + + // base hostname & port + $baseMediaUrl = Mage::getBaseUrl('media', $isSecure); + $hostname = parse_url($baseMediaUrl, PHP_URL_HOST); + $port = parse_url($baseMediaUrl, PHP_URL_PORT); + if (false === $port) { + $port = $isSecure ? 443 : 80; + } + + // merge into target file + $targetFilename = md5(implode(',', $files) . "|{$hostname}|{$port}") . '.css'; + if ($this->_mergeFiles($files, $targetDir . DS . $targetFilename, false, array($this, 'beforeMergeCss'), 'css')) { + return $baseMediaUrl . $mergerDir . '/' . $targetFilename; } return ''; - } + } + + /** + * Merges files into one and saves it into DB (if DB file storage is on) + * + * @see Mage_Core_Helper_Data::mergeFiles() + * @param array $srcFiles + * @param string|false $targetFile - file path to be written + * @param bool $mustMerge + * @param callback $beforeMergeCallback + * @param array|string $extensionsFilter + * @return bool|string + */ + protected function _mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = false, $beforeMergeCallback = null, $extensionsFilter = array()) + { + if (Mage::helper('core/file_storage_database')->checkDbUsage()) { + if (!file_exists($targetFile)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($targetFile); + } + if (file_exists($targetFile)) { + $filemtime = filemtime($targetFile); + } else { + $filemtime = null; + } + $result = Mage::helper('core')->mergeFiles($srcFiles, $targetFile, $mustMerge, $beforeMergeCallback, $extensionsFilter); + if ($result && (filemtime($targetFile) > $filemtime)) { + Mage::helper('core/file_storage_database')->saveFile($targetFile); + } + return $result; + + } else { + return Mage::helper('core')->mergeFiles($srcFiles, $targetFile, $mustMerge, $beforeMergeCallback, $extensionsFilter); + } + } /** * Remove all merged js/css files @@ -628,7 +694,8 @@ public function getMergedCssUrl($files) public function cleanMergedJsCss() { $result = (bool)$this->_initMergerDir('js', true); - return (bool)$this->_initMergerDir('css', true) && $result; + $result = (bool)$this->_initMergerDir('css', true) && $result; + return (bool)$this->_initMergerDir('css_secure', true) && $result; } /** @@ -645,6 +712,7 @@ protected function _initMergerDir($dirRelativeName, $cleanup = false) $dir = Mage::getBaseDir('media') . DS . $dirRelativeName; if ($cleanup) { Varien_Io_File::rmdirRecursive($dir); + Mage::helper('core/file_storage_database')->deleteFolder($dir); } if (!is_dir($dir)) { mkdir($dir); diff --git a/app/code/core/Mage/Core/Model/File/Storage.php b/app/code/core/Mage/Core/Model/File/Storage.php new file mode 100644 index 0000000000..5f49288719 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage.php @@ -0,0 +1,162 @@ + + */ +class Mage_Core_Model_File_Storage extends Mage_Core_Model_Abstract +{ + /** + * Storage systems ids + */ + const STORAGE_MEDIA_FILE_SYSTEM = 0; + const STORAGE_MEDIA_DATABASE = 1; + + /** + * Config pathes for storing storage configuration + */ + const XML_PATH_STORAGE_MEDIA = 'default/system/media_storage_configuration/media_storage'; + const XML_PATH_STORAGE_MEDIA_DATABASE = 'default/system/media_storage_configuration/media_database'; + + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage'; + + /** + * Retrieve storage model + * If storage not defined - retrieve current storage + * + * params = array( + * connection => string, - define connection for model if needed + * init => bool - force initialization process for storage + * ) + * + * @param int|null $storage + * @param array $params + * @return Mage_Core_Model_Abstract|bool + */ + public function getStorageModel($storage = null, $params = array()) + { + if (is_null($storage)) { + $storage = Mage::helper('core/file_storage')->getCurrentStorage(); + } + + switch ($storage) { + case self::STORAGE_MEDIA_FILE_SYSTEM: + $model = Mage::getModel('core/file_storage_file'); + break; + case self::STORAGE_MEDIA_DATABASE: + $connection = (isset($params['connection'])) ? $params['connection'] : null; + $model = Mage::getModel('core/file_storage_database', array('connection' => $connection)); + break; + default: + return false; + } + + if (isset($params['init']) && $params['init']) { + $model->init(); + } + + return $model; + } + + /** + * Synchronize current media storage with defined + * $storage = array( + * type => int + * connection => string + * ) + * + * @param array $storage + * @param Mage_Core_Model_File_Storage_Flag|null $flag + * @return Mage_Core_Model_File_Storage + */ + public function synchronize($storage, Mage_Core_Model_File_Storage_Flag $flag = null) + { + if (isset($storage['type'])) { + $storageDest = (int) $storage['type']; + $connection = (isset($storage['connection'])) ? $storage['connection'] : null; + $helper = Mage::helper('core/file_storage'); + + // if unable to sync to internal storage from itself + if ($storageDest == $helper->getCurrentStorage() && $helper->isInternalStorage()) { + return $this; + } + + $sourceModel = $this->getStorageModel(); + $destinationModel = $this->getStorageModel( + $storageDest, + array( + 'connection' => $connection, + 'init' => true + ) + ); + + if (!$sourceModel || !$destinationModel) { + return $this; + } + + if ($flag) { + $flag->setFlagData(array( + 'source' => $sourceModel->getStorageName(), + 'destination' => $destinationModel->getStorageName() + )); + } + + $destinationModel->clear(); + + $offset = 0; + while (($dirs = $sourceModel->exportDirectories($offset)) !== false) { + if ($flag) { + $flag->save(); + } + + $destinationModel->importDirectories($dirs); + $offset += count($dirs); + } + + $offset = 0; + while (($files = $sourceModel->exportFiles($offset, 1)) !== false) { + if ($flag) { + $flag->save(); + } + + $destinationModel->importFiles($files); + $offset += count($files); + } + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Abstract.php b/app/code/core/Mage/Core/Model/File/Storage/Abstract.php new file mode 100644 index 0000000000..1f11992f46 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Abstract.php @@ -0,0 +1,95 @@ + + */ +abstract class Mage_Core_Model_File_Storage_Abstract extends Mage_Core_Model_Abstract +{ + /** + * Store media base directory path + * + * @var string + */ + protected $_mediaBaseDirectory = null; + + /** + * Retrieve media base directory path + * + * @return string + */ + public function getMediaBaseDirectory() + { + if (is_null($this->_mediaBaseDirectory)) { + $this->_mediaBaseDirectory = Mage::helper('core/file_storage_database')->getMediaBaseDir(); + } + + return $this->_mediaBaseDirectory; + } + + /** + * Collect file info + * Return array( + * filename => string + * content => string|bool + * update_time => string + * directory => string + * ) + * + * @param string $path + * @return array + */ + public function collectFileInfo($path) + { + $path = ltrim($path, DS); + $fullPath = $this->getMediaBaseDirectory() . DS . $path; + + if (!file_exists($fullPath) || !is_file($fullPath)) { + Mage::throwException(Mage::helper('core')->__('File %s does not exist', $fullPath)); + } + if (!is_readable($fullPath)) { + Mage::throwException(Mage::helper('core')->__('File %s is not readable', $fullPath)); + } + + $path = str_replace(DS, '/', $path); + $directory = dirname($path); + if ($directory == '.') { + $directory = null; + } + + return array( + 'filename' => basename($path), + 'content' => @file_get_contents($fullPath), + 'update_time' => Mage::getSingleton('core/date')->date(), + 'directory' => $directory + ); + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Database.php b/app/code/core/Mage/Core/Model/File/Storage/Database.php new file mode 100644 index 0000000000..43ad4bd9bf --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Database.php @@ -0,0 +1,303 @@ + + */ +class Mage_Core_Model_File_Storage_Database extends Mage_Core_Model_File_Storage_Database_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage_database'; + + /** + * Directory singleton + * + * @var Mage_Core_Model_File_Storage_Directory_Database + */ + protected $_directoryModel = null; + + /** + * Class construct + * + * @param string $connectionName + */ + public function __construct($connectionName = null) + { + $this->_init('core/file_storage_database'); + + parent::__construct($connectionName); + } + + /** + * Retrieve directory model + * + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function getDirectoryModel() + { + if (is_null($this->_directoryModel)) { + $this->_directoryModel = Mage::getModel( + 'core/file_storage_directory_database', + array('connection' => $this->getConnectionName())); + } + + return $this->_directoryModel; + } + + /** + * Create tables for file and directory storages + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function init() + { + $this->getDirectoryModel()->prepareStorage(); + $this->prepareStorage(); + + return $this; + } + + /** + * Return storage name + * + * @return string + */ + public function getStorageName() + { + return Mage::helper('core')->__('database "%s"', $this->getConnectionName()); + } + + /** + * Load object data by filename + * + * @param string $filePath + * @return Mage_Core_Model_File_Storage_Database + */ + public function loadByFilename($filePath) + { + $filename = basename($filePath); + $path = dirname($filePath); + $this->_getResource()->loadByFilename($this, $filename, $path); + return $this; + } + + /** + * Clear files and directories in storage + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function clear() + { + $this->getDirectoryModel()->clearDirectories(); + $this->_getResource()->clearFiles(); + return $this; + } + + /** + * Export directories from storage + * + * @param int $offset + * @param int $count + * @return bool|array + */ + public function exportDirectories($offset = 0, $count = 100) { + return $this->getDirectoryModel()->exportDirectories($offset, $count); + } + + /** + * Import directories to storage + * + * @param array $dirs + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function importDirectories($dirs) { + return $this->getDirectoryModel()->importDirectories($dirs); + } + + /** + * Export files list in defined range + * + * @param int $offset + * @param int $count + * @return array|bool + */ + public function exportFiles($offset = 0, $count = 100) + { + $offset = ((int) $offset >= 0) ? (int) $offset : 0; + $count = ((int) $count >= 1) ? (int) $count : 1; + + $result = $this->_getResource()->getFiles($offset, $count); + if (empty($result)) { + return false; + } + + return $result; + } + + /** + * Import files list + * + * @param array $files + * @return Mage_Core_Model_File_Storage_Database + */ + public function importFiles($files) + { + if (!is_array($files)) { + return $this; + } + + $dateSingleton = Mage::getSingleton('core/date'); + foreach ($files as $file) { + if (!isset($file['filename']) || !strlen($file['filename']) || !isset($file['content'])) { + continue; + } + + try { + $file['update_time'] = $dateSingleton->date(); + $file['directory_id'] = (isset($file['directory']) && strlen($file['directory'])) + ? Mage::getModel( + 'core/file_storage_directory_database', + array('connection' => $this->getConnectionName())) + ->loadByPath($file['directory'])->getId() + : null; + + $this->_getResource()->saveFile($file); + } catch (Exception $e) { + Mage::logException($e); + } + } + + return $this; + } + + /** + * Store file into database + * + * @param string $filename + * @return Mage_Core_Model_File_Storage_Database + */ + public function saveFile($filename) + { + $fileInfo = $this->collectFileInfo($filename); + $filePath = $fileInfo['directory']; + + $directory = Mage::getModel('core/file_storage_directory_database')->loadByPath($filePath); + + if (!$directory->getId()) { + $directory = $this->getDirectoryModel()->createRecursive($filePath); + } + + $fileInfo['directory_id'] = $directory->getId(); + $this->_getResource()->saveFile($fileInfo); + + return $this; + } + + /** + * Check whether file exists in DB + * + * @param string $filePath + * @return bool + */ + public function fileExists($filePath) + { + return $this->_getResource()->fileExists(basename($filePath), dirname($filePath)); + } + + /** + * Copy files + * + * @param string $oldFilePath + * @param string $newFilePath + * @return Mage_Core_Model_File_Storage_Database + */ + public function copyFile($oldFilePath, $newFilePath) + { + $this->_getResource()->copyFile( + basename($oldFilePath), + dirname($oldFilePath), + basename($newFilePath), + dirname($newFilePath) + ); + + return $this; + } + + /** + * Rename files in database + * + * @param string $oldFilePath + * @param string $newFilePath + * @return Mage_Core_Model_File_Storage_Database + */ + public function renameFile($oldFilePath, $newFilePath) + { + $this->_getResource()->renameFile( + basename($oldFilePath), + dirname($oldFilePath), + basename($newFilePath), + dirname($newFilePath) + ); + + return $this; + } + + /** + * Return directory listing + * + * @param string $directory + * @return mixed + */ + public function getDirectoryFiles($directory) + { + $directory = Mage::helper('core/file_storage_database')->getMediaRelativePath($directory); + return $this->_getResource()->getDirectoryFiles($directory); + } + + /** + * Delete file from database + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Database + */ + public function deleteFile($path) + { + $filename = basename($path); + $directory = dirname($path); + $this->_getResource()->deleteFile($filename, $directory); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Database/Abstract.php b/app/code/core/Mage/Core/Model/File/Storage/Database/Abstract.php new file mode 100644 index 0000000000..e859088a9d --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Database/Abstract.php @@ -0,0 +1,96 @@ + + */ +abstract class Mage_Core_Model_File_Storage_Database_Abstract extends Mage_Core_Model_File_Storage_Abstract +{ + /** + * Class construct + * + * @param string $databaseConnection + */ + public function __construct($params = array()) + { + $connectionName = (isset($params['connection'])) ? $params['connection'] : null; + if (empty($connectionName)) { + $connectionName = (string) Mage::app()->getConfig() + ->getNode(Mage_Core_Model_File_Storage::XML_PATH_STORAGE_MEDIA_DATABASE); + if (empty($connectionName)) { + $connectionName = 'default_setup'; + } + } + + $this->setConnectionName($connectionName); + } + + /** + * Get resource instance + * + * @return Mage_Core_Model_Mysql4_Abstract + */ + protected function _getResource() + { + $resource = parent::_getResource(); + $resource->setConnectionName($this->getConnectionName()); + + return $resource; + } + + /** + * Prepare data storage + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function prepareStorage() + { + $this->_getResource()->createDatabaseScheme(); + + return $this; + } + + /** + * Specify connection name + * + * @param $connectionName + * @return Mage_Core_Model_File_Storage_Database + */ + public function setConnectionName($connectionName) + { + if (!empty($connectionName)) { + $this->setData('connection_name', $connectionName); + $this->_getResource()->setConnectionName($connectionName); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Directory/Database.php b/app/code/core/Mage/Core/Model/File/Storage/Directory/Database.php new file mode 100644 index 0000000000..1c7e0799c8 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Directory/Database.php @@ -0,0 +1,235 @@ + + */ +class Mage_Core_Model_File_Storage_Directory_Database extends Mage_Core_Model_File_Storage_Database_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage_directory_database'; + + /** + * Class construct + * + * @param string $databaseConnection + */ + public function __construct($connectionName = null) + { + $this->_init('core/file_storage_directory_database'); + + parent::__construct($connectionName); + } + + /** + * Load object data by path + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function loadByPath($path) + { + /** + * Clear model data + * addData() is used because it's needed to clear only db storaged data + */ + $this->addData( + array( + 'directory_id' => null, + 'name' => null, + 'path' => null, + 'upload_time' => null, + 'parent_id' => null + ) + ); + + $this->_getResource()->loadByPath($this, $path); + return $this; + } + + /** + * Retrieve directory parent id + * + * @return int + */ + public function getParentId() + { + if (!$this->getData('parent_id')) { + $parentId = $this->_getResource()->getParentId($this->getPath()); + if (empty($parentId)) { + $parentId = null; + } + + $this->setData('parent_id', $parentId); + } + + return $parentId; + } + + /** + * Create directories recursively + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function createRecursive($path) + { + $directory = Mage::getModel('core/file_storage_directory_database')->loadByPath($path); + + if (!$directory->getId()) { + $dirName = basename($path); + $dirPath = dirname($path); + + if ($dirPath != '.') { + $parentDir = $this->createRecursive($dirPath); + $parentId = $parentDir->getId(); + } else { + $dirPath = ''; + $parentId = null; + } + + $directory->setName($dirName); + $directory->setPath($dirPath); + $directory->setParentId($parentId); + $directory->save(); + } + + return $directory; + } + + /** + * Export directories from storage + * + * @param int $offset + * @param int $count + * @return bool + */ + public function exportDirectories($offset = 0, $count = 100) + { + $offset = ((int) $offset >= 0) ? (int) $offset : 0; + $count = ((int) $count >= 1) ? (int) $count : 1; + + $result = $this->_getResource()->exportDirectories($offset, $count); + + if (empty($result)) { + return false; + } + + return $result; + } + + /** + * Import directories to storage + * + * @param array $dirs + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function importDirectories($dirs) + { + if (!is_array($dirs)) { + return $this; + } + + $dateSingleton = Mage::getSingleton('core/date'); + foreach ($dirs as $dir) { + if (!is_array($dir) || !isset($dir['name']) || !strlen($dir['name'])) { + continue; + } + + try { + $directory = Mage::getModel('core/file_storage_directory_database', array('connection' => $this->getConnectionName())); + $directory->setPath($dir['path']); + + $parentId = $directory->getParentId(); + if ($parentId || $dir['path'] == '') { + $directory->setName($dir['name']); + $directory->setUploadTime($dateSingleton->date()); + $directory->save(); + } else { + Mage::throwException(Mage::helper('core')->__('Parent directory does not exist: %s', $dir['path'])); + } + } catch (Exception $e) { + Mage::logException($e); + } + } + + return $this; + } + + /** + * Clean directories at storage + * + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function clearDirectories() + { + $this->_getResource()->clearDirectories(); + return $this; + } + + /** + * Return subdirectories + * + * @param string $directory + * @return mixed + */ + public function getSubdirectories($directory) + { + $directory = Mage::helper('core/file_storage_database')->getMediaRelativePath($directory); + + return $this->_getResource()->getSubdirectories($directory); + } + + /** + * Delete directory from database + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function deleteDirectory($dirPath) + { + $dirPath = Mage::helper('core/file_storage_database')->getMediaRelativePath($dirPath); + $name = basename($dirPath); + $path = dirname($dirPath); + + if ('.' == $path) { + $path = ''; + } + + $this->_getResource()->deleteDirectory($name, $path); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/File.php b/app/code/core/Mage/Core/Model/File/Storage/File.php new file mode 100644 index 0000000000..3afb26ac85 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/File.php @@ -0,0 +1,257 @@ + + */ +class Mage_Core_Model_File_Storage_File extends Mage_Core_Model_File_Storage_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage_file'; + + /** + * Data at storage + * + * @var array + */ + protected $_data = null; + + /** + * Class construct + */ + public function __construct() + { + $this->_init('core/file_storage_file'); + } + + /** + * Initialization + * + * @return Mage_Core_Model_File_Storage_File + */ + public function init() + { + return $this; + } + + /** + * Return storage name + * + * @return string + */ + public function getStorageName() + { + return Mage::helper('core')->__('File system'); + } + + /** + * Get files and directories from storage + * + * @return array + */ + public function getStorageData() + { + return $this->_getResource()->getStorageData(); + } + + /** + * Clear files and directories in storage + * + * @return Mage_Core_Model_File_Storage_File + */ + public function clear() + { + $this->_getResource()->clear(); + return $this; + } + + /** + * Collect files and directories from storage + * + * @param int $offset + * @param int $count + * @param string $type + * @return array|bool + */ + public function collectData($offset = 0, $count = 100, $type = 'files') + { + if (!in_array($type, array('files', 'directories'))) { + return false; + } + + $offset = ((int) $offset >= 0) ? (int) $offset : 0; + $count = ((int) $count >= 1) ? (int) $count : 1; + + if (is_null($this->_data)) { + $this->_data = $this->getStorageData(); + } + + $slice = array_slice($this->_data[$type], $offset, $count); + if (empty($slice)) { + return false; + } + + return $slice; + } + + /** + * Export directories list from storage + * + * @param int $offset + * @param int $count + * @return array|bool + */ + public function exportDirectories($offset = 0, $count = 100) + { + return $this->collectData($offset, $count, 'directories'); + } + + /** + * Export files list in defined range + * + * @param int $offset + * @param int $count + * @return array|bool + */ + public function exportFiles($offset = 0, $count = 1) + { + $slice = $this->collectData($offset, $count, 'files'); + + if (!$slice) { + return false; + } + + $result = array(); + foreach ($slice as $fileName) { + try { + $fileInfo = $this->collectFileInfo($fileName); + } catch (Exception $e) { + Mage::logException($e); + continue; + } + + $result[] = $fileInfo; + } + + return $result; + } + + /** + * Import entities to storage + * + * @param array $data + * @param string $callback + * @return Mage_Core_Model_File_Storage_File + */ + public function import($data, $callback) + { + if (!is_array($data) || !method_exists($this, $callback)) { + return $this; + } + + foreach ($data as $part) { + try { + $this->$callback($part); + } catch (Exception $e) { + Mage::logException($e); + } + } + + return $this; + } + + /** + * Import directories to storage + * + * @param array $dirs + * @return Mage_Core_Model_File_Storage_File + */ + public function importDirectories($dirs) + { + return $this->import($dirs, 'saveDir'); + } + + /** + * Import files list + * + * @param array $files + * @return Mage_Core_Model_File_Storage_File + */ + public function importFiles($files) + { + return $this->import($files, 'saveFile'); + } + + /** + * Save directory to storage + * + * @param array $dir + * @return bool + */ + public function saveDir($dir) + { + return $this->_getResource()->saveDir($dir); + } + + /** + * Save file to storage + * + * @param array|Mage_Core_Model_File_Storage_Database $file + * @param bool $overwrite + * @return bool|int + */ + public function saveFile($file, $overwrite = true) + { + if (isset($file['filename']) && !empty($file['filename']) + && isset($file['content']) && !empty($file['content']) + ) { + try { + $filename = (isset($file['directory']) && !empty($file['directory'])) + ? $file['directory'] . DS . $file['filename'] + : $file['filename']; + + return $this->_getResource() + ->saveFile($filename, $file['content'], $overwrite); + } catch (Exception $e) { + Mage::logException($e); + Mage::throwException(Mage::helper('core')->__('Unable to save file: %s', $file['filename'])); + } + } else { + Mage::throwException(Mage::helper('core')->__('Wrong file info format')); + } + + return false; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Flag.php b/app/code/core/Mage/Core/Model/File/Storage/Flag.php new file mode 100644 index 0000000000..38d7aaae49 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Flag.php @@ -0,0 +1,51 @@ + + */ +class Mage_Core_Model_File_Storage_Flag extends Mage_Core_Model_Flag +{ + const STATE_INACTIVE = 0; + const STATE_RUNNING = 1; + + /** + * Flag time to life in seconds + */ + const FLAG_TTL = 300; + + /** + * Synchronize flag code + * + * @var string + */ + protected $_flagCode = 'synchronize'; +} diff --git a/app/code/core/Mage/Core/Model/File/Uploader.php b/app/code/core/Mage/Core/Model/File/Uploader.php new file mode 100644 index 0000000000..e335b254ca --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Uploader.php @@ -0,0 +1,62 @@ + + */ +class Mage_Core_Model_File_Uploader extends Varien_File_Uploader +{ + /** + * Save file to storage + * + * @param array $result + * @return Mage_Core_Model_File_Uploader + */ + protected function _afterSave($result) + { + if (!isset($result['path']) || empty($result['path']) + || !isset($result['file']) || empty($result['file']) + ) { + return $this; + } + + $helper = Mage::helper('core/file_storage'); + + if ($helper->isInternalStorage()) { + return $this; + } + + $dbHelper = Mage::helper('core/file_storage_database'); + $dbHelper->saveFile($result['path'] . $result['file']); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Locale.php b/app/code/core/Mage/Core/Model/Locale.php index 2c70899f97..4f3e02d132 100644 --- a/app/code/core/Mage/Core/Model/Locale.php +++ b/app/code/core/Mage/Core/Model/Locale.php @@ -43,6 +43,9 @@ class Mage_Core_Model_Locale */ const XML_PATH_DEFAULT_LOCALE = 'general/locale/code'; const XML_PATH_DEFAULT_TIMEZONE = 'general/locale/timezone'; + /** + * @deprecated since 1.4.1.0 + */ const XML_PATH_DEFAULT_COUNTRY = 'general/country/default'; const XML_PATH_ALLOW_CODES = 'global/locale/allow/codes'; const XML_PATH_ALLOW_CURRENCIES = 'global/locale/allow/currencies'; diff --git a/app/code/core/Mage/Core/Model/Locale/Config.php b/app/code/core/Mage/Core/Model/Locale/Config.php index 5a10448453..d48b238e96 100644 --- a/app/code/core/Mage/Core/Model/Locale/Config.php +++ b/app/code/core/Mage/Core/Model/Locale/Config.php @@ -58,7 +58,7 @@ class Mage_Core_Model_Locale_Config 'th_TH' /*Thai (Thailand)*/, 'tr_TR' /*Turkish (Turkey)*/, 'uk_UA' /*Ukrainian (Ukraine)*/, 'vi_VN' /*Vietnamese (Vietnam)*/, 'zh_CN' /*Chinese (China)*/, 'zh_HK' /*Chinese (Hong Kong SAR)*/, 'zh_TW' /*Chinese (Taiwan)*/, 'es_CL' /*Spanich (Chile)*/, 'lo_LA' /*Laotian*/, - 'es_VE' /*Spanish (Venezuela)*/, + 'es_VE' /*Spanish (Venezuela)*/, 'en_IE' /*English (Ireland)*/, ); /** diff --git a/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php b/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php index 39275c5ba5..a85e6ac289 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php @@ -92,13 +92,15 @@ abstract class Mage_Core_Model_Mysql4_Collection_Abstract extends Varien_Data_Co protected $_resetItemsDataChanged = false; /** - * Event prefix key + * Name prefix of events that are dispatched by model + * * @var string */ protected $_eventPrefix = ''; /** - * Event object key + * Name of event parameter + * * @var string */ protected $_eventObject = ''; @@ -539,6 +541,11 @@ protected function _afterLoad() } } Mage::dispatchEvent('core_collection_abstract_load_after', array('collection' => $this)); + if ($this->_eventPrefix && $this->_eventObject) { + Mage::dispatchEvent($this->_eventPrefix.'_load_after', array( + $this->_eventObject => $this + )); + } return $this; } diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Abstract.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Abstract.php new file mode 100644 index 0000000000..e72826184d --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Abstract.php @@ -0,0 +1,87 @@ + + */ +abstract class Mage_Core_Model_Mysql4_File_Storage_Abstract extends Mage_Core_Model_Mysql4_Abstract +{ + /** + * File storage connection name + * + * @var string + */ + protected $_connectionName = null; + + + public function setConnectionName($name) + { + $this->_connectionName = $name; + return $this; + } + + /** + * Retrieve connection for read data + * + * @return Varien_Db_Adapter_Pdo_Mysql + */ + protected function _getReadAdapter() + { + return $this->_getConnection($this->_connectionName); + } + + /** + * Retrieve connection for write data + * + * @return Varien_Db_Adapter_Pdo_Mysql + */ + protected function _getWriteAdapter() + { + return $this->_getConnection($this->_connectionName); + } + + /** + * Get connection by name or type + * + * @param string $connectionName + * @return Zend_Db_Adapter_Abstract + */ + protected function _getConnection($connectionName) + { + if (isset($this->_connections[$connectionName])) { + return $this->_connections[$connectionName]; + } + + $this->_connections[$connectionName] = $this->_resources->getConnection($connectionName); + + return $this->_connections[$connectionName]; + } +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Database.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Database.php new file mode 100644 index 0000000000..86cca0596b --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Database.php @@ -0,0 +1,279 @@ + + */ +class Mage_Core_Model_Mysql4_File_Storage_Database extends Mage_Core_Model_Mysql4_File_Storage_Abstract +{ + /** + * Define table name and id field for resource + */ + protected function _construct() + { + $this->_init('core/file_storage', 'file_id'); + } + + /** + * Create database scheme for storing files + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function createDatabaseScheme() + { + $this->_getWriteAdapter()->multi_query("CREATE TABLE IF NOT EXISTS {$this->getMainTable()} ( + `file_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `content` LONGBLOB NOT NULL, + `upload_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `filename` varchar(255) NOT NULL DEFAULT '', + `directory_id` int(10) unsigned DEFAULT NULL, + `directory` varchar(255) DEFAULT NULL, + PRIMARY KEY (`file_id`), + UNIQUE KEY `IDX_FILENAME` (`filename`, `directory`), + KEY (`directory_id`), + CONSTRAINT `FK_FILE_DIRECTORY` FOREIGN KEY (`directory_id`) + REFERENCES {$this->getTable('core/directory_storage')} (`directory_id`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='File storage'"); + + return $this; + } + + /** + * Load entity by filename + * + * @param Mage_Core_Model_File_Storage_Database $object + * @param string $filename + * @param string $path + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function loadByFilename(Mage_Core_Model_File_Storage_Database $object, $filename, $path) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('filename = ?', $filename) + ->where('directory = ?', $path); + + if ($data = $adapter->fetchRow($select)) { + $object->setData($data); + $this->_afterLoad($object); + } + + return $this; + } + + /** + * Clear files in storage + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function clearFiles() + { + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable()); + + return $this; + } + + /** + * Get files from storage at defined range + * + * @param int $offset + * @param int $count + * @return array + */ + public function getFiles($offset = 0, $count = 100) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('filename', 'content', 'directory') + ) + ->order('file_id') + ->limit($count, $offset); + + return $adapter->fetchAll($select); + } + + /** + * Save matched product Ids + * + * @param Mage_Core_Model_File_Storage_Database|array $object + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function saveFile($file) + { + $adapter = $this->_getWriteAdapter(); + $data = array( + 'content' => $file['content'], + 'upload_time' => $file['update_time'], + 'filename' => $file['filename'], + 'directory_id' => $file['directory_id'], + 'directory' => $file['directory'] + ); + + $adapter->insertOnDuplicate($this->getMainTable(), $data, array('content', 'upload_time')); + + return $this; + } + + /** + * Rename files in database + * + * @param string $oldFilename + * @param string $oldPath + * @param string $newFilename + * @param string $newPath + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function renameFile($oldFilename, $oldPath, $newFilename, $newPath) + { + $adapter = $this->_getWriteAdapter(); + $dataUpdate = array('filename' => $newFilename, 'directory' => $newPath); + $dataWhere = array('filename = ?' => $oldFilename, 'directory = ?' => $oldPath); + + $adapter->update($this->getMainTable(), $dataUpdate, $dataWhere); + + return $this; + } + + /** + * Copy files in database + * + * @param string $oldFilename + * @param string $oldPath + * @param string $newFilename + * @param string $newPath + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function copyFile($oldFilename, $oldPath, $newFilename, $newPath) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('filename = ?', $oldFilename) + ->where('directory = ?', $oldPath); + + $data = $adapter->fetchRow($select); + if (!$data) { + return $this; + } + + if (isset($data['file_id']) && isset($data['filename'])) { + unset($data['file_id']); + $data['filename'] = $newFilename; + $data['directory'] = $newPath; + + $writeAdapter = $this->_getWriteAdapter(); + $writeAdapter->insertOnDuplicate($this->getMainTable(), $data, array('content', 'upload_time')); + } + + return $this; + } + + /** + * Check whether file exists in DB + * + * @param string $filename + * @param string $path + * @return bool + */ + public function fileExists($filename, $path) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('filename = ?', $filename) + ->where('directory = ?', $path) + ->limit(1); + + $data = $adapter->fetchRow($select); + return (bool)$data; + } + + /** + * Delete files that starts with given $folderName + * + * @param string $folderName + */ + public function deleteFolder($folderName = '') + { + if ($folderName && ($folderName !== '/')) { + $folderName = str_replace(array('%','_'), array('\%','\_'), $folderName); + $folderName = rtrim($folderName, '/'); + + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable(), new Zend_Db_Expr('filename LIKE "' . $folderName . '/%"')); + } + } + + /** + * Delete file + * + * @param string $filename + * @param string $directory + */ + public function deleteFile($filename, $directory) + { + $this->_getWriteAdapter() + ->delete($this->getMainTable(), array('filename = ?' => $filename, 'directory = ?' => $directory)); + } + + /** + * Return directory file listing + * + * @param string $directory + * @return mixed + */ + public function getDirectoryFiles($directory) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array( + 'filename', + 'directory', + 'content' + ) + ) + ->where('directory = ?', trim($directory, '/')) + ->order('file_id'); + + return $adapter->fetchAll($select); + } +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Directory/Database.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Directory/Database.php new file mode 100644 index 0000000000..fd1dccd882 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Directory/Database.php @@ -0,0 +1,192 @@ + + */ +class Mage_Core_Model_Mysql4_File_Storage_Directory_Database extends Mage_Core_Model_Mysql4_File_Storage_Abstract +{ + /** + * Define table name and id field for resource + */ + protected function _construct() + { + $this->_init('core/directory_storage', 'directory_id'); + } + + /** + * Create database scheme for storing files + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function createDatabaseScheme() + { + $this->_getWriteAdapter()->multi_query("CREATE TABLE IF NOT EXISTS {$this->getMainTable()} ( + `directory_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '', + `path` varchar(255) NOT NULL DEFAULT '', + `upload_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `parent_id` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`directory_id`), + UNIQUE KEY `IDX_DIRECTORY_PATH` (`name`, `path`), + KEY `parent_id` (`parent_id`), + CONSTRAINT `FK_DIRECTORY_PARENT_ID` FOREIGN KEY (`parent_id`) + REFERENCES `core_directory_storage` (`directory_id`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Directory storage'"); + + return $this; + } + + /** + * Load entity by path + * + * @param Mage_Core_Model_File_Storage_Directory_Database $object + * @param string $path + * @return Mage_Core_Model_Mysql4_File_Storage_Directory_Database + */ + public function loadByPath(Mage_Core_Model_File_Storage_Directory_Database $object, $path) + { + $adapter = $this->_getReadAdapter(); + + $name = basename($path); + $path = dirname($path); + if ($path == '.') { + $path = ''; + } + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('name = ?', $name) + ->where('path = ?', $path); + + if ($data = $adapter->fetchRow($select)) { + $object->setData($data); + $this->_afterLoad($object); + } + + return $this; + } + + /** + * Return parent id + * + * @param string $path + * @return int + */ + public function getParentId($path) + { + $adapter = $this->_getReadAdapter(); + + $name = basename($path); + $path = dirname($path); + if ($path == '.') { + $path = ''; + } + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('directory_id') + ) + ->where('name = ?', $name) + ->where('path = ?', $path); + + return $adapter->fetchOne($select); + } + + /** + * Delete all directories from storage + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function clearDirectories() + { + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable()); + + return $this; + } + + /** + * Export directories from database + * + * @param int $offset + * @param int $count + * @return mixed + */ + public function exportDirectories($offset, $count = 100) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('name', 'path') + ) + ->order('directory_id') + ->limit($count, $offset); + + return $adapter->fetchAll($select); + } + + /** + * Return directory file listing + * + * @param string $directory + * @return mixed + */ + public function getSubdirectories($directory) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('name', 'path') + ) + ->where('path = ?', trim($directory, '/')) + ->order('directory_id'); + + return $adapter->fetchAll($select); + } + + /** + * Delete directory + * + * @param string $name + * @param string $path + */ + public function deleteDirectory($name, $path) + { + $this->_getWriteAdapter() + ->delete($this->getMainTable(), array('name = ?' => $name, 'path = ?' => $path)); + } +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/File.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/File.php new file mode 100644 index 0000000000..9d2545e480 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/File.php @@ -0,0 +1,199 @@ + + */ +class Mage_Core_Model_Mysql4_File_Storage_File +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_mediaBaseDirectory = null; + + /** + * Files at storage + * + * @var array + */ + public function getMediaBaseDirectory() + { + if (is_null($this->_mediaBaseDirectory)) { + $this->_mediaBaseDirectory = Mage::helper('core/file_storage_database')->getMediaBaseDir(); + } + + return $this->_mediaBaseDirectory; + } + + /** + * Collect files and directories recursively + * + * @param string$dir + * @return array + */ + public function getStorageData($dir = '') + { + $files = array(); + $directories = array(); + $currentDir = $this->getMediaBaseDirectory() . $dir; + + if (is_dir($currentDir)) { + $dh = opendir($currentDir); + if ($dh) { + while (($file = readdir($dh)) !== false) { + if ($file == '.' || $file == '..') { + continue; + } + + $fullPath = $currentDir . DS . $file; + $relativePath = $dir . DS . $file; + if (is_dir($fullPath)) { + $directories[] = array( + 'name' => $file, + 'path' => str_replace(DS, '/', ltrim($dir, DS)) + ); + + $data = $this->getStorageData($relativePath); + $directories = array_merge($directories, $data['directories']); + $files = array_merge($files, $data['files']); + } else { + $files[] = $relativePath; + } + } + closedir($dh); + } + } + + return array('files' => $files, 'directories' => $directories); + } + + /** + * Clear files and directories in storage + * + * @param string $dir + * @return Mage_Core_Model_Mysql4_File_Storage_File + */ + public function clear($dir = '') + { + $currentDir = $this->getMediaBaseDirectory() . $dir; + + if (is_dir($currentDir)) { + $dh = opendir($currentDir); + if ($dh) { + while (($file = readdir($dh)) !== false) { + if ($file == '.' || $file == '..') { + continue; + } + + $fullPath = $currentDir . DS . $file; + if (is_dir($fullPath)) { + $this->clear($dir . DS . $file); + } else { + @unlink($fullPath); + } + } + closedir($dh); + @rmdir($currentDir); + } + } + + return $this; + } + + /** + * Save directory to storage + * + * @param array $dir + * @return bool + */ + public function saveDir($dir) + { + if (!isset($dir['name']) || !strlen($dir['name']) + || !isset($dir['path']) + ) { + return false; + } + + $path = (strlen($dir['path'])) + ? $dir['path'] . DS . $dir['name'] + : $dir['name']; + $path = Mage::helper('core/file_storage_database')->getMediaBaseDir() . DS . str_replace('/', DS, $path); + + if (!file_exists($path) || !is_dir($path)) { + if (!@mkdir($path, 0777, true)) { + Mage::throwException(Mage::helper('core')->__('Unable to create directory: %s', $path)); + } + } + + return true; + } + + /** + * Save file to storage + * + * @param string $filePath + * @param string $content + * @param bool $overwrite + * @return bool + */ + public function saveFile($filePath, $content, $overwrite = false) + { + $filename = basename($filePath); + $path = $this->getMediaBaseDirectory() . DS . str_replace('/', DS ,dirname($filePath)); + + if (!file_exists($path) || !is_dir($path)) { + @mkdir($path, 0777, true); + } + + $ioFile = new Varien_Io_File(); + $ioFile->cd($path); + + if ($ioFile->fileExists($filename)) { + if (!$overwrite) { + return false; + } + if (!$ioFile->rm($filename)) { + return false; + } + } + + $ioFile->streamOpen($filename); + $ioFile->streamLock(true); + $result = $ioFile->streamWrite($content); + $ioFile->streamUnlock(); + $ioFile->streamClose(); + + return $result; + } +} diff --git a/app/code/core/Mage/Core/Model/Store.php b/app/code/core/Mage/Core/Model/Store.php index e49f41a172..bf2a618e32 100644 --- a/app/code/core/Mage/Core/Model/Store.php +++ b/app/code/core/Mage/Core/Model/Store.php @@ -36,30 +36,37 @@ class Mage_Core_Model_Store extends Mage_Core_Model_Abstract { const ENTITY = 'core_store'; - const XML_PATH_STORE_IN_URL = 'web/url/use_store'; - const XML_PATH_USE_REWRITES = 'web/seo/use_rewrites'; - const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url'; - const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url'; - const XML_PATH_SECURE_IN_FRONTEND = 'web/secure/use_in_frontend'; - const XML_PATH_SECURE_IN_ADMINHTML = 'web/secure/use_in_adminhtml'; + const XML_PATH_STORE_IN_URL = 'web/url/use_store'; + const XML_PATH_USE_REWRITES = 'web/seo/use_rewrites'; + const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url'; + const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url'; + const XML_PATH_SECURE_IN_FRONTEND = 'web/secure/use_in_frontend'; + const XML_PATH_SECURE_IN_ADMINHTML = 'web/secure/use_in_adminhtml'; + const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url'; + const XML_PATH_UNSECURE_BASE_LINK_URL = 'web/unsecure/base_link_url'; - const XML_PATH_PRICE_SCOPE = 'catalog/price/scope'; - const PRICE_SCOPE_GLOBAL = 0; - const PRICE_SCOPE_WEBSITE = 1; + const XML_PATH_PRICE_SCOPE = 'catalog/price/scope'; + const PRICE_SCOPE_GLOBAL = 0; + const PRICE_SCOPE_WEBSITE = 1; - const URL_TYPE_LINK = 'link'; - const URL_TYPE_DIRECT_LINK = 'direct_link'; - const URL_TYPE_WEB = 'web'; - const URL_TYPE_SKIN = 'skin'; - const URL_TYPE_JS = 'js'; - const URL_TYPE_MEDIA = 'media'; + const URL_TYPE_LINK = 'link'; + const URL_TYPE_DIRECT_LINK = 'direct_link'; + const URL_TYPE_WEB = 'web'; + const URL_TYPE_SKIN = 'skin'; + const URL_TYPE_JS = 'js'; + const URL_TYPE_MEDIA = 'media'; - const DEFAULT_CODE = 'default'; - const ADMIN_CODE = 'admin'; + const DEFAULT_CODE = 'default'; + const ADMIN_CODE = 'admin'; - const CACHE_TAG = 'store'; + const CACHE_TAG = 'store'; - const COOKIE_NAME = 'store'; + const COOKIE_NAME = 'store'; + + /** + * Script name, which returns all the images + */ + const MEDIA_REWRITE_SCRIPT = 'get.php/'; protected $_cacheTag = true; @@ -121,8 +128,8 @@ protected function _construct() self::XML_PATH_STORE_IN_URL, self::XML_PATH_UNSECURE_BASE_URL, self::XML_PATH_USE_REWRITES, - 'web/unsecure/base_link_url', - 'web/secure/base_link_url', + self::XML_PATH_UNSECURE_BASE_LINK_URL, + self::XML_PATH_SECURE_BASE_LINK_URL, 'general/locale/code' ); } @@ -351,9 +358,9 @@ protected function _processConfigValue($fullPath, $path, $node) } /** - * Enter description here... + * Convert config values for url pathes * - * @todo check and delete this if it is not used anymore + * @deprecated after 1.4.2.0 * @param string $value * @return string */ @@ -427,12 +434,15 @@ public function getBaseUrl($type=self::URL_TYPE_LINK, $secure=null) break; case self::URL_TYPE_SKIN: - case self::URL_TYPE_MEDIA: case self::URL_TYPE_JS: $secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure; $url = $this->getConfig('web/'.($secure ? 'secure' : 'unsecure').'/base_'.$type.'_url'); break; + case self::URL_TYPE_MEDIA: + $url = $this->_updateMediaPathUseRewrites($secure); + break; + default: throw Mage::exception('Mage_Core', Mage::helper('core')->__('Invalid base url type')); } @@ -459,6 +469,29 @@ protected function _updatePathUseRewrites($url) return $url; } + /** + * Gets URL for media catalog. + * If we use Database file storage and server doesn't support rewrites (.htaccess in media folder) + * we have to put name of fetching media script exactly into URL + * + * @param null|boolean $secure + * @param string $type + * @return string + */ + protected function _updateMediaPathUseRewrites($secure=null, $type = self::URL_TYPE_MEDIA) + { + $secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure; + $secureStringFlag = $secure ? 'secure' : 'unsecure'; + $url = $this->getConfig('web/' . $secureStringFlag . '/base_' . $type . '_url'); + if (!$this->getConfig(self::XML_PATH_USE_REWRITES) + && Mage::helper('core/file_storage_database')->checkDbUsage()) { + + $urlStart = $this->getConfig('web/' . $secureStringFlag . '/base_url'); + $url = str_replace($urlStart, $urlStart . self::MEDIA_REWRITE_SCRIPT, $url); + } + return $url; + } + /** * Add store code to url in case if it is enabled in configuration * diff --git a/app/code/core/Mage/Core/Model/Template.php b/app/code/core/Mage/Core/Model/Template.php index 27f55b25ae..2ac49f8dcd 100644 --- a/app/code/core/Mage/Core/Model/Template.php +++ b/app/code/core/Mage/Core/Model/Template.php @@ -52,14 +52,14 @@ abstract class Mage_Core_Model_Template extends Mage_Core_Model_Abstract */ protected $_designConfig; - + /** * Configuration of emulated desing package. * * @var Varien_Object|boolean */ protected $_emulatedDesignConfig = false; - + /** * Applying of design config * @@ -104,8 +104,10 @@ protected function _cancelDesignConfig() Mage::getDesign()->setArea($this->getDesignConfig()->getOldArea()); } - if ($this->getDesignConfig()->getOldStore()) { - Mage::getDesign()->setStore($this->getDesignConfig()->getOldStore()); + if ($this->getDesignConfig()->hasOldStore()) { + $oldStore = $this->getDesignConfig()->getOldStore(); + Mage::getDesign()->setStore($oldStore); + Mage::app()->setCurrentStore($oldStore); Mage::getDesign()->setTheme(''); Mage::getDesign()->setPackageName(''); } diff --git a/app/code/core/Mage/Core/Model/Translate/Inline.php b/app/code/core/Mage/Core/Model/Translate/Inline.php index c99087498d..68efa5bf8c 100644 --- a/app/code/core/Mage/Core/Model/Translate/Inline.php +++ b/app/code/core/Mage/Core/Model/Translate/Inline.php @@ -246,7 +246,8 @@ protected function _insertInlineScriptsHtml() } $baseJsUrl = Mage::getBaseUrl('js'); - $ajaxUrl = Mage::getUrl('core/ajax/translate', array('_secure'=>Mage::app()->getStore()->isCurrentlySecure())); + $url_prefix = Mage::app()->getStore()->isAdmin() ? 'adminhtml' : 'core'; + $ajaxUrl = Mage::getUrl($url_prefix.'/ajax/translate', array('_secure'=>Mage::app()->getStore()->isCurrentlySecure())); $trigImg = Mage::getDesign()->getSkinUrl('images/fam_book_open.png'); ob_start(); @@ -359,7 +360,12 @@ protected function _specialTags() $tagMatch = array(); while (preg_match($tagRegExp, $this->_content, $tagMatch, PREG_OFFSET_CAPTURE, $nextTag)) { $tagClosure = ''; - $tagLength = stripos($this->_content, $tagClosure, $tagMatch[0][1])-$tagMatch[0][1]+strlen($tagClosure); + $tagClosurePos = stripos($this->_content, $tagClosure, $tagMatch[0][1]); + if ($tagClosurePos === false) { + $tagClosure = '<\/'.$tagMatch[1][0].'>'; + $tagClosurePos = stripos($this->_content, $tagClosure, $tagMatch[0][1]); + } + $tagLength = $tagClosurePos-$tagMatch[0][1]+strlen($tagClosure); $next = 0; $tagHtml = substr($this->_content, $tagMatch[0][1], $tagLength); @@ -427,7 +433,6 @@ protected function _otherText() while (preg_match('#(>|title=\")*('.$this->_tokenRegex.')#', $this->_content, $m, PREG_OFFSET_CAPTURE, $next)) { if(-1 == $m[1][1])//title was not found - this is not an attribute { - $tr = '{shown:\''.$this->_escape($m[3][0]).'\',' .'translated:\''.$this->_escape($m[4][0]).'\',' .'original:\''.$this->_escape($m[5][0]).'\',' diff --git a/app/code/core/Mage/Core/controllers/AjaxController.php b/app/code/core/Mage/Core/controllers/AjaxController.php index 54dd5d0d5a..6f1a595e6b 100644 --- a/app/code/core/Mage/Core/controllers/AjaxController.php +++ b/app/code/core/Mage/Core/controllers/AjaxController.php @@ -23,22 +23,25 @@ * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + +/** + * Frontend ajax controller + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team + */ class Mage_Core_AjaxController extends Mage_Core_Controller_Front_Action { + /** + * Ajax action for inline translation + * + */ public function translateAction () { - if ($translate = $this->getRequest()->getPost('translate')) { - try { - if ($area = $this->getRequest()->getPost('area')) { - Mage::getDesign()->setArea($area); - } - Mage::getModel('core/translate_inline')->processAjaxPost($translate); - echo "{success:true}"; - } - catch (Exception $e) { - echo "{error:true,message:'" . $e->getMessage() . "'}"; - } - } + $translation = $this->getRequest()->getPost('translate'); + $area = $this->getRequest()->getPost('area'); + echo Mage::helper('core/translate')->apply($translation, $area); exit(); } } diff --git a/app/code/core/Mage/Core/etc/config.xml b/app/code/core/Mage/Core/etc/config.xml index 9bd36c6277..8b9c723f5c 100644 --- a/app/code/core/Mage/Core/etc/config.xml +++ b/app/code/core/Mage/Core/etc/config.xml @@ -54,6 +54,8 @@ core_convert_history
    design_change
    core_flag
    + core_file_storage
    + core_directory_storage
    @@ -203,6 +205,9 @@ 0 0 + + + 0 @@ -221,6 +226,11 @@ localhost 25 + + + 0 + default_setup + @@ -300,7 +310,6 @@ 1 1 - diff --git a/app/code/core/Mage/Core/etc/system.xml b/app/code/core/Mage/Core/etc/system.xml index 9bf0bf0ddc..c09e98296f 100644 --- a/app/code/core/Mage/Core/etc/system.xml +++ b/app/code/core/Mage/Core/etc/system.xml @@ -291,7 +291,7 @@ adminhtml/system_config_form_field_regexceptions - adminhtml/system_config_backend_serialized_array + adminhtml/system_config_backend_design_exception 2 1 1 @@ -512,6 +512,7 @@ select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_translate 10 1 1 @@ -521,11 +522,12 @@ select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_translate 20 1 0 0 - Translate cache should be disabled for both frontend and admin inline translations. + Translate, blocks and other output caches should be disabled for both frontend and admin inline translations. @@ -603,14 +605,13 @@ 1 - + select adminhtml/system_config_source_yesno 10 1 1 1 - Experimental. Turn this feature off if there are troubles with relative urls inside css-files. @@ -831,6 +832,48 @@ + + + text + 900 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_storage_media_storage + 100 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_storage_media_database + adminhtml/system_config_backend_storage_media_database + 200 + 1 + 0 + 0 + 1 + + + button + adminhtml/system_config_system_storage_media_synchronize + 300 + 1 + 0 + 0 + When changing media storage or selecting another media database, + use the Synchronize function to transfer media to the selected + storage. After synchronization is completed, all new media will be + automatically placed to the selected media storage. + + + @@ -1078,7 +1121,7 @@ 1 1 - + text adminhtml/system_config_backend_baseurl @@ -1086,6 +1129,7 @@ 1 1 1 + <strong style="color:red">Warning!</strong> When using CDN, in some cases JavaScript may not run properly if CDN is not in your subdomain @@ -1135,7 +1179,7 @@ 1 1 - + text adminhtml/system_config_backend_baseurl @@ -1143,6 +1187,7 @@ 1 1 1 + <strong style="color:red">Warning!</strong> When using CDN, in some cases JavaScript may not run properly if CDN is not in your subdomain @@ -1248,7 +1293,7 @@ text 60 1 - 0 + 1 0 diff --git a/app/code/core/Mage/Customer/Model/Attribute/Data/File.php b/app/code/core/Mage/Customer/Model/Attribute/Data/File.php index e94a89f11a..dd27ca00b1 100644 --- a/app/code/core/Mage/Customer/Model/Attribute/Data/File.php +++ b/app/code/core/Mage/Customer/Model/Attribute/Data/File.php @@ -140,33 +140,36 @@ protected function _validateByRules($value) */ public function validateValue($value) { - if ($this->getIsAjaxRequest()) { - return true; - } - $errors = array(); $attribute = $this->getAttribute(); $label = $attribute->getStoreLabel(); - $toDelete = !empty($value['delete']) ? true : false; - $toUpload = !empty($value['tmp_name']) ? true : false; - - if (!$toUpload && !$toDelete && $this->getEntity()->getData($attribute->getAttributeCode())) { - return true; - } - - if (!$attribute->getIsRequired() && !$toUpload) { - return true; - } - - if ($attribute->getIsRequired() && !$toUpload) { - $errors[] = Mage::helper('customer')->__('"%s" is a required value.', $label); - } - - if ($toUpload) { - $errors = array_merge($errors, $this->_validateByRules($value)); + if (is_array($value)) { + $toDelete = !empty($value['delete']) ? true : false; + $toUpload = !empty($value['tmp_name']) ? true : false; + + if (!$toUpload && !$toDelete && $this->getEntity()->getData($attribute->getAttributeCode())) { + return true; + } + + if (!$attribute->getIsRequired() && !$toUpload) { + return true; + } + + if ($attribute->getIsRequired() && !$toUpload) { + $errors[] = Mage::helper('customer')->__('"%s" is a required value.', $label); + } + + if ($toUpload) { + $errors = array_merge($errors, $this->_validateByRules($value)); + } + } else { + $filePath = Mage::getBaseDir('media') . DS . 'customer' . $value; + if ($attribute->getIsRequired() && !file_exists($filePath)) { + $errors[] = Mage::helper('customer')->__('"%s" is a required value.', $label); + } } - + if (count($errors) == 0) { return true; } @@ -214,7 +217,7 @@ public function compactValue($value) if (!empty($value['tmp_name'])) { try { - $uploader = new Varien_File_Uploader($value); + $uploader = new Mage_Core_Model_File_Uploader($value); $uploader->setFilesDispersion(true); $uploader->setFilenamesCaseSensitivity(false); $uploader->setAllowRenameFiles(true); diff --git a/app/code/core/Mage/Customer/Model/Attribute/Data/Multiline.php b/app/code/core/Mage/Customer/Model/Attribute/Data/Multiline.php index 356440ca61..9653b59822 100644 --- a/app/code/core/Mage/Customer/Model/Attribute/Data/Multiline.php +++ b/app/code/core/Mage/Customer/Model/Attribute/Data/Multiline.php @@ -74,6 +74,8 @@ public function validateValue($value) for ($i = 0; $i < $attribute->getMultilineCount(); $i ++) { if (!isset($value[$i])) { $value[$i] = null; + } else { + $value[$i] = trim($value[$i]); } // validate first line if ($i == 0) { diff --git a/app/code/core/Mage/Customer/Model/Attribute/Data/Select.php b/app/code/core/Mage/Customer/Model/Attribute/Data/Select.php index 9a9bee19c5..1c474c1749 100644 --- a/app/code/core/Mage/Customer/Model/Attribute/Data/Select.php +++ b/app/code/core/Mage/Customer/Model/Attribute/Data/Select.php @@ -125,6 +125,7 @@ public function outputValue($format = Mage_Customer_Model_Attribute_Data::OUTPUT switch ($format) { case Mage_Customer_Model_Attribute_Data::OUTPUT_FORMAT_JSON: $output = $value; + break; default: if ($value != '') { $output = $this->_getOptionText($value); diff --git a/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php b/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php index 71bbfbcd65..fcd59d1469 100644 --- a/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php +++ b/app/code/core/Mage/Customer/Model/Convert/Parser/Customer.php @@ -57,6 +57,12 @@ class Mage_Customer_Model_Convert_Parser_Customer protected $_fields; + /** + * Array to contain customer groups + * @var null|array + */ + protected $_customerGroups = null; + public function getFields() { if (!$this->_fields) { @@ -322,10 +328,16 @@ public function unparse() ? 1 : 0; if($customer->getGroupId()){ - $group = Mage::getResourceModel('customer/group_collection') - ->addFilter('customer_group_id',$customer->getGroupId()) - ->load(); - $row['group'] = $group->getFirstItem()->getCustomerGroupCode(); + $groupCode = $this->_getCustomerGroupCode($customer); + if (is_null($groupCode)) { + $this->addException( + Mage::helper('catalog')->__("An invalid group ID is specified, skipping the record."), + Mage_Dataflow_Model_Convert_Exception::ERROR + ); + continue; + } else { + $row['group'] = $groupCode; + } } $batchExport = $this->getBatchExportModel() @@ -380,7 +392,12 @@ public function getExternalAttributes() if (in_array($code, $internal) || $attr->getFrontendInput()=='hidden') { continue; } - $attributes['billing_'.$code] = 'billing_'.$code; + + if ($code == 'street') { + $attributes['billing_'.$code.'_full'] = 'billing_'.$code; + } else { + $attributes['billing_'.$code] = 'billing_'.$code; + } } $attributes['billing_country'] = 'billing_country'; @@ -389,13 +406,42 @@ public function getExternalAttributes() if (in_array($code, $internal) || $attr->getFrontendInput()=='hidden') { continue; } - $attributes['shipping_'.$code] = 'shipping_'.$code; + + if ($code == 'street') { + $attributes['shipping_'.$code.'_full'] = 'shipping_'.$code; + } else { + $attributes['shipping_'.$code] = 'shipping_'.$code; + } } $attributes['shipping_country'] = 'shipping_country'; return $attributes; } + /** + * Gets group code by customer's groupId + * + * @param Mage_Customer_Model_Customer $customer + * @return string|null + */ + protected function _getCustomerGroupCode($customer) + { + if (is_null($this->_customerGroups)) { + $groups = Mage::getResourceModel('customer/group_collection') + ->load(); + + foreach ($groups as $group) { + $this->_customerGroups[$group->getId()] = $group->getData('customer_group_code'); + } + } + + if (isset($this->_customerGroups[$customer->getGroupId()])) { + return $this->_customerGroups[$customer->getGroupId()]; + } else { + return null; + } + } + /* ########### THE CODE BELOW IS NOT USED ############# */ public function unparse__OLD() diff --git a/app/code/core/Mage/Customer/Model/Customer.php b/app/code/core/Mage/Customer/Model/Customer.php index 6467cc122e..da2ae96a25 100644 --- a/app/code/core/Mage/Customer/Model/Customer.php +++ b/app/code/core/Mage/Customer/Model/Customer.php @@ -514,11 +514,8 @@ public function sendNewAccountEmail($type = 'registered', $backUrl = '', $storeI /* @var $translate Mage_Core_Model_Translate */ $translate->setTranslateInline(false); - $storeId = ($storeId == '0')?$this->getSendemailStoreId():$storeId; - if ($this->getWebsiteId() != '0' && $storeId == '0') { - $storeIds = Mage::app()->getWebsite($this->getWebsiteId())->getStoreIds(); - reset($storeIds); - $storeId = current($storeIds); + if (!$storeId) { + $storeId = $this->_getWebsiteStoreId($this->getSendemailStoreId()); } Mage::getModel('core/email_template') @@ -568,10 +565,8 @@ public function sendPasswordReminderEmail() $translate->setTranslateInline(false); $storeId = $this->getStoreId(); - if ($this->getWebsiteId() != '0' && $storeId == '0') { - $storeIds = Mage::app()->getWebsite($this->getWebsiteId())->getStoreIds(); - reset($storeIds); - $storeId = current($storeIds); + if (!$storeId) { + $storeId = $this->_getWebsiteStoreId(); } Mage::getModel('core/email_template') @@ -1135,4 +1130,21 @@ public function getEntityTypeId() } return $entityTypeId; } + + /** + * Get either first store ID from a set website or the provided as default + * + * @param int|string|null $storeId + * + * @return int + */ + protected function _getWebsiteStoreId($defaultStoreId = null) + { + if ($this->getWebsiteId() != 0 && empty($defaultStoreId)) { + $storeIds = Mage::app()->getWebsite($this->getWebsiteId())->getStoreIds(); + reset($storeIds); + $defaultStoreId = current($storeIds); + } + return $defaultStoreId; + } } diff --git a/app/code/core/Mage/Customer/Model/Session.php b/app/code/core/Mage/Customer/Model/Session.php index 3f5b461ea7..d330e14517 100644 --- a/app/code/core/Mage/Customer/Model/Session.php +++ b/app/code/core/Mage/Customer/Model/Session.php @@ -219,6 +219,7 @@ public function logout() if ($this->isLoggedIn()) { Mage::dispatchEvent('customer_logout', array('customer' => $this->getCustomer()) ); $this->setId(null); + $this->getCookie()->delete($this->getSessionName()); } return $this; } diff --git a/app/code/core/Mage/Directory/Block/Data.php b/app/code/core/Mage/Directory/Block/Data.php index a0bb33f7aa..8d6efdfc1c 100644 --- a/app/code/core/Mage/Directory/Block/Data.php +++ b/app/code/core/Mage/Directory/Block/Data.php @@ -120,7 +120,7 @@ public function getCountryId() { $countryId = $this->getData('country_id'); if (is_null($countryId)) { - $countryId = Mage::getStoreConfig('general/country/default'); + $countryId = Mage::helper('core')->getDefaultCountry(); } return $countryId; } diff --git a/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php b/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php new file mode 100644 index 0000000000..813e121480 --- /dev/null +++ b/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php @@ -0,0 +1,37 @@ + + */ +class Mage_Downloadable_Block_Adminhtml_Catalog_Product_Composite_Fieldset_Downloadable + extends Mage_Downloadable_Block_Catalog_Product_Links +{ +} diff --git a/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php b/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php index 43899bfd1c..84cc80343f 100644 --- a/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php +++ b/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php @@ -170,8 +170,13 @@ public function getLinkData() $file = Mage::helper('downloadable/file')->getFilePath( Mage_Downloadable_Model_Link::getBasePath(), $item->getLinkFile() ); + + if ($item->getLinkFile() && !is_file($file)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($file); + } + if ($item->getLinkFile() && is_file($file)) { - $name = '' . + $name = '' . Mage::helper('downloadable/file')->getFileFromPathFile($item->getLinkFile()) . ''; $tmpLinkItem['file_save'] = array( @@ -264,7 +269,7 @@ public function getUploadButtonHtml() */ public function getConfigJson($type='links') { - $this->getConfig()->setUrl(Mage::getModel('adminhtml/url')->addSessionParam()->getUrl('downloadableadmin/file/upload', array('type' => $type, '_secure' => true))); + $this->getConfig()->setUrl(Mage::getModel('adminhtml/url')->addSessionParam()->getUrl('*/downloadable_file/upload', array('type' => $type, '_secure' => true))); $this->getConfig()->setParams(array('form_key' => $this->getFormKey())); $this->getConfig()->setFileField($type); $this->getConfig()->setFilters(array( diff --git a/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php b/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php index 9ee24d6565..74073b4c3f 100644 --- a/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php +++ b/app/code/core/Mage/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php @@ -100,6 +100,9 @@ public function getSampleData() $file = Mage::helper('downloadable/file')->getFilePath( Mage_Downloadable_Model_Sample::getBasePath(), $item->getSampleFile() ); + if ($item->getSampleFile() && !is_file($file)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($file); + } if ($item->getSampleFile() && is_file($file)) { $tmpSampleItem['file_save'] = array( array( @@ -173,7 +176,7 @@ public function getUploadButtonHtml() */ public function getConfigJson() { - $this->getConfig()->setUrl(Mage::getModel('adminhtml/url')->addSessionParam()->getUrl('downloadableadmin/file/upload', array('type' => 'samples', '_secure' => true))); + $this->getConfig()->setUrl(Mage::getModel('adminhtml/url')->addSessionParam()->getUrl('*/downloadable_file/upload', array('type' => 'samples', '_secure' => true))); $this->getConfig()->setParams(array('form_key' => $this->getFormKey())); $this->getConfig()->setFileField('samples'); $this->getConfig()->setFilters(array( diff --git a/app/code/core/Mage/Downloadable/Block/Catalog/Product/Links.php b/app/code/core/Mage/Downloadable/Block/Catalog/Product/Links.php index 29beac019f..66262753d2 100644 --- a/app/code/core/Mage/Downloadable/Block/Catalog/Product/Links.php +++ b/app/code/core/Mage/Downloadable/Block/Catalog/Product/Links.php @@ -90,19 +90,26 @@ public function getFormattedLinkPrice($link) return ''; } - $_priceInclTax = Mage::helper('tax')->getPrice($link->getProduct(), $price, true); - $_priceExclTax = Mage::helper('tax')->getPrice($link->getProduct(), $price); + $taxCalculation = Mage::getSingleton('tax/calculation'); + if (!$taxCalculation->getCustomer() && Mage::registry('current_customer')) { + $taxCalculation->setCustomer(Mage::registry('current_customer')); + } + + $taxHelper = Mage::helper('tax'); + $coreHelper = $this->helper('core'); + $_priceInclTax = $taxHelper->getPrice($link->getProduct(), $price, true); + $_priceExclTax = $taxHelper->getPrice($link->getProduct(), $price); $priceStr = '+'; - if (Mage::helper('tax')->displayPriceIncludingTax()) { - $priceStr .= $this->helper('core')->currency($_priceInclTax, true, true); - } elseif (Mage::helper('tax')->displayPriceExcludingTax()) { - $priceStr .= $this->helper('core')->currency($_priceExclTax, true, true); - } elseif (Mage::helper('tax')->displayBothPrices()) { - $priceStr .= $this->helper('core')->currency($_priceExclTax, true, true); + if ($taxHelper->displayPriceIncludingTax()) { + $priceStr .= $coreHelper->currencyByStore($_priceInclTax, 3); + } elseif ($taxHelper->displayPriceExcludingTax()) { + $priceStr .= $coreHelper->currencyByStore($_priceExclTax, 3); + } elseif ($taxHelper->displayBothPrices()) { + $priceStr .= $coreHelper->currencyByStore($_priceExclTax, 3); if ($_priceInclTax != $_priceExclTax) { - $priceStr .= ' (+'.$this->helper('core') - ->currency($_priceInclTax, true, true).' '.$this->__('Incl. Tax').')'; + $priceStr .= ' (+'.$coreHelper + ->currencyByStore($_priceInclTax, 3).' '.$this->__('Incl. Tax').')'; } } $priceStr .= ''; @@ -118,12 +125,13 @@ public function getFormattedLinkPrice($link) public function getJsonConfig() { $config = array(); + $coreHelper = Mage::helper('core'); foreach ($this->getLinks() as $link) { - $config[$link->getId()] = Mage::helper('core')->currency($link->getPrice(), false, false); + $config[$link->getId()] = $coreHelper->currency($link->getPrice(), false, false); } - return Mage::helper('core')->jsonEncode($config); + return $coreHelper->jsonEncode($config); } public function getLinkSamlpeUrl($link) @@ -154,4 +162,30 @@ public function getIsOpenInNewWindow() return Mage::getStoreConfigFlag(Mage_Downloadable_Model_Link::XML_PATH_TARGET_NEW_WINDOW); } + /** + * Returns whether link checked by default or not + * + * @param Mage_Downloadable_Model_Link $link + * @return bool + */ + public function getIsLinkChecked($link) + { + $configValue = $this->getProduct()->getPreconfiguredValues()->getLinks(); + if (!$configValue || !is_array($configValue)) { + return false; + } + + return $configValue && (in_array($link->getId(), $configValue)); + } + + /** + * Returns value for link's input checkbox - either 'checked' or '' + * + * @param Mage_Downloadable_Model_Link $link + * @return string + */ + public function getLinkCheckedValue($link) + { + return $this->getIsLinkChecked($link) ? 'checked' : ''; + } } diff --git a/app/code/core/Mage/Downloadable/Block/Checkout/Cart/Item/Renderer.php b/app/code/core/Mage/Downloadable/Block/Checkout/Cart/Item/Renderer.php index a9c2f5c087..5cde4a385f 100644 --- a/app/code/core/Mage/Downloadable/Block/Checkout/Cart/Item/Renderer.php +++ b/app/code/core/Mage/Downloadable/Block/Checkout/Cart/Item/Renderer.php @@ -35,23 +35,13 @@ class Mage_Downloadable_Block_Checkout_Cart_Item_Renderer extends Mage_Checkout_ { /** - * Enter description here... + * Retrieves item links options * * @return array */ public function getLinks() { - $itemLinks = array(); - if ($linkIds = $this->getItem()->getOptionByCode('downloadable_link_ids')) { - $productLinks = $this->getProduct()->getTypeInstance(true) - ->getLinks($this->getProduct()); - foreach (explode(',', $linkIds->getValue()) as $linkId) { - if (isset($productLinks[$linkId])) { - $itemLinks[] = $productLinks[$linkId]; - } - } - } - return $itemLinks; + return Mage::helper('downloadable/catalog_product_configuration')->getLinks($this->getItem()); } /** @@ -61,10 +51,6 @@ public function getLinks() */ public function getLinksTitle() { - if ($this->getProduct()->getLinksTitle()) { - return $this->getProduct()->getLinksTitle(); - } - return Mage::getStoreConfig(Mage_Downloadable_Model_Link::XML_PATH_LINKS_TITLE); + return Mage::helper('downloadable/catalog_product_configuration')->getLinksTitle($this->getProduct()); } - } diff --git a/app/code/core/Mage/Downloadable/Helper/Catalog/Product/Configuration.php b/app/code/core/Mage/Downloadable/Helper/Catalog/Product/Configuration.php new file mode 100644 index 0000000000..a0c4eaa3d8 --- /dev/null +++ b/app/code/core/Mage/Downloadable/Helper/Catalog/Product/Configuration.php @@ -0,0 +1,99 @@ + + */ +class Mage_Downloadable_Helper_Catalog_Product_Configuration extends Mage_Core_Helper_Abstract + implements Mage_Catalog_Helper_Product_Configuration_Interface +{ + /** + * Retrieves item links options + * + * @param Mage_Catalog_Model_Product_Configuration_Item_Interface $item + * @return array + */ + public function getLinks(Mage_Catalog_Model_Product_Configuration_Item_Interface $item) + { + $product = $item->getProduct(); + $itemLinks = array(); + $linkIds = $item->getOptionByCode('downloadable_link_ids'); + if ($linkIds) { + $productLinks = $product->getTypeInstance(true) + ->getLinks($product); + foreach (explode(',', $linkIds->getValue()) as $linkId) { + if (isset($productLinks[$linkId])) { + $itemLinks[] = $productLinks[$linkId]; + } + } + } + return $itemLinks; + } + + /** + * Retrieves product links section title + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getLinksTitle($product) + { + $title = $product->getLinksTitle(); + if (strlen($title)) { + return $title; + } + return Mage::getStoreConfig(Mage_Downloadable_Model_Link::XML_PATH_LINKS_TITLE); + } + + /** + * Retrieves product options + * + * @param Mage_Catalog_Model_Product_Configuration_Item_Interface $item + * @return array + */ + public function getOptions(Mage_Catalog_Model_Product_Configuration_Item_Interface $item) + { + $options = Mage::helper('catalog/product_configuration')->getOptions($item); + + $links = $this->getLinks($item); + if ($links) { + $linksOption = array( + 'label' => $this->getLinksTitle($item->getProduct()), + 'value' => array() + ); + foreach ($links as $link) { + $linksOption['value'][] = $link->getTitle(); + } + $options[] = $linksOption; + } + + return $options; + } +} diff --git a/app/code/core/Mage/Downloadable/Helper/Download.php b/app/code/core/Mage/Downloadable/Helper/Download.php index f21f717046..6ba7756c8e 100644 --- a/app/code/core/Mage/Downloadable/Helper/Download.php +++ b/app/code/core/Mage/Downloadable/Helper/Download.php @@ -165,6 +165,9 @@ protected function _getHandle() } elseif ($this->_linkType == self::LINK_TYPE_FILE) { $this->_handle = new Varien_Io_File(); + if (!is_file($this->_resourceFile)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($this->_resourceFile); + } $this->_handle->open(array('path'=>Mage::getBaseDir('var'))); if (!$this->_handle->fileExists($this->_resourceFile, true)) { Mage::throwException(Mage::helper('downloadable')->__('The file does not exist.')); diff --git a/app/code/core/Mage/Downloadable/Helper/File.php b/app/code/core/Mage/Downloadable/Helper/File.php index c5c4978b08..14edd45660 100644 --- a/app/code/core/Mage/Downloadable/Helper/File.php +++ b/app/code/core/Mage/Downloadable/Helper/File.php @@ -95,6 +95,12 @@ protected function _moveFileFromTmp($baseTmpPath, $basePath, $file) $destFile = dirname($file) . $ioObject->dirsep() . Varien_File_Uploader::getNewFileName($this->getFilePath($basePath, $file)); + + Mage::helper('core/file_storage_database')->copyFile( + $this->getFilePath($baseTmpPath, $file), + $this->getFilePath($basePath, $destFile) + ); + $result = $ioObject->mv( $this->getFilePath($baseTmpPath, $file), $this->getFilePath($basePath, $destFile) diff --git a/app/code/core/Mage/Downloadable/Model/Product/Type.php b/app/code/core/Mage/Downloadable/Model/Product/Type.php index 1ebe384c83..35d974df72 100644 --- a/app/code/core/Mage/Downloadable/Model/Product/Type.php +++ b/app/code/core/Mage/Downloadable/Model/Product/Type.php @@ -273,15 +273,17 @@ public function save($product = null) } /** - * Prepare Product object before adding to Shopping Cart + * Prepare product and its configuration to be added to some products list. + * Perform standard preparation process and then prepare options for downloadable links. * * @param Varien_Object $buyRequest * @param Mage_Catalog_Model_Product $product + * @param string $processMode * @return array|string */ - public function prepareForCart(Varien_Object $buyRequest, $product = null) + protected function _prepareProduct(Varien_Object $buyRequest, $product, $processMode) { - $result = parent::prepareForCart($buyRequest, $product); + $result = parent::_prepareProduct($buyRequest, $product, $processMode); if (is_string($result)) { return $result; @@ -315,12 +317,35 @@ public function prepareForCart(Varien_Object $buyRequest, $product = null) $this->getProduct($product)->addCustomOption('downloadable_link_ids', implode(',', $preparedLinks)); return $result; } - if ($this->getLinkSelectionRequired($product)) { + if ($this->getLinkSelectionRequired($product) && $this->_isStrictProcessMode($processMode)) { return Mage::helper('downloadable')->__('Please specify product link(s).'); } return $result; } + /** + * Check if product can be bought + * + * @param Mage_Catalog_Model_Product $product + * @return Mage_Bundle_Model_Product_Type + * @throws Mage_Core_Exception + */ + public function checkProductBuyState($product = null) + { + parent::checkProductBuyState($product); + $product = $this->getProduct($product); + $option = $product->getCustomOption('info_buyRequest'); + if ($option instanceof Mage_Sales_Model_Quote_Item_Option) { + $buyRequest = new Varien_Object(unserialize($option->getValue())); + if (!$buyRequest->hasLinks()) { + Mage::throwException( + Mage::helper('downloadable')->__('Please specify product link(s).') + ); + } + } + return $this; + } + /** * Prepare additional options/information for order item which will be * created from this product @@ -424,4 +449,21 @@ public function isSalable($product = null) { return $this->hasLinks($product) && parent::isSalable($product); } + + /** + * Prepare selected options for downloadable product + * + * @param Mage_Catalog_Model_Product $product + * @param Varien_Object $buyRequest + * @return array + */ + public function processBuyRequest($product, $buyRequest) + { + $links = $buyRequest->getLinks(); + $links = (is_array($links)) ? array_filter($links, 'intval') : array(); + + $options = array('links' => $links); + + return $options; + } } diff --git a/app/code/core/Mage/Downloadable/controllers/Adminhtml/Downloadable/FileController.php b/app/code/core/Mage/Downloadable/controllers/Adminhtml/Downloadable/FileController.php new file mode 100644 index 0000000000..3b3b969bb5 --- /dev/null +++ b/app/code/core/Mage/Downloadable/controllers/Adminhtml/Downloadable/FileController.php @@ -0,0 +1,87 @@ + + */ +class Mage_Downloadable_Adminhtml_Downloadable_FileController extends Mage_Adminhtml_Controller_Action +{ + + /** + * Upload file controller action + */ + public function uploadAction() + { + $type = $this->getRequest()->getParam('type'); + $tmpPath = ''; + if ($type == 'samples') { + $tmpPath = Mage_Downloadable_Model_Sample::getBaseTmpPath(); + } elseif ($type == 'links') { + $tmpPath = Mage_Downloadable_Model_Link::getBaseTmpPath(); + } elseif ($type == 'link_samples') { + $tmpPath = Mage_Downloadable_Model_Link::getBaseSampleTmpPath(); + } + $result = array(); + try { + $uploader = new Varien_File_Uploader($type); + $uploader->setAllowRenameFiles(true); + $uploader->setFilesDispersion(true); + $result = $uploader->save($tmpPath); + + if (isset($result['file'])) { + $fullPath = rtrim($tmpPath, DS) . DS . ltrim($result['file'], DS); + Mage::helper('core/file_storage_database')->saveFile($fullPath); + } + + $result['cookie'] = array( + 'name' => session_name(), + 'value' => $this->_getSession()->getSessionId(), + 'lifetime' => $this->_getSession()->getCookieLifetime(), + 'path' => $this->_getSession()->getCookiePath(), + 'domain' => $this->_getSession()->getCookieDomain() + ); + } catch (Exception $e) { + $result = array('error'=>$e->getMessage(), 'errorcode'=>$e->getCode()); + } + + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('catalog/products'); + } + +} diff --git a/app/code/core/Mage/Downloadable/controllers/Adminhtml/Downloadable/Product/EditController.php b/app/code/core/Mage/Downloadable/controllers/Adminhtml/Downloadable/Product/EditController.php new file mode 100644 index 0000000000..0417ccbf90 --- /dev/null +++ b/app/code/core/Mage/Downloadable/controllers/Adminhtml/Downloadable/Product/EditController.php @@ -0,0 +1,129 @@ + + */ +class Mage_Downloadable_Adminhtml_Downloadable_Product_EditController extends Mage_Adminhtml_Catalog_ProductController +{ + + /** + * Varien class constructor + * + */ + protected function _construct() + { + $this->setUsedModuleName('Mage_Downloadable'); + } + + /** + * Load downloadable tab fieldsets + * + */ + public function formAction() + { + $this->_initProduct(); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('downloadable/adminhtml_catalog_product_edit_tab_downloadable', 'admin.product.downloadable.information') + ->toHtml() + ); + } + + /** + * Download process + * + * @param string $resource + * @param string $resourceType + */ + protected function _processDownload($resource, $resourceType) + { + $helper = Mage::helper('downloadable/download'); + /* @var $helper Mage_Downloadable_Helper_Download */ + + $helper->setResource($resource, $resourceType); + + $fileName = $helper->getFilename(); + $contentType = $helper->getContentType(); + + $this->getResponse() + ->setHttpResponseCode(200) + ->setHeader('Pragma', 'public', true) + ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) + ->setHeader('Content-type', $contentType, true); + + if ($fileSize = $helper->getFilesize()) { + $this->getResponse() + ->setHeader('Content-Length', $fileSize); + } + + if ($contentDisposition = $helper->getContentDisposition()) { + $this->getResponse() + ->setHeader('Content-Disposition', $contentDisposition . '; filename='.$fileName); + } + + $this->getResponse() + ->clearBody(); + $this->getResponse() + ->sendHeaders(); + + $helper->output(); + } + + /** + * Download link action + * + */ + public function linkAction() + { + $linkId = $this->getRequest()->getParam('id', 0); + $link = Mage::getModel('downloadable/link')->load($linkId); + if ($link->getId()) { + $resource = ''; + $resourceType = ''; + if ($link->getLinkType() == Mage_Downloadable_Helper_Download::LINK_TYPE_URL) { + $resource = $link->getLinkUrl(); + $resourceType = Mage_Downloadable_Helper_Download::LINK_TYPE_URL; + } elseif ($link->getLinkType() == Mage_Downloadable_Helper_Download::LINK_TYPE_FILE) { + $resource = Mage::helper('downloadable/file')->getFilePath( + Mage_Downloadable_Model_Link::getBasePath(), $link->getLinkFile() + ); + $resourceType = Mage_Downloadable_Helper_Download::LINK_TYPE_FILE; + } + try { + $this->_processDownload($resource, $resourceType); + } catch (Mage_Core_Exception $e) { + $this->_getCustomerSession()->addError(Mage::helper('downloadable')->__('An error occurred while getting the requested content.')); + } + } + } + +} diff --git a/app/code/core/Mage/Downloadable/controllers/FileController.php b/app/code/core/Mage/Downloadable/controllers/FileController.php index e9b0b2fd65..16c8310333 100644 --- a/app/code/core/Mage/Downloadable/controllers/FileController.php +++ b/app/code/core/Mage/Downloadable/controllers/FileController.php @@ -24,58 +24,24 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +require_once 'Mage/Downloadable/controllers/Adminhtml/Downloadable/FileController.php'; + /** * Downloadable File upload controller * * @category Mage * @package Mage_Downloadable * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_Downloadable_Adminhtml_Downloadable_FileController is used */ -class Mage_Downloadable_FileController extends Mage_Adminhtml_Controller_Action +class Mage_Downloadable_FileController extends Mage_Downloadable_Adminhtml_Downloadable_FileController { - /** - * Upload file controller action + * Controller predispatch method + * Show 404 front page */ - public function uploadAction() + public function preDispatch() { - $type = $this->getRequest()->getParam('type'); - $tmpPath = ''; - if ($type == 'samples') { - $tmpPath = Mage_Downloadable_Model_Sample::getBaseTmpPath(); - } elseif ($type == 'links') { - $tmpPath = Mage_Downloadable_Model_Link::getBaseTmpPath(); - } elseif ($type == 'link_samples') { - $tmpPath = Mage_Downloadable_Model_Link::getBaseSampleTmpPath(); - } - $result = array(); - try { - $uploader = new Varien_File_Uploader($type); - $uploader->setAllowRenameFiles(true); - $uploader->setFilesDispersion(true); - $result = $uploader->save($tmpPath); - $result['cookie'] = array( - 'name' => session_name(), - 'value' => $this->_getSession()->getSessionId(), - 'lifetime' => $this->_getSession()->getCookieLifetime(), - 'path' => $this->_getSession()->getCookiePath(), - 'domain' => $this->_getSession()->getCookieDomain() - ); - } catch (Exception $e) { - $result = array('error'=>$e->getMessage(), 'errorcode'=>$e->getCode()); - } - - $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + $this->_forward('defaultIndex', 'cms_index'); } - - /** - * Check admin permissions for this controller - * - * @return boolean - */ - protected function _isAllowed() - { - return Mage::getSingleton('admin/session')->isAllowed('catalog/products'); - } - } diff --git a/app/code/core/Mage/Downloadable/controllers/Product/EditController.php b/app/code/core/Mage/Downloadable/controllers/Product/EditController.php index c96337e08b..be25913427 100644 --- a/app/code/core/Mage/Downloadable/controllers/Product/EditController.php +++ b/app/code/core/Mage/Downloadable/controllers/Product/EditController.php @@ -24,7 +24,7 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -require_once 'Mage/Adminhtml/controllers/Catalog/ProductController.php'; +require_once 'Mage/Downloadable/controllers/Adminhtml/Downloadable/Product/EditController.php'; /** * Adminhtml downloadable product edit @@ -32,98 +32,16 @@ * @category Mage * @package Mage_Downloadable * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_Downloadable_Adminhtml_Downloadable_Product_EditController is used */ -class Mage_Downloadable_Product_EditController extends Mage_Adminhtml_Catalog_ProductController +class Mage_Downloadable_Product_EditController extends Mage_Downloadable_Adminhtml_Downloadable_Product_EditController { - - /** - * Varien class constructor - * - */ - protected function _construct() - { - $this->setUsedModuleName('Mage_Downloadable'); - } - /** - * Load downloadable tab fieldsets - * - */ - public function formAction() - { - $this->_initProduct(); - $this->getResponse()->setBody( - $this->getLayout()->createBlock('downloadable/adminhtml_catalog_product_edit_tab_downloadable', 'admin.product.downloadable.information') - ->toHtml() - ); - } - - /** - * Download process - * - * @param string $resource - * @param string $resourceType - */ - protected function _processDownload($resource, $resourceType) - { - $helper = Mage::helper('downloadable/download'); - /* @var $helper Mage_Downloadable_Helper_Download */ - - $helper->setResource($resource, $resourceType); - - $fileName = $helper->getFilename(); - $contentType = $helper->getContentType(); - - $this->getResponse() - ->setHttpResponseCode(200) - ->setHeader('Pragma', 'public', true) - ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) - ->setHeader('Content-type', $contentType, true); - - if ($fileSize = $helper->getFilesize()) { - $this->getResponse() - ->setHeader('Content-Length', $fileSize); - } - - if ($contentDisposition = $helper->getContentDisposition()) { - $this->getResponse() - ->setHeader('Content-Disposition', $contentDisposition . '; filename='.$fileName); - } - - $this->getResponse() - ->clearBody(); - $this->getResponse() - ->sendHeaders(); - - $helper->output(); - } - -/** - * Download link action - * + * Controller predispatch method + * Show 404 front page */ - public function linkAction() + public function preDispatch() { - $linkId = $this->getRequest()->getParam('id', 0); - $link = Mage::getModel('downloadable/link')->load($linkId); - if ($link->getId()) { - $resource = ''; - $resourceType = ''; - if ($link->getLinkType() == Mage_Downloadable_Helper_Download::LINK_TYPE_URL) { - $resource = $link->getLinkUrl(); - $resourceType = Mage_Downloadable_Helper_Download::LINK_TYPE_URL; - } elseif ($link->getLinkType() == Mage_Downloadable_Helper_Download::LINK_TYPE_FILE) { - $resource = Mage::helper('downloadable/file')->getFilePath( - Mage_Downloadable_Model_Link::getBasePath(), $link->getLinkFile() - ); - $resourceType = Mage_Downloadable_Helper_Download::LINK_TYPE_FILE; - } - try { - $this->_processDownload($resource, $resourceType); - } catch (Mage_Core_Exception $e) { - $this->_getCustomerSession()->addError(Mage::helper('downloadable')->__('An error occurred while getting the requested content.')); - } - } + $this->_forward('defaultIndex', 'cms_index'); } - } diff --git a/app/code/core/Mage/Downloadable/etc/config.xml b/app/code/core/Mage/Downloadable/etc/config.xml index 41e03535b4..8654fe713d 100644 --- a/app/code/core/Mage/Downloadable/etc/config.xml +++ b/app/code/core/Mage/Downloadable/etc/config.xml @@ -332,13 +332,13 @@ - - admin + - Mage_Downloadable - downloadableadmin + + Mage_Downloadable_Adminhtml + - + diff --git a/app/code/core/Mage/Eav/Model/Entity/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Abstract.php index c87709c55a..229d6fe836 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Abstract.php @@ -84,7 +84,7 @@ abstract class Mage_Eav_Model_Entity_Abstract * @var array */ protected $_staticAttributes = array(); - + /** * Default Attributes that are static * @@ -412,7 +412,7 @@ public function getAttribute($attribute) return $attribute; } - + /** * Return default static virtual attribute that doesn't exists in EAV attributes * @@ -628,6 +628,10 @@ public function walkAttributes($partMethod, array $args=array()) break; } + if (!$this->_isCallableAttributeInstance($instance, $method, $args)) { + continue; + } + try { $results[$attrCode] = call_user_func_array(array($instance, $method), $args); } @@ -643,6 +647,23 @@ public function walkAttributes($partMethod, array $args=array()) return $results; } + /** + * Check whether attribute instance (attribute, backend, frontend or source) has method and applicable + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract|Mage_Eav_Model_Entity_Attribute_Backend_Abstract|Mage_Eav_Model_Entity_Attribute_Frontend_Abstract|Mage_Eav_Model_Entity_Attribute_Source_Abstract $instance + * @param string $method + * @param array $args array of arguments + * @return boolean + */ + protected function _isCallableAttributeInstance($instance, $method, $args) + { + if (!is_object($instance) || !method_exists($instance, $method)) { + return false; + } + + return true; + } + /** * Get attributes by name array * @@ -1405,20 +1426,18 @@ public function saveAttribute(Varien_Object $object, $attributeCode) if ($origValueId === false && !is_null($newValue)) { $this->_insertAttribute($object, $attribute, $newValue); - $backend->setValueId($this->_getWriteAdapter()->lastInsertId()); } elseif ($origValueId !== false && !is_null($newValue)) { $this->_updateAttribute($object, $attribute, $origValueId, $newValue); } elseif ($origValueId !== false && is_null($newValue)) { $this->_getWriteAdapter()->delete($table, $where); } + $this->_processAttributeValues(); $this->_getWriteAdapter()->commit(); } catch (Exception $e) { $this->_getWriteAdapter()->rollback(); throw $e; } - $this->_processAttributeValues(); - return $this; } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php index bbe93f8b93..4157be4d5e 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php @@ -443,7 +443,11 @@ public function getAttributeCodesByFrontendType($type) */ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $store) { - $joinCondition = "`e`.`entity_id`=`t1`.`entity_id`"; + $joinConditionTemplate = "`e`.`entity_id`=`%s`.`entity_id`" + ." AND `%s`.`entity_type_id` = ".$attribute->getEntityTypeId() + ." AND `%s`.`attribute_id` = ".$attribute->getId() + ." AND `%s`.`store_id` = %d"; + $joinCondition = sprintf($joinConditionTemplate, 't1', 't1', 't1', 't1', Mage_Core_Model_App::ADMIN_STORE_ID); if ($attribute->getFlatAddChildData()) { $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; } @@ -455,14 +459,8 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at ) ->joinLeft( array('t2' => $attribute->getBackend()->getTable()), - "t2.entity_id = t1.entity_id" - . " AND t1.entity_type_id = t2.entity_type_id" - . " AND t1.attribute_id = t2.attribute_id" - . " AND t2.store_id = {$store}", - array($attribute->getAttributeCode() => "IF(t2.value_id>0, t2.value, t1.value)")) - ->where("t1.entity_type_id=?", $attribute->getEntityTypeId()) - ->where("t1.attribute_id=?", $attribute->getId()) - ->where("t1.store_id=?", 0); + sprintf($joinConditionTemplate, 't2', 't2', 't2', 't2', $store), + array($attribute->getAttributeCode() => "IF(t2.value_id>0, t2.value, t1.value)")); if ($attribute->getFlatAddChildData()) { $select->where("e.is_child=?", 0); } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php index faed05b554..a3d4b6ff35 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php @@ -85,7 +85,11 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at $attributeTable = $attribute->getBackend()->getTable(); $attributeCode = $attribute->getAttributeCode(); - $joinCondition = "`e`.`entity_id`=`t1`.`entity_id`"; + $joinConditionTemplate = "`e`.`entity_id`=`%s`.`entity_id`" + ." AND `%s`.`entity_type_id` = ".$attribute->getEntityTypeId() + ." AND `%s`.`attribute_id` = ".$attribute->getId() + ." AND `%s`.`store_id` = %d"; + $joinCondition = sprintf($joinConditionTemplate, 't1', 't1', 't1', 't1', Mage_Core_Model_App::ADMIN_STORE_ID); if ($attribute->getFlatAddChildData()) { $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; } @@ -99,10 +103,7 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at ) ->joinLeft( array('t2' => $attributeTable), - "`t2`.`entity_id`=`t1`.`entity_id`" - . " AND `t1`.`entity_type_id`=`t2`.`entity_type_id`" - . " AND `t1`.`attribute_id`=`t2`.`attribute_id`" - . " AND `t2`.`store_id`={$store}", + sprintf($joinConditionTemplate, 't2', 't2', 't2', 't2', $store), array($attributeCode => $valueExpr)); if (($attribute->getFrontend()->getInputType() != 'multiselect') && $hasValueField) { $select->joinLeft( @@ -117,10 +118,6 @@ public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $at array($attributeCode . '_value' => "IFNULL(`to2`.`value`, `to1`.`value`)") ); } - $select - ->where('t1.entity_type_id=?', $attribute->getEntityTypeId()) - ->where('t1.attribute_id=?', $attribute->getId()) - ->where('t1.store_id=?', 0); if ($attribute->getFlatAddChildData()) { $select->where("e.is_child=?", 0); diff --git a/app/code/core/Mage/GiftMessage/Block/Adminhtml/Sales/Order/Create/Items.php b/app/code/core/Mage/GiftMessage/Block/Adminhtml/Sales/Order/Create/Items.php new file mode 100644 index 0000000000..55b89d075f --- /dev/null +++ b/app/code/core/Mage/GiftMessage/Block/Adminhtml/Sales/Order/Create/Items.php @@ -0,0 +1,94 @@ + + */ +class Mage_GiftMessage_Block_Adminhtml_Sales_Order_Create_Items extends Mage_Adminhtml_Block_Sales_Items_Abstract +{ + /** + * Get Order Item + * + * @return Mage_Sales_Model_Quote_Item + */ + public function getItem() + { + $item = $this->getParentBlock()->getData('item'); + return $item; + } + + + /** + * Prepare html output + * + * @return string + */ + protected function _toHtml() + { + $_item = $this->getItem(); + if ($_item && $this->isGiftMessagesAvailable($_item)) { + return parent::_toHtml(); + } else { + return false; + } + } + + /** + * Check available Gift Messages + * + * @param Mage_Sales_Model_Quote_Item $item + * @return boolean + */ + public function isGiftMessagesAvailable($item=null) + { + if(is_null($item)) { + return $this->helper('giftmessage/message')->getIsMessagesAvailable( + 'items', $this->getQuote(), $this->getStore() + ); + } + + return $this->helper('giftmessage/message')->getIsMessagesAvailable( + 'item', $item, $this->getStore() + ); + } + + /** + * Checks allowed quote item for gift messages + * + * @param Mage_Sales_Model_Quote_Item $item + * @return boolean + */ + public function isAllowedForGiftMessage($item) + { + + return Mage::getSingleton('adminhtml/giftmessage_save')->getIsAllowedQuoteItem($item); + } + +} diff --git a/app/code/core/Mage/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php b/app/code/core/Mage/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php new file mode 100644 index 0000000000..0fff9f8cac --- /dev/null +++ b/app/code/core/Mage/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php @@ -0,0 +1,231 @@ + + */ +class Mage_GiftMessage_Block_Adminhtml_Sales_Order_View_Items extends Mage_Adminhtml_Block_Sales_Items_Abstract +{ + /** + * Get Order Item + * + * @return Mage_Sales_Model_Order_Item + */ + public function getItem() + { + return $this->getParentBlock()->getData('item'); + } + + + /** + * Prepare html output + * + * @return string + */ + protected function _toHtml() + { + $_item = $this->getItem(); + if ($_item && $this->isMessagesAvailable()) { + return parent::_toHtml(); + } else { + return false; + } + } + + + /** + * Indicates that block can display giftmessages form + * + * @return boolean + */ + public function isMessagesAvailable() + { + return $this->helper('giftmessage/message')->getIsMessagesAvailable( + 'order_item', $this->getItem(), $this->getItem()->getOrder()->getStoreId() + ); + } + + + /** + * Giftmessage object + * + * @var Mage_GiftMessage_Model_Message + */ + protected $_giftMessage = array(); + + /** + * Retrive default value for giftmessage sender + * + * @return string + */ + public function getDefaultSender() + { + if(!$this->getItem()) { + return ''; + } + + if($this->getItem()->getOrder()) { + return $this->getItem()->getOrder()->getBillingAddress()->getName(); + } + + return $this->getItem()->getBillingAddress()->getName(); + } + + /** + * Retrive default value for giftmessage recipient + * + * @return string + */ + public function getDefaultRecipient() + { + if(!$this->getItem()) { + return ''; + } + + if($this->getItem()->getOrder()) { + if ($this->getItem()->getOrder()->getShippingAddress()) { + return $this->getItem()->getOrder()->getShippingAddress()->getName(); + } else if ($this->getItem()->getOrder()->getBillingAddress()) { + return $this->getItem()->getOrder()->getBillingAddress()->getName(); + } + } + + if ($this->getItem()->getShippingAddress()) { + return $this->getItem()->getShippingAddress()->getName(); + } else if ($this->getItem()->getBillingAddress()) { + return $this->getItem()->getBillingAddress()->getName(); + } + + return ''; + } + + /** + * Retrive real name for field + * + * @param string $name + * @return string + */ + public function getFieldName($name) + { + return 'giftmessage[' . $this->getItem()->getId() . '][' . $name . ']'; + } + + /** + * Retrive real html id for field + * + * @param string $name + * @return string + */ + public function getFieldId($id) + { + return $this->getFieldIdPrefix() . $id; + } + + /** + * Retrive field html id prefix + * + * @return string + */ + public function getFieldIdPrefix() + { + return 'giftmessage_item_' . $this->getItem()->getId() . '_'; + } + + /** + * Initialize gift message for entity + * + * @return Mage_Adminhtml_Block_Sales_Order_Edit_Items_Grid_Renderer_Name_Giftmessage + */ + protected function _initMessage() + { + $this->_giftMessage[$this->getItem()->getGiftMessageId()] = + $this->helper('giftmessage/message')->getGiftMessage($this->getItem()->getGiftMessageId()); + + // init default values for giftmessage form + if(!$this->getMessage()->getSender()) { + $this->getMessage()->setSender($this->getDefaultSender()); + } + if(!$this->getMessage()->getRecipient()) { + $this->getMessage()->setRecipient($this->getDefaultRecipient()); + } + + return $this; + } + + /** + * Retrive gift message for entity + * + * @return Mage_GiftMessage_Model_Message + */ + public function getMessage() + { + if(!isset($this->_giftMessage[$this->getItem()->getGiftMessageId()])) { + $this->_initMessage(); + } + + return $this->_giftMessage[$this->getItem()->getGiftMessageId()]; + } + + /** + * Retrieve save url + * + * @return array + */ + public function getSaveUrl() + { + return $this->getUrl('*/sales_order_view_giftmessage/save', array( + 'entity' => $this->getItem()->getId(), + 'type' => 'order_item', + 'reload' => true + )); + } + + /** + * Retrive block html id + * + * @return string + */ + public function getHtmlId() + { + return substr($this->getFieldIdPrefix(), 0, -1); + } + + /** + * Indicates that block can display giftmessages form + * + * @return boolean + */ + public function canDisplayGiftmessage() + { + return $this->helper('giftmessage/message')->getIsMessagesAvailable( + 'order_item', $this->getItem(), $this->getItem()->getOrder()->getStoreId() + ); + } +} diff --git a/app/code/core/Mage/GiftMessage/Block/Message/Inline.php b/app/code/core/Mage/GiftMessage/Block/Message/Inline.php index 9bbe9fd4f2..d6c42a7445 100644 --- a/app/code/core/Mage/GiftMessage/Block/Message/Inline.php +++ b/app/code/core/Mage/GiftMessage/Block/Message/Inline.php @@ -172,13 +172,18 @@ public function getItems() { if (!$this->getData('items')) { $items = array(); - foreach ($this->getEntity()->getAllItems() as $item) { + + $entityItems = $this->getEntity()->getAllItems(); + Mage::dispatchEvent('gift_options_prepare_items', array('items' => $entityItems)); + + $type = substr($this->getType(), 0, 5) == 'multi' ? 'address_item' : 'item'; + foreach ($entityItems as $item) { if ($item->getParentItem()) { continue; } - $type = substr($this->getType(), 0, 5) == 'multi' ? 'address_item' : 'item'; - if ($this->helper('giftmessage/message')->isMessagesAvailable($type, $item)) { - $items[] = $item; + if ($this->helper('giftmessage/message')->isMessagesAvailable($type, $item) + || $item->getIsGiftOptionsAvailable()) { + $items[] = $item; } } $this->setData('items', $items); diff --git a/app/code/core/Mage/GiftMessage/Helper/Message.php b/app/code/core/Mage/GiftMessage/Helper/Message.php index 18bc40b751..22b1a47b38 100644 --- a/app/code/core/Mage/GiftMessage/Helper/Message.php +++ b/app/code/core/Mage/GiftMessage/Helper/Message.php @@ -38,8 +38,8 @@ class Mage_GiftMessage_Helper_Message extends Mage_Core_Helper_Data * Giftmessages allow section in configuration * */ - const XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ITEMS = 'sales/gift_messages/allow_items'; - const XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ORDER = 'sales/gift_messages/allow_order'; + const XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ITEMS = 'sales/gift_options/allow_items'; + const XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ORDER = 'sales/gift_options/allow_order'; /** * Next id for edit gift message block @@ -104,37 +104,41 @@ public function getInline($type, Varien_Object $entity, $dontDisplayContainer=fa * @param Mage_Core_Model_Store|integer $store * @return boolean */ - public function isMessagesAvailable($type, Varien_Object $entity, $store=null) + public function isMessagesAvailable($type, Varien_Object $entity, $store = null) { - $resultItems = Mage::getStoreConfig(self::XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ITEMS, $store); - $resultOrder = Mage::getStoreConfig(self::XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ORDER, $store); - if ($type == 'items') { - return $resultItems || $resultOrder; - } - - if (is_object($store)) { - $storeId = $store->getId(); - } elseif (is_numeric($store)) { - $storeId = $store; - } else { - $storeId = Mage::app()->getStore()->getId(); - } + $items = $entity->getAllItems(); + if(!is_array($items) || empty($items)) { + return Mage::getStoreConfig(self::XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ITEMS, $store); + } + if($entity instanceof Mage_Sales_Model_Quote) { + $_type = $entity->getIsMultiShipping() ? 'address_item' : 'item'; + } + else { + $_type = 'order_item'; + } - if ($type=='item') { + foreach ($items as $item) { + if ($item->getParentItem()) { + continue; + } + if ($this->isMessagesAvailable($_type, $item)) { + return true; + } + } + } elseif ($type == 'item') { return !$entity->getProduct()->isVirtual() && $this->_getDependenceFromStoreConfig( $entity->getProduct()->getGiftMessageAvailable(), $store ); - } elseif ($type=='order_item') { + } elseif ($type == 'order_item') { return !$entity->getIsVirtual() && $this->_getDependenceFromStoreConfig( $entity->getGiftMessageAvailable(), $store ); - } elseif ($type=='address_item') { - if (!$resultItems) { - return false; - } + } elseif ($type == 'address_item') { + $storeId = is_numeric($store) ? $store : Mage::app()->getStore($store)->getId(); + if (!$this->isCached('address_item_' . $entity->getProductId())) { $this->setCached( 'address_item_' . $entity->getProductId(), @@ -149,7 +153,7 @@ public function isMessagesAvailable($type, Varien_Object $entity, $store=null) $store ); } else { - return $resultOrder; + return Mage::getStoreConfig(self::XPATH_CONFIG_GIFT_MESSAGE_ALLOW_ORDER, $store); } return false; diff --git a/app/code/core/Mage/GiftMessage/etc/config.xml b/app/code/core/Mage/GiftMessage/etc/config.xml index 33e7d21d7c..6440d3520f 100644 --- a/app/code/core/Mage/GiftMessage/etc/config.xml +++ b/app/code/core/Mage/GiftMessage/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.2 + 0.7.3 @@ -146,6 +146,13 @@ + + + + giftmessage.xml + + + @@ -223,11 +230,11 @@ - - - giftmessage.xml - - - + + + giftmessage.xml + + + diff --git a/app/code/core/Mage/GiftMessage/etc/system.xml b/app/code/core/Mage/GiftMessage/etc/system.xml index 6d0a298acd..c1f502d35f 100644 --- a/app/code/core/Mage/GiftMessage/etc/system.xml +++ b/app/code/core/Mage/GiftMessage/etc/system.xml @@ -29,34 +29,34 @@ - - + + text 100 1 1 0 - - + + select adminhtml/system_config_source_yesno 1 1 1 0 - - - + + + select adminhtml/system_config_source_yesno - 1 + 5 1 1 0 - + - + diff --git a/app/code/core/Mage/GiftMessage/sql/giftmessage_setup/mysql4-upgrade-0.7.2-0.7.3.php b/app/code/core/Mage/GiftMessage/sql/giftmessage_setup/mysql4-upgrade-0.7.2-0.7.3.php new file mode 100644 index 0000000000..4158e52c6e --- /dev/null +++ b/app/code/core/Mage/GiftMessage/sql/giftmessage_setup/mysql4-upgrade-0.7.2-0.7.3.php @@ -0,0 +1,53 @@ + 'sales/gift_options/allow_order', + 'sales/gift_messages/allow_items' => 'sales/gift_options/allow_items' +); + +foreach ($pathesForReplace as $from => $to) { + $installer->run(sprintf("UPDATE `%s` SET `path` = '%s' WHERE `path` = '%s'", + $this->getTable('core/config_data'), $to, $from + )); +} + +/* + * Create new attribute group and move gift_message_available attribute to this group + */ +$entityTypeId = $installer->getEntityTypeId('catalog_product'); +$attributeId = $installer->getAttributeId('catalog_product', 'gift_message_available'); + +$attributeSets = $installer->_conn->fetchAll('select * from '.$this->getTable('eav/attribute_set').' where entity_type_id=?', $entityTypeId); +foreach ($attributeSets as $attributeSet) { + $setId = $attributeSet['attribute_set_id']; + $installer->addAttributeGroup($entityTypeId, $setId, 'Gift Options'); + $groupId = $installer->getAttributeGroupId($entityTypeId, $setId, 'Gift Options'); + $installer->addAttributeToGroup($entityTypeId, $setId, $groupId, $attributeId); +} diff --git a/app/code/core/Mage/GoogleAnalytics/Block/Ga.php b/app/code/core/Mage/GoogleAnalytics/Block/Ga.php index 0f7815795a..2e46201cc2 100644 --- a/app/code/core/Mage/GoogleAnalytics/Block/Ga.php +++ b/app/code/core/Mage/GoogleAnalytics/Block/Ga.php @@ -85,17 +85,14 @@ public function getPageName() */ protected function _getPageTrackingCode($accountId) { - $optPageURL = trim($this->getPageName()); - if ($optPageURL && preg_match('/^\/.*/i', $optPageURL)) { - $optPageURL = "'{$this->jsQuoteEscape($optPageURL)}'"; + $pageName = trim($this->getPageName()); + $optPageURL = ''; + if ($pageName && preg_match('/^\/.*/i', $pageName)) { + $optPageURL = ", '{$this->jsQuoteEscape($pageName)}'"; } - // the code compatible with google checkout shortcut (it requires a global pageTracker variable) return " -_gaq.push(function() { - // the global variable is created intentionally - pageTracker = _gat._getTracker('{$this->jsQuoteEscape($accountId)}'); - pageTracker._trackPageview({$optPageURL}); -}); +_gaq.push(['_setAccount', '{$this->jsQuoteEscape($accountId)}']); +_gaq.push(['_trackPageview'{$optPageURL}]); "; } diff --git a/app/code/core/Mage/GoogleAnalytics/Model/Observer.php b/app/code/core/Mage/GoogleAnalytics/Model/Observer.php index de42bc03ee..6c30581162 100644 --- a/app/code/core/Mage/GoogleAnalytics/Model/Observer.php +++ b/app/code/core/Mage/GoogleAnalytics/Model/Observer.php @@ -86,7 +86,7 @@ public function injectAnalyticsInGoogleCheckoutLink(Varien_Event_Observer $obser // make sure to track google checkout "onsubmit" $onsubmitJs = $block->getOnsubmitJs(); - $block->setOnsubmitJs($onsubmitJs . ($onsubmitJs ? '; ' : '') . 'setUrchinInputCode(pageTracker);'); + $block->setOnsubmitJs($onsubmitJs . ($onsubmitJs ? '; ' : '') . '_gaq.push(function() {var pageTracker = _gaq._getAsyncTracker(); setUrchinInputCode(pageTracker);});'); // add a link that includes google checkout/analytics script, to the first instance of the link block if ($this->_isGoogleCheckoutLinkAdded) { diff --git a/app/code/core/Mage/GoogleBase/Block/Adminhtml/Items/Product.php b/app/code/core/Mage/GoogleBase/Block/Adminhtml/Items/Product.php index 9ff9cde58c..4cc9d47642 100644 --- a/app/code/core/Mage/GoogleBase/Block/Adminhtml/Items/Product.php +++ b/app/code/core/Mage/GoogleBase/Block/Adminhtml/Items/Product.php @@ -145,7 +145,7 @@ protected function _prepareMassaction() public function getGridUrl() { - return $this->getUrl('googlebase/selection/grid', array('index' => $this->getIndex(),'_current'=>true)); + return $this->getUrl('*/googlebase_selection/grid', array('index' => $this->getIndex(),'_current'=>true)); } protected function _getGoogleBaseProductIds() diff --git a/app/code/core/Mage/GoogleBase/Model/Service.php b/app/code/core/Mage/GoogleBase/Model/Service.php index bdda5dfafd..469a8e2cb0 100644 --- a/app/code/core/Mage/GoogleBase/Model/Service.php +++ b/app/code/core/Mage/GoogleBase/Model/Service.php @@ -60,6 +60,8 @@ public function getClient($storeId = null, $loginToken = null, $loginCaptcha = n Zend_Gdata_ClientLogin::CLIENTLOGIN_URI, $type ); + $configTimeout = array('timeout' => 60); + $client->setConfig($configTimeout); Mage::register($this->_clientRegistryId, $client); } } catch (Zend_Gdata_App_CaptchaRequiredException $e) { diff --git a/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/ItemsController.php b/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/ItemsController.php new file mode 100755 index 0000000000..1623a0c4e6 --- /dev/null +++ b/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/ItemsController.php @@ -0,0 +1,393 @@ + +*/ +class Mage_GoogleBase_Adminhtml_Googlebase_ItemsController extends Mage_Adminhtml_Controller_Action +{ + protected function _initAction() + { + $this->loadLayout() + ->_setActiveMenu('catalog/googlebase/items') + ->_addBreadcrumb(Mage::helper('adminhtml')->__('Catalog'), Mage::helper('adminhtml')->__('Catalog')) + ->_addBreadcrumb(Mage::helper('adminhtml')->__('Google Base'), Mage::helper('adminhtml')->__('Google Base')); + return $this; + } + + public function indexAction() + { + $this->_title($this->__('Catalog')) + ->_title($this->__('Google base')) + ->_title($this->__('Manage Items')); + + if (0 === (int)$this->getRequest()->getParam('store')) { + $this->_redirect('*/*/', array('store' => Mage::app()->getAnyStoreView()->getId(), '_current' => true)); + return; + } + $contentBlock = $this->getLayout()->createBlock('googlebase/adminhtml_items')->setStore($this->_getStore()); + + if ($this->getRequest()->getParam('captcha_token') && $this->getRequest()->getParam('captcha_url')) { + $contentBlock->setGbaseCaptchaToken( + Mage::helper('core')->urlDecode($this->getRequest()->getParam('captcha_token')) + ); + $contentBlock->setGbaseCaptchaUrl( + Mage::helper('core')->urlDecode($this->getRequest()->getParam('captcha_url')) + ); + } + + if (!$this->_getConfig()->isValidBaseCurrencyCode($this->_getStore()->getId())) { + $_countryInfo = $this->_getConfig()->getTargetCountryInfo($this->_getStore()->getId()); + $this->_getSession()->addNotice( + $this->__( + "Base Currency should be set to %s for %s in system configuration. Otherwise item prices won't be correct in Google Base.", + $_countryInfo['currency_name'], + $_countryInfo['name'] + ) + ); + } + + $this->_initAction() + ->_addBreadcrumb(Mage::helper('googlebase')->__('Items'), Mage::helper('googlebase')->__('Items')) + ->_addContent($contentBlock) + ->renderLayout(); + } + + public function gridAction() + { + $this->loadLayout(); + return $this->getResponse()->setBody( + $this->getLayout() + ->createBlock('googlebase/adminhtml_items_item') + ->setIndex($this->getRequest()->getParam('index')) + ->toHtml() + ); + } + + public function massAddAction() + { + $storeId = $this->_getStore()->getId(); + $productIds = $this->getRequest()->getParam('product', null); + + $totalAdded = 0; + + try { + if (is_array($productIds)) { + foreach ($productIds as $productId) { + $product = Mage::getSingleton('catalog/product') + ->setStoreId($storeId) + ->load($productId); + + if ($product->getId()) { + Mage::getModel('googlebase/item') + ->setProduct($product) + ->insertItem() + ->save(); + + $totalAdded++; + } + } + } + + if ($totalAdded > 0) { + $this->_getSession()->addSuccess( + $this->__('Total of %d product(s) have been added to Google Base.', $totalAdded) + ); + } elseif (is_null($productIds)) { + $this->_getSession()->addError($this->__('Session expired during export. Please revise exported products and repeat the process if necessary.')); + } else { + $this->_getSession()->addError($this->__('No products were added to Google Base')); + } + } catch (Zend_Gdata_App_CaptchaRequiredException $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirectToCaptcha($e); + return; + } catch (Zend_Gdata_App_Exception $e) { + $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + + $this->_redirect('*/*/index', array('store'=>$storeId)); + } + + public function massDeleteAction() + { + $storeId = $this->_getStore()->getId(); + $itemIds = $this->getRequest()->getParam('item'); + + $totalDeleted = 0; + + try { + foreach ($itemIds as $itemId) { + $item = Mage::getModel('googlebase/item')->load($itemId); + if ($item->getId()) { + $item->deleteItem(); + $item->delete(); + $totalDeleted++; + } + } + if ($totalDeleted > 0) { + $this->_getSession()->addSuccess( + $this->__('Total of %d items(s) have been removed from Google Base.', $totalDeleted) + ); + } else { + $this->_getSession()->addError($this->__('No items were deleted from Google Base')); + } + } catch (Zend_Gdata_App_CaptchaRequiredException $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirectToCaptcha($e); + return; + } catch (Zend_Gdata_App_Exception $e) { + $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + + $this->_redirect('*/*/index', array('store'=>$storeId)); + } + + public function massPublishAction() + { + $storeId = $this->_getStore()->getId(); + $itemIds = $this->getRequest()->getParam('item'); + + $totalPublished = 0; + + try { + foreach ($itemIds as $itemId) { + $item = Mage::getModel('googlebase/item')->load($itemId); + if ($item->getId()) { + $item->activateItem(); + $totalPublished++; + } + } + if ($totalPublished > 0) { + $this->_getSession()->addSuccess( + $this->__('Total of %d items(s) have been published.', $totalPublished) + ); + } else { + $this->_getSession()->addError($this->__('No items were published')); + } + } catch (Zend_Gdata_App_CaptchaRequiredException $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirectToCaptcha($e); + return; + } catch (Zend_Gdata_App_Exception $e) { + $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + + $this->_redirect('*/*/index', array('store'=>$storeId)); + } + + public function massHideAction() + { + $storeId = $this->_getStore()->getId(); + $itemIds = $this->getRequest()->getParam('item'); + + $totalHidden = 0; + + try { + foreach ($itemIds as $itemId) { + $item = Mage::getModel('googlebase/item')->load($itemId); + if ($item->getId()) { + $item->hideItem(); + $totalHidden++; + } + } + if ($totalHidden > 0) { + $this->_getSession()->addSuccess( + $this->__('Total of %d items(s) have been saved as inactive items.', $totalHidden) + ); + } else { + $this->_getSession()->addError($this->__('No items were saved as inactive items')); + } + } catch (Zend_Gdata_App_CaptchaRequiredException $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirectToCaptcha($e); + return; + } catch (Zend_Gdata_App_Exception $e) { + $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + + $this->_redirect('*/*/index', array('store'=>$storeId)); + } + + /** + * Update items statistics and remove the items which are not available in Google Base + */ + public function refreshAction() + { + $storeId = $this->_getStore()->getId(); + $totalUpdated = 0; + $totalDeleted = 0; + + try { + $itemIds = $this->getRequest()->getParam('item'); + foreach ($itemIds as $itemId) { + $item = Mage::getModel('googlebase/item')->load($itemId); + + $stats = Mage::getSingleton('googlebase/service_feed')->getItemStats($item->getGbaseItemId(), $storeId); + if ($stats === null) { + $item->delete(); + $totalDeleted++; + continue; + } + + if ($stats['draft'] != $item->getIsHidden()) { + $item->setIsHidden($stats['draft']); + } + + if (isset($stats['expires'])) { + $item->setExpires($stats['expires']); + } + + $item->save(); + $totalUpdated++; + } + + $this->_getSession()->addSuccess( + $this->__('Total of %d items(s) have been deleted; total of %d items(s) have been updated.', $totalDeleted, $totalUpdated) + ); + + } catch (Zend_Gdata_App_CaptchaRequiredException $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirectToCaptcha($e); + return; + } catch (Zend_Gdata_App_Exception $e) { + $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + + $this->_redirect('*/*/index', array('store'=>$storeId)); + } + + public function confirmCaptchaAction() + { + $storeId = $this->_getStore()->getId(); + try { + Mage::getModel('googlebase/service')->getClient( + $storeId, + Mage::helper('core')->urlDecode($this->getRequest()->getParam('captcha_token')), + $this->getRequest()->getParam('user_confirm') + ); + $this->_getSession()->addSuccess($this->__('Captcha has been confirmed.')); + + } catch (Zend_Gdata_App_CaptchaRequiredException $e) { + $this->_getSession()->addError($this->__('Captcha confirmation error: %s', $e->getMessage())); + $this->_redirectToCaptcha($e); + return; + } catch (Zend_Gdata_App_Exception $e) { + $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); + } catch (Exception $e) { + $this->_getSession()->addError($this->__('Captcha confirmation error: %s', $e->getMessage())); + } + + $this->_redirect('*/*/index', array('store'=>$storeId)); + } + + /** + * Redirect user to Google Captcha challenge + * + * @param Zend_Gdata_App_CaptchaRequiredException $e + */ + protected function _redirectToCaptcha($e) + { + $this->_redirect('*/*/index', + array('store' => $this->_getStore()->getId(), + 'captcha_token' => Mage::helper('core')->urlEncode($e->getCaptchaToken()), + 'captcha_url' => Mage::helper('core')->urlEncode($e->getCaptchaUrl()) + ) + ); + } + + /** + * Get store object, basing on request + * + * @return Mage_Core_Model_Store + * @throws Mage_Core_Exception + */ + public function _getStore() + { + $store = Mage::app()->getStore((int)$this->getRequest()->getParam('store', 0)); + if ((!$store) || 0 == $store->getId()) { + Mage::throwException($this->__('Unable to select a Store View.')); + } + return $store; + } + + protected function _getConfig() + { + return Mage::getSingleton('googlebase/config'); + } + + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('catalog/googlebase/items'); + } + + /** + * Parse Exception Response Body + * + * @param string $message Exception message to parse + * @return string + */ + protected function _parseGdataExceptionMessage($message) + { + $result = array(); + foreach (explode("\n", $message) as $row) { + if (strip_tags($row) == $row) { + $result[] = $row; + continue; + } + + // parse not well-formatted xml + preg_match_all('/(reason|field|type)=\"([^\"]+)\"/', $row, $matches); + + if (is_array($matches) && count($matches) == 3) { + if (is_array($matches[1]) && count($matches[1]) > 0) { + $c = count($matches[1]); + for ($i = 0; $i < $c; $i++) { + if (isset($matches[2][$i])) { + $result[] = ucfirst($matches[1][$i]) . ': ' . $matches[2][$i]; + } + } + } + } + } + return implode(". ", $result); + } +} diff --git a/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/SelectionController.php b/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/SelectionController.php new file mode 100755 index 0000000000..050fcd1cae --- /dev/null +++ b/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/SelectionController.php @@ -0,0 +1,57 @@ + + */ +class Mage_GoogleBase_Adminhtml_Googlebase_SelectionController extends Mage_Adminhtml_Controller_Action +{ + public function searchAction() + { + return $this->getResponse()->setBody( + $this->getLayout() + ->createBlock('googlebase/adminhtml_items_product') + ->setIndex($this->getRequest()->getParam('index')) + ->setFirstShow(true) + ->toHtml() + ); + } + + public function gridAction() + { + $this->loadLayout(); + return $this->getResponse()->setBody( + $this->getLayout() + ->createBlock('googlebase/adminhtml_items_product') + ->setIndex($this->getRequest()->getParam('index')) + ->toHtml() + ); + } +} diff --git a/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/TypesController.php b/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/TypesController.php new file mode 100755 index 0000000000..1c6bb32952 --- /dev/null +++ b/app/code/core/Mage/GoogleBase/controllers/Adminhtml/Googlebase/TypesController.php @@ -0,0 +1,265 @@ + +*/ +class Mage_GoogleBase_Adminhtml_Googlebase_TypesController extends Mage_Adminhtml_Controller_Action +{ + /** + * Dispatches controller_action_postdispatch_adminhtml Event (as not Adminhtml router) + */ + public function postDispatch() + { + parent::postDispatch(); + if ($this->getFlag('', self::FLAG_NO_POST_DISPATCH)) { + return; + } + Mage::dispatchEvent('controller_action_postdispatch_adminhtml', array('controller_action' => $this)); + } + + protected function _initItemType() + { + $this->_title($this->__('Catalog')) + ->_title($this->__('Google Base')) + ->_title($this->__('Manage Attributes')); + + Mage::register('current_item_type', Mage::getModel('googlebase/type')); + $typeId = $this->getRequest()->getParam('id'); + if (!is_null($typeId)) { + Mage::registry('current_item_type')->load($typeId); + } + } + + protected function _initAction() + { + $this->loadLayout() + ->_setActiveMenu('catalog/googlebase/types') + ->_addBreadcrumb(Mage::helper('adminhtml')->__('Catalog'), Mage::helper('adminhtml')->__('Catalog')) + ->_addBreadcrumb(Mage::helper('adminhtml')->__('Google Base'), Mage::helper('adminhtml')->__('Google Base')); + return $this; + } + + public function indexAction() + { + $this->_title($this->__('Catalog')) + ->_title($this->__('Google base')) + ->_title($this->__('Manage Attributes')); + + $this->_initAction() + ->_addBreadcrumb(Mage::helper('googlebase')->__('Item Types'), Mage::helper('googlebase')->__('Item Types')) + ->_addContent($this->getLayout()->createBlock('googlebase/adminhtml_types')) + ->renderLayout(); + } + + /** + * Grid for AJAX request + */ + public function gridAction() + { + $this->getResponse()->setBody( + $this->getLayout()->createBlock('googlebase/adminhtml_types_grid')->toHtml() + ); + } + + public function newAction() + { + try { + $this->_initItemType(); + + $this->_title($this->__('New ItemType')); + + $this->_initAction() + ->_addBreadcrumb(Mage::helper('googlebase')->__('New Item Type'), Mage::helper('adminhtml')->__('New Item Type')) + ->_addContent($this->getLayout()->createBlock('googlebase/adminhtml_types_edit')) + ->renderLayout(); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirect('*/*/index', array('store' => $this->_getStore()->getId())); + } + } + + public function editAction() + { + $this->_title($this->__('Catalog')) + ->_title($this->__('Google base')) + ->_title($this->__('Manage Attributes')); + + $id = $this->getRequest()->getParam('id'); + $model = Mage::getModel('googlebase/type'); + + try { + $result = array(); + if ($id) { + $model->load($id); + $collection = Mage::getResourceModel('googlebase/attribute_collection') + ->addTypeFilter($model->getTypeId()) + ->load(); + foreach ($collection as $attribute) { + $result[] = $attribute->getData(); + } + } + + $this->_title($this->__('Edit Item Type')); + + Mage::register('current_item_type', $model); + Mage::register('attributes', $result); + + $this->_initAction() + ->_addBreadcrumb($id ? Mage::helper('googlebase')->__('Edit Item Type') : Mage::helper('googlebase')->__('New Item Type'), $id ? Mage::helper('googlebase')->__('Edit Item Type') : Mage::helper('googlebase')->__('New Item Type')) + ->_addContent($this->getLayout()->createBlock('googlebase/adminhtml_types_edit')) + ->renderLayout(); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + $this->_redirect('*/*/index'); + } + } + + public function saveAction() + { + $typeModel = Mage::getModel('googlebase/type'); + $id = $this->getRequest()->getParam('type_id'); + if (!is_null($id)) { + $typeModel->load($id); + } + + try { + if ($typeModel->getId()) { + $collection = Mage::getResourceModel('googlebase/attribute_collection') + ->addTypeFilter($typeModel->getId()) + ->load(); + foreach ($collection as $attribute) { + $attribute->delete(); + } + } + $typeModel->setAttributeSetId($this->getRequest()->getParam('attribute_set_id')) + ->setGbaseItemtype($this->getRequest()->getParam('gbase_itemtype')) + ->setTargetCountry($this->getRequest()->getParam('target_country')) + ->save(); + + + $attributes = $this->getRequest()->getParam('attributes'); + if (is_array($attributes)) { + $typeId = $typeModel->getId(); + foreach ($attributes as $attrInfo) { + if (isset($attrInfo['delete']) && $attrInfo['delete'] == 1) { + continue; + } + Mage::getModel('googlebase/attribute') + ->setAttributeId($attrInfo['attribute_id']) + ->setGbaseAttribute($attrInfo['gbase_attribute']) + ->setTypeId($typeId) + ->save(); + } + } + + Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('googlebase')->__('The item type has been saved.')); + } catch (Exception $e) { + Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + } + $this->_redirect('*/*/index', array('store' => $this->_getStore()->getId())); + } + + public function deleteAction () + { + try { + $id = $this->getRequest()->getParam('id'); + $model = Mage::getModel('googlebase/type'); + $model->load($id); + if ($model->getTypeId()) { + $model->delete(); + } + $this->_getSession()->addSuccess($this->__('Item Type was deleted')); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + $this->_redirect('*/*/index', array('store' => $this->_getStore()->getId())); + } + + public function loadAttributesAction () + { + try { + $this->getResponse()->setBody( + $this->getLayout()->createBlock('googlebase/adminhtml_types_edit_attributes') + ->setAttributeSetId($this->getRequest()->getParam('attribute_set_id')) + ->setGbaseItemtype($this->getRequest()->getParam('gbase_itemtype')) + ->setTargetCountry($this->getRequest()->getParam('target_country')) + ->setAttributeSetSelected(true) + ->toHtml() + ); + } catch (Exception $e) { + // just need to output text with error + $this->_getSession()->addError($e->getMessage()); + } + } + + public function loadItemTypesAction() + { + try { + $this->getResponse()->setBody( + $this->getLayout()->getBlockSingleton('googlebase/adminhtml_types_edit_form') + ->getItemTypesSelectElement($this->getRequest()->getParam('target_country')) + ->toHtml() + ); + } catch (Exception $e) { + // just need to output text with error + $this->_getSession()->addError($e->getMessage()); + } + } + + protected function loadAttributeSetsAction() + { + try { + $this->getResponse()->setBody( + $this->getLayout()->getBlockSingleton('googlebase/adminhtml_types_edit_form') + ->getAttributeSetsSelectElement($this->getRequest()->getParam('target_country')) + ->toHtml() + ); + } catch (Exception $e) { + // just need to output text with error + $this->_getSession()->addError($e->getMessage()); + } + } + + public function _getStore() + { + $storeId = (int) $this->getRequest()->getParam('store', 0); + if ($storeId == 0) { + return Mage::app()->getDefaultStoreView(); + } + return Mage::app()->getStore($storeId); + } + + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('catalog/googlebase/types'); + } +} diff --git a/app/code/core/Mage/GoogleBase/controllers/ItemsController.php b/app/code/core/Mage/GoogleBase/controllers/ItemsController.php index 9fc0494ef5..a7dca1b6aa 100644 --- a/app/code/core/Mage/GoogleBase/controllers/ItemsController.php +++ b/app/code/core/Mage/GoogleBase/controllers/ItemsController.php @@ -24,370 +24,18 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +require_once 'Mage/GoogleBase/controllers/Adminhtml/Googlebase/ItemsController.php'; + /** * GoogleBase Admin Items Controller * * @category Mage * @package Mage_GoogleBase - * @name Mage_GoogleBase_ItemTypesController + * @name Mage_GoogleBase_ItemsController * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_GoogleBase_Adminhtml_Googlebase_ItemsController is used */ -class Mage_GoogleBase_ItemsController extends Mage_Adminhtml_Controller_Action +class Mage_GoogleBase_ItemsController extends Mage_GoogleBase_Adminhtml_Googlebase_ItemsController { - protected function _initAction() - { - $this->loadLayout() - ->_setActiveMenu('catalog/googlebase/items') - ->_addBreadcrumb(Mage::helper('adminhtml')->__('Catalog'), Mage::helper('adminhtml')->__('Catalog')) - ->_addBreadcrumb(Mage::helper('adminhtml')->__('Google Base'), Mage::helper('adminhtml')->__('Google Base')); - return $this; - } - - public function indexAction() - { - $this->_title($this->__('Catalog')) - ->_title($this->__('Google base')) - ->_title($this->__('Manage Items')); - - if (0 === (int)$this->getRequest()->getParam('store')) { - $this->_redirect('*/*/', array('store' => Mage::app()->getAnyStoreView()->getId(), '_current' => true)); - return; - } - $contentBlock = $this->getLayout()->createBlock('googlebase/adminhtml_items')->setStore($this->_getStore()); - - if ($this->getRequest()->getParam('captcha_token') && $this->getRequest()->getParam('captcha_url')) { - $contentBlock->setGbaseCaptchaToken( - Mage::helper('core')->urlDecode($this->getRequest()->getParam('captcha_token')) - ); - $contentBlock->setGbaseCaptchaUrl( - Mage::helper('core')->urlDecode($this->getRequest()->getParam('captcha_url')) - ); - } - - if (!$this->_getConfig()->isValidBaseCurrencyCode($this->_getStore()->getId())) { - $_countryInfo = $this->_getConfig()->getTargetCountryInfo($this->_getStore()->getId()); - $this->_getSession()->addNotice( - $this->__( - "Base Currency should be set to %s for %s in system configuration. Otherwise item prices won't be correct in Google Base.", - $_countryInfo['currency_name'], - $_countryInfo['name'] - ) - ); - } - - $this->_initAction() - ->_addBreadcrumb(Mage::helper('googlebase')->__('Items'), Mage::helper('googlebase')->__('Items')) - ->_addContent($contentBlock) - ->renderLayout(); - } - - public function gridAction() - { - $this->loadLayout(); - return $this->getResponse()->setBody( - $this->getLayout() - ->createBlock('googlebase/adminhtml_items_item') - ->setIndex($this->getRequest()->getParam('index')) - ->toHtml() - ); - } - - public function massAddAction() - { - $storeId = $this->_getStore()->getId(); - $productIds = $this->getRequest()->getParam('product', null); - - $totalAdded = 0; - - try { - if (is_array($productIds)) { - foreach ($productIds as $productId) { - $product = Mage::getSingleton('catalog/product') - ->setStoreId($storeId) - ->load($productId); - - if ($product->getId()) { - Mage::getModel('googlebase/item') - ->setProduct($product) - ->insertItem() - ->save(); - - $totalAdded++; - } - } - } - - if ($totalAdded > 0) { - $this->_getSession()->addSuccess( - $this->__('Total of %d product(s) have been added to Google Base.', $totalAdded) - ); - } elseif (is_null($productIds)) { - $this->_getSession()->addError($this->__('Session expired during export. Please revise exported products and repeat the process if necessary.')); - } else { - $this->_getSession()->addError($this->__('No products were added to Google Base')); - } - } catch (Zend_Gdata_App_CaptchaRequiredException $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirectToCaptcha($e); - return; - } catch (Zend_Gdata_App_Exception $e) { - $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - } - - $this->_redirect('*/*/index', array('store'=>$storeId)); - } - - public function massDeleteAction() - { - $storeId = $this->_getStore()->getId(); - $itemIds = $this->getRequest()->getParam('item'); - - $totalDeleted = 0; - - try { - foreach ($itemIds as $itemId) { - $item = Mage::getModel('googlebase/item')->load($itemId); - if ($item->getId()) { - $item->deleteItem(); - $item->delete(); - $totalDeleted++; - } - } - if ($totalDeleted > 0) { - $this->_getSession()->addSuccess( - $this->__('Total of %d items(s) have been removed from Google Base.', $totalDeleted) - ); - } else { - $this->_getSession()->addError($this->__('No items were deleted from Google Base')); - } - } catch (Zend_Gdata_App_CaptchaRequiredException $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirectToCaptcha($e); - return; - } catch (Zend_Gdata_App_Exception $e) { - $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - } - - $this->_redirect('*/*/index', array('store'=>$storeId)); - } - - public function massPublishAction() - { - $storeId = $this->_getStore()->getId(); - $itemIds = $this->getRequest()->getParam('item'); - - $totalPublished = 0; - - try { - foreach ($itemIds as $itemId) { - $item = Mage::getModel('googlebase/item')->load($itemId); - if ($item->getId()) { - $item->activateItem(); - $totalPublished++; - } - } - if ($totalPublished > 0) { - $this->_getSession()->addSuccess( - $this->__('Total of %d items(s) have been published.', $totalPublished) - ); - } else { - $this->_getSession()->addError($this->__('No items were published')); - } - } catch (Zend_Gdata_App_CaptchaRequiredException $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirectToCaptcha($e); - return; - } catch (Zend_Gdata_App_Exception $e) { - $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - } - - $this->_redirect('*/*/index', array('store'=>$storeId)); - } - - public function massHideAction() - { - $storeId = $this->_getStore()->getId(); - $itemIds = $this->getRequest()->getParam('item'); - - $totalHidden = 0; - - try { - foreach ($itemIds as $itemId) { - $item = Mage::getModel('googlebase/item')->load($itemId); - if ($item->getId()) { - $item->hideItem(); - $totalHidden++; - } - } - if ($totalHidden > 0) { - $this->_getSession()->addSuccess( - $this->__('Total of %d items(s) have been saved as inactive items.', $totalHidden) - ); - } else { - $this->_getSession()->addError($this->__('No items were saved as inactive items')); - } - } catch (Zend_Gdata_App_CaptchaRequiredException $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirectToCaptcha($e); - return; - } catch (Zend_Gdata_App_Exception $e) { - $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - } - - $this->_redirect('*/*/index', array('store'=>$storeId)); - } - - /** - * Update items statistics and remove the items which are not available in Google Base - */ - public function refreshAction() - { - $storeId = $this->_getStore()->getId(); - $totalUpdated = 0; - $totalDeleted = 0; - - try { - $itemIds = $this->getRequest()->getParam('item'); - foreach ($itemIds as $itemId) { - $item = Mage::getModel('googlebase/item')->load($itemId); - - $stats = Mage::getSingleton('googlebase/service_feed')->getItemStats($item->getGbaseItemId(), $storeId); - if ($stats === null) { - $item->delete(); - $totalDeleted++; - continue; - } - - if ($stats['draft'] != $item->getIsHidden()) { - $item->setIsHidden($stats['draft']); - } - - if (isset($stats['expires'])) { - $item->setExpires($stats['expires']); - } - - $item->save(); - $totalUpdated++; - } - - $this->_getSession()->addSuccess( - $this->__('Total of %d items(s) have been deleted; total of %d items(s) have been updated.', $totalDeleted, $totalUpdated) - ); - - } catch (Zend_Gdata_App_CaptchaRequiredException $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirectToCaptcha($e); - return; - } catch (Zend_Gdata_App_Exception $e) { - $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - } - - $this->_redirect('*/*/index', array('store'=>$storeId)); - } - - public function confirmCaptchaAction() - { - $storeId = $this->_getStore()->getId(); - try { - Mage::getModel('googlebase/service')->getClient( - $storeId, - Mage::helper('core')->urlDecode($this->getRequest()->getParam('captcha_token')), - $this->getRequest()->getParam('user_confirm') - ); - $this->_getSession()->addSuccess($this->__('Captcha has been confirmed.')); - - } catch (Zend_Gdata_App_CaptchaRequiredException $e) { - $this->_getSession()->addError($this->__('Captcha confirmation error: %s', $e->getMessage())); - $this->_redirectToCaptcha($e); - return; - } catch (Zend_Gdata_App_Exception $e) { - $this->_getSession()->addError( $this->_parseGdataExceptionMessage($e->getMessage()) ); - } catch (Exception $e) { - $this->_getSession()->addError($this->__('Captcha confirmation error: %s', $e->getMessage())); - } - - $this->_redirect('*/*/index', array('store'=>$storeId)); - } - - /** - * Redirect user to Google Captcha challenge - * - * @param Zend_Gdata_App_CaptchaRequiredException $e - */ - protected function _redirectToCaptcha($e) - { - $this->_redirect('*/*/index', - array('store' => $this->_getStore()->getId(), - 'captcha_token' => Mage::helper('core')->urlEncode($e->getCaptchaToken()), - 'captcha_url' => Mage::helper('core')->urlEncode($e->getCaptchaUrl()) - ) - ); - } - - /** - * Get store object, basing on request - * - * @return Mage_Core_Model_Store - * @throws Mage_Core_Exception - */ - public function _getStore() - { - $store = Mage::app()->getStore((int)$this->getRequest()->getParam('store', 0)); - if ((!$store) || 0 == $store->getId()) { - Mage::throwException($this->__('Unable to select a Store View.')); - } - return $store; - } - - protected function _getConfig() - { - return Mage::getSingleton('googlebase/config'); - } - - protected function _isAllowed() - { - return Mage::getSingleton('admin/session')->isAllowed('catalog/googlebase/items'); - } - - /** - * Parse Exception Response Body - * - * @param string $message Exception message to parse - * @return string - */ - protected function _parseGdataExceptionMessage($message) - { - $result = array(); - foreach (explode("\n", $message) as $row) { - if (strip_tags($row) == $row) { - $result[] = $row; - continue; - } - - // parse not well-formatted xml - preg_match_all('/(reason|field|type)=\"([^\"]+)\"/', $row, $matches); - if (is_array($matches) && count($matches) == 3) { - if (is_array($matches[1]) && count($matches[1]) > 0) { - $c = count($matches[1]); - for ($i = 0; $i < $c; $i++) { - if (isset($matches[2][$i])) { - $result[] = ucfirst($matches[1][$i]) . ': ' . $matches[2][$i]; - } - } - } - } - } - return implode(". ", $result); - } } diff --git a/app/code/core/Mage/GoogleBase/controllers/SelectionController.php b/app/code/core/Mage/GoogleBase/controllers/SelectionController.php index 4a4770b72d..f47397684f 100644 --- a/app/code/core/Mage/GoogleBase/controllers/SelectionController.php +++ b/app/code/core/Mage/GoogleBase/controllers/SelectionController.php @@ -24,34 +24,17 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +require_once 'Mage/GoogleBase/controllers/Adminhtml/Googlebase/SelectionController.php'; + /** * GoogleBase Products selection grid controller * * @category Mage * @package Mage_GoogleBase * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_GoogleBase_Adminhtml_Googlebase_SelectionController is used */ -class Mage_GoogleBase_SelectionController extends Mage_Adminhtml_Controller_Action +class Mage_GoogleBase_SelectionController extends Mage_GoogleBase_Adminhtml_Googlebase_SelectionController { - public function searchAction() - { - return $this->getResponse()->setBody( - $this->getLayout() - ->createBlock('googlebase/adminhtml_items_product') - ->setIndex($this->getRequest()->getParam('index')) - ->setFirstShow(true) - ->toHtml() - ); - } - public function gridAction() - { - $this->loadLayout(); - return $this->getResponse()->setBody( - $this->getLayout() - ->createBlock('googlebase/adminhtml_items_product') - ->setIndex($this->getRequest()->getParam('index')) - ->toHtml() - ); - } } diff --git a/app/code/core/Mage/GoogleBase/controllers/TypesController.php b/app/code/core/Mage/GoogleBase/controllers/TypesController.php index 32b4281186..ce840a701a 100644 --- a/app/code/core/Mage/GoogleBase/controllers/TypesController.php +++ b/app/code/core/Mage/GoogleBase/controllers/TypesController.php @@ -24,242 +24,18 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +require_once 'Mage/GoogleBase/controllers/Adminhtml/Googlebase/TypesController.php'; + /** * GoogleBase Admin Item Types Controller * * @category Mage * @package Mage_GoogleBase - * @name Mage_GoogleBase_ItemTypesController + * @name Mage_GoogleBase_TypesController * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_GoogleBase_Adminhtml_Googlebase_TypesController is used */ -class Mage_GoogleBase_TypesController extends Mage_Adminhtml_Controller_Action +class Mage_GoogleBase_TypesController extends Mage_GoogleBase_Adminhtml_Googlebase_TypesController { - /** - * Dispatches controller_action_postdispatch_adminhtml Event (as not Adminhtml router) - */ - public function postDispatch() - { - parent::postDispatch(); - if ($this->getFlag('', self::FLAG_NO_POST_DISPATCH)) { - return; - } - Mage::dispatchEvent('controller_action_postdispatch_adminhtml', array('controller_action' => $this)); - } - - protected function _initItemType() - { - $this->_title($this->__('Catalog')) - ->_title($this->__('Google Base')) - ->_title($this->__('Manage Attributes')); - - Mage::register('current_item_type', Mage::getModel('googlebase/type')); - $typeId = $this->getRequest()->getParam('id'); - if (!is_null($typeId)) { - Mage::registry('current_item_type')->load($typeId); - } - } - - protected function _initAction() - { - $this->loadLayout() - ->_setActiveMenu('catalog/googlebase/types') - ->_addBreadcrumb(Mage::helper('adminhtml')->__('Catalog'), Mage::helper('adminhtml')->__('Catalog')) - ->_addBreadcrumb(Mage::helper('adminhtml')->__('Google Base'), Mage::helper('adminhtml')->__('Google Base')); - return $this; - } - - public function indexAction() - { - $this->_title($this->__('Catalog')) - ->_title($this->__('Google base')) - ->_title($this->__('Manage Attributes')); - - $this->_initAction() - ->_addBreadcrumb(Mage::helper('googlebase')->__('Item Types'), Mage::helper('googlebase')->__('Item Types')) - ->_addContent($this->getLayout()->createBlock('googlebase/adminhtml_types')) - ->renderLayout(); - } - - /** - * Grid for AJAX request - */ - public function gridAction() - { - $this->getResponse()->setBody( - $this->getLayout()->createBlock('googlebase/adminhtml_types_grid')->toHtml() - ); - } - - public function newAction() - { - try { - $this->_initItemType(); - - $this->_title($this->__('New ItemType')); - - $this->_initAction() - ->_addBreadcrumb(Mage::helper('googlebase')->__('New Item Type'), Mage::helper('adminhtml')->__('New Item Type')) - ->_addContent($this->getLayout()->createBlock('googlebase/adminhtml_types_edit')) - ->renderLayout(); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirect('*/*/index', array('store' => $this->_getStore()->getId())); - } - } - - public function editAction() - { - $this->_title($this->__('Catalog')) - ->_title($this->__('Google base')) - ->_title($this->__('Manage Attributes')); - - $id = $this->getRequest()->getParam('id'); - $model = Mage::getModel('googlebase/type'); - - try { - $result = array(); - if ($id) { - $model->load($id); - $collection = Mage::getResourceModel('googlebase/attribute_collection') - ->addTypeFilter($model->getTypeId()) - ->load(); - foreach ($collection as $attribute) { - $result[] = $attribute->getData(); - } - } - - $this->_title($this->__('Edit Item Type')); - - Mage::register('current_item_type', $model); - Mage::register('attributes', $result); - - $this->_initAction() - ->_addBreadcrumb($id ? Mage::helper('googlebase')->__('Edit Item Type') : Mage::helper('googlebase')->__('New Item Type'), $id ? Mage::helper('googlebase')->__('Edit Item Type') : Mage::helper('googlebase')->__('New Item Type')) - ->_addContent($this->getLayout()->createBlock('googlebase/adminhtml_types_edit')) - ->renderLayout(); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - $this->_redirect('*/*/index'); - } - } - - public function saveAction() - { - $typeModel = Mage::getModel('googlebase/type'); - $id = $this->getRequest()->getParam('type_id'); - if (!is_null($id)) { - $typeModel->load($id); - } - - try { - if ($typeModel->getId()) { - $collection = Mage::getResourceModel('googlebase/attribute_collection') - ->addTypeFilter($typeModel->getId()) - ->load(); - foreach ($collection as $attribute) { - $attribute->delete(); - } - } - $typeModel->setAttributeSetId($this->getRequest()->getParam('attribute_set_id')) - ->setGbaseItemtype($this->getRequest()->getParam('gbase_itemtype')) - ->setTargetCountry($this->getRequest()->getParam('target_country')) - ->save(); - - - $attributes = $this->getRequest()->getParam('attributes'); - if (is_array($attributes)) { - $typeId = $typeModel->getId(); - foreach ($attributes as $attrInfo) { - if (isset($attrInfo['delete']) && $attrInfo['delete'] == 1) { - continue; - } - Mage::getModel('googlebase/attribute') - ->setAttributeId($attrInfo['attribute_id']) - ->setGbaseAttribute($attrInfo['gbase_attribute']) - ->setTypeId($typeId) - ->save(); - } - } - - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('googlebase')->__('The item type has been saved.')); - } catch (Exception $e) { - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); - } - $this->_redirect('*/*/index', array('store' => $this->_getStore()->getId())); - } - - public function deleteAction () - { - try { - $id = $this->getRequest()->getParam('id'); - $model = Mage::getModel('googlebase/type'); - $model->load($id); - if ($model->getTypeId()) { - $model->delete(); - } - $this->_getSession()->addSuccess($this->__('Item Type was deleted')); - } catch (Exception $e) { - $this->_getSession()->addError($e->getMessage()); - } - $this->_redirect('*/*/index', array('store' => $this->_getStore()->getId())); - } - - public function loadAttributesAction () - { - try { - $this->getResponse()->setBody( - $this->getLayout()->createBlock('googlebase/adminhtml_types_edit_attributes') - ->setAttributeSetId($this->getRequest()->getParam('attribute_set_id')) - ->setGbaseItemtype($this->getRequest()->getParam('gbase_itemtype')) - ->setTargetCountry($this->getRequest()->getParam('target_country')) - ->setAttributeSetSelected(true) - ->toHtml() - ); - } catch (Exception $e) { - // just need to output text with error - $this->_getSession()->addError($e->getMessage()); - } - } - - public function loadItemTypesAction() - { - try { - $this->getResponse()->setBody( - $this->getLayout()->getBlockSingleton('googlebase/adminhtml_types_edit_form') - ->getItemTypesSelectElement($this->getRequest()->getParam('target_country')) - ->toHtml() - ); - } catch (Exception $e) { - // just need to output text with error - $this->_getSession()->addError($e->getMessage()); - } - } - - protected function loadAttributeSetsAction() - { - try { - $this->getResponse()->setBody( - $this->getLayout()->getBlockSingleton('googlebase/adminhtml_types_edit_form') - ->getAttributeSetsSelectElement($this->getRequest()->getParam('target_country')) - ->toHtml() - ); - } catch (Exception $e) { - // just need to output text with error - $this->_getSession()->addError($e->getMessage()); - } - } - - public function _getStore() - { - $storeId = (int) $this->getRequest()->getParam('store', 0); - if ($storeId == 0) { - return Mage::app()->getDefaultStoreView(); - } - return Mage::app()->getStore($storeId); - } - protected function _isAllowed() - { - return Mage::getSingleton('admin/session')->isAllowed('catalog/googlebase/types'); - } } diff --git a/app/code/core/Mage/GoogleBase/etc/adminhtml.xml b/app/code/core/Mage/GoogleBase/etc/adminhtml.xml index e1f5435f69..ce7ad2b65a 100644 --- a/app/code/core/Mage/GoogleBase/etc/adminhtml.xml +++ b/app/code/core/Mage/GoogleBase/etc/adminhtml.xml @@ -34,11 +34,11 @@ Manage Attributes - googlebase/types + adminhtml/googlebase_types Manage Items - googlebase/items + adminhtml/googlebase_items 70 diff --git a/app/code/core/Mage/GoogleBase/etc/config.xml b/app/code/core/Mage/GoogleBase/etc/config.xml index df8ec7dba4..d79f191556 100644 --- a/app/code/core/Mage/GoogleBase/etc/config.xml +++ b/app/code/core/Mage/GoogleBase/etc/config.xml @@ -70,13 +70,13 @@ - - admin + - Mage_GoogleBase - googlebase + + Mage_GoogleBase_Adminhtml + - + @@ -115,17 +115,6 @@ - - - - standard - - Mage_GoogleBase - googlebase - - - - diff --git a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Abstract.php b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Abstract.php index 2eb31b3b05..e80f90b60a 100644 --- a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Abstract.php +++ b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Abstract.php @@ -217,4 +217,15 @@ protected function _reCalculateToStoreCurrency($amount, $quote) } return $amount; } + + /** + * Get Tax Class for Shipping option + * + * @param Mage_Sales_Model_Quote $quote + * @return mixed + */ + protected function _getTaxClassForShipping($quote) + { + return Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_SHIPPING_TAX_CLASS, $quote->getStoreId()); + } } diff --git a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Callback.php b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Callback.php index 958bff55de..dd02ca634f 100644 --- a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Callback.php +++ b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Callback.php @@ -247,7 +247,29 @@ protected function _responseMerchantCalculationCallback() $result->setTaxDetails($taxAmount); } } else { - $result->SetShippingDetails($methodName, 0, "false"); + $shippingTaxClass = $this->_getTaxClassForShipping($quote->getStoreId()); + if ($shippingTaxClass && + $this->getData('root/calculate/tax/VALUE') == 'true') { + $i = 1; + $price = Mage::getStoreConfig('google/checkout_shipping_flatrate/price_'.$i, $quote->getStoreId()); + $price = number_format($price, 2, '.',''); + $price = (float) Mage::helper('tax')->getShippingPrice($price, false, false); + $address->setShippingMethod(null); + $address->setCollectShippingRates(true)->collectTotals(); + $billingAddress->setCollectShippingRates(true)->collectTotals(); + $address->setBaseShippingAmount($price); + $address->setShippingAmount( + $this->_reCalculateToStoreCurrency($price, $quote) + ); + $this->_applyShippingTaxClass($address); + $taxAmount = $address->getBaseTaxAmount(); + $taxAmount += $billingAddress->getBaseTaxAmount(); + $result->SetShippingDetails($methodName, $price - $address->getBaseShippingDiscountAmount(), "true"); + $result->setTaxDetails($taxAmount); + $i++; + } else { + $result->SetShippingDetails($methodName, 0, "false"); + } } $merchantCalculations->AddResult($result); } @@ -256,6 +278,7 @@ protected function _responseMerchantCalculationCallback() $address->setShippingMethod(null); $address->setCollectShippingRates(true)->collectTotals(); $billingAddress->setCollectShippingRates(true)->collectTotals(); + $this->_applyShippingTaxClass($address); $taxAmount = $address->getBaseTaxAmount(); $taxAmount += $billingAddress->getBaseTaxAmount(); @@ -269,6 +292,35 @@ protected function _responseMerchantCalculationCallback() $this->getGResponse()->ProcessMerchantCalculations($merchantCalculations); } + /** + * Apply shipping tax class + * + * @param Varien_Object $qAddress + * @param mixed $shippingTaxClass + */ + protected function _applyShippingTaxClass($qAddress, $shippingTaxClass) + { + $quote = $qAddress->getQuote(); + $taxCalculationModel = Mage::getSingleton('tax/calculation'); + $request = $taxCalculationModel->getRateRequest($qAddress); + $taxCalculationModel->processShippingAmount($qAddress); + $rate = $taxCalculationModel->getRate($request->setProductClassId($shippingTaxClass)); + + if (!Mage::helper('tax')->shippingPriceIncludesTax()) { + $shippingTax = $qAddress->getShippingAmount() * $rate/100; + $shippingBaseTax= $qAddress->getBaseShippingAmount() * $rate/100; + } else { + $shippingTax = $qAddress->getShippingTaxAmount(); + $shippingBaseTax= $qAddress->getBaseShippingTaxAmount(); + } + + $shippingTax = $quote->getStore()->roundPrice($shippingTax); + $shippingBaseTax= $quote->getStore()->roundPrice($shippingBaseTax); + + $qAddress->setTaxAmount($qAddress->getTaxAmount() + $shippingTax); + $qAddress->setBaseTaxAmount($qAddress->getBaseTaxAmount() + $shippingBaseTax); + } + /** * Process new order creation notification from google. * Convert customer quote to order @@ -306,6 +358,8 @@ protected function _responseNewOrderNotification() $quote->getPayment()->importData(array('method'=>'googlecheckout')); + $taxMessage = $this->_applyCustomTax($quote->getShippingAddress()); + // CONVERT QUOTE TO ORDER $convertQuote = Mage::getSingleton('sales/convert_quote'); @@ -355,6 +409,9 @@ protected function _responseNewOrderNotification() $message = $this->__('Google Order Number: %s', ''.$this->getGoogleOrderNumber()).'
    '. $this->__('Google Buyer ID: %s', ''.$this->getData('root/buyer-id/VALUE').'
    '). $this->__('Is Buyer Willing to Receive Marketing Emails: %s', '' . $emailStr . ''); + if ($taxMessage) { + $message .= $this->__('
    Warning: %s
    ', $taxMessage); + } $order->addStatusToHistory($order->getStatus(), $message); $order->place(); @@ -372,6 +429,61 @@ protected function _responseNewOrderNotification() $this->getGRequest()->SendMerchantOrderNumber($order->getExtOrderId(), $order->getIncrementId()); } + /** + * If tax value differs tax which is setted on magento, + * apply Google tax and recollect quote + * + * @param Varien_Object $qAddress + * @return string | false + */ + protected function _applyCustomTax($qAddress) + { + $quote = $qAddress->getQuote(); + $qTaxAmount = $qAddress->getBaseTaxAmount(); + $newTaxAmount = $this->getData('root/order-adjustment/total-tax/VALUE'); + + if ($qTaxAmount != $newTaxAmount) { + $taxQuotient = (int) $qTaxAmount ? $newTaxAmount/$qTaxAmount : $newTaxAmount; + + $qAddress->setTaxAmount( + $this->_reCalculateToStoreCurrency($newTaxAmount, $quote) + ); + $qAddress->setBaseTaxAmount($newTaxAmount); + + $grandTotal = $qAddress->getBaseGrandTotal() - $qTaxAmount + $newTaxAmount; + $qAddress->setGrandTotal( + $this->_reCalculateToStoreCurrency($grandTotal, $quote) + ); + $qAddress->setBaseGrandTotal($grandTotal); + + $subtotalInclTax = $qAddress->getSubtotalInclTax() - $qTaxAmount + $newTaxAmount; + $qAddress->setSubtotalInclTax($subtotalInclTax); + + foreach ($quote->getAllVisibleItems() as $item) { + if ($item->getParentItem()) { + continue; + } + if ($item->getTaxAmount()) { + $item->setTaxAmount($item->getTaxAmount()*$taxQuotient); + $item->setBaseTaxAmount($item->getBaseTaxAmount()*$taxQuotient); + $taxPercent = round(($item->getTaxAmount()/$item->getRowTotal())*100); + $item->setTaxPercent($taxPercent); + } + } + + $grandTotal = $quote->getBaseGrandTotal() - $qTaxAmount + $newTaxAmount; + $quote->setGrandTotal( + $this->_reCalculateToStoreCurrency($grandTotal, $quote) + ); + $quote->setBaseGrandTotal($grandTotal); + + $message = $this->__('The tax amount has been applied based on the information received from Google Checkout, because tax amount received from Google Checkout is different from the calculated tax amount'); + return $message; + } + + return false; + } + /** * Import address data from goole request to address object * @@ -674,7 +786,9 @@ protected function _responseChargeAmountNotification() $open = Mage_Sales_Model_Order_Invoice::STATE_OPEN; $paid = Mage_Sales_Model_Order_Invoice::STATE_PAID; if ($orderInvoice->getState() == $open && $orderInvoice->getBaseGrandTotal() == $latestCharged) { - $orderInvoice->setState($paid)->save(); + $orderInvoice->setState($paid) + ->setTransactionId($this->getGoogleOrderNumber()) + ->save(); break; } } diff --git a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Checkout.php b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Checkout.php index 0a4ef8d396..ec509a9c53 100644 --- a/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Checkout.php +++ b/app/code/core/Mage/GoogleCheckout/Model/Api/Xml/Checkout.php @@ -355,6 +355,10 @@ protected function _getFlatRateShippingXml() return ''; } + //if isset Tax Class for Shipping - create abbility to manage shipping rates in MerchantCalculationCallback + $nodeName = $this->_getTaxClassForShipping($this->getQuote()) ? + 'merchant-calculated-shipping' : 'flat-rate-shipping'; + for ($xml='', $i=1; $i<=3; $i++) { $allowSpecific = Mage::getStoreConfigFlag('google/checkout_shipping_flatrate/sallowspecific_'.$i, $this->getQuote()->getStoreId()); $specificCountries = Mage::getStoreConfig('google/checkout_shipping_flatrate/specificcountry_'.$i, $this->getQuote()->getStoreId()); @@ -370,14 +374,14 @@ protected function _getFlatRateShippingXml() } $xml .= << + <{$nodeName} name="{$title}"> {$allowedAreasXml} {$price} - + EOT; } $this->_shippingCalculated = true; diff --git a/app/code/core/Mage/GoogleCheckout/controllers/RedirectController.php b/app/code/core/Mage/GoogleCheckout/controllers/RedirectController.php index 511c1dfd21..a2b2eca36f 100644 --- a/app/code/core/Mage/GoogleCheckout/controllers/RedirectController.php +++ b/app/code/core/Mage/GoogleCheckout/controllers/RedirectController.php @@ -94,6 +94,8 @@ protected function _getApi () public function checkoutAction() { + $session = Mage::getSingleton('checkout/session'); + Mage::dispatchEvent('googlecheckout_checkout_before', array('quote' => $session->getQuote())); $api = $this->_getApi(); if ($api->getError()) { diff --git a/app/code/core/Mage/GoogleOptimizer/Block/Js.php b/app/code/core/Mage/GoogleOptimizer/Block/Js.php index 95613fe47f..92a0d60ac3 100644 --- a/app/code/core/Mage/GoogleOptimizer/Block/Js.php +++ b/app/code/core/Mage/GoogleOptimizer/Block/Js.php @@ -45,7 +45,7 @@ public function getMaxCountOfAttributes() public function getExportUrl() { - return $this->getUrl('googleoptimizer/index/codes'); + return $this->getUrl('*/googleoptimizer_index/codes'); } public function getControlFieldKey () diff --git a/app/code/core/Mage/GoogleOptimizer/controllers/Adminhtml/Googleoptimizer/IndexController.php b/app/code/core/Mage/GoogleOptimizer/controllers/Adminhtml/Googleoptimizer/IndexController.php new file mode 100755 index 0000000000..9ec487c79a --- /dev/null +++ b/app/code/core/Mage/GoogleOptimizer/controllers/Adminhtml/Googleoptimizer/IndexController.php @@ -0,0 +1,57 @@ + +*/ +class Mage_GoogleOptimizer_Adminhtml_Googleoptimizer_IndexController extends Mage_Adminhtml_Controller_Action +{ + /** + * Retrieve js scripts by parsing remote Google Optimizer page + */ + public function codesAction() + { + if ($this->getRequest()->getQuery('url')) { + $client = new Varien_Http_Client($this->getRequest()->getQuery('url')); + $response = $client->request(Varien_Http_Client::GET); + $result = array(); + if (preg_match_all('/]*id="([_a-zA-Z0-9]+)"[^>]*>([^<]+)<\/textarea>/', $response->getRawBody(), $matches)) { + $c = count($matches[1]); + for ($i = 0; $i < $c; $i++) { + $id = $matches[1][$i]; + $code = $matches[2][$i]; + $result[$id] = $code; + } + } + $this->getResponse()->setBody( Mage::helper('core')->jsonEncode($result) ); + } + } +} diff --git a/app/code/core/Mage/GoogleOptimizer/controllers/IndexController.php b/app/code/core/Mage/GoogleOptimizer/controllers/IndexController.php index b0b83954a1..fafa3dd180 100644 --- a/app/code/core/Mage/GoogleOptimizer/controllers/IndexController.php +++ b/app/code/core/Mage/GoogleOptimizer/controllers/IndexController.php @@ -24,6 +24,8 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +require_once 'Mage/GoogleOptimizer/controllers/Adminhtml/Googleoptimizer/IndexController.php'; + /** * GoogleOptimizer Front Controller * @@ -31,27 +33,9 @@ * @package Mage_GoogleOptimizer * @name Mage_GoogleOptimizer_IndexController * @author Magento Core Team + * @deprecated after 1.4.2.0 Mage_GoogleOptimizer_Adminhtml_Googleoptimizer_IndexController is used */ -class Mage_GoogleOptimizer_IndexController extends Mage_Adminhtml_Controller_Action +class Mage_GoogleOptimizer_IndexController extends Mage_GoogleOptimizer_Adminhtml_Googleoptimizer_IndexController { - /** - * Retrieve js scripts by parsing remote Google Optimizer page - */ - public function codesAction() - { - if ($this->getRequest()->getQuery('url')) { - $client = new Varien_Http_Client($this->getRequest()->getQuery('url')); - $response = $client->request(Varien_Http_Client::GET); - $result = array(); - if (preg_match_all('/]*id="([_a-zA-Z0-9]+)"[^>]*>([^<]+)<\/textarea>/', $response->getRawBody(), $matches)) { - $c = count($matches[1]); - for ($i = 0; $i < $c; $i++) { - $id = $matches[1][$i]; - $code = $matches[2][$i]; - $result[$id] = $code; - } - } - $this->getResponse()->setBody( Mage::helper('core')->jsonEncode($result) ); - } - } + } diff --git a/app/code/core/Mage/GoogleOptimizer/etc/config.xml b/app/code/core/Mage/GoogleOptimizer/etc/config.xml index 1931c564ba..658976ac5d 100644 --- a/app/code/core/Mage/GoogleOptimizer/etc/config.xml +++ b/app/code/core/Mage/GoogleOptimizer/etc/config.xml @@ -81,13 +81,13 @@ - - admin + - Mage_GoogleOptimizer - googleoptimizer + + Mage_GoogleOptimizer_Adminhtml + - +
    diff --git a/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Edit.php b/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Edit.php new file mode 100644 index 0000000000..cbc32de095 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Edit.php @@ -0,0 +1,73 @@ + + */ +class Mage_ImportExport_Block_Adminhtml_Export_Edit extends Mage_Adminhtml_Block_Widget_Form_Container +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->removeButton('back') + ->removeButton('reset') + ->removeButton('save'); + } + + /** + * Internal constructor + * + * @return void + */ + protected function _construct() + { + parent::_construct(); + + $this->_objectId = 'export_id'; + $this->_blockGroup = 'importexport'; + $this->_controller = 'adminhtml_export'; + } + + /** + * Get header text + * + * @return string + */ + public function getHeaderText() + { + return Mage::helper('importexport')->__('Export'); + } +} diff --git a/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Edit/Form.php b/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Edit/Form.php new file mode 100644 index 0000000000..275fc35fa8 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Edit/Form.php @@ -0,0 +1,71 @@ + + */ +class Mage_ImportExport_Block_Adminhtml_Export_Edit_Form extends Mage_Adminhtml_Block_Widget_Form +{ + /** + * Prepare form before rendering HTML. + * + * @return Mage_ImportExport_Block_Adminhtml_Export_Edit_Form + */ + protected function _prepareForm() + { + $helper = Mage::helper('importexport'); + $form = new Varien_Data_Form(array( + 'id' => 'edit_form', + 'action' => $this->getUrl('*/*/getfilter'), + 'method' => 'post' + )); + $fieldset = $form->addFieldset('base_fieldset', array('legend' => $helper->__('Export Settings'))); + $fieldset->addField('entity', 'select', array( + 'name' => 'entity', + 'title' => $helper->__('Entity Type'), + 'label' => $helper->__('Entity Type'), + 'required' => false, + 'onchange' => 'editForm.getFilter();', + 'values' => Mage::getModel('importexport/source_export_entity')->toOptionArray() + )); + $fieldset->addField('file_format', 'select', array( + 'name' => 'file_format', + 'title' => $helper->__('Export File Format'), + 'label' => $helper->__('Export File Format'), + 'required' => false, + 'values' => Mage::getModel('importexport/source_export_format')->toOptionArray() + )); + + $form->setUseContainer(true); + $this->setForm($form); + + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Filter.php b/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Filter.php new file mode 100644 index 0000000000..041241e437 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Block/Adminhtml/Export/Filter.php @@ -0,0 +1,298 @@ + + */ +class Mage_ImportExport_Block_Adminhtml_Export_Filter extends Mage_Adminhtml_Block_Widget_Grid +{ + /** + * Helper object. + * + * @var Mage_Core_Helper_Abstract + */ + protected $_helper; + + /** + * Set grid parameters. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->_helper = Mage::helper('importexport'); + + $this->setRowClickCallback(null); + $this->setId('export_filter_grid'); + $this->setDefaultSort('attribute_code'); + $this->setDefaultDir('ASC'); + $this->setPagerVisibility(false); + $this->setDefaultLimit(null); + $this->setUseAjax(true); + } + + /** + * Date 'from-to' filter HTML. + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return string + */ + protected function _getDateFromToHtml(Mage_Eav_Model_Entity_Attribute $attribute) + { + $dateBlock = new Mage_Core_Block_Html_Date(array( + 'name' => $this->getFilterElementName($attribute->getAttributeCode()) . '[]', + 'id' => $this->getFilterElementId($attribute->getAttributeCode()), + 'class' => 'input-text', + 'format' => Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT), + 'extra_params' => 'style="width:85px !important"', + 'image' => $this->getSkinUrl('images/grid-cal.gif') + )); + return '' . $this->_helper->__('From') . ': ' . $dateBlock->getHtml() + . ' ' . $this->_helper->__('To') . ': ' + . $dateBlock->setId($dateBlock->getId() . '_to')->getHtml(); + } + + /** + * Input text filter HTML. + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return string + */ + protected function _getInputHtml(Mage_Eav_Model_Entity_Attribute $attribute) + { + return ''; + } + + /** + * Multiselect field filter HTML. + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return string + */ + protected function _getMultiSelectHtml(Mage_Eav_Model_Entity_Attribute $attribute) + { + if ($attribute->getFilterOptions()) { + $options = $attribute->getFilterOptions(); + } else { + $options = $attribute->getSource()->getAllOptions(false); + + foreach ($options as $key => $optionParams) { + if ('' === $optionParams['value']) { + unset($options[$key]); + break; + } + } + } + if (($size = count($options))) { + $selectBlock = new Mage_Core_Block_Html_Select(array( + 'name' => $this->getFilterElementName($attribute->getAttributeCode()). '[]', + 'id' => $this->getFilterElementId($attribute->getAttributeCode()), + 'class' => 'multiselect', + 'extra_params' => 'multiple="multiple" size="' . ($size > 5 ? 5 : ($size < 2 ? 2 : $size)) + . '" style="width:280px"' + )); + return $selectBlock->setOptions($options)->getHtml(); + } else { + return $this->_helper->__('Attribute does not has options, so filtering is impossible'); + } + } + + /** + * Number 'from-to' field filter HTML. + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return string + */ + protected function _getNumberFromToHtml(Mage_Eav_Model_Entity_Attribute $attribute) + { + $name = $this->getFilterElementName($attribute->getAttributeCode()); + return '' . $this->_helper->__('From') . ': ' + . ' ' . $this->_helper->__('To') + . ': '; + } + + /** + * Select field filter HTML. + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return string + */ + protected function _getSelectHtml(Mage_Eav_Model_Entity_Attribute $attribute) + { + if ($attribute->getFilterOptions()) { + $options = array(); + + foreach ($attribute->getFilterOptions() as $value => $label) { + $options[] = array('value' => $value, 'label' => $label); + } + } else { + $options = $attribute->getSource()->getAllOptions(false); + } + if (($size = count($options))) { + // add empty vaue option + $firstOption = reset($options); + + if ('' === $firstOption['value']) { + $options[key($options)]['label'] = ''; + } else { + array_unshift($options, array('value' => '', 'label' => '')); + } + $selectBlock = new Mage_Core_Block_Html_Select(array( + 'name' => $this->getFilterElementName($attribute->getAttributeCode()), + 'id' => $this->getFilterElementId($attribute->getAttributeCode()), + 'class' => 'select', + 'extra_params' => 'style="width:280px"' + )); + return $selectBlock->setOptions($options)->getHtml(); + } else { + return $this->_helper->__('Attribute does not has options, so filtering is impossible'); + } + } + + /** + * Add columns to grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareColumns() + { + parent::_prepareColumns(); + + $this->addColumn('skip', array( + 'header' => $this->_helper->__('Skip'), + 'type' => 'checkbox', + 'name' => 'skip', + 'field_name' => Mage_ImportExport_Model_Export::FILTER_ELEMENT_SKIP . '[]', + 'filter' => false, + 'sortable' => false, + 'align' => 'center', + 'index' => 'attribute_id' + )); + $this->addColumn('frontend_label', array( + 'header' => $this->_helper->__('Attribute Label'), + 'index' => 'frontend_label', + 'sortable' => false, + )); + $this->addColumn('attribute_code', array( + 'header' => $this->_helper->__('Attribute Code'), + 'index' => 'attribute_code' + )); + $this->addColumn('filter', array( + 'header' => $this->_helper->__('Filter'), + 'sortable' => false, + 'filter' => false, + 'frame_callback' => array($this, 'decorateFilter') + )); + + return $this; + } + + /** + * Create filter fields for 'Filter' column. + * + * @param mixed $value + * @param Mage_Eav_Model_Entity_Attribute $row + * @param Varien_Object $column + * @param boolean $isExport + * @return string + */ + public function decorateFilter($value, Mage_Eav_Model_Entity_Attribute $row, Varien_Object $column, $isExport) + { + switch (Mage_ImportExport_Model_Export::getAttributeFilterType($row)) { + case Mage_ImportExport_Model_Export::FILTER_TYPE_SELECT: + $cell = $this->_getSelectHtml($row); + break; + case Mage_ImportExport_Model_Export::FILTER_TYPE_INPUT: + $cell = $this->_getInputHtml($row); + break; + case Mage_ImportExport_Model_Export::FILTER_TYPE_DATE: + $cell = $this->_getDateFromToHtml($row); + break; + case Mage_ImportExport_Model_Export::FILTER_TYPE_NUMBER: + $cell = $this->_getNumberFromToHtml($row); + break; + default: + $cell = $this->_helper->__('Unknown attribute filter type'); + } + return $cell; + } + + /** + * Element filter ID getter. + * + * @param string $attributeCode + * @return string + */ + public function getFilterElementId($attributeCode) + { + return Mage_ImportExport_Model_Export::FILTER_ELEMENT_GROUP . "_{$attributeCode}"; + } + + /** + * Element filter full name getter. + * + * @param string $attributeCode + * @return string + */ + public function getFilterElementName($attributeCode) + { + return Mage_ImportExport_Model_Export::FILTER_ELEMENT_GROUP . "[{$attributeCode}]"; + } + + /** + * Get row edit URL. + * + * @return string + */ + public function getRowUrl($row) + { + return false; + } + + /** + * Prepare collection by setting page number, sorting etc.. + * + * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + public function prepareCollection(Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection) + { + $this->_collection = $collection; + + $this->_prepareGrid(); + + return $this->_collection; + } +} diff --git a/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Edit.php b/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Edit.php new file mode 100644 index 0000000000..a267c85fe5 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Edit.php @@ -0,0 +1,75 @@ + + */ +class Mage_ImportExport_Block_Adminhtml_Import_Edit extends Mage_Adminhtml_Block_Widget_Form_Container +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->removeButton('back') + ->removeButton('reset') + ->_updateButton('save', 'label', $this->__('Check Data')) + ->_updateButton('save', 'id', 'upload_button') + ->_updateButton('save', 'onclick', 'editForm.postToFrame();'); + } + + /** + * Internal constructor + * + * @return void + */ + protected function _construct() + { + parent::_construct(); + + $this->_objectId = 'import_id'; + $this->_blockGroup = 'importexport'; + $this->_controller = 'adminhtml_import'; + } + + /** + * Get header text + * + * @return string + */ + public function getHeaderText() + { + return Mage::helper('importexport')->__('Import'); + } +} diff --git a/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Edit/Form.php new file mode 100644 index 0000000000..b4cfe89b42 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Edit/Form.php @@ -0,0 +1,83 @@ + + */ +class Mage_ImportExport_Block_Adminhtml_Import_Edit_Form extends Mage_Adminhtml_Block_Widget_Form +{ + /** + * Add fieldset + * + * @return Mage_ImportExport_Block_Adminhtml_Import_Edit_Form + */ + protected function _prepareForm() + { + $helper = Mage::helper('importexport'); + $form = new Varien_Data_Form(array( + 'id' => 'edit_form', + 'action' => $this->getUrl('*/*/validate'), + 'method' => 'post', + 'enctype' => 'multipart/form-data' + )); + $fieldset = $form->addFieldset('base_fieldset', array('legend' => $helper->__('Import Settings'))); + $fieldset->addField('entity', 'select', array( + 'name' => 'entity', + 'title' => $helper->__('Entity Type'), + 'label' => $helper->__('Entity Type'), + 'required' => true, + 'values' => Mage::getModel('importexport/source_import_entity')->toOptionArray() + )); + $fieldset->addField('behavior', 'select', array( + 'name' => 'behavior', + 'title' => $helper->__('Import Behavior'), + 'label' => $helper->__('Import Behavior'), + 'required' => true, + 'values' => Mage::getModel('importexport/source_import_behavior')->toOptionArray() + )); + $fieldset->addField(Mage_ImportExport_Model_Import::FIELD_NAME_SOURCE_FILE, 'file', array( + 'name' => Mage_ImportExport_Model_Import::FIELD_NAME_SOURCE_FILE, + 'label' => $helper->__('Select File to Import'), + 'title' => $helper->__('Select File to Import'), + 'required' => true + )); + /*$fieldset->addField(Mage_ImportExport_Model_Import::FIELD_NAME_IMG_ARCHIVE_FILE, 'file', array( + 'name' => Mage_ImportExport_Model_Import::FIELD_NAME_IMG_ARCHIVE_FILE, + 'label' => $helper->__('Select Image Archive File to Import'), + 'title' => $helper->__('Select Image Archive File to Import'), + 'required' => false + ));*/ + + $form->setUseContainer(true); + $this->setForm($form); + + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Frame/Result.php b/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Frame/Result.php new file mode 100644 index 0000000000..87d5dc28c4 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Block/Adminhtml/Import/Frame/Result.php @@ -0,0 +1,209 @@ + + */ +class Mage_ImportExport_Block_Adminhtml_Import_Frame_Result extends Mage_Adminhtml_Block_Template +{ + /** + * JavaScript actions for response. + * + * @var array + */ + protected $_actions = array( + 'clear' => array(), // remove element from DOM + 'innerHTML' => array(), // set innerHTML property (use: elementID => new content) + 'value' => array(), // set value for form element (use: elementID => new value) + 'show' => array(), // show specified element + 'hide' => array(), // hide specified element + 'removeClassName' => array(), // remove specified class name from element + 'addClassName' => array() // add specified class name to element + ); + + /** + * Validation messages. + * + * @var array + */ + protected $_messages = array( + 'error' => array(), + 'success' => array(), + 'notice' => array() + ); + + /** + * Add action for response. + * + * @param string $actionName + * @param string $elementId + * @param mixed $value OPTIONAL + * @return Mage_ImportExport_Block_Adminhtml_Import_Frame_Result + */ + public function addAction($actionName, $elementId, $value = null) + { + if (isset($this->_actions[$actionName])) { + if (null === $value) { + if (is_array($elementId)) { + foreach ($elementId as $oneId) { + $this->_actions[$actionName][] = $oneId; + } + } else { + $this->_actions[$actionName][] = $elementId; + } + } else { + $this->_actions[$actionName][$elementId] = $value; + } + } + return $this; + } + + /** + * Add error message. + * + * @param string $message Error message + * @return Mage_ImportExport_Block_Adminhtml_Import_Frame_Result + */ + public function addError($message) + { + if (is_array($message)) { + foreach ($message as $row) { + $this->addError($row); + } + } else { + $this->_messages['error'][] = $message; + } + return $this; + } + + /** + * Add notice message. + * + * @param mixed $message Message text + * @param boolean $appendImportButton OPTIONAL Append import button to message? + * @return Mage_ImportExport_Block_Adminhtml_Import_Frame_Result + */ + public function addNotice($message, $appendImportButton = false) + { + if (is_array($message)) { + foreach ($message as $row) { + $this->addNotice($row); + } + } else { + $this->_messages['notice'][] = $message . ($appendImportButton ? $this->getImportButtonHtml() : ''); + } + return $this; + } + + /** + * Add success message. + * + * @param mixed $message Message text + * @param boolean $appendImportButton OPTIONAL Append import button to message? + * @return Mage_ImportExport_Block_Adminhtml_Import_Frame_Result + */ + public function addSuccess($message, $appendImportButton = false) + { + if (is_array($message)) { + foreach ($message as $row) { + $this->addSuccess($row); + } + } else { + $this->_messages['success'][] = $message . ($appendImportButton ? $this->getImportButtonHtml() : ''); + } + return $this; + } + + /** + * Import button HTML for append to message. + * + * @return string + */ + public function getImportButtonHtml() + { + return '  '; + } + + /** + * Import start action URL. + * + * @return string + */ + public function getImportStartUrl() + { + return $this->getUrl('*/*/start'); + } + + /** + * Messages getter. + * + * @return array + */ + public function getMessages() + { + return $this->_messages; + } + + /** + * Messages rendered HTML getter. + * + * @return string + */ + public function getMessagesHtml() + { + /** @var $messagesBlock Mage_Core_Block_Messages */ + $messagesBlock = $this->_layout->createBlock('core/messages'); + + foreach ($this->_messages as $priority => $messages) { + $method = "add{$priority}"; + + foreach ($messages as $message) { + $messagesBlock->$method($message); + } + } + return $messagesBlock->toHtml(); + } + + /** + * Return response as JSON. + * + * @return string + */ + public function getResponseJson() + { + // add messages HTML if it is not already specified + if (!isset($this->_actions['import_validation_messages'])) { + $this->addAction('innerHTML', 'import_validation_messages', $this->getMessagesHtml()); + } + return Mage::helper('core')->jsonEncode($this->_actions); + } +} diff --git a/app/code/core/Mage/ImportExport/Helper/Data.php b/app/code/core/Mage/ImportExport/Helper/Data.php new file mode 100644 index 0000000000..4a3a2067ae --- /dev/null +++ b/app/code/core/Mage/ImportExport/Helper/Data.php @@ -0,0 +1,45 @@ + + */ +class Mage_ImportExport_Helper_Data extends Mage_Core_Helper_Data +{ + /** + * Maximum size of uploaded files. + * + * @return int + */ + public function getMaxUploadSize() + { + return min(ini_get('post_max_size'), ini_get('upload_max_filesize')); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Config.php b/app/code/core/Mage/ImportExport/Model/Config.php new file mode 100644 index 0000000000..1b4ba53af1 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Config.php @@ -0,0 +1,80 @@ + + */ +class Mage_ImportExport_Model_Config +{ + /** + * Get data about models from specified config key. + * + * @static + * @param string $configKey + * @throws Exception + * @return array + */ + public static function getModels($configKey) + { + $entities = array(); + + foreach (Mage::getConfig()->getNode($configKey)->asCanonicalArray() as $entityType => $entityParams) { + if (empty($entityParams['model_token'])) { + Mage::throwException(Mage::helper('importexport')->__('Node does not has model token tag')); + } + $entities[$entityType] = array( + 'model' => $entityParams['model_token'], + 'label' => empty($entityParams['label']) ? $entityType : $entityParams['label'] + ); + } + return $entities; + } + + /** + * Get model params as combo-box options. + * + * @static + * @param string $configKey + * @param boolean $withEmpty OPTIONAL Include 'Please Select' option or not + * @return array + */ + public static function getModelsComboOptions($configKey, $withEmpty = false) + { + $options = array(); + + if ($withEmpty) { + $options[] = array('label' => Mage::helper('importexport')->__('-- Please Select --'), 'value' => ''); + } + foreach (self::getModels($configKey) as $type => $params) { + $options[] = array('value' => $type, 'label' => $params['label']); + } + return $options; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export.php b/app/code/core/Mage/ImportExport/Model/Export.php new file mode 100644 index 0000000000..4af83fe530 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export.php @@ -0,0 +1,243 @@ + + */ +class Mage_ImportExport_Model_Export extends Varien_Object +{ + const FILTER_ELEMENT_GROUP = 'export_filter'; + const FILTER_ELEMENT_SKIP = 'skip_attr'; + + /** + * Filter fields types. + */ + const FILTER_TYPE_SELECT = 'select'; + const FILTER_TYPE_INPUT = 'input'; + const FILTER_TYPE_DATE = 'date'; + const FILTER_TYPE_NUMBER = 'number'; + + /** + * Config keys. + */ + const CONFIG_KEY_ENTITIES = 'global/importexport/export_entities'; + const CONFIG_KEY_FORMATS = 'global/importexport/export_file_formats'; + + /** + * Entity adapter. + * + * @var Mage_ImportExport_Model_Export_Entity_Abstract + */ + protected $_entityAdapter; + + /** + * Writer object instance. + * + * @var Mage_ImportExport_Model_Export_Adapter_Abstract + */ + protected $_writer; + + /** + * Create instance of entity adapter and returns it. + * + * @throws Exception + * @return Mage_ImportExport_Model_Export_Entity_Abstract + */ + protected function _getEntityAdapter() + { + if (!$this->_entityAdapter) { + $validTypes = Mage_ImportExport_Model_Config::getModels(self::CONFIG_KEY_ENTITIES); + + if (isset($validTypes[$this->getEntity()])) { + try { + $this->_entityAdapter = Mage::getModel($validTypes[$this->getEntity()]['model']); + } catch (Exception $e) { + Mage::throwException(Mage::getIsDeveloperMode() ? $e : 'Invalid entity model'); + } + if (! $this->_entityAdapter instanceof Mage_ImportExport_Model_Export_Entity_Abstract) { + Mage::throwException( + 'Entity adapter obejct must be an instance of Mage_ImportExport_Model_Export_Entity_Abstract' + ); + } + } else { + Mage::throwException(Mage::helper('importexport')->__('Invalid entity')); + } + // check for entity codes integrity + if ($this->getEntity() != $this->_entityAdapter->getEntityTypeCode()) { + Mage::throwException( + Mage::helper('importexport')->__('Input entity code is not equal to entity adapter code') + ); + } + $this->_entityAdapter->setParameters($this->getData()); + } + return $this->_entityAdapter; + } + + /** + * Get writer object. + * + * @throws Exception + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + protected function _getWriter() + { + if (!$this->_writer) { + $validWriters = Mage_ImportExport_Model_Config::getModels(self::CONFIG_KEY_FORMATS); + + if (isset($validWriters[$this->getFileFormat()])) { + try { + $this->_writer = Mage::getModel($validWriters[$this->getFileFormat()]['model']); + } catch (Exception $e) { + Mage::throwException(Mage::getIsDeveloperMode() ? $e : 'Invalid entity model'); + } + if (! $this->_writer instanceof Mage_ImportExport_Model_Export_Adapter_Abstract) { + Mage::throwException(Mage::helper('importexport')->__( + 'Adapter object must be an instance of Mage_ImportExport_Model_Export_Adapter_Abstract' + )); + } + } else { + Mage::throwException(Mage::helper('importexport')->__('Invalid file format')); + } + } + return $this->_writer; + } + + /** + * Export data. + * + * @throws Exception + * @return string + */ + public function export() + { + if (isset($this->_data[self::FILTER_ELEMENT_GROUP])) { + return $this->_getEntityAdapter() + ->setWriter($this->_getWriter()) + ->export(); + } else { + Mage::throwException(Mage::helper('importexport')->__('No filter data provided')); + } + } + + /** + * Clean up already loaded attribute collection. + * + * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + public function filterAttributeCollection(Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection) + { + return $this->_getEntityAdapter()->filterAttributeCollection($collection); + } + + /** + * Determine filter type for specified attribute. + * + * @static + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @throws Exception + * @return string + */ + public static function getAttributeFilterType(Mage_Eav_Model_Entity_Attribute $attribute) + { + if ($attribute->usesSource() || $attribute->getFilterOptions()) { + return self::FILTER_TYPE_SELECT; + } elseif ($attribute->isStatic() + || 'varchar' == $attribute->getBackendType() + || 'text' == $attribute->getBackendType() + ) { + return self::FILTER_TYPE_INPUT; + } elseif ('datetime' == $attribute->getBackendType()) { + return self::FILTER_TYPE_DATE; + } elseif ('decimal' == $attribute->getBackendType() || 'int' == $attribute->getBackendType()) { + return self::FILTER_TYPE_NUMBER; + } else { + Mage::throwException(Mage::helper('importexport')->__('Can not determine attribute filter type')); + } + } + + /** + * MIME-type for 'Content-Type' header. + * + * @return string + */ + public function getContentType() + { + return $this->_getWriter()->getContentType(); + } + + /** + * Override standard entity getter. + * + * @throw Exception + * @return string + */ + public function getEntity() + { + if (empty($this->_data['entity'])) { + Mage::throwException(Mage::helper('importexport')->__('Entity is unknown')); + } + return $this->_data['entity']; + } + + /** + * Entity attributes collection getter. + * + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + public function getEntityAttributeCollection() + { + return $this->_getEntityAdapter()->getAttributeCollection(); + } + + /** + * Override standard entity getter. + * + * @throw Exception + * @return string + */ + public function getFileFormat() + { + if (empty($this->_data['file_format'])) { + Mage::throwException(Mage::helper('importexport')->__('File format is unknown')); + } + return $this->_data['file_format']; + } + + /** + * Return file name for downloading. + * + * @return string + */ + public function getFileName() + { + return $this->getEntity() . '_' . date('Ymd_His') . '.' . $this->_getWriter()->getFileExtension(); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Adapter/Abstract.php b/app/code/core/Mage/ImportExport/Model/Export/Adapter/Abstract.php new file mode 100644 index 0000000000..5efd098461 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Adapter/Abstract.php @@ -0,0 +1,146 @@ + + */ +abstract class Mage_ImportExport_Model_Export_Adapter_Abstract +{ + /** + * Destination file path. + * + * @var string + */ + protected $_destination; + + /** + * Header columns names. + * + * @var array + */ + protected $_headerCols = null; + + /** + * Adapter object constructor. + * + * @param string $destination OPTIONAL Destination file path. + * @throws Exception + * @return void + */ + final public function __construct($destination = null) + { + if (!$destination) { + $destination = tempnam(sys_get_temp_dir(), 'importexport_'); + } + if (!is_string($destination)) { + Mage::throwException(Mage::helper('importexport')->__('Destination file path must be a string')); + } + $pathinfo = pathinfo($destination); + + if (empty($pathinfo['dirname']) || !is_writable($pathinfo['dirname'])) { + Mage::throwException(Mage::helper('importexport')->__('Destination directory is not writable')); + } + if (is_file($destination) && !is_writable($destination)) { + Mage::throwException(Mage::helper('importexport')->__('Destination file is not writable')); + } + $this->_destination = $destination; + + $this->_init(); + } + + /** + * Method called as last step of object instance creation. Can be overridden in child classes. + * + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + protected function _init() + { + return $this; + } + + /** + * Get contents of export file. + * + * @return string + */ + public function getContents() + { + return file_get_contents($this->_destination); + } + + /** + * MIME-type for 'Content-Type' header. + * + * @return string + */ + public function getContentType() + { + return 'application/octet-stream'; + } + + /** + * Return file extension for downloading. + * + * @return string + */ + public function getFileExtension() + { + return ''; + } + + /** + * Set column names. + * + * @param array $headerCols + * @throws Exception + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + public function setHeaderCols(array $headerCols) + { + if (null !== $this->_headerCols) { + Mage::throwException(Mage::helper('importexport')->__('Header column names already set')); + } + if ($headerCols) { + foreach ($headerCols as $colName) { + $this->_headerCols[$colName] = false; + } + fputcsv($this->_fileHandler, array_keys($this->_headerCols), $this->_delimiter, $this->_enclosure); + } + return $this; + } + + /** + * Write row data to source file. + * + * @param array $rowData + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + abstract public function writeRow(array $rowData); +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Adapter/Csv.php b/app/code/core/Mage/ImportExport/Model/Export/Adapter/Csv.php new file mode 100644 index 0000000000..092b0a2510 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Adapter/Csv.php @@ -0,0 +1,121 @@ + + */ +class Mage_ImportExport_Model_Export_Adapter_Csv extends Mage_ImportExport_Model_Export_Adapter_Abstract +{ + /** + * Field delimiter. + * + * @var string + */ + protected $_delimiter = ','; + + /** + * Field enclosure character. + * + * @var string + */ + protected $_enclosure = '"'; + + /** + * Source file handler. + * + * @var resource + */ + protected $_fileHandler; + + /** + * Object destructor. + * + * @return void + */ + public function __destruct() + { + if (is_resource($this->_fileHandler)) { + fclose($this->_fileHandler); + } + } + + /** + * Method called as last step of object instance creation. Can be overrided in child classes. + * + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + protected function _init() + { + $this->_fileHandler = fopen($this->_destination, 'w'); + return $this; + } + + /** + * MIME-type for 'Content-Type' header. + * + * @return string + */ + public function getContentType() + { + return 'text/csv'; + } + + /** + * Return file extension for downloading. + * + * @return string + */ + public function getFileExtension() + { + return 'csv'; + } + + /** + * Write row data to source file. + * + * @param array $rowData + * @throws Exception + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + public function writeRow(array $rowData) + { + if (null === $this->_headerCols) { + $this->setHeaderCols(array_keys($rowData)); + } + fputcsv( + $this->_fileHandler, + array_merge($this->_headerCols, array_intersect_key($rowData, $this->_headerCols)), + $this->_delimiter, + $this->_enclosure + ); + + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Abstract.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Abstract.php new file mode 100644 index 0000000000..9dd178d949 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Abstract.php @@ -0,0 +1,477 @@ + + */ +abstract class Mage_ImportExport_Model_Export_Entity_Abstract +{ + /** + * Attribute code to its values. Only attributes with options and only default store values used. + * + * @var array + */ + protected $_attributeValues = array(); + + /** + * DB connection. + * + * @var Varien_Db_Adapter_Pdo_Mysql + */ + protected $_connection; + + /** + * Array of attributes codes which are disabled for export. + * + * @var array + */ + protected $_disabledAttrs = array(); + + /** + * Entity type id. + * + * @var int + */ + protected $_entityTypeId; + + /** + * Error codes with arrays of corresponding row numbers. + * + * @var array + */ + protected $_errors = array(); + + /** + * Error counter. + * + * @var int + */ + protected $_errorsCount = 0; + + /** + * Limit of errors after which pre-processing will exit. + * + * @var int + */ + protected $_errorsLimit = 100; + + /** + * Export filter data. + * + * @var array + */ + protected $_filter = array(); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array(); + + /** + * Validation failure message template definitions. + * + * @var array + */ + protected $_messageTemplates = array(); + + /** + * Parameters. + * + * @var array + */ + protected $_parameters = array(); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array(); + + /** + * Permanent entity columns. + * + * @var array + */ + protected $_permanentAttributes = array(); + + /** + * Number of entities processed by validation. + * + * @var int + */ + protected $_processedEntitiesCount = 0; + + /** + * Number of rows processed by validation. + * + * @var int + */ + protected $_processedRowsCount = 0; + + /** + * Source model. + * + * @var Mage_ImportExport_Model_Export_Adapter_Abstract + */ + protected $_writer; + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + $entityCode = $this->getEntityTypeCode(); + $this->_entityTypeId = Mage::getSingleton('eav/config')->getEntityType($entityCode)->getEntityTypeId(); + $this->_connection = Mage::getSingleton('core/resource')->getConnection('write'); + } + + /** + * Get attributes' codes which are appropriate for export. + * + * @return array + */ + protected function _getExportAttrCodes() + { + static $attrCodes = null; + + if (null === $attrCodes) { + if (!empty($this->_parameters[Mage_ImportExport_Model_Export::FILTER_ELEMENT_SKIP]) + && is_array($this->_parameters[Mage_ImportExport_Model_Export::FILTER_ELEMENT_SKIP])) { + $skipAttr = array_flip($this->_parameters[Mage_ImportExport_Model_Export::FILTER_ELEMENT_SKIP]); + } else { + $skipAttr = array(); + } + $attrCodes = array(); + + foreach ($this->filterAttributeCollection($this->getAttributeCollection()) as $attribute) { + if (!isset($skipAttr[$attribute->getAttributeId()]) + || in_array($attribute->getAttributeCode(), $this->_permanentAttributes)) { + $attrCodes[] = $attribute->getAttributeCode(); + } + } + } + return $attrCodes; + } + + /** + * Initialize attribute option values. + * + * @return Mage_ImportExport_Model_Export_Entity_Abstract + */ + protected function _initAttrValues() + { + foreach ($this->getAttributeCollection() as $attribute) { + $this->_attributeValues[$attribute->getAttributeCode()] = $this->getAttributeOptions($attribute); + } + return $this; + } + + /** + * Apply filter to collection and add not skipped attributes to select. + * + * @param Mage_Eav_Model_Entity_Collection_Abstract $collection + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _prepareEntityCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) + { + if (!isset($this->_parameters[Mage_ImportExport_Model_Export::FILTER_ELEMENT_GROUP]) + || !is_array($this->_parameters[Mage_ImportExport_Model_Export::FILTER_ELEMENT_GROUP])) { + $exportFilter = array(); + } else { + $exportFilter = $this->_parameters[Mage_ImportExport_Model_Export::FILTER_ELEMENT_GROUP]; + } + $exportAttrCodes = $this->_getExportAttrCodes(); + + foreach ($this->filterAttributeCollection($this->getAttributeCollection()) as $attribute) { + $attrCode = $attribute->getAttributeCode(); + + // filter applying + if (isset($exportFilter[$attrCode])) { + $attrFilterType = Mage_ImportExport_Model_Export::getAttributeFilterType($attribute); + + if (Mage_ImportExport_Model_Export::FILTER_TYPE_SELECT == $attrFilterType) { + if (is_scalar($exportFilter[$attrCode]) && trim($exportFilter[$attrCode])) { + $collection->addAttributeToFilter($attrCode, array('eq' => $exportFilter[$attrCode])); + } + } elseif (Mage_ImportExport_Model_Export::FILTER_TYPE_INPUT == $attrFilterType) { + if (is_scalar($exportFilter[$attrCode]) && trim($exportFilter[$attrCode])) { + $collection->addAttributeToFilter($attrCode, array('like' => "%{$exportFilter[$attrCode]}%")); + } + } elseif (Mage_ImportExport_Model_Export::FILTER_TYPE_DATE == $attrFilterType) { + if (is_array($exportFilter[$attrCode]) && count($exportFilter[$attrCode]) == 2) { + $from = array_shift($exportFilter[$attrCode]); + $to = array_shift($exportFilter[$attrCode]); + + if (is_scalar($from) && strtotime($from)) { + $collection->addAttributeToFilter($attrCode, array('from' => $from, 'date' => true)); + } + if (is_scalar($to) && strtotime($to)) { + $collection->addAttributeToFilter($attrCode, array('to' => $to, 'date' => true)); + } + } + } elseif (Mage_ImportExport_Model_Export::FILTER_TYPE_NUMBER == $attrFilterType) { + if (is_array($exportFilter[$attrCode]) && count($exportFilter[$attrCode]) == 2) { + $from = array_shift($exportFilter[$attrCode]); + $to = array_shift($exportFilter[$attrCode]); + + if (is_scalar($from) && is_numeric($from)) { + $collection->addAttributeToFilter($attrCode, array('from' => $from)); + } + if (is_scalar($to) && is_numeric($to)) { + $collection->addAttributeToFilter($attrCode, array('to' => $to)); + } + } + } + } + if (in_array($attrCode, $exportAttrCodes)) { + $collection->addAttributeToSelect($attrCode); + } + } + return $collection; + } + + /** + * Add error with corresponding current data source row number. + * + * @param string $errorCode Error code or simply column name + * @param int $errorRowNum Row number. + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + public function addRowError($errorCode, $errorRowNum) + { + $this->_errors[$errorCode][] = $errorRowNum + 1; // one added for human readability + $this->_invalidRows[$errorRowNum] = true; + $this->_errorsCount ++; + + return $this; + } + + /** + * Add message template for specific error code from outside. + * + * @param string $errorCode Error code + * @param string $message Message template + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + public function addMessageTemplate($errorCode, $message) + { + $this->_messageTemplates[$errorCode] = $message; + + return $this; + } + + /** + * Export process. + * + * @return string + */ + abstract public function export(); + + /** + * Clean up already loaded attribute collection. + * + * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + public function filterAttributeCollection(Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection) + { + $collection->load(); + + foreach ($collection as $attribute) { + if (in_array($attribute->getAttributeCode(), $this->_disabledAttrs)) { + $collection->removeItemByKey($attribute->getId()); + } + } + return $collection; + } + + /** + * Entity attributes collection getter. + * + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + abstract public function getAttributeCollection(); + + /** + * Returns attributes all values in label-value or value-value pairs form. Labels are lower-cased. + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return array + */ + public function getAttributeOptions(Mage_Eav_Model_Entity_Attribute_Abstract $attribute) + { + $options = array(); + + if ($attribute->usesSource()) { + // should attribute has index (option value) instead of a label? + $index = in_array($attribute->getAttributeCode(), $this->_indexValueAttributes) ? 'value' : 'label'; + + // only default (admin) store values used + $attribute->setStoreId(Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID); + + try { + foreach ($attribute->getSource()->getAllOptions(false) as $option) { + foreach (is_array($option['value']) ? $option['value'] : array($option) as $innerOption) { + if (strlen($innerOption['value'])) { // skip ' -- Please Select -- ' option + $options[$innerOption['value']] = $innerOption[$index]; + } + } + } + } catch (Exception $e) { + // ignore exceptions connected with source models + } + } + return $options; + } + + /** + * EAV entity type code getter. + * + * @abstract + * @return string + */ + abstract public function getEntityTypeCode(); + + /** + * Entity type ID getter. + * + * @return int + */ + public function getEntityTypeId() + { + return $this->_entityTypeId; + } + + /** + * Returns error information. + * + * @return array + */ + public function getErrorMessages() + { + $translator = Mage::helper('importexport'); + $messages = array(); + + foreach ($this->_errors as $errorCode => $errorRows) { + if (isset($this->_messageTemplates[$errorCode])) { + $message = $translator->__($this->_messageTemplates[$errorCode]); + } else { + $message = $translator->__("Invalid value for '%s' column", $errorCode); + } + $messages[$message] = $errorRows; + } + return $messages; + } + + /** + * Returns error counter value. + * + * @return int + */ + public function getErrorsCount() + { + return $this->_errorsCount; + } + + /** + * Returns invalid rows count. + * + * @return int + */ + public function getInvalidRowsCount() + { + return count($this->_invalidRows); + } + + /** + * Returns number of checked entities. + * + * @return int + */ + public function getProcessedEntitiesCount() + { + return $this->_processedEntitiesCount; + } + + /** + * Returns number of checked rows. + * + * @return int + */ + public function getProcessedRowsCount() + { + return $this->_processedRowsCount; + } + + /** + * Inner writer object getter. + * + * @throws Exception + * @return Mage_ImportExport_Model_Export_Adapter_Abstract + */ + public function getWriter() + { + if (!$this->_writer) { + Mage::throwException(Mage::helper('importexport')->__('No writer specified')); + } + return $this->_writer; + } + + /** + * Set parameters. + * + * @param array $parameters + * @return Mage_ImportExport_Model_Export_Entity_Abstract + */ + public function setParameters(array $parameters) + { + $this->_parameters = $parameters; + + return $this; + } + + /** + * Writer model setter. + * + * @param Mage_ImportExport_Model_Export_Adapter_Abstract $writer + * @return Mage_ImportExport_Model_Export_Entity_Abstract + */ + public function setWriter(Mage_ImportExport_Model_Export_Adapter_Abstract $writer) + { + $this->_writer = $writer; + + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Customer.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Customer.php new file mode 100644 index 0000000000..06b5ab6751 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Customer.php @@ -0,0 +1,289 @@ + + */ +class Mage_ImportExport_Model_Export_Entity_Customer extends Mage_ImportExport_Model_Export_Entity_Abstract +{ + /** + * Permanent column names. + * + * Names that begins with underscore is not an attribute. This name convention is for + * to avoid interference with same attribute name. + */ + const COL_EMAIL = 'email'; + const COL_WEBSITE = '_website'; + const COL_STORE = '_store'; + + /** + * Overriden attributes parameters. + * + * @var array + */ + protected $_attributeOverrides = array( + 'created_at' => array('backend_type' => 'datetime'), + 'reward_update_notification' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'reward_warning_notification' => array('source_model' => 'eav/entity_attribute_source_boolean') + ); + + /** + * Array of attributes codes which are disabled for export. + * + * @var array + */ + protected $_disabledAttrs = array('default_billing', 'default_shipping'); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array('group_id', 'website_id', 'store_id'); + + /** + * Permanent entity columns. + * + * @var array + */ + protected $_permanentAttributes = array(self::COL_EMAIL, self::COL_WEBSITE, self::COL_STORE); + + /** + * Array of pairs store ID to its code. + * + * @var array + */ + protected $_storeIdToCode = array(); + + /** + * Website ID-to-code. + * + * @var array + */ + protected $_websiteIdToCode = array(); + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->_initAttrValues() + ->_initStores() + ->_initWebsites(); + } + + /** + * Initialize stores hash. + * + * @return Mage_ImportExport_Model_Export_Entity_Customer + */ + protected function _initStores() + { + foreach (Mage::app()->getStores(true) as $store) { + $this->_storeIdToCode[$store->getId()] = $store->getCode(); + } + return $this; + } + + /** + * Initialize website values. + * + * @return Mage_ImportExport_Model_Export_Entity_Customer + */ + protected function _initWebsites() + { + /** @var $website Mage_Core_Model_Website */ + foreach (Mage::app()->getWebsites(true) as $website) { + $this->_websiteIdToCode[$website->getId()] = $website->getCode(); + } + return $this; + } + + /** + * Apply filter to collection and add not skipped attributes to select. + * + * @param Mage_Eav_Model_Entity_Collection_Abstract $collection + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _prepareEntityCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection) + { + // forced addition default billing and shipping addresses attributes + return parent::_prepareEntityCollection($collection)->addAttributeToSelect( + Mage_ImportExport_Model_Import_Entity_Customer_Address::getDefaultAddressAttrMapping() + ); + } + + /** + * Export process. + * + * @return string + */ + public function export() + { + $collection = $this->_prepareEntityCollection(Mage::getResourceModel('customer/customer_collection')); + $validAttrCodes = $this->_getExportAttrCodes(); + $writer = $this->getWriter(); + $defaultAddrMap = Mage_ImportExport_Model_Import_Entity_Customer_Address::getDefaultAddressAttrMapping(); + + // prepare address data + $addrAttributes = array(); + $addrColNames = array(); + $customerAddrs = array(); + + foreach (Mage::getResourceModel('customer/address_attribute_collection') + ->addSystemHiddenFilter() + ->addExcludeHiddenFrontendFilter() as $attribute) { + $options = array(); + $attrCode = $attribute->getAttributeCode(); + + if ($attribute->usesSource() && 'country_id' != $attrCode) { + foreach ($attribute->getSource()->getAllOptions(false) as $option) { + foreach (is_array($option['value']) ? $option['value'] : array($option) as $innerOption) { + if (strlen($innerOption['value'])) { // skip ' -- Please Select -- ' option + $options[$innerOption['value']] = $innerOption['label']; + } + } + } + } + $addrAttributes[$attrCode] = $options; + $addrColNames[] = Mage_ImportExport_Model_Import_Entity_Customer_Address::getColNameForAttrCode($attrCode); + } + foreach (Mage::getResourceModel('customer/address_collection')->addAttributeToSelect('*') as $address) { + $addrRow = array(); + + foreach ($addrAttributes as $attrCode => $attrValues) { + if (null !== $address->getData($attrCode)) { + $value = $address->getData($attrCode); + + if ($attrValues) { + $value = $attrValues[$value]; + } + $addrRow[Mage_ImportExport_Model_Import_Entity_Customer_Address::getColNameForAttrCode($attrCode)] = $value; + } + } + $customerAddrs[$address['parent_id']][$address->getId()] = $addrRow; + } + + // create export file + $writer->setHeaderCols(array_merge( + $this->_permanentAttributes, $validAttrCodes, + array('password'), $addrColNames, + array_keys($defaultAddrMap) + )); + foreach ($collection as $itemId => $item) { // go through all customers + $row = array(); + + // go through all valid attribute codes + foreach ($validAttrCodes as $attrCode) { + $attrValue = $item->getData($attrCode); + + if (isset($this->_attributeValues[$attrCode]) + && isset($this->_attributeValues[$attrCode][$attrValue]) + ) { + $attrValue = $this->_attributeValues[$attrCode][$attrValue]; + } + if (null !== $attrValue) { + $row[$attrCode] = $attrValue; + } + } + $row[self::COL_WEBSITE] = $this->_websiteIdToCode[$item['website_id']]; + $row[self::COL_STORE] = $this->_storeIdToCode[$item['store_id']]; + + // addresses injection + $defaultAddrs = array(); + + foreach ($defaultAddrMap as $colName => $addrAttrCode) { + if (!empty($item[$addrAttrCode])) { + $defaultAddrs[$item[$addrAttrCode]][] = $colName; + } + } + if (isset($customerAddrs[$itemId])) { + while (($addrRow = each($customerAddrs[$itemId]))) { + if (isset($defaultAddrs[$addrRow['key']])) { + foreach ($defaultAddrs[$addrRow['key']] as $colName) { + $row[$colName] = 1; + } + } + $writer->writeRow(array_merge($row, $addrRow['value'])); + + $row = array(); + } + } else { + $writer->writeRow($row); + } + } + return $writer->getContents(); + } + + /** + * Clean up already loaded attribute collection. + * + * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + public function filterAttributeCollection(Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection) + { + foreach (parent::filterAttributeCollection($collection) as $attribute) { + if (!empty($this->_attributeOverrides[$attribute->getAttributeCode()])) { + $data = $this->_attributeOverrides[$attribute->getAttributeCode()]; + + if (isset($data['options_method']) && method_exists($this, $data['options_method'])) { + $data['filter_options'] = $this->$data['options_method'](); + } + $attribute->addData($data); + } + } + return $collection; + } + + /** + * Entity attributes collection getter. + * + * @return Mage_Customer_Model_Entity_Attribute_Collection + */ + public function getAttributeCollection() + { + return Mage::getResourceModel('customer/attribute_collection'); + } + + /** + * EAV entity type code getter. + * + * @return string + */ + public function getEntityTypeCode() + { + return 'customer'; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php new file mode 100644 index 0000000000..1ceb41e7f1 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product.php @@ -0,0 +1,727 @@ + + */ +class Mage_ImportExport_Model_Export_Entity_Product extends Mage_ImportExport_Model_Export_Entity_Abstract +{ + const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/export_product_types'; + + /** + * Value that means all entities (e.g. websites, groups etc.) + */ + const VALUE_ALL = 'all'; + + /** + * Permanent column names. + * + * Names that begins with underscore is not an attribute. This name convention is for + * to avoid interference with same attribute name. + */ + const COL_STORE = '_store'; + const COL_ATTR_SET = '_attribute_set'; + const COL_TYPE = '_type'; + const COL_CATEGORY = '_category'; + const COL_SKU = 'sku'; + + /** + * Pairs of attribute set ID-to-name. + * + * @var array + */ + protected $_attrSetIdToName = array(); + + /** + * Categories ID to text-path hash. + * + * @var array + */ + protected $_categories = array(); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array( + 'status', + 'tax_class_id', + 'visibility', + 'enable_googlecheckout', + 'gift_message_available', + 'custom_design' + ); + + /** + * Permanent entity columns. + * + * @var array + */ + protected $_permanentAttributes = array(self::COL_SKU); + + /** + * Array of supported product types as keys with appropriate model object as value. + * + * @var array + */ + protected $_productTypeModels = array(); + + /** + * Array of pairs store ID to its code. + * + * @var array + */ + protected $_storeIdToCode = array(); + + /** + * Website ID-to-code. + * + * @var array + */ + protected $_websiteIdToCode = array(); + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->_initTypeModels() + ->_initAttrValues() + ->_initStores() + ->_initAttributeSets() + ->_initWebsites() + ->_initCategories(); + } + + /** + * Initialize attribute sets code-to-id pairs. + * + * @return Mage_ImportExport_Model_Export_Entity_Product + */ + protected function _initAttributeSets() + { + $productTypeId = Mage::getModel('catalog/product')->getResource()->getTypeId(); + foreach (Mage::getResourceModel('eav/entity_attribute_set_collection') + ->setEntityTypeFilter($productTypeId) as $attributeSet) { + $this->_attrSetIdToName[$attributeSet->getId()] = $attributeSet->getAttributeSetName(); + } + return $this; + } + + /** + * Initialize categories ID to text-path hash. + * + * @return Mage_ImportExport_Model_Export_Entity_Product + */ + protected function _initCategories() + { + $collection = Mage::getResourceModel('catalog/category_collection')->addNameToResult(); + /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */ + foreach ($collection as $category) { + $structure = explode('/', $category->getPath()); + $pathSize = count($structure); + if ($pathSize > 2) { + $path = array(); + for ($i = 2; $i < $pathSize; $i++) { + $path[] = $collection->getItemById($structure[$i])->getName(); + } + $this->_categories[$category->getId()] = implode('/', $path); + } + } + return $this; + } + + /** + * Initialize stores hash. + * + * @return Mage_ImportExport_Model_Export_Entity_Product + */ + protected function _initStores() + { + foreach (Mage::app()->getStores(true) as $store) { + $this->_storeIdToCode[$store->getId()] = $store->getCode(); + } + ksort($this->_storeIdToCode); // to ensure that 'admin' store (ID is zero) goes first + + return $this; + } + + /** + * Initialize product type models. + * + * @throws Exception + * @return Mage_ImportExport_Model_Export_Entity_Product + */ + protected function _initTypeModels() + { + $config = Mage::getConfig()->getNode(self::CONFIG_KEY_PRODUCT_TYPES)->asCanonicalArray(); + foreach ($config as $type => $typeModel) { + if (!($model = Mage::getModel($typeModel, array($this, $type)))) { + Mage::throwException("Entity type model '{$typeModel}' is not found"); + } + if (! $model instanceof Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract) { + Mage::throwException(Mage::helper('importexport')->__('Entity type model must be an instance of Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract')); + } + if ($model->isSuitable()) { + $this->_productTypeModels[$type] = $model; + $this->_disabledAttrs = array_merge($this->_disabledAttrs, $model->getDisabledAttrs()); + $this->_indexValueAttributes = array_merge( + $this->_indexValueAttributes, $model->getIndexValueAttributes() + ); + } + } + if (!$this->_productTypeModels) { + Mage::throwException(Mage::helper('importexport')->__('There are no product types available for export')); + } + $this->_disabledAttrs = array_unique($this->_disabledAttrs); + + return $this; + } + + /** + * Initialize website values. + * + * @return Mage_ImportExport_Model_Export_Entity_Product + */ + protected function _initWebsites() + { + /** @var $website Mage_Core_Model_Website */ + foreach (Mage::app()->getWebsites() as $website) { + $this->_websiteIdToCode[$website->getId()] = $website->getCode(); + } + return $this; + } + + /** + * Export process. + * + * @return string + */ + public function export() + { + /** @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection */ + $collection = $this->_prepareEntityCollection(Mage::getResourceModel('catalog/product_collection')); + $validAttrCodes = $this->_getExportAttrCodes(); + $writer = $this->getWriter(); + $resource = Mage::getSingleton('core/resource'); + $dataRows = array(); + $rowCategories = array(); + $rowWebsites = array(); + $rowTierPrices = array(); + $stockItemRows = array(); + $linksRows = array(); + $gfAmountFields = array(); + $defaultStoreId = Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID; + + // prepare multi-store values and system columns values + foreach ($this->_storeIdToCode as $storeId => $storeCode) { // go through all stores + $collection->clear()->setStoreId($storeId)->load(); + + if ($defaultStoreId == $storeId) { + $collection->addCategoryIds()->addWebsiteNamesToResult(); + + // tier price data getting only once + $tierPrices = $this->_connection->fetchAll($this->_connection->select() + ->from($resource->getTableName('catalog/product_attribute_tier_price')) + ->where('entity_id IN(?)', $collection->getAllIds()) + ); + foreach ($tierPrices as $tierRow) { + $rowTierPrices[$tierRow['entity_id']][] = array( + '_tier_price_customer_group' => $tierRow['all_groups'] + ? self::VALUE_ALL : $tierRow['customer_group_id'], + '_tier_price_website' => 0 == $tierRow['website_id'] + ? self::VALUE_ALL + : $this->_websiteIdToCode[$tierRow['website_id']], + '_tier_price_qty' => $tierRow['qty'], + '_tier_price_price' => $tierRow['value'] + ); + } + } + foreach ($collection as $itemId => $item) { // go through all products + $rowIsEmpty = true; // row is empty by default + + foreach ($validAttrCodes as $attrCode) { // go through all valid attribute codes + $attrValue = $item->getData($attrCode); + + if (!empty($this->_attributeValues[$attrCode])) { + if (isset($this->_attributeValues[$attrCode][$attrValue])) { + $attrValue = $this->_attributeValues[$attrCode][$attrValue]; + } else { + $attrValue = null; + } + } + if (null === $attrValue // do not save value same as default or not existent + || ($storeId != $defaultStoreId + && $dataRows[$itemId][$defaultStoreId][$attrCode] == $attrValue)) { + $attrValue = null; + } + if (is_scalar($attrValue)) { + $dataRows[$itemId][$storeId][$attrCode] = $attrValue; + $rowIsEmpty = false; // mark row as not empty + } + } + if ($rowIsEmpty) { // remove empty rows + unset($dataRows[$itemId][$storeId]); + } else { + $attrSetId = $item->getAttributeSetId(); + $dataRows[$itemId][$storeId][self::COL_STORE] = $storeCode; + $dataRows[$itemId][$storeId][self::COL_ATTR_SET] = $this->_attrSetIdToName[$attrSetId]; + $dataRows[$itemId][$storeId][self::COL_TYPE] = $item->getTypeId(); + + if ($defaultStoreId == $storeId) { + $rowWebsites[$itemId] = $item->getWebsites(); + $rowCategories[$itemId] = $item->getCategoryIds(); + } + } + } + } + + // remove root categories + foreach ($rowCategories as $productId => &$categories) { + $categories = array_intersect($categories, array_keys($this->_categories)); + } + + // prepare catalog inventory information + $stockInfo = $this->_connection->fetchAll($this->_connection->select() + ->from(Mage::getResourceModel('cataloginventory/stock_item')->getMainTable()) + ->where('product_id IN (?)', array_keys($dataRows)) + ); + + foreach ($stockInfo as $stockItemRow) { + $productId = $stockItemRow['product_id']; + unset( + $stockItemRow['item_id'], $stockItemRow['product_id'], $stockItemRow['low_stock_date'], + $stockItemRow['stock_id'], $stockItemRow['stock_status_changed_automatically'] + ); + $stockItemRows[$productId] = $stockItemRow; + } + + // prepare links information + $select = $this->_connection->select() + ->from( + array('cpl' => $resource->getTableName('catalog/product_link')), + array( + 'cpl.product_id', 'cpe.sku', 'cpl.link_type_id', + 'position' => 'cplai.value', 'default_qty' => 'cplad.value' + ) + ) + ->joinLeft( + array('cpe' => $resource->getTableName('catalog_product_entity')), + '(cpe.entity_id = cpl.linked_product_id)', + array() + ) + ->joinLeft( + array('cpla' => $resource->getTableName('catalog/product_link_attribute')), + '(cpla.link_type_id = cpl.link_type_id AND cpla.product_link_attribute_code = "position")', + array() + ) + ->joinLeft( + array('cplaq' => $resource->getTableName('catalog/product_link_attribute')), + '(cplaq.link_type_id = cpl.link_type_id AND cplaq.product_link_attribute_code = "qty")', + array() + ) + ->joinLeft( + array('cplai' => $resource->getTableName('catalog/product_link_attribute_int')), + '(cplai.link_id = cpl.link_id AND cplai.product_link_attribute_id = cpla.product_link_attribute_id)', + array() + ) + ->joinLeft( + array('cplad' => $resource->getTableName('catalog/product_link_attribute_decimal')), + '(cplad.link_id = cpl.link_id AND cplad.product_link_attribute_id = cplaq.product_link_attribute_id)', + array() + ) + ->where('cpl.product_id IN (?)', array_keys($dataRows)) + ->where('cpl.link_type_id IN (?)', array( + Mage_Catalog_Model_Product_Link::LINK_TYPE_RELATED, + Mage_Catalog_Model_Product_Link::LINK_TYPE_UPSELL, + Mage_Catalog_Model_Product_Link::LINK_TYPE_CROSSSELL, + Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED + )); + + foreach ($this->_connection->fetchAll($select) as $linksRow) { + $linksRows[$linksRow['product_id']][$linksRow['link_type_id']][] = array( + 'sku' => $linksRow['sku'], + 'position' => $linksRow['position'], + 'default_qty' => $linksRow['default_qty'] + ); + } + $linkIdColPrefix = array( + Mage_Catalog_Model_Product_Link::LINK_TYPE_RELATED => '_links_related_', + Mage_Catalog_Model_Product_Link::LINK_TYPE_UPSELL => '_links_upsell_', + Mage_Catalog_Model_Product_Link::LINK_TYPE_CROSSSELL => '_links_crosssell_', + Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED => '_associated_' + ); + + // prepare configurable products data + $configurableData = array(); + $configurablePrice = array(); + + $select = $this->_connection->select() + ->from( + array('cpsl' => $resource->getTableName('catalog/product_super_link')), + array('cpsl.parent_id', 'cpe.sku') + ) + ->joinLeft( + array('cpe' => $resource->getTableName('catalog_product_entity')), + '(cpe.entity_id = cpsl.product_id)', + array() + ) + ->where('parent_id IN (?)', array_keys($dataRows)); + + foreach ($this->_connection->fetchAll($select) as $cfgLinkRow) { + $configurableData[$cfgLinkRow['parent_id']][] = array('_super_products_sku' => $cfgLinkRow['sku']); + } + if ($configurableData) { + $select = $this->_connection->select() + ->from( + array('cpsa' => $resource->getTableName('catalog/product_super_attribute')), + array( + 'cpsa.product_id', 'ea.attribute_code', 'eaov.value', 'cpsap.pricing_value', 'cpsap.is_percent' + ) + ) + ->joinLeft( + array('cpsap' => $resource->getTableName('catalog/product_super_attribute_pricing')), + '(cpsap.product_super_attribute_id = cpsa.product_super_attribute_id)', + array() + ) + ->joinLeft( + array('ea' => $resource->getTableName('eav/attribute')), + '(ea.attribute_id = cpsa.attribute_id)', + array() + ) + ->joinLeft( + array('eaov' => $resource->getTableName('eav/attribute_option_value')), + '(eaov.option_id = cpsap.value_index AND store_id = 0)', + array() + ) + ->where('cpsa.product_id IN (?)', array_keys($configurableData)); + + foreach ($this->_connection->fetchAll($select) as $priceRow) { + $configurablePrice[$priceRow['product_id']][] = array( + '_super_attribute_code' => $priceRow['attribute_code'], + '_super_attribute_option' => $priceRow['value'], + '_super_attribute_price_corr' => $priceRow['pricing_value'] + . ($priceRow['is_percent'] ? '%' : '') + ); + } + foreach ($configurableData as $productId => $rows) { + if (isset($configurablePrice[$productId])) { + $largest = max(count($rows), count($configurablePrice[$productId])); + + for ($i = 0; $i < $largest; $i++) { + if (!isset($configurableData[$productId][$i])) { + $configurableData[$productId][$i] = array(); + } + if (isset($configurablePrice[$productId][$i])) { + $configurableData[$productId][$i] = array_merge( + $configurableData[$productId][$i], + $configurablePrice[$productId][$i] + ); + } + } + } + } + } + + // prepare custom options information + $customOptionsData = array(); + $customOptionsDataPre = array(); + $customOptCols = array( + '_custom_option_store', '_custom_option_type', '_custom_option_title', '_custom_option_is_required', + '_custom_option_price', '_custom_option_sku', '_custom_option_max_characters', + '_custom_option_sort_order', '_custom_option_row_title', '_custom_option_row_price', + '_custom_option_row_sku', '_custom_option_row_sort' + ); + + foreach ($this->_storeIdToCode as $storeId => $storeCode) { + $options = Mage::getModel('catalog/product_option') + ->getCollection() + ->reset() + ->addTitleToResult($storeId) + ->addPriceToResult($storeId) + ->addProductToFilter(array_keys($dataRows)) + ->addValuesToResult($storeId); + + foreach ($options as $option) { + $row = array(); + $productId = $option['product_id']; + $optionId = $option['option_id']; + $customOptions = isset($customOptionsDataPre[$productId][$optionId]) + ? $customOptionsDataPre[$productId][$optionId] + : array(); + + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID == $storeId) { + $row['_custom_option_type'] = $option['type']; + $row['_custom_option_title'] = $option['title']; + $row['_custom_option_is_required'] = $option['is_require']; + $row['_custom_option_price'] = $option['price'] . ($option['price_type'] == 'percent' ? '%' : ''); + $row['_custom_option_sku'] = $option['sku']; + $row['_custom_option_max_characters'] = $option['max_characters']; + $row['_custom_option_sort_order'] = $option['sort_order']; + + // remember default title for later comparisons + $defaultTitles[$option['option_id']] = $option['title']; + } elseif ($option['title'] != $customOptions[0]['_custom_option_title']) { + $row['_custom_option_title'] = $option['title']; + } + if (($values = $option->getValues())) { + $firstValue = array_shift($values); + $priceType = $firstValue['price_type'] == 'percent' ? '%' : ''; + + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID == $storeId) { + $row['_custom_option_row_title'] = $firstValue['title']; + $row['_custom_option_row_price'] = $firstValue['price'] . $priceType; + $row['_custom_option_row_sku'] = $firstValue['sku']; + $row['_custom_option_row_sort'] = $firstValue['sort_order']; + + $defaultValueTitles[$firstValue['option_type_id']] = $firstValue['title']; + } elseif ($firstValue['title'] != $customOptions[0]['_custom_option_row_title']) { + $row['_custom_option_row_title'] = $firstValue['title']; + } + } + if ($row) { + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID != $storeId) { + $row['_custom_option_store'] = $this->_storeIdToCode[$storeId]; + } + $customOptionsDataPre[$productId][$optionId][] = $row; + } + foreach ($values as $value) { + $row = array(); + $valuePriceType = $value['price_type'] == 'percent' ? '%' : ''; + + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID == $storeId) { + $row['_custom_option_row_title'] = $value['title']; + $row['_custom_option_row_price'] = $value['price'] . $valuePriceType; + $row['_custom_option_row_sku'] = $value['sku']; + $row['_custom_option_row_sort'] = $value['sort_order']; + } elseif ($value['title'] != $customOptions[0]['_custom_option_row_title']) { + $row['_custom_option_row_title'] = $value['title']; + } + if ($row) { + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID != $storeId) { + $row['_custom_option_store'] = $this->_storeIdToCode[$storeId]; + } + $customOptionsDataPre[$option['product_id']][$option['option_id']][] = $row; + } + } + } + } + foreach ($customOptionsDataPre as $productId => $optionsData) { + $customOptionsData[$productId] = array(); + + foreach ($optionsData as $optionId => $optionRows) { + $customOptionsData[$productId] = array_merge($customOptionsData[$productId], $optionRows); + } + } + unset($customOptionsDataPre); + + // create export file + $headerCols = array_merge( + array( + self::COL_SKU, self::COL_STORE, self::COL_ATTR_SET, + self::COL_TYPE, self::COL_CATEGORY, '_product_websites' + ), + $validAttrCodes, + $stockInfo ? array_keys($stockItemRow) : array(), + $gfAmountFields, + array( + '_links_related_sku', '_links_related_position', '_links_crosssell_sku', + '_links_crosssell_position', '_links_upsell_sku', '_links_upsell_position', + '_associated_sku', '_associated_default_qty', '_associated_position' + ), + array('_tier_price_website', '_tier_price_customer_group', '_tier_price_qty', '_tier_price_price') + ); + + // have we merge custom options columns + if ($customOptionsData) { + $headerCols = array_merge($headerCols, $customOptCols); + } + + // have we merge configurable products data + if ($configurableData) { + $headerCols = array_merge($headerCols, array( + '_super_products_sku', '_super_attribute_code', + '_super_attribute_option', '_super_attribute_price_corr' + )); + } + + $writer->setHeaderCols($headerCols); + + foreach ($dataRows as $productId => $productData) { + foreach ($productData as $storeId => $dataRow) { + if (Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID != $storeId) { + $dataRow[self::COL_SKU] = null; + $dataRow[self::COL_ATTR_SET] = null; + $dataRow[self::COL_TYPE] = null; + } else { + $dataRow[self::COL_STORE] = null; + $dataRow += $stockItemRows[$productId]; + } + if ($rowCategories[$productId]) { + $dataRow[self::COL_CATEGORY] = $this->_categories[array_shift($rowCategories[$productId])]; + } + if ($rowWebsites[$productId]) { + $dataRow['_product_websites'] = $this->_websiteIdToCode[array_shift($rowWebsites[$productId])]; + } + if (!empty($rowTierPrices[$productId])) { + $dataRow = array_merge($dataRow, array_shift($rowTierPrices[$productId])); + } + foreach ($linkIdColPrefix as $linkId => $colPrefix) { + if (!empty($linksRows[$productId][$linkId])) { + $linkData = array_shift($linksRows[$productId][$linkId]); + $dataRow[$colPrefix . 'position'] = $linkData['position']; + $dataRow[$colPrefix . 'sku'] = $linkData['sku']; + + if (null !== $linkData['default_qty']) { + $dataRow[$colPrefix . 'default_qty'] = $linkData['default_qty']; + } + } + } + if (!empty($customOptionsData[$productId])) { + $dataRow = array_merge($dataRow, array_shift($customOptionsData[$productId])); + } + if (!empty($configurableData[$productId])) { + $dataRow = array_merge($dataRow, array_shift($configurableData[$productId])); + } + + $writer->writeRow($dataRow); + } + // calculate largest links block + $largestLinks = 0; + + if (isset($linksRows[$productId])) { + foreach ($linksRows[$productId] as $linkData) { + $largestLinks = max($largestLinks, count($linkData)); + } + } + $additionalRowsCount = max( + count($rowCategories[$productId]), + count($rowWebsites[$productId]), + $largestLinks + ); + if (!empty($rowTierPrices[$productId])) { + $additionalRowsCount = max($additionalRowsCount, count($rowTierPrices[$productId])); + } + if (!empty($customOptionsData[$productId])) { + $additionalRowsCount = max($additionalRowsCount, count($customOptionsData[$productId])); + } + if (!empty($configurableData[$productId])) { + $additionalRowsCount = max($additionalRowsCount, count($configurableData[$productId])); + } + + if ($additionalRowsCount) { + for ($i = 0; $i < $additionalRowsCount; $i++) { + $dataRow = array(); + + if ($rowCategories[$productId]) { + $dataRow[self::COL_CATEGORY] = $this->_categories[array_shift($rowCategories[$productId])]; + } + if ($rowWebsites[$productId]) { + $dataRow['_product_websites'] = $this->_websiteIdToCode[array_shift($rowWebsites[$productId])]; + } + if (!empty($rowTierPrices[$productId])) { + $dataRow = array_merge($dataRow, array_shift($rowTierPrices[$productId])); + } + foreach ($linkIdColPrefix as $linkId => $colPrefix) { + if (!empty($linksRows[$productId][$linkId])) { + $linkData = array_shift($linksRows[$productId][$linkId]); + $dataRow[$colPrefix . 'position'] = $linkData['position']; + $dataRow[$colPrefix . 'sku'] = $linkData['sku']; + + if (null !== $linkData['default_qty']) { + $dataRow[$colPrefix . 'default_qty'] = $linkData['default_qty']; + } + } + } + if (!empty($customOptionsData[$productId])) { + $dataRow = array_merge($dataRow, array_shift($customOptionsData[$productId])); + } + if (!empty($configurableData[$productId])) { + $dataRow = array_merge($dataRow, array_shift($configurableData[$productId])); + } + $writer->writeRow($dataRow); + } + } + } + return $writer->getContents(); + } + + /** + * Clean up already loaded attribute collection. + * + * @param Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection + * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection + */ + public function filterAttributeCollection(Mage_Eav_Model_Mysql4_Entity_Attribute_Collection $collection) + { + $validTypes = array_keys($this->_productTypeModels); + + foreach (parent::filterAttributeCollection($collection) as $attribute) { + $attrApplyTo = $attribute->getApplyTo(); + $attrApplyTo = $attrApplyTo ? array_intersect($attrApplyTo, $validTypes) : $validTypes; + + if ($attrApplyTo) { + foreach ($attrApplyTo as $productType) { // override attributes by its product type model + if ($this->_productTypeModels[$productType]->overrideAttribute($attribute)) { + break; + } + } + } else { // remove attributes of not-supported product types + $collection->removeItemByKey($attribute->getId()); + } + } + return $collection; + } + + /** + * Entity attributes collection getter. + * + * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection + */ + public function getAttributeCollection() + { + return Mage::getResourceModel('catalog/product_attribute_collection'); + } + + /** + * EAV entity type code getter. + * + * @return string + */ + public function getEntityTypeCode() + { + return 'catalog_product'; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Abstract.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Abstract.php new file mode 100644 index 0000000000..8ba7adca60 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Abstract.php @@ -0,0 +1,107 @@ + + */ +abstract class Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract +{ + /** + * Overriden attributes parameters. + * + * @var array + */ + protected $_attributeOverrides = array(); + + /** + * Array of attributes codes which are disabled for export. + * + * @var array + */ + protected $_disabledAttrs = array(); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array(); + + /** + * Return disabled attributes codes. + * + * @return array + */ + public function getDisabledAttrs() + { + return $this->_disabledAttrs; + } + + /** + * Get attribute codes with index (not label) value. + * + * @return array + */ + public function getIndexValueAttributes() + { + return $this->_indexValueAttributes; + } + + /** + * Additional check for model availability. If method returns FALSE - model is not suitable for data processing. + * + * @return bool + */ + public function isSuitable() + { + return true; + } + + /** + * Add additional data to attribute. + * + * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute + * @return boolean + */ + public function overrideAttribute(Mage_Catalog_Model_Resource_Eav_Attribute $attribute) + { + if (!empty($this->_attributeOverrides[$attribute->getAttributeCode()])) { + $data = $this->_attributeOverrides[$attribute->getAttributeCode()]; + + if (isset($data['options_method']) && method_exists($this, $data['options_method'])) { + $data['filter_options'] = $this->$data['options_method'](); + } + $attribute->addData($data); + + return true; + } + return false; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Configurable.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Configurable.php new file mode 100644 index 0000000000..db876129fc --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Configurable.php @@ -0,0 +1,37 @@ + + */ +class Mage_ImportExport_Model_Export_Entity_Product_Type_Configurable + extends Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract +{ +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Giftcard.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Giftcard.php new file mode 100644 index 0000000000..55f9c9391f --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Giftcard.php @@ -0,0 +1,89 @@ + + */ +class Mage_ImportExport_Model_Export_Entity_Product_Type_Giftcard + extends Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract +{ + /** + * Overriden attributes parameters. + * + * @var array + */ + protected $_attributeOverrides = array( + 'allow_message' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'is_redeemable' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'use_config_allow_message' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'use_config_email_template' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'use_config_is_redeemable' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'use_config_lifetime' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'email_template' => array('options_method' => 'getEmailTemplates') + ); + + /** + * Array of attributes codes which are disabled for export. + * + * @var array + */ + protected $_disabledAttrs = array('giftcard_amounts'); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array('allow_open_amount', 'giftcard_type'); + + /** + * Return email template select options. + * + * @return array + */ + public function getEmailTemplates() + { + return Mage::getModel( + Mage::getConfig()->getBlockClassName('enterprise_giftcard/adminhtml_catalog_product_edit_tab_giftcard') + )->getEmailTemplates(); + } + + /** + * Additional check for model availability. If method returns FALSE - model is not suitable for data processing. + * + * @return bool + */ + public function isSuitable() + { + $moduleNode = Mage::getConfig()->getNode('modules/Enterprise_GiftCard/active'); + + return $moduleNode && 'true' == (string) $moduleNode; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Grouped.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Grouped.php new file mode 100644 index 0000000000..8c847c4cb8 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Grouped.php @@ -0,0 +1,37 @@ + + */ +class Mage_ImportExport_Model_Export_Entity_Product_Type_Grouped + extends Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract +{ +} diff --git a/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Simple.php b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Simple.php new file mode 100644 index 0000000000..e3b04539c5 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Export/Entity/Product/Type/Simple.php @@ -0,0 +1,55 @@ + + */ +class Mage_ImportExport_Model_Export_Entity_Product_Type_Simple + extends Mage_ImportExport_Model_Export_Entity_Product_Type_Abstract +{ + /** + * Overriden attributes parameters. + * + * @var array + */ + protected $_attributeOverrides = array( + 'has_options' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'required_options' => array('source_model' => 'eav/entity_attribute_source_boolean'), + 'created_at' => array('backend_type' => 'datetime'), + 'updated_at' => array('backend_type' => 'datetime') + ); + + /** + * Array of attributes codes which are disabled for export. + * + * @var array + */ + protected $_disabledAttrs = array('old_id', 'recurring_profile', 'is_recurring', 'tier_price', 'category_ids'); +} diff --git a/app/code/core/Mage/ImportExport/Model/Import.php b/app/code/core/Mage/ImportExport/Model/Import.php new file mode 100644 index 0000000000..fecb9530cb --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import.php @@ -0,0 +1,401 @@ + + */ +class Mage_ImportExport_Model_Import extends Varien_Object +{ + /** + * Key in config with entities. + */ + const CONFIG_KEY_ENTITIES = 'global/importexport/import_entities'; + + /** + * Import behavior. + */ + const BEHAVIOR_APPEND = 'append'; + const BEHAVIOR_REPLACE = 'replace'; + const BEHAVIOR_DELETE = 'delete'; + + /** + * Form field names (and IDs) + */ + const FIELD_NAME_SOURCE_FILE = 'import_file'; + const FIELD_NAME_IMG_ARCHIVE_FILE = 'import_image_archive'; + + /** + * Entity adapter. + * + * @var Mage_ImportExport_Model_Import_Entity_Abstract + */ + protected $_entityAdapter; + + /** + * Entity invalidated indexes. + * + * @var Mage_ImportExport_Model_Import_Entity_Abstract + */ + protected static $_entityInvalidatedIndexes = array ( + 'catalog_product' => array ( + 'catalog_product_price', + 'catalog_category_product', + 'catalogsearch_fulltext' + ) + ); + + /** + * Create instance of entity adapter and returns it. + * + * @throws Exception + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + protected function _getEntityAdapter() + { + if (!$this->_entityAdapter) { + $validTypes = Mage_ImportExport_Model_Config::getModels(self::CONFIG_KEY_ENTITIES); + + if (isset($validTypes[$this->getEntity()])) { + try { + $this->_entityAdapter = Mage::getModel($validTypes[$this->getEntity()]['model']); + } catch (Exception $e) { + Mage::throwException(Mage::getIsDeveloperMode() ? $e : 'Invalid entity model'); + } + if (! $this->_entityAdapter instanceof Mage_ImportExport_Model_Import_Entity_Abstract) { + Mage::throwException(Mage::helper('importexport')->__('Entity adapter object must be an instance of Mage_ImportExport_Model_Import_Entity_Abstract')); + } + } else { + Mage::throwException(Mage::helper('importexport')->__('Invalid entity')); + } + // check for entity codes integrity + if ($this->getEntity() != $this->_entityAdapter->getEntityTypeCode()) { + Mage::throwException(Mage::helper('importexport')->__('Input entity code is not equal to entity adapter code')); + } + $this->_entityAdapter->setParameters($this->getData()); + } + return $this->_entityAdapter; + } + + /** + * Returns source adapter object. + * + * @param string $sourceFile Full path to source file + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + protected function _getSourceAdapter($sourceFile) + { + return Mage_ImportExport_Model_Import_Adapter::findAdapterFor($sourceFile); + } + + /** + * Get attribute type for upcoming validation. + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return string + */ + public static function getAttributeType(Mage_Eav_Model_Entity_Attribute $attribute) + { + if ($attribute->usesSource()) { + return 'select'; + } elseif ($attribute->isStatic()) { + return $attribute->getFrontendInput() == 'date' ? 'datetime' : 'varchar'; + } else { + return $attribute->getBackendType(); + } + } + + /** + * DB data source model getter. + * + * @static + * @return Mage_ImportExport_Model_Mysql4_Import_Data + */ + public static function getDataSourceModel() + { + return Mage::getResourceSingleton('importexport/import_data'); + } + + /** + * Default import behavior getter. + * + * @static + * @return string + */ + public static function getDefaultBehavior() + { + return self::BEHAVIOR_APPEND; + } + + /** + * Override standard entity getter. + * + * @throw Exception + * @return string + */ + public function getEntity() + { + if (empty($this->_data['entity'])) { + Mage::throwException(Mage::helper('importexport')->__('Entity is unknown')); + } + return $this->_data['entity']; + } + + /** + * Get entity adapter errors. + * + * @return array + */ + public function getErrors() + { + return $this->_getEntityAdapter()->getErrorMessages(); + } + + /** + * Returns error counter. + * + * @return int + */ + public function getErrorsCount() + { + return $this->_getEntityAdapter()->getErrorsCount(); + } + + /** + * Returns error limit value. + * + * @return int + */ + public function getErrorsLimit() + { + return $this->_getEntityAdapter()->getErrorsLimit(); + } + + /** + * Returns invalid rows count. + * + * @return int + */ + public function getInvalidRowsCount() + { + return $this->_getEntityAdapter()->getInvalidRowsCount(); + } + + /** + * Returns entity model noticees. + * + * @return array + */ + public function getNotices() + { + return $this->_getEntityAdapter()->getNotices(); + } + + /** + * Returns number of checked entities. + * + * @return int + */ + public function getProcessedEntitiesCount() + { + return $this->_getEntityAdapter()->getProcessedEntitiesCount(); + } + + /** + * Returns number of checked rows. + * + * @return int + */ + public function getProcessedRowsCount() + { + return $this->_getEntityAdapter()->getProcessedRowsCount(); + } + + /** + * Get valid import entities from configuration. + * + * @static + * @return array + */ + public static function getValidEntities() + { + return array_keys(Mage_ImportExport_Model_Config::getModels(self::CONFIG_KEY_ENTITIES)); + } + + /** + * Import/Export working directory (source files, result files, lock files etc.). + * + * @return string + */ + public static function getWorkingDir() + { + return Mage::getBaseDir('var') . DS . 'importexport' . DS; + } + + /** + * Import source file structure to DB. + * + * @return bool + */ + public function importSource() + { + $this->setData(array( + 'entity' => self::getDataSourceModel()->getEntityTypeCode(), + 'behavior' => self::getDataSourceModel()->getBehavior() + )); + return $this->_getEntityAdapter()->importData(); + } + + /** + * Import possibility getter. + * + * @return bool + */ + public function isImportAllowed() + { + return $this->_getEntityAdapter()->isImportAllowed(); + } + + /** + * Import source file structure to DB. + * + * @return bool + */ + public function expandSource() + { + $writer = Mage::getModel('importexport/export_adapter_csv', self::getWorkingDir() . "big0.csv"); + $regExps = array('last' => '/(.*?)(\d+)$/', 'middle' => '/(.*?)(\d+)(.*)$/'); + $colReg = array( + 'sku' => 'last', 'name' => 'last', 'description' => 'last', 'short_description' => 'last', + 'url_key' => 'middle', 'meta_title' => 'last', 'meta_keyword' => 'last', 'meta_description' => 'last', + '_links_related_sku' => 'last', '_links_crosssell_sku' => 'last', '_links_upsell_sku' => 'last', + '_custom_option_sku' => 'middle', '_custom_option_row_sku' => 'middle', '_super_products_sku' => 'last', + '_associated_sku' => 'last' + ); + $size = 50; + + foreach ($this->_getSourceAdapter(self::getWorkingDir() . 'catalog_product.csv') as $row) { + $writer->writeRow($row); + } + for ($i = 1; $i < 4; $i++) { + $writer = Mage::getModel('importexport/export_adapter_csv', self::getWorkingDir() . "big{$i}.csv"); + + foreach ($this->_getSourceAdapter(self::getWorkingDir() . 'big' . ($i - 1) . '.csv') as $row) { + $writer->writeRow($row); + } + foreach ($this->_getSourceAdapter(self::getWorkingDir() . 'big' . ($i - 1) . '.csv') as $row) { + foreach ($colReg as $colName => $regExpType) { + if (!empty($row[$colName])) { + preg_match($regExps[$regExpType], $row[$colName], $m); + + $row[$colName] = $m[1] . ($m[2] + $size) . ('middle' == $regExpType ? $m[3] : ''); + } + } + $writer->writeRow($row); + } + $size *= 2; + } + } + + /** + * Move uploaded file and create source adapter instance. + * + * @throws Exception + * @return string Source file path + */ + public function uploadSource() + { + $entity = $this->getEntity(); + $uploader = new Varien_File_Uploader(self::FIELD_NAME_SOURCE_FILE); + $result = $uploader->save(self::getWorkingDir()); + $extension = pathinfo($result['file'], PATHINFO_EXTENSION); + + if (!$extension) { + unlink($result['path'] . $result['file']); + Mage::throwException(Mage::helper('importexport')->__('Uploaded file has no extension')); + } + $sourceFile = self::getWorkingDir() . $entity; + + if (($tenantId = Mage::getConfig()->getOptions()->getTenantId())) { + $sourceFile .= '_' . $tenantId; + } + $sourceFile .= '.' . $extension; + + if (!@rename($result['path'] . $result['file'], $sourceFile)) { + Mage::throwException(Mage::helper('importexport')->__('Source file moving failed')); + } + // trying to create source adapter for file and catch possible exception to be convinced in its adequacy + try { + $this->_getSourceAdapter($sourceFile); + } catch (Exception $e) { + unlink($sourceFile); + Mage::throwException($e->getMessage()); + } + return $sourceFile; + } + + /** + * Validates source file and returns validation result. + * + * @param string $sourceFile Full path to source file + * @return bool + */ + public function validateSource($sourceFile) + { + $result = $this->_getEntityAdapter() + ->setSource($this->_getSourceAdapter($sourceFile)) + ->isDataValid(); + + @unlink($sourceFile); + + return $result; + } + + /** + * Invalidate indexes by process codes. + * + * @param array $indexers indexer process codes + * @return Mage_ImportExport_Model_Import + */ + public function invalidateIndex() + { + if (!isset(self::$_entityInvalidatedIndexes[$this->getEntity()])) { + return $this; + } + + $indexers = self::$_entityInvalidatedIndexes[$this->getEntity()]; + foreach ($indexers as $indexer) { + if ($indexProcess = Mage::getSingleton('index/indexer')->getProcessByCode($indexer)) { + $indexProcess->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + } + } + + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Adapter.php b/app/code/core/Mage/ImportExport/Model/Import/Adapter.php new file mode 100644 index 0000000000..2f0bf003e2 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Adapter.php @@ -0,0 +1,78 @@ + + */ +class Mage_ImportExport_Model_Import_Adapter +{ + /** + * Adapter factory. Checks for availability, loads and create instance of import adapter object. + * + * @param string $type Adapter type ('csv', 'xml' etc.) + * @param mixed $options OPTIONAL Adapter constructor options + * @throws Exception + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + public static function factory($type, $options = null) + { + if (!is_string($type) || !$type) { + Mage::throwException(Mage::helper('importexport')->__('Adapter type must be a non empty string')); + } + $adapterClass = __CLASS__ . '_' . ucfirst(strtolower($type)); + + if (!class_exists($adapterClass, false)) { + $adapterFile = str_replace('_', '/', $adapterClass) . '.php'; + if (!@include_once($adapterFile)) { + Mage::throwException("'{$type}' file extension is not supported"); + } + if (!class_exists($adapterClass, false)) { + Mage::throwException("Can not find adapter class {$adapterClass} in adapter file {$adapterFile}"); + } + } + $adapter = new $adapterClass($options); + + if (! $adapter instanceof Mage_ImportExport_Model_Import_Adapter_Abstract) { + Mage::throwException(Mage::helper('importexport')->__('Adapter must be an instance of Mage_ImportExport_Model_Import_Adapter_Abstract')); + } + return $adapter; + } + + /** + * Create adapter instance for specified source file. + * + * @param string $source Source file path. + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + public static function findAdapterFor($source) + { + return self::factory(pathinfo($source, PATHINFO_EXTENSION), $source); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Adapter/Abstract.php b/app/code/core/Mage/ImportExport/Model/Import/Adapter/Abstract.php new file mode 100644 index 0000000000..92ede956c5 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Adapter/Abstract.php @@ -0,0 +1,178 @@ + + */ +abstract class Mage_ImportExport_Model_Import_Adapter_Abstract implements SeekableIterator +{ + /** + * Column names array. + * + * @var array + */ + protected $_colNames; + + /** + * Quantity of columns in first (column names) row. + * + * @var int + */ + protected $_colQuantity; + + /** + * Current row. + * + * @var array + */ + protected $_currentRow = null; + + /** + * Current row number. + * + * @var int + */ + protected $_currentKey = null; + + /** + * Source file path. + * + * @var string + */ + protected $_source; + + /** + * Adapter object constructor. + * + * @param string $source Source file path. + * @throws Exception + * @return void + */ + final public function __construct($source) + { + if (!is_string($source)) { + Mage::throwException(Mage::helper('importexport')->__('Source file path must be a string')); + } + if (!is_readable($source)) { + Mage::throwException("{$source} file does not exists or is not readable"); + } + $this->_source = $source; + + $this->_init(); + + // validate column names consistency + if (is_array($this->_colNames) && !empty($this->_colNames)) { + $this->_colQuantity = count($this->_colNames); + + if (count(array_unique($this->_colNames)) != $this->_colQuantity) { + Mage::throwException(Mage::helper('importexport')->__('Column names has duplicates')); + } + } else { + Mage::throwException(Mage::helper('importexport')->__('Column names is empty or is not an array')); + } + } + + /** + * Method called as last step of object instance creation. Can be overridden in child classes. + * + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + protected function _init() + { + return $this; + } + + /** + * Return the current element. + * + * @return mixed + */ + public function current() + { + return array_combine( + $this->_colNames, + count($this->_currentRow) != $this->_colQuantity + ? array_pad($this->_currentRow, $this->_colQuantity, '') + : $this->_currentRow + ); + } + + /** + * Column names getter. + * + * @return array + */ + public function getColNames() + { + return $this->_colNames; + } + + /** + * Return the key of the current element. + * + * @return int More than 0 integer on success, integer 0 on failure. + */ + public function key() + { + return $this->_currentKey; + } + + /** + * Seeks to a position. + * + * @param int $position The position to seek to. + * @return void + */ + public function seek($position) + { + Mage::throwException(Mage::helper('importexport')->__('Not implemented yet')); + } + + /** + * Checks if current position is valid. + * + * @return boolean Returns true on success or false on failure. + */ + public function valid() + { + return !empty($this->_currentRow); + } + + /** + * Check source file for validity. + * + * @throws Exception + * @return Mage_ImportExport_Model_Import_Adapter_Csv + */ + public function validateSource() + { + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Adapter/Csv.php b/app/code/core/Mage/ImportExport/Model/Import/Adapter/Csv.php new file mode 100644 index 0000000000..b589d5200b --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Adapter/Csv.php @@ -0,0 +1,134 @@ + + */ +class Mage_ImportExport_Model_Import_Adapter_Csv extends Mage_ImportExport_Model_Import_Adapter_Abstract +{ + /** + * Field delimiter. + * + * @var string + */ + protected $_delimiter = ','; + + /** + * Field enclosure character. + * + * @var string + */ + protected $_enclosure = '"'; + + /** + * Source file handler. + * + * @var resource + */ + protected $_fileHandler; + + /** + * Object destructor. + * + * @return void + */ + public function __destruct() + { + if (is_resource($this->_fileHandler)) { + fclose($this->_fileHandler); + } + } + + /** + * Method called as last step of object instance creation. Can be overrided in child classes. + * + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + protected function _init() + { + $this->_fileHandler = fopen($this->_source, 'r'); + $this->rewind(); + return $this; + } + + /** + * Move forward to next element + * + * @return void Any returned value is ignored. + */ + public function next() + { + $this->_currentRow = fgetcsv($this->_fileHandler, null, $this->_delimiter, $this->_enclosure); + $this->_currentKey = $this->_currentRow ? $this->_currentKey + 1 : null; + } + + /** + * Rewind the Iterator to the first element. + * + * @return void Any returned value is ignored. + */ + public function rewind() + { + // rewind resource, reset column names, read first row as current + rewind($this->_fileHandler); + $this->_colNames = fgetcsv($this->_fileHandler, null, $this->_delimiter, $this->_enclosure); + $this->_currentRow = fgetcsv($this->_fileHandler, null, $this->_delimiter, $this->_enclosure); + + if ($this->_currentRow) { + $this->_currentKey = 0; + } + } + + /** + * Seeks to a position. + * + * @param int $position The position to seek to. + * @throws OutOfBoundsException + * @return void + */ + public function seek($position) + { + if ($position != $this->_currentKey) { + if (0 == $position) { + return $this->rewind(); + } elseif ($position > 0) { + if ($position < $this->_currentKey) { + $this->rewind(); + } + while ($this->_currentRow = fgetcsv($this->_fileHandler, null, $this->_delimiter, $this->_enclosure)) { + if (++ $this->_currentKey == $position) { + return; + } + } + } + throw new OutOfBoundsException(Mage::helper('importexport')->__('Invalid seek position')); + } + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php new file mode 100644 index 0000000000..5b5831c47c --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Abstract.php @@ -0,0 +1,677 @@ + + */ +abstract class Mage_ImportExport_Model_Import_Entity_Abstract +{ + /** + * DB connection. + * + * @var Varien_Db_Adapter_Pdo_Mysql + */ + protected $_connection; + + /** + * Has data process validation done? + * + * @var bool + */ + protected $_dataValidated = false; + + /** + * DB data source model. + * + * @var Mage_ImportExport_Model_Mysql4_Import_Data + */ + protected $_dataSourceModel; + + /** + * Entity type id. + * + * @var int + */ + protected $_entityTypeId; + + /** + * Error codes with arrays of corresponding row numbers. + * + * @var array + */ + protected $_errors = array(); + + /** + * Error counter. + * + * @var int + */ + protected $_errorsCount = 0; + + /** + * Limit of errors after which pre-processing will exit. + * + * @var int + */ + protected $_errorsLimit = 100; + + /** + * Flag to disable import. + * + * @var bool + */ + protected $_importAllowed = true; + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array(); + + /** + * Array of invalid rows numbers. + * + * @var array + */ + protected $_invalidRows = array(); + + /** + * Validation failure message template definitions. + * + * @var array + */ + protected $_messageTemplates = array(); + + /** + * Notice messages. + * + * @var array + */ + protected $_notices = array(); + + /** + * Entity model parameters. + * + * @var array + */ + protected $_parameters = array(); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array(); + + /** + * Permanent entity columns. + * + * @var array + */ + protected $_permanentAttributes = array(); + + /** + * Number of entities processed by validation. + * + * @var int + */ + protected $_processedEntitiesCount = 0; + + /** + * Number of rows processed by validation. + * + * @var int + */ + protected $_processedRowsCount = 0; + + /** + * Rows to skip. Valid rows but we have some reasons to skip them. + * + * [Row number 1] => true, + * ... + * [Row number N] => true + * + * @var array + */ + protected $_rowsToSkip = array(); + + /** + * Array of numbers of validated rows as keys and boolean TRUE as values. + * + * @var array + */ + protected $_validatedRows = array(); + + /** + * Source model. + * + * @var Mage_ImportExport_Model_Import_Adapter_Abstract + */ + protected $_source; + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + $entityType = Mage::getSingleton('eav/config')->getEntityType($this->getEntityTypeCode()); + $this->_entityTypeId = $entityType->getEntityTypeId(); + $this->_dataSourceModel = Mage_ImportExport_Model_Import::getDataSourceModel(); + $this->_connection = Mage::getSingleton('core/resource')->getConnection('write'); + } + + /** + * Inner source object getter. + * + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + protected function _getSource() + { + if (!$this->_source) { + Mage::throwException(Mage::helper('importexport')->__('No source specified')); + } + return $this->_source; + } + + /** + * Import data rows. + * + * @abstract + * @return boolean + */ + abstract protected function _importData(); + + /** + * Returns boolean TRUE if row scope is default (fundamental) scope. + * + * @param array $rowData + * @return bool + */ + protected function _isRowScopeDefault(array $rowData) + { + return true; + } + + /** + * Change row data before saving in DB table. + * + * @param array $rowData + * @return array + */ + protected function _prepareRowForDb(array $rowData) + { + return $rowData; + } + + /** + * Validate data rows and save bunches to DB. + * + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + protected function _saveValidatedBunches() + { + $coefficient = 900000 / 1048576; // real-size to DB packet size coefficient + $maxPacketData = $this->_connection->fetchRow('SHOW VARIABLES LIKE "max_allowed_packet"'); + $maxPacket = (empty($maxPacketData['Value']) ? 1048576 : $maxPacketData['Value']) * $coefficient; + $source = $this->_getSource(); + $productDataSize = 0; + $bunchRows = array(); + $startNewBunch = false; + $nextRowBackup = array(); + + $source->rewind(); + $this->_dataSourceModel->cleanBunches(); + + while ($source->valid() || $bunchRows) { + if ($startNewBunch || !$source->valid()) { + $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows); + + $bunchRows = $nextRowBackup; + $productDataSize = strlen(serialize($bunchRows)); + $startNewBunch = false; + $nextRowBackup = array(); + } + if ($source->valid()) { + if ($this->_errorsCount >= $this->_errorsLimit) { // errors limit check + return; + } + $rowData = $source->current(); + + $this->_processedRowsCount++; + + if ($this->validateRow($rowData, $source->key())) { // add row to bunch for save + $rowData = $this->_prepareRowForDb($rowData); + $rowSize = strlen(serialize($rowData)); + + if (($productDataSize + $rowSize) >= $maxPacket) { // check bunch size + $startNewBunch = true; + $nextRowBackup = array($source->key() => $rowData); + } else { + $bunchRows[$source->key()] = $rowData; + $productDataSize += $rowSize; + } + } + $source->next(); + } + } + return $this; + } + + /** + * Add error with corresponding current data source row number. + * + * @param string $errorCode Error code or simply column name + * @param int $errorRowNum Row number. + * @param string $colName OPTIONAL Column name. + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + public function addRowError($errorCode, $errorRowNum, $colName = null) + { + $this->_errors[$errorCode][] = array($errorRowNum + 1, $colName); // one added for human readability + $this->_invalidRows[$errorRowNum] = true; + $this->_errorsCount ++; + + return $this; + } + + /** + * Add message template for specific error code from outside. + * + * @param string $errorCode Error code + * @param string $message Message template + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + public function addMessageTemplate($errorCode, $message) + { + $this->_messageTemplates[$errorCode] = $message; + + return $this; + } + + /** + * Returns attributes all values in label-value or value-value pairs form. Labels are lower-cased. + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param array $indexValAttrs OPTIONAL Additional attributes' codes with index values. + * @return array + */ + public function getAttributeOptions(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $indexValAttrs = array()) + { + $options = array(); + + if ($attribute->usesSource()) { + // merge global entity index value attributes + $indexValAttrs = array_merge($indexValAttrs, $this->_indexValueAttributes); + + // should attribute has index (option value) instead of a label? + $index = in_array($attribute->getAttributeCode(), $indexValAttrs) ? 'value' : 'label'; + + // only default (admin) store values used + $attribute->setStoreId(Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID); + + try { + foreach ($attribute->getSource()->getAllOptions(false) as $option) { + foreach (is_array($option['value']) ? $option['value'] : array($option) as $innerOption) { + if (strlen($innerOption['value'])) { // skip ' -- Please Select -- ' option + $options[strtolower($innerOption[$index])] = $innerOption['value']; + } + } + } + } catch (Exception $e) { + // ignore exceptions connected with source models + } + } + return $options; + } + + /** + * Import behavior getter. + * + * @return string + */ + public function getBehavior() + { + if (!isset($this->_parameters['behavior']) + || ($this->_parameters['behavior'] != Mage_ImportExport_Model_Import::BEHAVIOR_APPEND + && $this->_parameters['behavior'] != Mage_ImportExport_Model_Import::BEHAVIOR_REPLACE + && $this->_parameters['behavior'] != Mage_ImportExport_Model_Import::BEHAVIOR_DELETE)) { + return Mage_ImportExport_Model_Import::getDefaultBehavior(); + } + return $this->_parameters['behavior']; + } + + /** + * EAV entity type code getter. + * + * @abstract + * @return string + */ + abstract public function getEntityTypeCode(); + + /** + * Entity type ID getter. + * + * @return int + */ + public function getEntityTypeId() + { + return $this->_entityTypeId; + } + + /** + * Returns error information grouped by error types and translated (if possible). + * + * @return array + */ + public function getErrorMessages() + { + $translator = Mage::helper('importexport'); + $messages = array(); + + foreach ($this->_errors as $errorCode => $errorRows) { + if (isset($this->_messageTemplates[$errorCode])) { + $errorCode = $translator->__($this->_messageTemplates[$errorCode]); + } + foreach ($errorRows as $errorRowData) { + $messages[$errorRowData[1] ? sprintf($errorCode, $errorRowData[1]) : $errorCode][] = $errorRowData[0]; + } + } + return $messages; + } + + /** + * Returns error counter value. + * + * @return int + */ + public function getErrorsCount() + { + return $this->_errorsCount; + } + + /** + * Returns error limit value. + * + * @return int + */ + public function getErrorsLimit() + { + return $this->_errorsLimit; + } + + /** + * Returns invalid rows count. + * + * @return int + */ + public function getInvalidRowsCount() + { + return count($this->_invalidRows); + } + + /** + * Find net value of autoincrement key for specified table. + * + * @param string $tableName + * @throws Exception + * @return string + */ + public function getNextAutoincrement($tableName) + { + $entityStatus = $this->_connection->showTableStatus($tableName); + + if (empty($entityStatus['Auto_increment'])) { + Mage::throwException(Mage::helper('importexport')->__('Can not get autoincrement value')); + } + return $entityStatus['Auto_increment']; + } + + /** + * Returns model notices. + * + * @return array + */ + public function getNotices() + { + return $this->_notices; + } + + /** + * Returns number of checked entities. + * + * @return int + */ + public function getProcessedEntitiesCount() + { + return $this->_processedEntitiesCount; + } + + /** + * Returns number of checked rows. + * + * @return int + */ + public function getProcessedRowsCount() + { + return $this->_processedRowsCount; + } + + /** + * Source object getter. + * + * @throws Exception + * @return Mage_ImportExport_Model_Import_Adapter_Abstract + */ + public function getSource() + { + if (!$this->_source) { + Mage::throwException(Mage::helper('importexport')->__('Source is not set')); + } + return $this->_source; + } + + /** + * Import process start. + * + * @return bool Result of operation. + */ + public function importData() + { + return $this->_importData(); + } + + /** + * Is attribute contains particular data (not plain entity attribute). + * + * @param string $attrCode + * @return bool + */ + public function isAttributeParticular($attrCode) + { + return in_array($attrCode, $this->_particularAttributes); + } + + /** + * Check one attribute. Can be overridden in child. + * + * @param string $attrCode Attribute code + * @param array $attrParams Attribute params + * @param array $rowData Row data + * @param int $rowNum + * @return boolean + */ + public function isAttributeValid($attrCode, array $attrParams, array $rowData, $rowNum) + { + switch ($attrParams['type']) { + case 'varchar': + $val = Mage::helper('core/string')->cleanString($rowData[$attrCode]); + $valid = Mage::helper('core/string')->strlen($val) < 256; + break; + case 'decimal': + $val = trim($rowData[$attrCode]); + $valid = (float)$val == $val; + break; + case 'select': + $valid = isset($attrParams['options'][strtolower($rowData[$attrCode])]); + break; + case 'int': + $val = trim($rowData[$attrCode]); + $valid = (int)$val == $val; + break; + case 'datetime': + $val = trim($rowData[$attrCode]); + $valid = strtotime($val) + || preg_match('/^\d{2}.\d{2}.\d{2,4}(?:\s+\d{1,2}.\d{1,2}(?:.\d{1,2})?)?$/', $val); + break; + case 'text': + $val = Mage::helper('core/string')->cleanString($rowData[$attrCode]); + $valid = Mage::helper('core/string')->strlen($val) < 65536; + break; + default: + $valid = true; + break; + } + if (!$valid) { + $this->addRowError(Mage::helper('importexport')->__("Invalid value for '%s'"), $rowNum, $attrCode); + } + return (bool) $valid; + } + + /** + * Is all of data valid? + * + * @return bool + */ + public function isDataValid() + { + $this->validateData(); + return 0 == $this->_errorsCount; + } + + /** + * Import possibility getter. + * + * @return bool + */ + public function isImportAllowed() + { + return $this->_importAllowed; + } + + /** + * Returns TRUE if row is valid and not in skipped rows array. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + public function isRowAllowedToImport(array $rowData, $rowNum) + { + return $this->validateRow($rowData, $rowNum) && !isset($this->_rowsToSkip[$rowNum]); + } + + /** + * Validate data row. + * + * @param array $rowData + * @param int $rowNum + * @return boolean + */ + abstract public function validateRow(array $rowData, $rowNum); + + /** + * Set data from outside to change behavior. I.e. for setting some default parameters etc. + * + * @param array $params + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + public function setParameters(array $params) + { + $this->_parameters = $params; + return $this; + } + + /** + * Source model setter. + * + * @param Mage_ImportExport_Model_Import_Adapter_Abstract $source + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + public function setSource(Mage_ImportExport_Model_Import_Adapter_Abstract $source) + { + $this->_source = $source; + $this->_dataValidated = false; + + return $this; + } + + /** + * Validate data. + * + * @throws Exception + * @return Mage_ImportExport_Model_Import_Entity_Abstract + */ + public function validateData() + { + if (!$this->_dataValidated) { + // does all permanent columns exists? + if (($colsAbsent = array_diff($this->_permanentAttributes, $this->_getSource()->getColNames()))) { + Mage::throwException( + Mage::helper('importexport')->__('Can not find required columns: ') . implode(', ', $colsAbsent) + ); + } + + // initialize validation related attributes + $this->_errors = array(); + $this->_invalidRows = array(); + + // check attribute columns names validity + $invalidColumns = array(); + + foreach ($this->_getSource()->getColNames() as $colName) { + if (!preg_match('/^[a-z][a-z0-9_]*$/', $colName) && !$this->isAttributeParticular($colName)) { + $invalidColumns[] = $colName; + } + } + if ($invalidColumns) { + Mage::throwException( + Mage::helper('importexport')->__('Column names: "%s" are invalid', implode('", "', $invalidColumns)) + ); + } + $this->_saveValidatedBunches(); + + $this->_dataValidated = true; + } + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer.php new file mode 100644 index 0000000000..2c3515af8a --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer.php @@ -0,0 +1,602 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Customer extends Mage_ImportExport_Model_Import_Entity_Abstract +{ + /** + * Size of bunch - part of entities to save in one step. + */ + const BUNCH_SIZE = 20; + + /** + * Data row scopes. + */ + const SCOPE_DEFAULT = 1; + const SCOPE_ADDRESS = -1; + + /** + * Permanent column names. + * + * Names that begins with underscore is not an attribute. This name convention is for + * to avoid interference with same attribute name. + */ + const COL_EMAIL = 'email'; + const COL_WEBSITE = '_website'; + const COL_STORE = '_store'; + + /** + * Error codes. + */ + const ERROR_INVALID_WEBSITE = 'invalidWebsite'; + const ERROR_INVALID_EMAIL = 'invalidEmail'; + const ERROR_DUPLICATE_EMAIL_SITE = 'duplicateEmailSite'; + const ERROR_EMAIL_IS_EMPTY = 'emailIsEmpty'; + const ERROR_ROW_IS_ORPHAN = 'rowIsOrphan'; + const ERROR_VALUE_IS_REQUIRED = 'valueIsRequired'; + const ERROR_INVALID_STORE = 'invalidStore'; + const ERROR_EMAIL_SITE_NOT_FOUND = 'emailSiteNotFound'; + const ERROR_PASSWORD_LENGTH = 'passwordLength'; + + /** + * Customer address import entity model. + * + * @var Mage_ImportExport_Model_Import_Entity_Customer_Address + */ + protected $_addressEntity; + + /** + * Customer attributes parameters. + * + * [attr_code_1] => array( + * 'options' => array(), + * 'type' => 'text', 'price', 'textarea', 'select', etc. + * 'id' => .. + * ), + * ... + * + * @var array + */ + protected $_attributes = array(); + + /** + * Customer account sharing. TRUE - is global, FALSE - is per website. + * + * @var boolean + */ + protected $_customerGlobal; + + /** + * Customer groups ID-to-name. + * + * @var array + */ + protected $_customerGroups = array(); + + /** + * Customer entity DB table name. + * + * @var string + */ + protected $_entityTable; + + /** + * Array of attribute codes which will be ignored in validation and import procedures. + * For example, when entity attribute has own validation and import procedures + * or just to deny this attribute processing. + * + * @var array + */ + protected $_ignoredAttributes = array('website_id', 'store_id', 'default_billing', 'default_shipping'); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array('group_id'); + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::ERROR_INVALID_WEBSITE => 'Invalid value in Website column (website does not exists?)', + self::ERROR_INVALID_EMAIL => 'E-mail is invalid', + self::ERROR_DUPLICATE_EMAIL_SITE => 'E-mail is duplicated in import file', + self::ERROR_EMAIL_IS_EMPTY => 'E-mail is not specified', + self::ERROR_ROW_IS_ORPHAN => 'Orphan rows that will be skipped due default row errors', + self::ERROR_VALUE_IS_REQUIRED => "Required attribute '%s' has an empty value", + self::ERROR_INVALID_STORE => 'Invalid value in Store column (store does not exists?)', + self::ERROR_EMAIL_SITE_NOT_FOUND => 'E-mail and website combination is not found', + self::ERROR_PASSWORD_LENGTH => 'Invalid password length' + ); + + /** + * Dry-runned customers information from import file. + * + * @var array + */ + protected $_newCustomers = array(); + + /** + * Existing customers information. In form of: + * + * [customer e-mail] => array( + * [website code 1] => customer_id 1, + * [website code 2] => customer_id 2, + * ... => ... , + * [website code n] => customer_id n, + * ) + * + * @var array + */ + protected $_oldCustomers = array(); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array(self::COL_WEBSITE, self::COL_STORE); + + /** + * Permanent entity columns. + * + * @var array + */ + protected $_permanentAttributes = array(self::COL_EMAIL, self::COL_WEBSITE); + + /** + * All stores code-ID pairs. + * + * @var array + */ + protected $_storeCodeToId = array(); + + /** + * Website code-to-ID + * + * @var array + */ + protected $_websiteCodeToId = array(); + + /** + * Website ID-to-code + * + * @var array + */ + protected $_websiteIdToCode = array(); + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->_initWebsites() + ->_initStores() + ->_initCustomerGroups() + ->_initAttributes() + ->_initCustomers(); + + $this->_entityTable = Mage::getModel('customer/customer')->getResource()->getEntityTable(); + $this->_addressEntity = Mage::getModel('importexport/import_entity_customer_address', $this); + } + + /** + * Delete customers. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _deleteCustomers() + { + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $idToDelete = array(); + + foreach ($bunch as $rowNum => $rowData) { + if (self::SCOPE_DEFAULT == $this->getRowScope($rowData) && $this->validateRow($rowData, $rowNum)) { + $idToDelete[] = $this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]]; + } + } + if ($idToDelete) { + $this->_connection->query( + $this->_connection->quoteInto( + "DELETE FROM `{$this->_entityTable}` WHERE `entity_id` IN (?)", $idToDelete + ) + ); + } + } + return $this; + } + + /** + * Save customer data to DB. + * + * @throws Exception + * @return bool Result of operation. + */ + protected function _importData() + { + if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) { + $this->_deleteCustomers(); + } else { + $this->_saveCustomers(); + $this->_addressEntity->importData(); + } + return true; + } + + /** + * Initialize customer attributes. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _initAttributes() + { + foreach (Mage::getResourceModel('customer/attribute_collection')->addSystemHiddenFilter() as $attribute) { + $this->_attributes[$attribute->getAttributeCode()] = array( + 'id' => $attribute->getId(), + 'is_required' => $attribute->getIsRequired(), + 'is_static' => $attribute->isStatic(), + 'rules' => $attribute->getValidateRules() ? unserialize($attribute->getValidateRules()) : null, + 'type' => Mage_ImportExport_Model_Import::getAttributeType($attribute), + 'options' => $this->getAttributeOptions($attribute) + ); + } + return $this; + } + + /** + * Initialize customer groups. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _initCustomerGroups() + { + foreach (Mage::getResourceModel('customer/group_collection') as $customerGroup) { + $this->_customerGroups[$customerGroup->getId()] = true; + } + return $this; + } + + /** + * Initialize existent customers data. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _initCustomers() + { + foreach (Mage::getResourceModel('customer/customer_collection') as $customer) { + $email = $customer->getEmail(); + + if (!isset($this->_oldCustomers[$email])) { + $this->_oldCustomers[$email] = array(); + } + $this->_oldCustomers[$email][$this->_websiteIdToCode[$customer->getWebsiteId()]] = $customer->getId(); + } + $this->_customerGlobal = Mage::getModel('customer/customer')->getSharingConfig()->isGlobalScope(); + + return $this; + } + + /** + * Initialize stores hash. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _initStores() + { + foreach (Mage::app()->getStores(true) as $store) { + $this->_storeCodeToId[$store->getCode()] = $store->getId(); + } + return $this; + } + + /** + * Initialize website values. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _initWebsites() + { + /** @var $website Mage_Core_Model_Website */ + foreach (Mage::app()->getWebsites(true) as $website) { + $this->_websiteCodeToId[$website->getCode()] = $website->getId(); + $this->_websiteIdToCode[$website->getId()] = $website->getCode(); + } + return $this; + } + + /** + * Gather and save information about customer entities. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _saveCustomers() + { + /** @var $resource Mage_Customer_Model_Customer */ + $resource = Mage::getModel('customer/customer'); + $strftimeFormat = Varien_Date::convertZendToStrftime(Varien_Date::DATETIME_INTERNAL_FORMAT, true, true); + $nextEntityId = $this->getNextAutoincrement($resource->getResource()->getEntityTable()); + $passId = $resource->getAttribute('password_hash')->getId(); + $passTable = $resource->getAttribute('password_hash')->getBackend()->getTable(); + + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $entityRowsIn = array(); + $entityRowsUp = array(); + $attributes = array(); + + foreach ($bunch as $rowNum => $rowData) { + if (!$this->validateRow($rowData, $rowNum)) { + continue; + } + if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) { + // entity table data + $entityRow = array( + 'group_id' => empty($rowData['group_id']) ? 1 : $rowData['group_id'], + 'store_id' => empty($rowData[self::COL_STORE]) + ? 0 : $this->_storeCodeToId[$rowData[self::COL_STORE]], + 'created_at' => empty($rowData['created_at']) + ? now() : gmstrftime($strftimeFormat, strtotime($rowData['created_at'])), + 'updated_at' => now() + ); + if (isset($this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]])) { // edit + $entityId = $this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]]; + $entityRow['entity_id'] = $entityId; + $entityRowsUp[] = $entityRow; + } else { // create + $entityId = $nextEntityId++; + $entityRow['entity_id'] = $entityId; + $entityRow['entity_type_id'] = $this->_entityTypeId; + $entityRow['attribute_set_id'] = 0; + $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COL_WEBSITE]]; + $entityRow['email'] = $rowData[self::COL_EMAIL]; + $entityRow['is_active'] = 1; + $entityRowsIn[] = $entityRow; + + $this->_newCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]] = $entityId; + } + // attribute values + foreach (array_intersect_key($rowData, $this->_attributes) as $attrCode => $value) { + if (!$this->_attributes[$attrCode]['is_static'] && strlen($value)) { + /** @var $attribute Mage_Customer_Model_Attribute */ + $attribute = $resource->getAttribute($attrCode); + $backModel = $attribute->getBackendModel(); + $attrParams = $this->_attributes[$attrCode]; + + if ('select' == $attrParams['type']) { + $value = $attrParams['options'][strtolower($value)]; + } elseif ('datetime' == $attrParams['type']) { + $value = gmstrftime($strftimeFormat, strtotime($value)); + } elseif ($backModel) { + $attribute->getBackend()->beforeSave($resource->setData($attrCode, $value)); + $value = $resource->getData($attrCode); + } + $attributes[$attribute->getBackend()->getTable()][$entityId][$attrParams['id']] = $value; + + // restore 'backend_model' to avoid default setting + $attribute->setBackendModel($backModel); + } + } + // password change/set + if (isset($rowData['password']) && strlen($rowData['password'])) { + $attributes[$passTable][$entityId][$passId] = $resource->hashPassword($rowData['password']); + } + } + } + $this->_saveCustomerEntity($entityRowsIn, $entityRowsUp)->_saveCustomerAttributes($attributes); + } + return $this; + } + + /** + * Save customer attributes. + * + * @param array $attributesData + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _saveCustomerAttributes(array $attributesData) + { + foreach ($attributesData as $tableName => $data) { + $tableData = array(); + + foreach ($data as $customerId => $attrData) { + foreach ($attrData as $attributeId => $value) { + $tableData[] = array( + 'entity_id' => $customerId, + 'entity_type_id' => $this->_entityTypeId, + 'attribute_id' => $attributeId, + 'value' => $value + ); + } + } + $this->_connection->insertOnDuplicate($tableName, $tableData, array('value')); + } + return $this; + } + + /** + * Update and insert data in entity table. + * + * @param array $entityRowsIn Row for insert + * @param array $entityRowsUp Row for update + * @return Mage_ImportExport_Model_Import_Entity_Customer + */ + protected function _saveCustomerEntity(array $entityRowsIn, array $entityRowsUp) + { + if ($entityRowsIn) { + $this->_connection->insertMultiple($this->_entityTable, $entityRowsIn); + } + if ($entityRowsUp) { + $this->_connection->insertOnDuplicate( + $this->_entityTable, + $entityRowsUp, + array('group_id', 'store_id', 'updated_at', 'created_at') + ); + } + return $this; + } + + /** + * Get customer ID. Method tries to find ID from old and new customers. If it fails - it returns NULL. + * + * @param string $email + * @param string $websiteCode + * @return string|null + */ + public function getCustomerId($email, $websiteCode) + { + if (isset($this->_oldCustomers[$email][$websiteCode])) { + return $this->_oldCustomers[$email][$websiteCode]; + } elseif (isset($this->_newCustomers[$email][$websiteCode])) { + return $this->_newCustomers[$email][$websiteCode]; + } else { + return null; + } + } + + /** + * EAV entity type code getter. + * + * @abstract + * @return string + */ + public function getEntityTypeCode() + { + return 'customer'; + } + + /** + * Obtain scope of the row from row data. + * + * @param array $rowData + * @return int + */ + public function getRowScope(array $rowData) + { + return strlen(trim($rowData[self::COL_EMAIL])) ? self::SCOPE_DEFAULT : self::SCOPE_ADDRESS; + } + + /** + * Is attribute contains particular data (not plain entity attribute). + * + * @param string $attrCode + * @return bool + */ + public function isAttributeParticular($attrCode) + { + return parent::isAttributeParticular($attrCode) || $this->_addressEntity->isAttributeParticular($attrCode); + } + + /** + * Validate data row. + * + * @param array $rowData + * @param int $rowNum + * @return boolean + */ + public function validateRow(array $rowData, $rowNum) + { + static $email = null; // e-mail is remembered through all customer rows + static $website = null; // website is remembered through all customer rows + + if (isset($this->_validatedRows[$rowNum])) { // check that row is already validated + return !isset($this->_invalidRows[$rowNum]); + } + $this->_validatedRows[$rowNum] = true; + + $rowScope = $this->getRowScope($rowData); + + if (self::SCOPE_DEFAULT == $rowScope) { + $this->_processedEntitiesCount ++; + } + // BEHAVIOR_DELETE use specific validation logic + if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) { + if (self::SCOPE_DEFAULT == $rowScope + && !isset($this->_oldCustomers[$rowData[self::COL_EMAIL]][$rowData[self::COL_WEBSITE]])) { + $this->addRowError(self::ERROR_EMAIL_SITE_NOT_FOUND, $rowNum); + } + } elseif (self::SCOPE_DEFAULT == $rowScope) { // row is SCOPE_DEFAULT = new customer block begins + $email = $rowData[self::COL_EMAIL]; + $website = $rowData[self::COL_WEBSITE]; + + if (!Zend_Validate::is($email, 'EmailAddress')) { + $this->addRowError(self::ERROR_INVALID_EMAIL, $rowNum); + } elseif (!isset($this->_websiteCodeToId[$website])) { + $this->addRowError(self::ERROR_INVALID_WEBSITE, $rowNum); + } else { + if (isset($this->_newCustomers[$email][$website])) { + $this->addRowError(self::ERROR_DUPLICATE_EMAIL_SITE, $rowNum); + } + $this->_newCustomers[$email][$website] = false; + + if (!empty($rowData[self::COL_STORE]) && !isset($this->_storeCodeToId[$rowData[self::COL_STORE]])) { + $this->addRowError(self::ERROR_INVALID_STORE, $rowNum); + } + // check password + if (isset($rowData['password']) && strlen($rowData['password']) + && Mage::helper('core/string')->strlen($rowData['password']) < 6) { + $this->addRowError(self::ERROR_PASSWORD_LENGTH, $rowNum); + } + // check simple attributes + foreach ($this->_attributes as $attrCode => $attrParams) { + if (in_array($attrCode, $this->_ignoredAttributes)) { + continue; + } + if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) { + $this->isAttributeValid($attrCode, $attrParams, $rowData, $rowNum); + } elseif ($attrParams['is_required'] && !isset($this->_oldCustomers[$email][$website])) { + $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNum, $attrCode); + } + } + } + if (isset($this->_invalidRows[$rowNum])) { + $email = false; // mark row as invalid for next address rows + } + } else { + if (null === $email) { // first row is not SCOPE_DEFAULT + $this->addRowError(self::ERROR_EMAIL_IS_EMPTY, $rowNum); + } elseif (false === $email) { // SCOPE_DEFAULT row is invalid + $this->addRowError(self::ERROR_ROW_IS_ORPHAN, $rowNum); + } + } + // validate row data by address entity + $this->_addressEntity->validateRow($rowData, $rowNum); + + return !isset($this->_invalidRows[$rowNum]); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer/Address.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer/Address.php new file mode 100644 index 0000000000..39d9e48675 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Customer/Address.php @@ -0,0 +1,467 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Customer_Address extends Mage_ImportExport_Model_Import_Entity_Abstract +{ + /** + * Prefix for source file column name, which displays that column contains address data. + */ + const COL_NAME_PREFIX = '_address_'; + + /** + * Particular columns that contains of customer default addresses. + */ + const COL_NAME_DEFAULT_BILLING = '_address_default_billing_'; + const COL_NAME_DEFAULT_SHIPPING = '_address_default_shipping_'; + + /** + * Error codes. + */ + const ERROR_INVALID_REGION = 'invalidRegion'; + + /** + * Customer address attributes parameters. + * + * [attr_code_1] => array( + * 'options' => array(), + * 'type' => 'text', 'price', 'textarea', 'select', etc. + * 'id' => .. + * ), + * ... + * + * @var array + */ + protected $_attributes = array(); + + /** + * Countrys and its regions. + * + * array( + * [country_id_lowercased_1] => array( + * [region_code_lowercased_1] => region_id_1, + * [region_default_name_lowercased_1] => region_id_1, + * ..., + * [region_code_lowercased_n] => region_id_n, + * [region_default_name_lowercased_n] => region_id_n + * ), + * ... + * ) + * + * @var array + */ + protected $_countryRegions = array(); + + /** + * Customer import entity. + * + * @var Mage_ImportExport_Model_Import_Entity_Customer + */ + protected $_customer; + + /** + * Default addresses column names to appropriate customer attribute code. + * + * @var array + */ + protected static $_defaultAddressAttrMapping = array( + self::COL_NAME_DEFAULT_BILLING => 'default_billing', + self::COL_NAME_DEFAULT_SHIPPING => 'default_shipping' + ); + + /** + * Customer entity DB table name. + * + * @var string + */ + protected $_entityTable; + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array('country_id'); + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array(self::ERROR_INVALID_REGION => 'Region is invalid'); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array(self::COL_NAME_DEFAULT_BILLING, self::COL_NAME_DEFAULT_SHIPPING); + + /** + * Region ID to region default name pairs. + * + * @var array + */ + protected $_regions = array(); + + /** + * Constructor. + * + * @param Mage_ImportExport_Model_Import_Entity_Customer $customer + * @return void + */ + public function __construct(Mage_ImportExport_Model_Import_Entity_Customer $customer) + { + parent::__construct(); + + $this->_initAttributes()->_initCountryRegions(); + + $this->_entityTable = Mage::getModel('customer/address')->getResource()->getEntityTable(); + $this->_customer = $customer; + + foreach ($this->_messageTemplates as $errorCode => $message) { + $this->_customer->addMessageTemplate($errorCode, $message); + } + } + + /** + * Import data rows. + * + * @return boolean + */ + protected function _importData() + { + /** @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::getModel('customer/customer'); + /** @var $resource Mage_Customer_Model_Address */ + $resource = Mage::getModel('customer/address'); + $strftimeFormat = Varien_Date::convertZendToStrftime(Varien_Date::DATETIME_INTERNAL_FORMAT, true, true); + $nextEntityId = $this->getNextAutoincrement($resource->getResource()->getEntityTable()); + $customerId = null; + $regionColName = self::getColNameForAttrCode('region'); + $countryColName = self::getColNameForAttrCode('country_id'); + $regionIdAttr = Mage::getSingleton('eav/config')->getAttribute($this->getEntityTypeCode(), 'region_id'); + $regionIdTable = $regionIdAttr->getBackend()->getTable(); + $regionIdAttrId = $regionIdAttr->getId(); + + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $entityRows = array(); + $attributes = array(); + $defaults = array(); // customer default addresses (billing/shipping) data + + foreach ($bunch as $rowNum => $rowData) { + if (!empty($rowData[Mage_ImportExport_Model_Import_Entity_Customer::COL_EMAIL]) + && !empty($rowData[Mage_ImportExport_Model_Import_Entity_Customer::COL_WEBSITE]) + ) { + $customerId = $this->_customer->getCustomerId( + $rowData[Mage_ImportExport_Model_Import_Entity_Customer::COL_EMAIL], + $rowData[Mage_ImportExport_Model_Import_Entity_Customer::COL_WEBSITE] + ); + } + if (!$customerId || !$this->_isRowWithAddress($rowData) || !$this->validateRow($rowData, $rowNum)) { + continue; + } + $entityId = $nextEntityId++; + + // entity table data + $entityRows[] = array( + 'entity_id' => $entityId, + 'entity_type_id' => $this->_entityTypeId, + 'parent_id' => $customerId, + 'created_at' => now(), + 'updated_at' => now() + ); + // attribute values + foreach ($this->_attributes as $attrAlias => $attrParams) { + if (isset($rowData[$attrAlias]) && strlen($rowData[$attrAlias])) { + if ('select' == $attrParams['type']) { + $value = $attrParams['options'][strtolower($rowData[$attrAlias])]; + } elseif ('datetime' == $attrParams['type']) { + $value = gmstrftime($strftimeFormat, strtotime($rowData[$attrAlias])); + } else { + $value = $rowData[$attrAlias]; + } + $attributes[$attrParams['table']][$entityId][$attrParams['id']] = $value; + } + } + // customer default addresses + foreach (self::getDefaultAddressAttrMapping() as $colName => $customerAttrCode) { + if (!empty($rowData[$colName])) { + $attribute = $customer->getAttribute($customerAttrCode); + $defaults[$attribute->getBackend()->getTable()][$customerId][$attribute->getId()] = $entityId; + } + } + // let's try to find region ID + if (!empty($rowData[$regionColName])) { + $countryNormalized = strtolower($rowData[$countryColName]); + $regionNormalized = strtolower($rowData[$regionColName]); + + if (isset($this->_countryRegions[$countryNormalized][$regionNormalized])) { + $regionId = $this->_countryRegions[$countryNormalized][$regionNormalized]; + $attributes[$regionIdTable][$entityId][$regionIdAttrId] = $regionId; + // set 'region' attribute value as default name + $tbl = $this->_attributes[$regionColName]['table']; + $regionColNameId = $this->_attributes[$regionColName]['id']; + $attributes[$tbl][$entityId][$regionColNameId] = $this->_regions[$regionId]; + } + } + } + $this->_saveAddressEntity($entityRows) + ->_saveAddressAttributes($attributes) + ->_saveCustomerDefaults($defaults); + } + return true; + } + + /** + * Initialize customer address attributes. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer_Address + */ + protected function _initAttributes() + { + $addrCollection = Mage::getResourceModel('customer/address_attribute_collection') + ->addSystemHiddenFilter() + ->addExcludeHiddenFrontendFilter(); + + foreach ($addrCollection as $attribute) { + $this->_attributes[self::getColNameForAttrCode($attribute->getAttributeCode())] = array( + 'id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'table' => $attribute->getBackend()->getTable(), + 'is_required' => $attribute->getIsRequired(), + 'rules' => $attribute->getValidateRules() ? unserialize($attribute->getValidateRules()) : null, + 'type' => Mage_ImportExport_Model_Import::getAttributeType($attribute), + 'options' => $this->getAttributeOptions($attribute) + ); + } + return $this; + } + + /** + * Initialize country regions hash for clever recognition. + * + * @return Mage_ImportExport_Model_Import_Entity_Customer_Address + */ + protected function _initCountryRegions() + { + foreach (Mage::getResourceModel('directory/region_collection') as $regionRow) { + $countryNormalized = strtolower($regionRow['country_id']); + $regionCode = strtolower($regionRow['code']); + $regionName = strtolower($regionRow['default_name']); + $this->_countryRegions[$countryNormalized][$regionCode] = $regionRow['region_id']; + $this->_countryRegions[$countryNormalized][$regionName] = $regionRow['region_id']; + $this->_regions[$regionRow['region_id']] = $regionRow['default_name']; + } + return $this; + } + + /** + * Check address data availability in row data. + * + * @param array $rowData + * @return bool + */ + protected function _isRowWithAddress(array $rowData) + { + foreach (array_keys($this->_attributes) as $colName) { + if (isset($rowData[$colName]) && strlen($rowData[$colName])) { + return true; + } + } + return false; + } + + /** + * Save customer address attributes. + * + * @param array $attributesData + * @return Mage_ImportExport_Model_Import_Entity_Customer_Address + */ + protected function _saveAddressAttributes(array $attributesData) + { + foreach ($attributesData as $tableName => $data) { + $tableData = array(); + + foreach ($data as $addressId => $attrData) { + foreach ($attrData as $attributeId => $value) { + $tableData[] = array( + 'entity_id' => $addressId, + 'entity_type_id' => $this->_entityTypeId, + 'attribute_id' => $attributeId, + 'value' => $value + ); + } + } + $this->_connection->insertMultiple($tableName, $tableData); + } + return $this; + } + + /** + * Update and insert data in entity table. + * + * @param array $entityRows Rows for insert + * @return Mage_ImportExport_Model_Import_Entity_Customer_Address + */ + protected function _saveAddressEntity(array $entityRows) + { + if ($entityRows) { + if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->_customer->getBehavior()) { + $customersToClean = array(); + + foreach ($entityRows as $entityData) { + $customersToClean[$entityData['parent_id']] = true; + } + $this->_connection->delete( + $this->_entityTable, + $this->_connection->quoteInto('`parent_id` IN (?)', array_keys($customersToClean)) + ); + } + $this->_connection->insertMultiple($this->_entityTable, $entityRows); + } + return $this; + } + + /** + * Save customer default addresses. + * + * @param array $defaults + * @return Mage_ImportExport_Model_Import_Entity_Customer_Address + */ + protected function _saveCustomerDefaults(array $defaults) + { + foreach ($defaults as $tableName => $data) { + $tableData = array(); + + foreach ($data as $customerId => $attrData) { + foreach ($attrData as $attributeId => $value) { + $tableData[] = array( + 'entity_id' => $customerId, + 'entity_type_id' => $this->_customer->getEntityTypeId(), + 'attribute_id' => $attributeId, + 'value' => $value + ); + } + } + $this->_connection->insertOnDuplicate($tableName, $tableData, array('value')); + } + return $this; + } + + /** + * Get column name which holds value for attribute with specified code. + * + * @static + * @param string $attrCode + * @return string + */ + public static function getColNameForAttrCode($attrCode) + { + return self::COL_NAME_PREFIX . $attrCode; + } + + /** + * Customer default addresses column name to customer attribute mapping array. + * + * @static + * @return array + */ + public static function getDefaultAddressAttrMapping() + { + return self::$_defaultAddressAttrMapping; + } + + /** + * EAV entity type code getter. + * + * @return string + */ + public function getEntityTypeCode() + { + return 'customer_address'; + } + + /** + * Is attribute contains particular data (not plain entity attribute). + * + * @param string $attrCode + * @return bool + */ + public function isAttributeParticular($attrCode) + { + return isset($this->_attributes[$attrCode]) || in_array($attrCode, $this->_particularAttributes); + } + + /** + * Validate data row. + * + * @param array $rowData + * @param int $rowNum + * @return boolean + */ + public function validateRow(array $rowData, $rowNum) + { + $rowIsValid = true; + + if ($this->_isRowWithAddress($rowData)) { + foreach ($this->_attributes as $colName => $attrParams) { + if (isset($rowData[$colName]) && strlen($rowData[$colName])) { + $rowIsValid &= $this->_customer->isAttributeValid($colName, $attrParams, $rowData, $rowNum); + } elseif ($attrParams['is_required']) { + $this->_customer->addRowError( + Mage_ImportExport_Model_Import_Entity_Customer::ERROR_VALUE_IS_REQUIRED, $rowNum, $colName + ); + $rowIsValid = false; + } + } + // validate region for countries with known region list + if ($rowIsValid) { + $regionColName = self::getColNameForAttrCode('region'); + $countryColName = self::getColNameForAttrCode('country_id'); + $countryRegions = isset($this->_countryRegions[strtolower($rowData[$countryColName])]) + ? $this->_countryRegions[strtolower($rowData[$countryColName])] + : array(); + + if (!empty($rowData[$regionColName]) + && !empty($countryRegions) + && !isset($countryRegions[strtolower($rowData[$regionColName])]) + ) { + $this->_customer->addRowError(self::ERROR_INVALID_REGION, $rowNum); + + $rowIsValid = false; + } + } + } + return $rowIsValid; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php new file mode 100644 index 0000000000..f24a04469b --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php @@ -0,0 +1,1470 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Product extends Mage_ImportExport_Model_Import_Entity_Abstract +{ + const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types'; + + /** + * Size of bunch - part of products to save in one step. + */ + const BUNCH_SIZE = 20; + + /** + * Value that means all entities (e.g. websites, groups etc.) + */ + const VALUE_ALL = 'all'; + + /** + * Data row scopes. + */ + const SCOPE_DEFAULT = 1; + const SCOPE_WEBSITE = 2; + const SCOPE_STORE = 0; + const SCOPE_NULL = -1; + + /** + * Permanent column names. + * + * Names that begins with underscore is not an attribute. This name convention is for + * to avoid interference with same attribute name. + */ + const COL_STORE = '_store'; + const COL_ATTR_SET = '_attribute_set'; + const COL_TYPE = '_type'; + const COL_CATEGORY = '_category'; + const COL_SKU = 'sku'; + + /** + * Error codes. + */ + const ERROR_INVALID_SCOPE = 'invalidScope'; + const ERROR_INVALID_WEBSITE = 'invalidWebsite'; + const ERROR_INVALID_STORE = 'invalidStore'; + const ERROR_INVALID_ATTR_SET = 'invalidAttrSet'; + const ERROR_INVALID_TYPE = 'invalidType'; + const ERROR_INVALID_CATEGORY = 'invalidCategory'; + const ERROR_VALUE_IS_REQUIRED = 'isRequired'; + const ERROR_TYPE_CHANGED = 'typeChanged'; + const ERROR_SKU_IS_EMPTY = 'skuEmpty'; + const ERROR_NO_DEFAULT_ROW = 'noDefaultRow'; + const ERROR_CHANGE_TYPE = 'changeProductType'; + const ERROR_DUPLICATE_SCOPE = 'duplicateScope'; + const ERROR_DUPLICATE_SKU = 'duplicateSKU'; + const ERROR_CHANGE_ATTR_SET = 'changeAttrSet'; + const ERROR_TYPE_UNSUPPORTED = 'productTypeUnsupported'; + const ERROR_ROW_IS_ORPHAN = 'rowIsOrphan'; + const ERROR_INVALID_TIER_PRICE_QTY = 'invalidTierPriceOrQty'; + const ERROR_INVALID_TIER_PRICE_SITE = 'tierPriceWebsiteInvalid'; + const ERROR_INVALID_TIER_PRICE_GROUP = 'tierPriceGroupInvalid'; + const ERROR_TIER_DATA_INCOMPLETE = 'tierPriceDataIsIncomplete'; + const ERROR_SKU_NOT_FOUND_FOR_DELETE = 'skuNotFoundToDelete'; + + /** + * Pairs of attribute set ID-to-name. + * + * @var array + */ + protected $_attrSetIdToName = array(); + + /** + * Pairs of attribute set name-to-ID. + * + * @var array + */ + protected $_attrSetNameToId = array(); + + /** + * Categories text-path to ID hash. + * + * @var array + */ + protected $_categories = array(); + + /** + * Customer groups ID-to-name. + * + * @var array + */ + protected $_customerGroups = array(); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array( + 'status', + 'tax_class_id', + 'visibility', + 'enable_googlecheckout', + 'gift_message_available', + 'custom_design' + ); + + /** + * Links attribute name-to-link type ID. + * + * @var array + */ + protected $_linkNameToId = array( + '_links_related_' => Mage_Catalog_Model_Product_Link::LINK_TYPE_RELATED, + '_links_crosssell_' => Mage_Catalog_Model_Product_Link::LINK_TYPE_CROSSSELL, + '_links_upsell_' => Mage_Catalog_Model_Product_Link::LINK_TYPE_UPSELL + ); + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::ERROR_INVALID_SCOPE => 'Invalid value in Scope column', + self::ERROR_INVALID_WEBSITE => 'Invalid value in Website column (website does not exists?)', + self::ERROR_INVALID_STORE => 'Invalid value in Store column (store does not exists?)', + self::ERROR_INVALID_ATTR_SET => 'Invalid value for Attribute Set column (set does not exists?)', + self::ERROR_INVALID_TYPE => 'Product Type is invalid or not supported', + self::ERROR_INVALID_CATEGORY => 'Category does not exists', + self::ERROR_VALUE_IS_REQUIRED => "Required attribute '%s' has an empty value", + self::ERROR_TYPE_CHANGED => 'Trying to change type of existing products', + self::ERROR_SKU_IS_EMPTY => 'SKU is empty', + self::ERROR_NO_DEFAULT_ROW => 'Default values row does not exists', + self::ERROR_CHANGE_TYPE => 'Product type change is not allowed', + self::ERROR_DUPLICATE_SCOPE => 'Duplicate scope', + self::ERROR_DUPLICATE_SKU => 'Duplicate SKU', + self::ERROR_CHANGE_ATTR_SET => 'Product attribute set change is not allowed', + self::ERROR_TYPE_UNSUPPORTED => 'Product type is not supported', + self::ERROR_ROW_IS_ORPHAN => 'Orphan rows that will be skipped due default row errors', + self::ERROR_INVALID_TIER_PRICE_QTY => 'Tier Price data price or quantity value is invalid', + self::ERROR_INVALID_TIER_PRICE_SITE => 'Tier Price data website is invalid', + self::ERROR_INVALID_TIER_PRICE_GROUP => 'Tier Price customer group ID is invalid', + self::ERROR_TIER_DATA_INCOMPLETE => 'Tier Price data is incomplete', + self::ERROR_SKU_NOT_FOUND_FOR_DELETE => 'Product with specified SKU not found' + ); + + /** + * Dry-runned products information from import file. + * + * [SKU] => array( + * 'type_id' => (string) product type + * 'attr_set_id' => (int) product attribute set ID + * 'entity_id' => (int) product ID (value for new products will be set after entity save) + * 'attr_set_code' => (string) attribute set code + * ) + * + * @var array + */ + protected $_newSku = array(); + + /** + * Existing products SKU-related information in form of array: + * + * [SKU] => array( + * 'type_id' => (string) product type + * 'attr_set_id' => (int) product attribute set ID + * 'entity_id' => (int) product ID + * 'supported_type' => (boolean) is product type supported by current version of import module + * ) + * + * @var array + */ + protected $_oldSku = array(); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array( + '_store', '_attribute_set', '_type', '_category', '_product_websites', '_tier_price_website', + '_tier_price_customer_group', '_tier_price_qty', '_tier_price_price', '_links_related_sku', + '_links_related_position', '_links_crosssell_sku', '_links_crosssell_position', '_links_upsell_sku', + '_links_upsell_position', '_custom_option_store', '_custom_option_type', '_custom_option_title', + '_custom_option_is_required', '_custom_option_price', '_custom_option_sku', '_custom_option_max_characters', + '_custom_option_sort_order', '_custom_option_file_extension', '_custom_option_image_size_x', + '_custom_option_image_size_y', '_custom_option_row_title', '_custom_option_row_price', + '_custom_option_row_sku', '_custom_option_row_sort' + ); + + /** + * Permanent entity columns. + * + * @var array + */ + protected $_permanentAttributes = array(self::COL_SKU); + + /** + * Array of supported product types as keys with appropriate model object as value. + * + * @var array + */ + protected $_productTypeModels = array(); + + /** + * All stores code-ID pairs. + * + * @var array + */ + protected $_storeCodeToId = array(); + + /** + * Store ID to its website stores IDs. + * + * @var array + */ + protected $_storeIdToWebsiteStoreIds = array(); + + /** + * Website code-to-ID + * + * @var array + */ + protected $_websiteCodeToId = array(); + + /** + * Website code to store code-to-ID pairs which it consists. + * + * @var array + */ + protected $_websiteCodeToStoreIds = array(); + + /** + * Constructor. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->_initWebsites() + ->_initStores() + ->_initAttributeSets() + ->_initTypeModels() + ->_initCategories() + ->_initSkus() + ->_initCustomerGroups(); + } + + /** + * Delete products. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _deleteProducts() + { + $productEntityTable = Mage::getModel('importexport/import_proxy_product_resource')->getEntityTable(); + + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $idToDelete = array(); + + foreach ($bunch as $rowNum => $rowData) { + if ($this->validateRow($rowData, $rowNum) && self::SCOPE_DEFAULT == $this->getRowScope($rowData)) { + $idToDelete[] = $this->_oldSku[$rowData[self::COL_SKU]]['entity_id']; + } + } + if ($idToDelete) { + $this->_connection->query( + $this->_connection->quoteInto( + "DELETE FROM `{$productEntityTable}` WHERE `entity_id` IN (?)", $idToDelete + ) + ); + } + } + return $this; + } + + /** + * Create Product entity from raw data. + * + * @throws Exception + * @return bool Result of operation. + */ + protected function _importData() + { + if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) { + $this->_deleteProducts(); + } else { + $this->_saveProducts(); + $this->_saveStockItem(); + $this->_saveLinks(); + $this->_saveCustomOptions(); + foreach ($this->_productTypeModels as $productType => $productTypeModel) { + $productTypeModel->saveData(); + } + } + return true; + } + + /** + * Initialize attribute sets code-to-id pairs. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initAttributeSets() + { + foreach (Mage::getResourceModel('eav/entity_attribute_set_collection') + ->setEntityTypeFilter($this->_entityTypeId) as $attributeSet) { + $this->_attrSetNameToId[$attributeSet->getAttributeSetName()] = $attributeSet->getId(); + $this->_attrSetIdToName[$attributeSet->getId()] = $attributeSet->getAttributeSetName(); + } + return $this; + } + + /** + * Initialize categories text-path to ID hash. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initCategories() + { + $collection = Mage::getResourceModel('catalog/category_collection')->addNameToResult(); + /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection */ + foreach ($collection as $category) { + $structure = explode('/', $category->getPath()); + $pathSize = count($structure); + if ($pathSize > 2) { + $path = array(); + for ($i = 2; $i < $pathSize; $i++) { + $path[] = $collection->getItemById($structure[$i])->getName(); + } + $this->_categories[implode('/', $path)] = $category->getId(); + } + } + return $this; + } + + /** + * Initialize customer groups. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initCustomerGroups() + { + foreach (Mage::getResourceModel('customer/group_collection') as $customerGroup) { + $this->_customerGroups[$customerGroup->getId()] = true; + } + return $this; + } + + /** + * Initialize existent product SKUs. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initSkus() + { + foreach (Mage::getResourceModel('catalog/product_collection') as $product) { + $typeId = $product->getTypeId(); + $sku = $product->getSku(); + $this->_oldSku[$sku] = array( + 'type_id' => $typeId, + 'attr_set_id' => $product->getAttributeSetId(), + 'entity_id' => $product->getId(), + 'supported_type' => isset($this->_productTypeModels[$typeId]) + ); + } + return $this; + } + + /** + * Initialize stores hash. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initStores() + { + foreach (Mage::app()->getStores() as $store) { + $this->_storeCodeToId[$store->getCode()] = $store->getId(); + $this->_storeIdToWebsiteStoreIds[$store->getId()] = $store->getWebsite()->getStoreIds(); + } + return $this; + } + + /** + * Initialize product type models. + * + * @throws Exception + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initTypeModels() + { + $config = Mage::getConfig()->getNode(self::CONFIG_KEY_PRODUCT_TYPES)->asCanonicalArray(); + foreach ($config as $type => $typeModel) { + if (!($model = Mage::getModel($typeModel, array($this, $type)))) { + Mage::throwException("Entity type model '{$typeModel}' is not found"); + } + if (! $model instanceof Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract) { + Mage::throwException(Mage::helper('importexport')->__('Entity type model must be an instance of Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract')); + } + if ($model->isSuitable()) { + $this->_productTypeModels[$type] = $model; + } + $this->_particularAttributes = array_merge( + $this->_particularAttributes, + $model->getParticularAttributes() + ); + } + // remove doubles + $this->_particularAttributes = array_unique($this->_particularAttributes); + + return $this; + } + + /** + * Initialize website values. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _initWebsites() + { + /** @var $website Mage_Core_Model_Website */ + foreach (Mage::app()->getWebsites() as $website) { + $this->_websiteCodeToId[$website->getCode()] = $website->getId(); + $this->_websiteCodeToStoreIds[$website->getCode()] = array_flip($website->getStoreCodes()); + } + return $this; + } + + /** + * Check product category validity. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + protected function _isProductCategoryValid(array $rowData, $rowNum) + { + if (!empty($rowData[self::COL_CATEGORY]) && !isset($this->_categories[$rowData[self::COL_CATEGORY]])) { + $this->addRowError(self::ERROR_INVALID_CATEGORY, $rowNum); + return false; + } + return true; + } + + /** + * Check product website belonging. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + protected function _isProductWebsiteValid(array $rowData, $rowNum) + { + if (!empty($rowData['_product_websites']) && !isset($this->_websiteCodeToId[$rowData['_product_websites']])) { + $this->addRowError(self::ERROR_INVALID_WEBSITE, $rowNum); + return false; + } + return true; + } + + /** + * Set valid attribute set and product type to rows with all scopes + * to ensure that existing products doesn't changed. + * + * @param array $rowData + * @return array + */ + protected function _prepareRowForDb(array $rowData) + { + static $lastSku = null; + + if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) { + return $rowData; + } + if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) { + $lastSku = $rowData[self::COL_SKU]; + } + if (isset($this->_oldSku[$lastSku])) { + $rowData[self::COL_ATTR_SET] = $this->_newSku[$lastSku]['attr_set_code']; + $rowData[self::COL_TYPE] = $this->_newSku[$lastSku]['type_id']; + } + + return $rowData; + } + + /** + * Check tier orice data validity. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + protected function _isTierPriceValid(array $rowData, $rowNum) + { + if ((isset($rowData['_tier_price_website']) && strlen($rowData['_tier_price_website'])) + || (isset($rowData['_tier_price_customer_group']) && strlen($rowData['_tier_price_customer_group'])) + || (isset($rowData['_tier_price_qty']) && strlen($rowData['_tier_price_qty'])) + || (isset($rowData['_tier_price_price']) && strlen($rowData['_tier_price_price'])) + ) { + if (!isset($rowData['_tier_price_website']) || !isset($rowData['_tier_price_customer_group']) + || !isset($rowData['_tier_price_qty']) || !isset($rowData['_tier_price_price']) + || !strlen($rowData['_tier_price_website']) || !strlen($rowData['_tier_price_customer_group']) + || !strlen($rowData['_tier_price_qty']) || !strlen($rowData['_tier_price_price']) + ) { + $this->addRowError(self::ERROR_TIER_DATA_INCOMPLETE, $rowNum); + return false; + } elseif ($rowData['_tier_price_website'] != self::VALUE_ALL + && !isset($this->_websiteCodeToId[$rowData['_tier_price_website']])) { + $this->addRowError(self::ERROR_INVALID_TIER_PRICE_SITE, $rowNum); + return false; + } elseif ($rowData['_tier_price_customer_group'] != self::VALUE_ALL + && !isset($this->_customerGroups[$rowData['_tier_price_customer_group']])) { + $this->addRowError(self::ERROR_INVALID_TIER_PRICE_GROUP, $rowNum); + return false; + } elseif ($rowData['_tier_price_qty'] <= 0 || $rowData['_tier_price_price'] <= 0) { + $this->addRowError(self::ERROR_INVALID_TIER_PRICE_QTY, $rowNum); + return false; + } + } + return true; + } + + /** + * Custom options save. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveCustomOptions() + { + $productTable = Mage::getSingleton('core/resource')->getTableName('catalog/product'); + $optionTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option'); + $priceTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_price'); + $titleTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_title'); + $typePriceTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_type_price'); + $typeTitleTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_type_title'); + $typeValueTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_option_type_value'); + $nextOptionId = $this->getNextAutoincrement($optionTable); + $nextValueId = $this->getNextAutoincrement($typeValueTable); + $priceIsGlobal = Mage::helper('catalog')->isPriceGlobal(); + $type = null; + $typeSpecific = array( + 'date' => array('price', 'sku'), + 'date_time' => array('price', 'sku'), + 'time' => array('price', 'sku'), + 'field' => array('price', 'sku', 'max_characters'), + 'area' => array('price', 'sku', 'max_characters'), + //'file' => array('price', 'sku', 'file_extension', 'image_size_x', 'image_size_y'), + 'drop_down' => true, + 'radio' => true, + 'checkbox' => true, + 'multiple' => true + ); + + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $customOptions = array( + 'product_id' => array(), + $optionTable => array(), + $priceTable => array(), + $titleTable => array(), + $typePriceTable => array(), + $typeTitleTable => array(), + $typeValueTable => array() + ); + + foreach ($bunch as $rowNum => $rowData) { + if (!$this->isRowAllowedToImport($rowData, $rowNum)) { + continue; + } + if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) { + $productId = $this->_newSku[$rowData[self::COL_SKU]]['entity_id']; + } elseif (!isset($productId)) { + continue; + } + if (!empty($rowData['_custom_option_store'])) { + if (!isset($this->_storeCodeToId[$rowData['_custom_option_store']])) { + continue; + } + $storeId = $this->_storeCodeToId[$rowData['_custom_option_store']]; + } else { + $storeId = Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID; + } + if (!empty($rowData['_custom_option_type'])) { // get CO type if its specified + if (!isset($typeSpecific[$rowData['_custom_option_type']])) { + $type = null; + continue; + } + $type = $rowData['_custom_option_type']; + $rowIsMain = true; + } else { + if (null === $type) { + continue; + } + $rowIsMain = false; + } + if (!isset($customOptions['product_id'][$productId])) { // for update product entity table + $customOptions['product_id'][$productId] = array( + 'entity_id' => $productId, + 'has_options' => 0, + 'required_options' => 0, + 'updated_at' => now() + ); + } + if ($rowIsMain) { + $solidParams = array( + 'option_id' => $nextOptionId, + 'sku' => '', + 'max_characters' => 0, + 'file_extension' => null, + 'image_size_x' => 0, + 'image_size_y' => 0, + 'product_id' => $productId, + 'type' => $type, + 'is_require' => empty($rowData['_custom_option_is_required']) ? 0 : 1, + 'sort_order' => empty($rowData['_custom_option_sort_order']) + ? 0 : abs($rowData['_custom_option_sort_order']) + ); + + if (true !== $typeSpecific[$type]) { // simple option may have optional params + $priceTableRow = array( + 'option_id' => $nextOptionId, + 'store_id' => Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID, + 'price' => 0, + 'price_type' => 'fixed' + ); + + foreach ($typeSpecific[$type] as $paramSuffix) { + if (isset($rowData['_custom_option_' . $paramSuffix])) { + $data = $rowData['_custom_option_' . $paramSuffix]; + + if (array_key_exists($paramSuffix, $solidParams)) { + $solidParams[$paramSuffix] = $data; + } elseif ('price' == $paramSuffix) { + if ('%' == substr($data, -1)) { + $priceTableRow['price_type'] = 'percent'; + } + $priceTableRow['price'] = (float) rtrim($data, '%'); + } + } + } + $customOptions[$priceTable][] = $priceTableRow; + } + $customOptions[$optionTable][] = $solidParams; + $customOptions['product_id'][$productId]['has_options'] = 1; + + if (!empty($rowData['_custom_option_is_required'])) { + $customOptions['product_id'][$productId]['required_options'] = 1; + } + $prevOptionId = $nextOptionId++; // increment option id, but preserve value for $typeValueTable + } + if ($typeSpecific[$type] === true && !empty($rowData['_custom_option_row_title']) + && empty($rowData['_custom_option_store'])) { + // complex CO option row + $customOptions[$typeValueTable][$prevOptionId][] = array( + 'option_type_id' => $nextValueId, + 'sort_order' => empty($rowData['_custom_option_row_sort']) + ? 0 : abs($rowData['_custom_option_row_sort']), + 'sku' => !empty($rowData['_custom_option_row_sku']) + ? $rowData['_custom_option_row_sku'] : '' + ); + if (!isset($customOptions[$typeTitleTable][$nextValueId][0])) { // ensure default title is set + $customOptions[$typeTitleTable][$nextValueId][0] = $rowData['_custom_option_row_title']; + } + $customOptions[$typeTitleTable][$nextValueId][$storeId] = $rowData['_custom_option_row_title']; + + if (!empty($rowData['_custom_option_row_price'])) { + $typePriceRow = array( + 'price' => (float) rtrim($rowData['_custom_option_row_price'], '%'), + 'price_type' => 'fixed' + ); + if ('%' == substr($rowData['_custom_option_row_price'], -1)) { + $typePriceRow['price_type'] = 'percent'; + } + if ($priceIsGlobal) { + $customOptions[$typePriceTable][$nextValueId][0] = $typePriceRow; + } else { + // ensure default price is set + if (!isset($customOptions[$typePriceTable][$nextValueId][0])) { + $customOptions[$typePriceTable][$nextValueId][0] = $typePriceRow; + } + $customOptions[$typePriceTable][$nextValueId][$storeId] = $typePriceRow; + } + } + $nextValueId++; + } + if (!empty($rowData['_custom_option_title'])) { + if (!isset($customOptions[$titleTable][$prevOptionId][0])) { // ensure default title is set + $customOptions[$titleTable][$prevOptionId][0] = $rowData['_custom_option_title']; + } + $customOptions[$titleTable][$prevOptionId][$storeId] = $rowData['_custom_option_title']; + } + } + if ($this->getBehavior() != Mage_ImportExport_Model_Import::BEHAVIOR_APPEND) { // remove old data? + $this->_connection->delete( + $optionTable, + $this->_connection->quoteInto('product_id IN (?)', array_keys($customOptions['product_id'])) + ); + } + // if complex options does not contain values - ignore them + foreach ($customOptions[$optionTable] as $key => $optionData) { + if ($typeSpecific[$optionData['type']] === true + && !isset($customOptions[$typeValueTable][$optionData['option_id']])) { + unset($customOptions[$optionTable][$key], $customOptions[$titleTable][$optionData['option_id']]); + } + } + if ($customOptions[$optionTable]) { + $this->_connection->insertMultiple($optionTable, $customOptions[$optionTable]); + } else { + continue; // nothing to save + } + $titleRows = array(); + + foreach ($customOptions[$titleTable] as $optionId => $storeInfo) { + foreach ($storeInfo as $storeId => $title) { + $titleRows[] = array('option_id' => $optionId, 'store_id' => $storeId, 'title' => $title); + } + } + if ($titleRows) { + $this->_connection->insertOnDuplicate($titleTable, $titleRows, array('title')); + } + if ($customOptions[$priceTable]) { + $this->_connection->insertOnDuplicate( + $priceTable, + $customOptions[$priceTable], + array('price', 'price_type') + ); + } + $typeValueRows = array(); + + foreach ($customOptions[$typeValueTable] as $optionId => $optionInfo) { + foreach ($optionInfo as $row) { + $row['option_id'] = $optionId; + $typeValueRows[] = $row; + } + } + if ($typeValueRows) { + $this->_connection->insertMultiple($typeValueTable, $typeValueRows); + } + $optionTypePriceRows = array(); + $optionTypeTitleRows = array(); + + foreach ($customOptions[$typePriceTable] as $optionTypeId => $storesData) { + foreach ($storesData as $storeId => $row) { + $row['option_type_id'] = $optionTypeId; + $row['store_id'] = $storeId; + $optionTypePriceRows[] = $row; + } + } + foreach ($customOptions[$typeTitleTable] as $optionTypeId => $storesData) { + foreach ($storesData as $storeId => $title) { + $optionTypeTitleRows[] = array( + 'option_type_id' => $optionTypeId, + 'store_id' => $storeId, + 'title' => $title + ); + } + } + if ($optionTypePriceRows) { + $this->_connection->insertOnDuplicate( + $typePriceTable, + $optionTypePriceRows, + array('price', 'price_type') + ); + } + if ($optionTypeTitleRows) { + $this->_connection->insertOnDuplicate($typeTitleTable, $optionTypeTitleRows, array('title')); + } + if ($customOptions['product_id']) { // update product entity table to show that product has options + $this->_connection->insertOnDuplicate( + $productTable, + $customOptions['product_id'], + array('has_options', 'required_options', 'updated_at') + ); + } + } + return $this; + } + + /** + * Gather and save information about product links. + * Must be called after ALL products saving done. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveLinks() + { + $resource = Mage::getResourceModel('catalog/product_link'); + $mainTable = $resource->getMainTable(); + $positionAttrId = array(); + $nextLinkId = $this->getNextAutoincrement($mainTable); + + // pre-load 'position' attributes ID for each link type once + foreach ($this->_linkNameToId as $linkName => $linkId) { + $select = $this->_connection->select() + ->from( + $resource->getTable('catalog/product_link_attribute'), + array('id' => 'product_link_attribute_id') + ) + ->where('link_type_id = ? AND product_link_attribute_code = "position"', $linkId); + $positionAttrId[$linkId] = $this->_connection->fetchOne($select); + } + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $productIds = array(); + $linkRows = array(); + $positionRows = array(); + + foreach ($bunch as $rowNum => $rowData) { + if (!$this->isRowAllowedToImport($rowData, $rowNum)) { + continue; + } + if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) { + $sku = $rowData[self::COL_SKU]; + } + foreach ($this->_linkNameToId as $linkName => $linkId) { + if (isset($rowData[$linkName . 'sku'])) { + $productId = $this->_newSku[$sku]['entity_id']; + $productIds[] = $productId; + $linkedSku = $rowData[$linkName . 'sku']; + + if ((isset($this->_newSku[$linkedSku]) || isset($this->_oldSku[$linkedSku])) + && $linkedSku != $sku) { + if (isset($this->_newSku[$linkedSku])) { + $linkedId = $this->_newSku[$linkedSku]['entity_id']; + } else { + $linkedId = $this->_oldSku[$linkedSku]['entity_id']; + } + $linkKey = "{$productId}-{$linkedId}-{$linkId}"; + + if (!isset($linkRows[$linkKey])) { + $linkRows[$linkKey] = array( + 'link_id' => $nextLinkId, + 'product_id' => $productId, + 'linked_product_id' => $linkedId, + 'link_type_id' => $linkId + ); + if (!empty($rowData[$linkName . 'position'])) { + $positionRows[] = array( + 'link_id' => $nextLinkId, + 'product_link_attribute_id' => $positionAttrId[$linkId], + 'value' => $rowData[$linkName . 'position'] + ); + } + $nextLinkId++; + } + } + } + } + } + if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) { + $this->_connection->delete( + $mainTable, + $this->_connection->quoteInto('product_id IN (?)', array_keys($productIds)) + ); + } + if ($linkRows) { + $this->_connection->insertOnDuplicate( + $mainTable, + $linkRows, + array('link_id') + ); + } + if ($positionRows) { // process linked product positions + $this->_connection->insertOnDuplicate( + $resource->getAttributeTypeTable('int'), + $positionRows, + array('value') + ); + } + } + return $this; + } + + /** + * Save product attributes. + * + * @param array $attributesData + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveProductAttributes(array $attributesData) + { + foreach ($attributesData as $tableName => $skuData) { + $tableData = array(); + + foreach ($skuData as $sku => $attributes) { + $productId = $this->_newSku[$sku]['entity_id']; + + foreach ($attributes as $attributeId => $storeValues) { + foreach ($storeValues as $storeId => $storeValue) { + $tableData[] = array( + 'entity_id' => $productId, + 'entity_type_id' => $this->_entityTypeId, + 'attribute_id' => $attributeId, + 'store_id' => $storeId, + 'value' => $storeValue + ); + } + } + } + $this->_connection->insertOnDuplicate($tableName, $tableData, array('value')); + } + return $this; + } + + /** + * Save product categories. + * + * @param array $categoriesData + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveProductCategories(array $categoriesData) + { + static $tableName = null; + + if (!$tableName) { + $tableName = Mage::getModel('importexport/import_proxy_product_resource')->getProductCategoryTable(); + } + if ($categoriesData) { + $categoriesIn = array(); + $delProductId = array(); + + foreach ($categoriesData as $delSku => $categories) { + $productId = $this->_newSku[$delSku]['entity_id']; + $delProductId[] = $productId; + + foreach (array_keys($categories) as $categoryId) { + $categoriesIn[] = array('product_id' => $productId, 'category_id' => $categoryId, 'position' => 1); + } + } + if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior()) { + $this->_connection->delete( + $tableName, + $this->_connection->quoteInto('product_id IN (?)', $delProductId) + ); + } + if ($categoriesIn) { + $this->_connection->insertOnDuplicate($tableName, $categoriesIn, array('position')); + } + } + return $this; + } + + /** + * Update and insert data in entity table. + * + * @param array $entityRowsIn Row for insert + * @param array $entityRowsUp Row for update + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveProductEntity(array $entityRowsIn, array $entityRowsUp) + { + static $entityTable = null; + + if (!$entityTable) { + $entityTable = Mage::getModel('importexport/import_proxy_product_resource')->getEntityTable(); + } + if ($entityRowsUp) { + $this->_connection->insertOnDuplicate( + $entityTable, + $entityRowsUp, + array('updated_at') + ); + } + if ($entityRowsIn) { + $this->_connection->insertMultiple($entityTable, $entityRowsIn); + + $newProducts = $this->_connection->fetchPairs($this->_connection->select() + ->from($entityTable, array('sku', 'entity_id')) + ->where('sku IN (?)', array_keys($entityRowsIn)) + ); + foreach ($newProducts as $sku => $newId) { // fill up entity_id for new products + $this->_newSku[$sku]['entity_id'] = $newId; + } + } + return $this; + } + + /** + * Gather and save information about product entities. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveProducts() + { + /** @var $resource Mage_ImportExport_Model_Import_Proxy_Product_Resource */ + $resource = Mage::getModel('importexport/import_proxy_product_resource'); + $priceIsGlobal = Mage::helper('catalog')->isPriceGlobal(); + $strftimeFormat = Varien_Date::convertZendToStrftime(Varien_Date::DATETIME_INTERNAL_FORMAT, true, true); + $productLimit = null; + $productsQty = null; + + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $entityRowsIn = array(); + $entityRowsUp = array(); + $attributes = array(); + $websites = array(); + $categories = array(); + $tierPrices = array(); + + foreach ($bunch as $rowNum => $rowData) { + if (!$this->validateRow($rowData, $rowNum)) { + continue; + } + $rowScope = $this->getRowScope($rowData); + + if (self::SCOPE_DEFAULT == $rowScope) { + $rowSku = $rowData[self::COL_SKU]; + + // 1. Entity phase + if (isset($this->_oldSku[$rowSku])) { // existing row + $entityRowsUp[] = array( + 'updated_at' => now(), + 'entity_id' => $this->_oldSku[$rowSku]['entity_id'] + ); + } else { // new row + if (!$productLimit || $productsQty < $productLimit) { + $entityRowsIn[$rowSku] = array( + 'entity_type_id' => $this->_entityTypeId, + 'attribute_set_id' => $this->_newSku[$rowSku]['attr_set_id'], + 'type_id' => $this->_newSku[$rowSku]['type_id'], + 'sku' => $rowSku, + 'created_at' => now(), + 'updated_at' => now() + ); + $productsQty++; + } else { + $rowSku = null; // sign for child rows to be skipped + $this->_rowsToSkip[$rowNum] = true; + continue; + } + } + } elseif (null === $rowSku) { + $this->_rowsToSkip[$rowNum] = true; + continue; // skip rows when SKU is NULL + } elseif (self::SCOPE_STORE == $rowScope) { // set necessary data from SCOPE_DEFAULT row + $rowData[self::COL_TYPE] = $this->_newSku[$rowSku]['type_id']; + $rowData['attribute_set_id'] = $this->_newSku[$rowSku]['attr_set_id']; + $rowData[self::COL_ATTR_SET] = $this->_newSku[$rowSku]['attr_set_code']; + } + if (!empty($rowData['_product_websites'])) { // 2. Product-to-Website phase + $websites[$rowSku][$this->_websiteCodeToId[$rowData['_product_websites']]] = true; + } + if (!empty($rowData[self::COL_CATEGORY])) { // 3. Categories phase + $categories[$rowSku][$this->_categories[$rowData[self::COL_CATEGORY]]] = true; + } + if (!empty($rowData['_tier_price_website'])) { // 4. Tier prices phase + $tierPrices[$rowSku][] = array( + 'all_groups' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL, + 'customer_group_id' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL ? + 0 : $rowData['_tier_price_customer_group'], + 'qty' => $rowData['_tier_price_qty'], + 'value' => $rowData['_tier_price_price'], + 'website_id' => self::VALUE_ALL == $rowData['_tier_price_website'] || $priceIsGlobal ? + 0 : $this->_websiteCodeToId[$rowData['_tier_price_website']] + ); + } + // 5. Attributes phase + if (self::SCOPE_NULL == $rowScope) { + continue; // skip attribute processing for SCOPE_NULL rows + } + $rowStore = self::SCOPE_STORE == $rowScope ? $this->_storeCodeToId[$rowData[self::COL_STORE]] : 0; + $rowData = $this->_productTypeModels[$rowData[self::COL_TYPE]]->prepareAttributesForSave($rowData); + $product = Mage::getModel('importexport/import_proxy_product', $rowData); + + foreach ($rowData as $attrCode => $attrValue) { + $attribute = $resource->getAttribute($attrCode); + $attrId = $attribute->getId(); + $backModel = $attribute->getBackendModel(); + $attrTable = $attribute->getBackend()->getTable(); + $storeIds = array(0); + + if ('datetime' == $attribute->getBackendType()) { + $attrValue = gmstrftime($strftimeFormat, strtotime($attrValue)); + } elseif ($backModel) { + $attribute->getBackend()->beforeSave($product); + $attrValue = $product->getData($attribute->getAttributeCode()); + } + if (self::SCOPE_STORE == $rowScope) { + if (self::SCOPE_WEBSITE == $attribute->getIsGlobal()) { + // check website defaults already set + if (!isset($attributes[$attrTable][$rowSku][$attrId][$rowStore])) { + $storeIds = $this->_storeIdToWebsiteStoreIds[$rowStore]; + } + } elseif (self::SCOPE_STORE == $attribute->getIsGlobal()) { + $storeIds = array($rowStore); + } + } + foreach ($storeIds as $storeId) { + $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue; + } + $attribute->setBackendModel($backModel); // restore 'backend_model' to avoid 'default' setting + } + } + $this->_saveProductEntity($entityRowsIn, $entityRowsUp) + ->_saveProductWebsites($websites) + ->_saveProductCategories($categories) + ->_saveProductTierPrices($tierPrices) + ->_saveProductAttributes($attributes); + } + return $this; + } + + /** + * Save product tier prices. + * + * @param array $tierPriceData + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveProductTierPrices(array $tierPriceData) + { + static $tableName = null; + + if (!$tableName) { + $tableName = Mage::getModel('importexport/import_proxy_product_resource') + ->getTable('catalog/product_attribute_tier_price'); + } + if ($tierPriceData) { + $tierPriceIn = array(); + $delProductId = array(); + + foreach ($tierPriceData as $delSku => $tierPriceRows) { + $productId = $this->_newSku[$delSku]['entity_id']; + $delProductId[] = $productId; + + foreach ($tierPriceRows as $row) { + $row['entity_id'] = $productId; + $tierPriceIn[] = $row; + } + } + if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior()) { + $this->_connection->delete( + $tableName, + $this->_connection->quoteInto('entity_id IN (?)', $delProductId) + ); + } + if ($tierPriceIn) { + $this->_connection->insertOnDuplicate($tableName, $tierPriceIn, array('value')); + } + } + return $this; + } + + /** + * Save product websites. + * + * @param array $websiteData + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveProductWebsites(array $websiteData) + { + static $tableName = null; + + if (!$tableName) { + $tableName = Mage::getModel('importexport/import_proxy_product_resource')->getProductWebsiteTable(); + } + if ($websiteData) { + $websitesData = array(); + $delProductId = array(); + + foreach ($websiteData as $delSku => $websites) { + $productId = $this->_newSku[$delSku]['entity_id']; + $delProductId[] = $productId; + + foreach (array_keys($websites) as $websiteId) { + $websitesData[] = array( + 'product_id' => $productId, + 'website_id' => $websiteId + ); + } + } + if (Mage_ImportExport_Model_Import::BEHAVIOR_APPEND != $this->getBehavior()) { + $this->_connection->delete( + $tableName, + $this->_connection->quoteInto('product_id IN (?)', $delProductId) + ); + } + if ($websitesData) { + $this->_connection->insertOnDuplicate($tableName, $websitesData); + } + } + return $this; + } + + /** + * Stock item saving. + * + * @return Mage_ImportExport_Model_Import_Entity_Product + */ + protected function _saveStockItem() + { + $defaultStockData = array( + 'manage_stock' => 1, + 'use_config_manage_stock' => 1, + 'qty' => 0, + 'min_qty' => 0, + 'use_config_min_qty' => 1, + 'min_sale_qty' => 1, + 'use_config_min_sale_qty' => 1, + 'max_sale_qty' => 10000, + 'use_config_max_sale_qty' => 1, + 'is_qty_decimal' => 0, + 'backorders' => 0, + 'use_config_backorders' => 1, + 'notify_stock_qty' => 1, + 'use_config_notify_stock_qty' => 1, + 'enable_qty_increments' => 0, + 'use_config_enable_qty_increments' => 1, + 'qty_increments' => 0, + 'use_config_qty_increments' => 1, + 'is_in_stock' => 0, + 'low_stock_date' => null, + 'stock_status_changed_automatically' => 0 + ); + + $entityTable = Mage::getResourceModel('cataloginventory/stock_item')->getMainTable(); + $helper = Mage::helper('catalogInventory'); + + while ($bunch = $this->_dataSourceModel->getNextBunch()) { + $stockData = array(); + + foreach ($bunch as $rowNum => $rowData) { + if (!$this->isRowAllowedToImport($rowData, $rowNum)) { + continue; + } + // only SCOPE_DEFAULT can contain stock data + if (self::SCOPE_DEFAULT == $this->getRowScope($rowData)) { + $row = array_merge( + $defaultStockData, + array_intersect_key($rowData, $defaultStockData) + ); + $row['product_id'] = $this->_newSku[$rowData[self::COL_SKU]]['entity_id']; + $row['stock_id'] = 1; + /** @var $stockItem Mage_CatalogInventory_Model_Stock_Item */ + $stockItem = Mage::getModel('cataloginventory/stock_item', $row); + + if ($helper->isQty($this->_newSku[$rowData[self::COL_SKU]]['type_id'])) { + if ($stockItem->verifyNotification()) { + $stockItem->setLowStockDate(Mage::app()->getLocale() + ->date(null, null, null, false) + ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT) + ); + } + $stockItem->setStockStatusChangedAutomatically((int) !$stockItem->verifyStock()); + } else { + $stockItem->setQty(0); + } + $stockData[] = $stockItem->getData(); + } + } + if ($stockData) { + $this->_connection->insertOnDuplicate($entityTable, $stockData); + } + } + return $this; + } + + /** + * Atttribute set ID-to-name pairs getter. + * + * @return array + */ + public function getAttrSetIdToName() + { + return $this->_attrSetIdToName; + } + + /** + * DB connection getter. + * + * @return Varien_Db_Adapter_Pdo_Mysql + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * EAV entity type code getter. + * + * @abstract + * @return string + */ + public function getEntityTypeCode() + { + return 'catalog_product'; + } + + /** + * New products SKU data. + * + * @return array + */ + public function getNewSku() + { + return $this->_newSku; + } + + /** + * Get next bunch of validatetd rows. + * + * @return array|null + */ + public function getNextBunch() + { + return $this->_dataSourceModel->getNextBunch(); + } + + /** + * Existing products SKU getter. + * + * @return array + */ + public function getOldSku() + { + return $this->_oldSku; + } + + /** + * Obtain scope of the row from row data. + * + * @param array $rowData + * @return int + */ + public function getRowScope(array $rowData) + { + if (strlen(trim($rowData[self::COL_SKU]))) { + return self::SCOPE_DEFAULT; + } elseif (empty($rowData[self::COL_STORE])) { + return self::SCOPE_NULL; + } else { + return self::SCOPE_STORE; + } + } + + /** + * All website codes to ID getter. + * + * @return array + */ + public function getWebsiteCodes() + { + return $this->_websiteCodeToId; + } + + /** + * Validate data row. + * + * @param array $rowData + * @param int $rowNum + * @return boolean + */ + public function validateRow(array $rowData, $rowNum) + { + static $sku = null; // SKU is remembered through all product rows + + if (isset($this->_validatedRows[$rowNum])) { // check that row is already validated + return !isset($this->_invalidRows[$rowNum]); + } + $this->_validatedRows[$rowNum] = true; + + if (isset($this->_newSku[$rowData[self::COL_SKU]])) { + $this->addRowError(self::ERROR_DUPLICATE_SKU, $rowNum); + return false; + } + $rowScope = $this->getRowScope($rowData); + + // BEHAVIOR_DELETE use specific validation logic + if (Mage_ImportExport_Model_Import::BEHAVIOR_DELETE == $this->getBehavior()) { + if (self::SCOPE_DEFAULT == $rowScope && !isset($this->_oldSku[$rowData[self::COL_SKU]])) { + $this->addRowError(self::ERROR_SKU_NOT_FOUND_FOR_DELETE, $rowNum); + return false; + } + return true; + } + // common validation + $this->_isProductWebsiteValid($rowData, $rowNum); + $this->_isProductCategoryValid($rowData, $rowNum); + $this->_isTierPriceValid($rowData, $rowNum); + + if (self::SCOPE_DEFAULT == $rowScope) { // SKU is specified, row is SCOPE_DEFAULT, new product block begins + $this->_processedEntitiesCount ++; + + $sku = $rowData[self::COL_SKU]; + + if (isset($this->_oldSku[$sku])) { // can we get all necessary data from existant DB product? + // check for supported type of existing product + if (isset($this->_productTypeModels[$this->_oldSku[$sku]['type_id']])) { + $this->_newSku[$sku] = array( + 'entity_id' => $this->_oldSku[$sku]['entity_id'], + 'type_id' => $this->_oldSku[$sku]['type_id'], + 'attr_set_id' => $this->_oldSku[$sku]['attr_set_id'], + 'attr_set_code' => $this->_attrSetIdToName[$this->_oldSku[$sku]['attr_set_id']] + ); + } else { + $this->addRowError(self::ERROR_TYPE_UNSUPPORTED, $rowNum); + $sku = false; // child rows of legacy products with unsupported types are orphans + } + } else { // validate new product type and attribute set + if (!isset($rowData[self::COL_TYPE]) + || !isset($this->_productTypeModels[$rowData[self::COL_TYPE]]) + ) { + $this->addRowError(self::ERROR_INVALID_TYPE, $rowNum); + } elseif (!isset($rowData[self::COL_ATTR_SET]) + || !isset($this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]]) + ) { + $this->addRowError(self::ERROR_INVALID_ATTR_SET, $rowNum); + } elseif (!isset($this->_newSku[$sku])) { + $this->_newSku[$sku] = array( + 'entity_id' => null, + 'type_id' => $rowData[self::COL_TYPE], + 'attr_set_id' => $this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]], + 'attr_set_code' => $rowData[self::COL_ATTR_SET] + ); + } + if (isset($this->_invalidRows[$rowNum])) { + // mark SCOPE_DEFAULT row as invalid for future child rows if product not in DB already + $sku = false; + } + } + } else { + if (null === $sku) { + $this->addRowError(self::ERROR_SKU_IS_EMPTY, $rowNum); + } elseif (false === $sku) { + $this->addRowError(self::ERROR_ROW_IS_ORPHAN, $rowNum); + } elseif (self::SCOPE_STORE == $rowScope && !isset($this->_storeCodeToId[$rowData[self::COL_STORE]])) { + $this->addRowError(self::ERROR_INVALID_STORE, $rowNum); + } + } + if (!isset($this->_invalidRows[$rowNum])) { + // set attribute set code into row data for followed attribute validation in type model + $rowData[self::COL_ATTR_SET] = $this->_newSku[$sku]['attr_set_code']; + + $rowAttributesValid = $this->_productTypeModels[$this->_newSku[$sku]['type_id']]->isRowValid( + $rowData, $rowNum, !isset($this->_oldSku[$sku]) + ); + if (!$rowAttributesValid && self::SCOPE_DEFAULT == $rowScope && !isset($this->_oldSku[$sku])) { + $sku = false; // mark SCOPE_DEFAULT row as invalid for future child rows if product not in DB already + } + } + return !isset($this->_invalidRows[$rowNum]); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Abstract.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Abstract.php new file mode 100644 index 0000000000..c2382064f6 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Abstract.php @@ -0,0 +1,316 @@ + + */ +abstract class Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract +{ + /** + * Product type attribute sets and attributes parameters. + * + * [attr_set_name_1] => array( + * [attr_code_1] => array( + * 'options' => array(), + * 'type' => 'text', 'price', 'textarea', 'select', etc. + * 'id' => .. + * ), + * ... + * ), + * ... + * + * @var array + */ + protected $_attributes = array(); + + /** + * Attributes' codes which will be allowed anyway, independently from its visibility property. + * + * @var array + */ + protected $_forcedAttributesCodes = array(); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array(); + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array(); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array(); + + /** + * Product entity object. + * + * @var Mage_ImportExport_Model_Import_Entity_Product + */ + protected $_entityModel; + + /** + * Product type (simple, configurable, etc.). + * + * @var string + */ + protected $_type; + + /** + * Object constructor. + * + * @param array $params + * @param string $type Product type (simple, configurable, etc.) + * @throws Exception + * @return void + */ + final public function __construct(array $params) + { + if ($this->isSuitable()) { + if (!isset($params[0]) || !isset($params[1]) + || !is_object($params[0]) || !($params[0] instanceof Mage_ImportExport_Model_Import_Entity_Product)) { + Mage::throwException(Mage::helper('importexport')->__('Invalid parameters')); + } + $this->_entityModel = $params[0]; + $this->_type = $params[1]; + + foreach ($this->_messageTemplates as $errorCode => $message) { + $this->_entityModel->addMessageTemplate($errorCode, $message); + } + $this->_initAttributes(); + } + } + + /** + * Add attribute parameters to appropriate attribute set. + * + * @param string $attrSetName Name of attribute set. + * @param array $attrParams Refined attribute parameters. + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + protected function _addAttributeParams($attrSetName, array $attrParams) + { + if (!$attrParams['apply_to'] || in_array($this->_type, $attrParams['apply_to'])) { + $this->_attributes[$attrSetName][$attrParams['code']] = $attrParams; + } + return $this; + } + + /** + * Return product attributes for its attribute set specified in row data. + * + * @param array|string $attrSetData Product row data or simply attribute set name. + * @return array + */ + protected function _getProductAttributes($attrSetData) + { + if (is_array($attrSetData)) { + return $this->_attributes[$attrSetData[Mage_ImportExport_Model_Import_Entity_Product::COL_ATTR_SET]]; + } else { + return $this->_attributes[$attrSetData]; + } + } + + /** + * Initialize attributes parameters for all attributes' sets. + * + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + protected function _initAttributes() + { + // temporary storage for attributes' parameters to avoid double querying inside the loop + $attributesCache = array(); + + foreach (Mage::getResourceModel('eav/entity_attribute_set_collection') + ->setEntityTypeFilter($this->_entityModel->getEntityTypeId()) as $attributeSet) { + foreach (Mage::getResourceModel('catalog/product_attribute_collection') + ->setAttributeSetFilter($attributeSet->getId()) as $attribute) { + + $attributeCode = $attribute->getAttributeCode(); + $attributeId = $attribute->getId(); + + if ($attribute->getIsVisible() || in_array($attributeCode, $this->_forcedAttributesCodes)) { + if (!isset($attributesCache[$attributeId])) { + $attributesCache[$attributeId] = array( + 'id' => $attributeId, + 'code' => $attributeCode, + 'for_configurable' => $attribute->getIsConfigurable(), + 'is_global' => $attribute->getIsGlobal(), + 'is_required' => $attribute->getIsRequired(), + 'frontend_label' => $attribute->getFrontendLabel(), + 'is_static' => $attribute->isStatic(), + 'apply_to' => $attribute->getApplyTo(), + 'type' => Mage_ImportExport_Model_Import::getAttributeType($attribute), + 'default_value' => strlen($attribute->getDefaultValue()) + ? $attribute->getDefaultValue() : null, + 'options' => $this->_entityModel + ->getAttributeOptions($attribute, $this->_indexValueAttributes) + ); + } + $this->_addAttributeParams($attributeSet->getAttributeSetName(), $attributesCache[$attributeId]); + } + } + } + return $this; + } + + /** + * Have we check attribute for is_required? Used as last chance to disable this type of check. + * + * @param string $attrCode + * @return bool + */ + protected function _isAttributeRequiredCheckNeeded($attrCode) + { + return true; + } + + /** + * Validate particular attributes columns. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + protected function _isParticularAttributesValid(array $rowData, $rowNum) + { + return true; + } + + /** + * Check price correction value validity (signed integer or float with or without percentage sign). + * + * @param string $value + * @return int + */ + protected function _isPriceCorr($value) + { + return preg_match('/^-?\d+\.?\d*%?$/', $value); + } + + /** + * Particular attribute names getter. + * + * @return array + */ + public function getParticularAttributes() + { + return $this->_particularAttributes; + } + + /** + * Validate row attributes. Pass VALID row data ONLY as argument. + * + * @param array $rowData + * @param int $rowNum + * @param boolean $checkRequiredAttributes OPTIONAL Flag which can disable validation required values. + * @return boolean + */ + public function isRowValid(array $rowData, $rowNum, $checkRequiredAttributes = true) + { + $error = false; + $rowScope = $this->_entityModel->getRowScope($rowData); + + if (Mage_ImportExport_Model_Import_Entity_Product::SCOPE_NULL != $rowScope) { + foreach ($this->_getProductAttributes($rowData) as $attrCode => $attrParams) { + // check value for non-empty in the case of required attribute? + if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) { + $error |= !$this->_entityModel->isAttributeValid($attrCode, $attrParams, $rowData, $rowNum); + } elseif ( + $this->_isAttributeRequiredCheckNeeded($attrCode) + && $checkRequiredAttributes + && Mage_ImportExport_Model_Import_Entity_Product::SCOPE_DEFAULT == $rowScope + && $attrParams['is_required'] + ) { + $this->_entityModel->addRowError( + Mage_ImportExport_Model_Import_Entity_Product::ERROR_VALUE_IS_REQUIRED, $rowNum, $attrCode + ); + $error = true; + } + } + } + $error |= !$this->_isParticularAttributesValid($rowData, $rowNum); + + return !$error; + } + + /** + * Additional check for model availability. If method returns FALSE - model is not suitable for data processing. + * + * @return bool + */ + public function isSuitable() + { + return true; + } + + /** + * Prepare attributes values for save: remove non-existent, remove empty values, remove static. + * + * @param array $rowData + * @return array + */ + public function prepareAttributesForSave(array $rowData) + { + $resultAttrs = array(); + + foreach ($this->_getProductAttributes($rowData) as $attrCode => $attrParams) { + if (!$attrParams['is_static']) { + if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) { + $resultAttrs[$attrCode] = 'select' == $attrParams['type'] + ? $attrParams['options'][strtolower($rowData[$attrCode])] + : $rowData[$attrCode]; + } elseif (null !== $attrParams['default_value']) { + $resultAttrs[$attrCode] = $attrParams['default_value']; + } + } + } + return $resultAttrs; + } + + /** + * Save product type specific data. + * + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + public function saveData() + { + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Configurable.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Configurable.php new file mode 100644 index 0000000000..9bda2d660b --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Configurable.php @@ -0,0 +1,487 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Product_Type_Configurable + extends Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract +{ + /** + * Error codes. + */ + const ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER = 'attrCodeIsNotSuper'; + const ERROR_INVALID_PRICE_CORRECTION = 'invalidPriceCorr'; + const ERROR_INVALID_OPTION_VALUE = 'invalidOptionValue'; + const ERROR_INVALID_WEBSITE = 'invalidSuperAttrWebsite'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER => 'Attribute with this code is not super', + self::ERROR_INVALID_PRICE_CORRECTION => 'Super attribute price correction value is invalid', + self::ERROR_INVALID_OPTION_VALUE => 'Invalid option value', + self::ERROR_INVALID_WEBSITE => 'Invalid website code for super attribute' + ); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array( + '_super_products_sku', '_super_attribute_code', '_super_attribute_option', + '_super_attribute_price_corr', '_super_attribute_price_website' + ); + + /** + * Reference array of existing product-attribute to product super attribute ID. + * + * product_1 (underscore) attribute_id_1 => product_super_attr_id_1, + * product_1 (underscore) attribute_id_2 => product_super_attr_id_2, + * ..., + * product_n (underscore) attribute_id_n => product_super_attr_id_n + * + * @var array + */ + protected $_productSuperAttrs = array(); + + /** + * Array of SKU to array of super attribute values for all products. + * + * array ( + * attr_set_name_1 => array( + * product_id_1 => array( + * super_attribute_code_1 => attr_value_1, + * ... + * super_attribute_code_n => attr_value_n + * ), + * ... + * ), + * ... + * ) + * + * @var array + */ + protected $_skuSuperAttributeValues = array(); + + /** + * Array of SKU to array of super attributes data for validation new associated products. + * + * array ( + * product_id_1 => array( + * super_attribute_id_1 => array( + * value_index_1 => TRUE, + * ... + * value_index_n => TRUE + * ), + * ... + * ), + * ... + * ) + * + * @var array + */ + protected $_skuSuperData = array(); + + /** + * Super attributes codes in a form of code => TRUE array pairs. + * + * @var array + */ + protected $_superAttributes = array(); + + /** + * All super attributes values combinations for each attribute set. + * + * @var array + */ + protected $_superAttrValuesCombs = null; + + /** + * Add attribute parameters to appropriate attribute set. + * + * @param string $attrParams Name of attribute set. + * @param array $attrParams Refined attribute parameters. + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + protected function _addAttributeParams($attrSetName, array $attrParams) + { + // save super attributes for simplier and quicker search in future + if ('select' == $attrParams['type'] && 1 == $attrParams['is_global'] && $attrParams['for_configurable']) { + $this->_superAttributes[$attrParams['code']] = $attrParams; + } + return parent::_addAttributeParams($attrSetName, $attrParams); + } + + /** + * Get super attribute ID (if it is not possible - return NULL). + * + * @param int $productId + * @param int $attributeId + * @return array|null + */ + protected function _getSuperAttributeId($productId, $attributeId) + { + if (isset($this->_productSuperAttrs["{$productId}_{$attributeId}"])) { + return $this->_productSuperAttrs["{$productId}_{$attributeId}"]; + } else { + return null; + } + } + + /** + * Have we check attribute for is_required? Used as last chance to disable this type of check. + * + * @param string $attrCode + * @return bool + */ + protected function _isAttributeRequiredCheckNeeded($attrCode) + { + return !$this->_isAttributeSuper($attrCode); // do not check super attributes + } + + /** + * Is attribute is super-attribute? + * + * @param string $attrCode + * @return boolean + */ + protected function _isAttributeSuper($attrCode) + { + return isset($this->_superAttributes[$attrCode]); + } + + /** + * Validate particular attributes columns. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + protected function _isParticularAttributesValid(array $rowData, $rowNum) + { + if (!empty($rowData['_super_attribute_code'])) { + $superAttrCode = $rowData['_super_attribute_code']; + + if (!$this->_isAttributeSuper($superAttrCode)) { // check attribute superity + $this->_entityModel->addRowError(self::ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER, $rowNum); + return false; + } elseif (isset($rowData['_super_attribute_option']) && strlen($rowData['_super_attribute_option'])) { + $optionKey = strtolower($rowData['_super_attribute_option']); + if (!isset($this->_superAttributes[$superAttrCode]['options'][$optionKey])) { + $this->_entityModel->addRowError(self::ERROR_INVALID_OPTION_VALUE, $rowNum); + return false; + } + // check price value + if (!empty($rowData['_super_attribute_price_corr']) + && !$this->_isPriceCorr($rowData['_super_attribute_price_corr']) + ) { + $this->_entityModel->addRowError(self::ERROR_INVALID_PRICE_CORRECTION, $rowNum); + return false; + } + } + } + return true; + } + + /** + * Array of SKU to array of super attribute values for all products. + * + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Configurable + */ + protected function _loadSkuSuperAttributeValues() + { + if ($this->_superAttributes) { + $attrSetIdToName = $this->_entityModel->getAttrSetIdToName(); + $allowProductTypes = array(); + + foreach (Mage::getConfig() + ->getNode('global/catalog/product/type/configurable/allow_product_types')->children() as $type) { + $allowProductTypes[] = $type->getName(); + } + foreach (Mage::getResourceModel('catalog/product_collection') + ->addFieldToFilter('type_id', $allowProductTypes) + ->addAttributeToSelect(array_keys($this->_superAttributes)) as $product) { + $attrSetName = $attrSetIdToName[$product->getAttributeSetId()]; + + $data = array_intersect_key( + $product->getData(), + $this->_superAttributes + ); + foreach ($data as $attrCode => $value) { + $attrId = $this->_superAttributes[$attrCode]['id']; + $this->_skuSuperAttributeValues[$attrSetName][$product->getId()][$attrId] = $value; + } + } + } + return $this; + } + + /** + * Array of SKU to array of super attribute values for all products. + * + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Configurable + */ + protected function _loadSkuSuperData() + { + if (!$this->_skuSuperData) { + $connection = $this->_entityModel->getConnection(); + $mainTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_super_attribute'); + $priceTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_super_attribute_pricing'); + $select = $connection->select() + ->from(array('m' => $mainTable), array('product_id', 'attribute_id', 'product_super_attribute_id')) + ->joinLeft( + array('p' => $priceTable), + $connection->quoteIdentifier('p.product_super_attribute_id') . ' = ' + . $connection->quoteIdentifier('m.product_super_attribute_id'), + array('value_index') + ); + + foreach ($connection->fetchAll($select) as $row) { + $attrId = $row['attribute_id']; + $productId = $row['product_id']; + if ($row['value_index']) { + $this->_skuSuperData[$productId][$attrId][$row['value_index']] = true; + } + $this->_productSuperAttrs["{$productId}_{$attrId}"] = $row['product_super_attribute_id']; + } + } + return $this; + } + + /** + * Validate and prepare data about super attributes and associated products. + * + * @param array $superData + * @param array $superAttributes + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Configurable + */ + protected function _processSuperData(array $superData, array &$superAttributes) + { + if ($superData) { + $usedCombs = array(); + // is associated products applicable? + foreach (array_keys($superData['assoc_ids']) as $assocId) { + if (!isset($this->_skuSuperAttributeValues[$superData['attr_set_code']][$assocId])) { + continue; + } + if ($superData['used_attributes']) { + $skuSuperValues = $this->_skuSuperAttributeValues[$superData['attr_set_code']][$assocId]; + $usedCombParts = array(); + + foreach ($superData['used_attributes'] as $usedAttrId => $usedValues) { + if (empty($skuSuperValues[$usedAttrId]) || !isset($usedValues[$skuSuperValues[$usedAttrId]])) { + continue; // invalid value or value does not exists for associated product + } + $usedCombParts[] = $skuSuperValues[$usedAttrId]; + $superData['used_attributes'][$usedAttrId][$skuSuperValues[$usedAttrId]] = true; + } + $comb = implode('|', $usedCombParts); + + if (isset($usedCombs[$comb])) { + continue; // super attributes values combination was already used + } + $usedCombs[$comb] = true; + } + $superAttributes['super_link'][] = array( + 'product_id' => $assocId, 'parent_id' => $superData['product_id'] + ); + $superAttributes['relation'][] = array( + 'parent_id' => $superData['product_id'], 'child_id' => $assocId + ); + } + // clean up unused values pricing + foreach ($superData['used_attributes'] as $usedAttrId => $usedValues) { + foreach ($usedValues as $optionId => $isUsed) { + if (!$isUsed + && isset($superAttributes['pricing'][$superData['product_id']][$usedAttrId]) + ) { + foreach ($superAttributes['pricing'][$superData['product_id']][$usedAttrId] as $k => $params) { + if ($optionId == $params['value_index']) { + unset($superAttributes['pricing'][$superData['product_id']][$usedAttrId][$k]); + } + } + } + } + } + } + return $this; + } + + /** + * Save product type specific data. + * + * @throws Exception + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + public function saveData() + { + $connection = $this->_entityModel->getConnection(); + $mainTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_super_attribute'); + $labelTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_super_attribute_label'); + $priceTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_super_attribute_pricing'); + $linkTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_super_link'); + $relationTable = Mage::getSingleton('core/resource')->getTableName('catalog/product_relation'); + $newSku = $this->_entityModel->getNewSku(); + $oldSku = $this->_entityModel->getOldSku(); + $productSuperData = array(); + $productData = null; + $nextAttrId = $this->_entityModel->getNextAutoincrement($mainTable); + + if ($this->_entityModel->getBehavior() == Mage_ImportExport_Model_Import::BEHAVIOR_APPEND) { + $this->_loadSkuSuperData(); + } + $this->_loadSkuSuperAttributeValues(); + + while ($bunch = $this->_entityModel->getNextBunch()) { + $superAttributes = array( + 'attributes' => array(), + 'labels' => array(), + 'pricing' => array(), + 'super_link' => array(), + 'relation' => array() + ); + foreach ($bunch as $rowNum => $rowData) { + if (!$this->_entityModel->isRowAllowedToImport($rowData, $rowNum)) { + continue; + } + // remember SCOPE_DEFAULT row data + $scope = $this->_entityModel->getRowScope($rowData); + if (Mage_ImportExport_Model_Import_Entity_Product::SCOPE_DEFAULT == $scope) { + $productData = $newSku[$rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_SKU]]; + + if ($this->_type != $productData['type_id']) { + $productData = null; + continue; + } + $productId = $productData['entity_id']; + + $this->_processSuperData($productSuperData, $superAttributes); + + $productSuperData = array( + 'product_id' => $productId, + 'attr_set_code' => $productData['attr_set_code'], + 'used_attributes' => empty($this->_skuSuperData[$productId]) + ? array() : $this->_skuSuperData[$productId], + 'assoc_ids' => array() + ); + } elseif (null === $productData) { + continue; + } + if (!empty($rowData['_super_products_sku'])) { + if (isset($newSku[$rowData['_super_products_sku']])) { + $productSuperData['assoc_ids'][$newSku[$rowData['_super_products_sku']]['entity_id']] = true; + } elseif (isset($oldSku[$rowData['_super_products_sku']])) { + $productSuperData['assoc_ids'][$oldSku[$rowData['_super_products_sku']]['entity_id']] = true; + } + } + if (empty($rowData['_super_attribute_code'])) { + continue; + } + $attrParams = $this->_superAttributes[$rowData['_super_attribute_code']]; + + if ($this->_getSuperAttributeId($productId, $attrParams['id'])) { + $productSuperAttrId = $this->_getSuperAttributeId($productId, $attrParams['id']); + } elseif (!isset($superAttributes['attributes'][$productId][$attrParams['id']])) { + $productSuperAttrId = $nextAttrId++; + $superAttributes['attributes'][$productId][$attrParams['id']] = array( + 'product_super_attribute_id' => $productSuperAttrId, 'position' => 0 + ); + $superAttributes['labels'][] = array( + 'product_super_attribute_id' => $productSuperAttrId, + 'store_id' => 0, + 'use_default' => 1, + 'value' => $attrParams['frontend_label'] + ); + } + if (isset($rowData['_super_attribute_option']) && strlen($rowData['_super_attribute_option'])) { + $optionId = $attrParams['options'][strtolower($rowData['_super_attribute_option'])]; + + if (!isset($productSuperData['used_attributes'][$attrParams['id']][$optionId])) { + $productSuperData['used_attributes'][$attrParams['id']][$optionId] = false; + } + if (!empty($rowData['_super_attribute_price_corr'])) { + $superAttributes['pricing'][] = array( + 'product_super_attribute_id' => $productSuperAttrId, + 'value_index' => $optionId, + 'is_percent' => '%' == substr($rowData['_super_attribute_price_corr'], -1), + 'pricing_value' => (float) rtrim($rowData['_super_attribute_price_corr'], '%'), + 'website_id' => 0 + ); + } + } + } + // save last product super data + $this->_processSuperData($productSuperData, $superAttributes); + + // remove old data if needed + if ($this->_entityModel->getBehavior() != Mage_ImportExport_Model_Import::BEHAVIOR_APPEND + && $superAttributes['attributes']) { + $quoted = $connection->quoteInto('IN (?)', array_keys($superAttributes['attributes'])); + $connection->delete($mainTable, "product_id {$quoted}"); + $connection->delete($linkTable, "parent_id {$quoted}"); + $connection->delete($relationTable, "parent_id {$quoted}"); + } + $mainData = array(); + + foreach ($superAttributes['attributes'] as $productId => $attributesData) { + foreach ($attributesData as $attrId => $row) { + $row['product_id'] = $productId; + $row['attribute_id'] = $attrId; + $mainData[] = $row; + } + } + if ($mainData) { + $connection->insertOnDuplicate($mainTable, $mainData); + } + if ($superAttributes['labels']) { + $connection->insertOnDuplicate($labelTable, $superAttributes['labels']); + } + if ($superAttributes['pricing']) { + $connection->insertOnDuplicate( + $priceTable, + $superAttributes['pricing'], + array('is_percent', 'pricing_value') + ); + } + if ($superAttributes['super_link']) { + $connection->insertOnDuplicate($linkTable, $superAttributes['super_link']); + } + if ($superAttributes['relation']) { + $connection->insertOnDuplicate($relationTable, $superAttributes['relation']); + } + } + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Giftcard.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Giftcard.php new file mode 100644 index 0000000000..7025cf7f96 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Giftcard.php @@ -0,0 +1,183 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Product_Type_Giftcard + extends Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract +{ + /** + * Error codes. + */ + const ERROR_INVALID_GIFTCARD_AMOUNT_WEBSITE = 'invalidGiftcardAmountWebsite'; + const ERROR_INVALID_GIFTCARD_AMOUNT = 'invalidGiftcardAmount'; + + /** + * Attributes' codes which will be allowed anyway, independently from its visibility propertie. + * + * @var array + */ + protected $_forcedAttributesCodes = array( + 'allow_message', 'email_template', 'giftcard_type', 'is_redeemable', 'lifetime', + 'use_config_allow_message', 'use_config_email_template', 'use_config_is_redeemable', 'use_config_lifetime' + ); + + /** + * Attributes with index (not label) value. + * + * @var array + */ + protected $_indexValueAttributes = array('allow_open_amount', 'giftcard_type'); + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::ERROR_INVALID_GIFTCARD_AMOUNT_WEBSITE => 'Invalid value for GiftCard amount website', + self::ERROR_INVALID_GIFTCARD_AMOUNT => 'Invalid GiftCard amount' + ); + + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array('_giftcard_amounts_website', '_giftcard_amounts_amount'); + + /** + * Validate particular attributes columns. + * + * @param array $rowData + * @param int $rowNum + * @return bool + */ + protected function _isParticularAttributesValid(array $rowData, $rowNum) + { + if (!empty($rowData['_giftcard_amounts_website'])) { + $websiteCode = $rowData['_giftcard_amounts_website']; + $websiteCodes = $this->_entityModel->getWebsiteCodes(); + + if (Mage_ImportExport_Model_Import_Entity_Product::VALUE_ALL != $websiteCode + && !isset($websiteCodes[$websiteCode])) { + $this->_entityModel->addRowError(self::ERROR_INVALID_GIFTCARD_AMOUNT_WEBSITE, $rowNum); + return false; + } + if (!isset($rowData['_giftcard_amounts_amount']) || $rowData['_giftcard_amounts_amount'] < 0) { + $this->_entityModel->addRowError(self::ERROR_INVALID_GIFTCARD_AMOUNT, $rowNum); + return false; + } + } + return true; + } + + /** + * Additional check for model availability. If method returns FALSE - model is not suitable for data processing. + * + * @return bool + */ + public function isSuitable() + { + $moduleNode = Mage::getConfig()->getNode('modules/Enterprise_GiftCard/active'); + + return $moduleNode && 'true' == (string) $moduleNode; + } + + /** + * Save product type specific data. + * + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + public function saveData() + { + $connection = Mage::getSingleton('core/resource')->getConnection('write'); + $table = Mage::getSingleton('core/resource')->getTableName('enterprise_giftcard/amount'); + $newSku = $this->_entityModel->getNewSku(); + $entityTypeId = $this->_entityModel->getEntityTypeId(); + $websiteCodes = $this->_entityModel->getWebsiteCodes(); + $priceIsGlobal = Mage::helper('catalog')->isPriceGlobal(); + + while ($bunch = $this->_entityModel->getNextBunch()) { + $amountData = array( + 'product_id' => array(), + 'amounts' => array() + ); + foreach ($bunch as $rowNum => $rowData) { + if (!$this->_entityModel->isRowAllowedToImport($rowData, $rowNum) + || empty($rowData['_giftcard_amounts_website']) + ) { + continue; + } + $scope = $this->_entityModel->getRowScope($rowData); + if (Mage_ImportExport_Model_Import_Entity_Product::SCOPE_DEFAULT == $scope) { + $productData = $newSku[$rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_SKU]]; + } else { + $collAttrSet = Mage_ImportExport_Model_Import_Entity_Product::COL_ATTR_SET; + $rowData[$collAttrSet] = $productData['attr_set_code']; + $rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_TYPE] = $productData['type_id']; + } + $productId = $productData['entity_id']; + + if ($this->_type != $rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_TYPE]) { + continue; + } + $attributes = $this->_getProductAttributes($rowData); + $amountData['product_id'][$productId] = true; + + if (Mage_ImportExport_Model_Import_Entity_Product::VALUE_ALL == $rowData['_giftcard_amounts_website'] + || Mage::app()->isSingleStoreMode() || $priceIsGlobal) { + $websiteId = 0; + } else { + $websiteId = $websiteCodes[$rowData['_giftcard_amounts_website']]; + } + $amountData['amounts'][] = array( + 'website_id' => $websiteId, + 'value' => $rowData['_giftcard_amounts_amount'], + 'entity_id' => $productId, + 'entity_type_id' => $entityTypeId, + 'attribute_id' => $attributes['giftcard_amounts']['id'] + ); + } + // remove old data + if ($amountData['product_id']) { + $connection->delete( + $table, $connection->quoteInto('entity_id IN (?)', array_keys($amountData['product_id'])) + ); + } + // save amounts + if ($amountData['amounts']) { + $connection->insertMultiple($table, $amountData['amounts']); + } + } + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Grouped.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Grouped.php new file mode 100644 index 0000000000..663ea0fc6e --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Grouped.php @@ -0,0 +1,186 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Product_Type_Grouped + extends Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract +{ + /** + * Column names that holds values with particular meaning. + * + * @var array + */ + protected $_particularAttributes = array( + '_associated_sku', '_associated_default_qty', '_associated_position' + ); + + /** + * Save product type specific data. + * + * @return Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract + */ + public function saveData() + { + $groupedLinkId = Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED; + $connection = Mage::getSingleton('core/resource')->getConnection('write'); + $resource = Mage::getResourceModel('catalog/product_link'); + $mainTable = $resource->getMainTable(); + $relationTable = $resource->getTable('catalog/product_relation'); + $newSku = $this->_entityModel->getNewSku(); + $oldSku = $this->_entityModel->getOldSku(); + $attributes = array(); + + // pre-load attributes parameters + $select = $connection->select() + ->from($resource->getTable('catalog/product_link_attribute'), array( + 'id' => 'product_link_attribute_id', + 'code' => 'product_link_attribute_code', + 'type' => 'data_type' + ))->where('link_type_id = ?', $groupedLinkId); + foreach ($connection->fetchAll($select) as $row) { + $attributes[$row['code']] = array( + 'id' => $row['id'], + 'table' => $resource->getAttributeTypeTable($row['type']) + ); + } + while ($bunch = $this->_entityModel->getNextBunch()) { + $linksData = array( + 'product_ids' => array(), + 'links' => array(), + 'attr_product_ids' => array(), + 'position' => array(), + 'qty' => array(), + 'relation' => array() + ); + foreach ($bunch as $rowNum => $rowData) { + if (!$this->_entityModel->isRowAllowedToImport($rowData, $rowNum) + || empty($rowData['_associated_sku']) + ) { + continue; + } + if (isset($newSku[$rowData['_associated_sku']])) { + $linkedProductId = $newSku[$rowData['_associated_sku']]['entity_id']; + } elseif (isset($oldSku[$rowData['_associated_sku']])) { + $linkedProductId = $oldSku[$rowData['_associated_sku']]['entity_id']; + } else { + continue; + } + $scope = $this->_entityModel->getRowScope($rowData); + if (Mage_ImportExport_Model_Import_Entity_Product::SCOPE_DEFAULT == $scope) { + $productData = $newSku[$rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_SKU]]; + } else { + $colAttrSet = Mage_ImportExport_Model_Import_Entity_Product::COL_ATTR_SET; + $rowData[$colAttrSet] = $productData['attr_set_code']; + $rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_TYPE] = $productData['type_id']; + } + $productId = $productData['entity_id']; + + if ($this->_type != $rowData[Mage_ImportExport_Model_Import_Entity_Product::COL_TYPE]) { + continue; + } + $linksData['product_ids'][$productId] = true; + $linksData['links'][$productId][$linkedProductId] = $groupedLinkId; + $linksData['relation'][] = array('parent_id' => $productId, 'child_id' => $linkedProductId); + $qty = empty($rowData['_associated_default_qty']) ? 0 : $rowData['_associated_default_qty']; + $pos = empty($rowData['_associated_position']) ? 0 : $rowData['_associated_position']; + + if ($qty || $pos) { + $linksData['attr_product_ids'][$productId] = true; + if ($pos) { + $linksData['position']["{$productId} {$linkedProductId}"] = array( + 'product_link_attribute_id' => $attributes['position']['id'], + 'value' => $pos + ); + } + if ($qty) { + $linksData['qty']["{$productId} {$linkedProductId}"] = array( + 'product_link_attribute_id' => $attributes['qty']['id'], + 'value' => $qty + ); + } + } + } + // save links and relations + if ($linksData['product_ids']) { + $connection->delete( + $mainTable, + $connection->quoteInto( + 'product_id IN (?) AND link_type_id = ' . $groupedLinkId, + array_keys($linksData['product_ids']) + ) + ); + } + if ($linksData['links']) { + $mainData = array(); + + foreach ($linksData['links'] as $productId => $linkedData) { + foreach ($linkedData as $linkedId => $linkType) { + $mainData[] = array( + 'product_id' => $productId, + 'linked_product_id' => $linkedId, + 'link_type_id' => $linkType + ); + } + } + $connection->insertMultiple($mainTable, $mainData); + $connection->insertOnDuplicate($relationTable, $linksData['relation']); + } + // save positions and default quantity + if ($linksData['attr_product_ids']) { + $savedData = $connection->fetchPairs($connection->select() + ->from($mainTable, array( + new Zend_Db_Expr('CONCAT_WS(" ", product_id, linked_product_id)'), 'link_id' + )) + ->where( + 'product_id IN (?) AND link_type_id = ' . $groupedLinkId, + array_keys($linksData['attr_product_ids']) + ) + ); + foreach ($savedData as $pseudoKey => $linkId) { + if (isset($linksData['position'][$pseudoKey])) { + $linksData['position'][$pseudoKey]['link_id'] = $linkId; + } + if (isset($linksData['qty'][$pseudoKey])) { + $linksData['qty'][$pseudoKey]['link_id'] = $linkId; + } + } + if ($linksData['position']) { + $connection->insertMultiple($attributes['position']['table'], $linksData['position']); + } + if ($linksData['qty']) { + $connection->insertMultiple($attributes['qty']['table'], $linksData['qty']); + } + } + } + return $this; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Simple.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Simple.php new file mode 100644 index 0000000000..321a8a662e --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Type/Simple.php @@ -0,0 +1,46 @@ + + */ +class Mage_ImportExport_Model_Import_Entity_Product_Type_Simple + extends Mage_ImportExport_Model_Import_Entity_Product_Type_Abstract +{ + /** + * Attributes' codes which will be allowed anyway, independently from its visibility property. + * + * @var array + */ + protected $_forcedAttributesCodes = array( + 'related_targetrule_position_behavior', 'related_targetrule_position_limit', + 'upsell_targetrule_position_behavior', 'upsell_targetrule_position_limit' + ); +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Proxy/Product.php b/app/code/core/Mage/ImportExport/Model/Import/Proxy/Product.php new file mode 100644 index 0000000000..801989a744 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Proxy/Product.php @@ -0,0 +1,44 @@ + + */ +class Mage_ImportExport_Model_Import_Proxy_Product extends Mage_Catalog_Model_Product +{ + /** + * DO NOT Initialize resources. + * + * @return void + */ + protected function _construct() + { + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Import/Proxy/Product/Resource.php b/app/code/core/Mage/ImportExport/Model/Import/Proxy/Product/Resource.php new file mode 100644 index 0000000000..c6f129c7c5 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Import/Proxy/Product/Resource.php @@ -0,0 +1,55 @@ + + */ +class Mage_ImportExport_Model_Import_Proxy_Product_Resource extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product +{ + /** + * Product to category table. + * + * @return string + */ + public function getProductCategoryTable() + { + return $this->_productCategoryTable; + } + + /** + * Product to website table. + * + * @return string + */ + public function getProductWebsiteTable() + { + return $this->_productWebsiteTable; + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Mysql4/Import/Data.php b/app/code/core/Mage/ImportExport/Model/Mysql4/Import/Data.php new file mode 100644 index 0000000000..99fe1e01ee --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Mysql4/Import/Data.php @@ -0,0 +1,147 @@ + + */ +class Mage_ImportExport_Model_Mysql4_Import_Data extends Mage_Core_Model_Mysql4_Abstract implements IteratorAggregate +{ + /** + * @var IteratorIterator + */ + protected $_iterator = null; + + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('importexport/importdata', 'id'); + } + + /** + * Retrieve an external iterator + * + * @return IteratorIterator + */ + public function getIterator () + { + $stmt = $this->_getWriteAdapter()->query( + $this->_getWriteAdapter()->select()->from($this->getMainTable(), array('data'))->order('id ASC') + ); + $stmt->setFetchMode(PDO::FETCH_NUM); + + return $stmt->getIterator(); + } + + /** + * Clean all bunches from table. + * + * @return int + */ + public function cleanBunches() + { + return $this->_getWriteAdapter()->query( + 'TRUNCATE ' . $this->_getWriteAdapter()->quoteIdentifier($this->getMainTable()) + ); + } + + /** + * Return behavior from import data table. + * + * @throws Exception + * @return string + */ + public function getBehavior() + { + $behaviors = array_unique($this->_getReadAdapter()->fetchCol( + $this->_getReadAdapter()->select()->from($this->getMainTable(), array('behavior')) + )); + if (count($behaviors) != 1) { + Mage::throwException(Mage::helper('importexport')->__('Error in data structure: behaviors are mixed')); + } + return $behaviors[0]; + } + + /** + * Return entity type code from import data table. + * + * @throws Exception + * @return string + */ + public function getEntityTypeCode() + { + $entityCodes = array_unique($this->_getReadAdapter()->fetchCol( + $this->_getReadAdapter()->select()->from($this->getMainTable(), array('entity')) + )); + if (count($entityCodes) != 1) { + Mage::throwException(Mage::helper('importexport')->__('Error in data structure: entity codes are mixed')); + } + return $entityCodes[0]; + } + + /** + * Get next bunch of validatetd rows. + * + * @return array|null + */ + public function getNextBunch() + { + if (null === $this->_iterator) { + $this->_iterator = $this->getIterator(); + $this->_iterator->rewind(); + } + if ($this->_iterator->valid()) { + $dataRow = $this->_iterator->current(); + $dataRow = unserialize($dataRow[0]); + $this->_iterator->next(); + } else { + $this->_iterator = null; + $dataRow = null; + } + return $dataRow; + } + + /** + * Save import rows bunch. + * + * @param string $entity + * @param string $behavior + * @param array $data + * @return int + */ + public function saveBunch($entity, $behavior, array $data) + { + return $this->_getWriteAdapter()->insert( + $this->getMainTable(), + array('behavior' => $behavior, 'entity' => $entity, 'data' => serialize($data)) + ); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Mysql4/Setup.php b/app/code/core/Mage/ImportExport/Model/Mysql4/Setup.php new file mode 100644 index 0000000000..a5d5885fa6 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Mysql4/Setup.php @@ -0,0 +1,36 @@ + + */ +class Mage_ImportExport_Model_Mysql4_Setup extends Mage_Core_Model_Resource_Setup +{ +} diff --git a/app/code/core/Mage/ImportExport/Model/Source/Export/Entity.php b/app/code/core/Mage/ImportExport/Model/Source/Export/Entity.php new file mode 100644 index 0000000000..75588d448f --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Source/Export/Entity.php @@ -0,0 +1,47 @@ + + */ +class Mage_ImportExport_Model_Source_Export_Entity +{ + /** + * Prepare and return array of export entities ids and their names + * + * @return array + */ + public function toOptionArray() + { + return Mage_ImportExport_Model_Config::getModelsComboOptions( + Mage_ImportExport_Model_Export::CONFIG_KEY_ENTITIES, true + ); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Source/Export/Format.php b/app/code/core/Mage/ImportExport/Model/Source/Export/Format.php new file mode 100644 index 0000000000..b36c93f1ba --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Source/Export/Format.php @@ -0,0 +1,46 @@ + + */ +class Mage_ImportExport_Model_Source_Export_Format +{ + /** + * Prepare and return array of available export file formats. + * + * @return array + */ + public function toOptionArray() + { + $formats = Mage_ImportExport_Model_Export::CONFIG_KEY_FORMATS; + return Mage_ImportExport_Model_Config::getModelsComboOptions($formats); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Source/Import/Behavior.php b/app/code/core/Mage/ImportExport/Model/Source/Import/Behavior.php new file mode 100644 index 0000000000..d268ea47d9 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Source/Import/Behavior.php @@ -0,0 +1,60 @@ + + */ +class Mage_ImportExport_Model_Source_Import_Behavior +{ + /** + * Prepare and return array of import behavior. + * + * @return array + */ + public function toOptionArray() + { + $helper = Mage::helper('importexport'); + + return array( + array( + 'value' => Mage_ImportExport_Model_Import::BEHAVIOR_APPEND, + 'label' => $helper->__('Append Complex Data') + ), + array( + 'value' => Mage_ImportExport_Model_Import::BEHAVIOR_REPLACE, + 'label' => $helper->__('Replace Existing Complex Data') + ), + array( + 'value' => Mage_ImportExport_Model_Import::BEHAVIOR_DELETE, + 'label' => $helper->__('Delete Entities') + ) + ); + } +} diff --git a/app/code/core/Mage/ImportExport/Model/Source/Import/Entity.php b/app/code/core/Mage/ImportExport/Model/Source/Import/Entity.php new file mode 100644 index 0000000000..b9e02c9ef1 --- /dev/null +++ b/app/code/core/Mage/ImportExport/Model/Source/Import/Entity.php @@ -0,0 +1,52 @@ + + */ +class Mage_ImportExport_Model_Source_Import_Entity +{ + /** + * Prepare and return array of import entities ids and their names + * + * @return array + */ + public function toOptionArray() + { + $options = array(); + $entities = Mage_ImportExport_Model_Import::CONFIG_KEY_ENTITIES; + $comboOptions = Mage_ImportExport_Model_Config::getModelsComboOptions($entities); + + foreach ($comboOptions as $option) { + $options[] = $option; + } + return $options; + } +} diff --git a/app/code/core/Mage/ImportExport/controllers/Adminhtml/ExportController.php b/app/code/core/Mage/ImportExport/controllers/Adminhtml/ExportController.php new file mode 100644 index 0000000000..3bb6ad2d3a --- /dev/null +++ b/app/code/core/Mage/ImportExport/controllers/Adminhtml/ExportController.php @@ -0,0 +1,143 @@ + + */ +class Mage_ImportExport_Adminhtml_ExportController extends Mage_Adminhtml_Controller_Action +{ + /** + * Custom constructor. + * + * @return void + */ + protected function _construct() + { + // Define module dependent translate + $this->setUsedModuleName('Mage_ImportExport'); + } + + /** + * Initialize layout. + * + * @return Mage_ImportExport_Adminhtml_ExportController + */ + protected function _initAction() + { + $this->_title($this->__('Import/Export')) + ->loadLayout() + ->_setActiveMenu('system/importexport'); + + return $this; + } + + /** + * Check access (in the ACL) for current user + * + * @return bool + */ + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('system/convert/export'); + } + + /** + * Load data with filter applying and create file for download. + * + * @return void + */ + public function exportAction() + { + if ($this->getRequest()->getPost(Mage_ImportExport_Model_Export::FILTER_ELEMENT_GROUP)) { + try { + /** @var $export Mage_ImportExport_Model_Export */ + $export = Mage::getModel('importexport/export'); + + $export->setData($this->getRequest()->getParams()); + + return $this->_prepareDownloadResponse( + $export->getFileName(), + $export->export(), + $export->getContentType() + ); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + } else { + $this->_getSession()->addError($this->__('No valid data sent')); + } + return $this->_redirect('*/*/index'); + } + + /** + * Get grid of entity attributes and filter action. + * + * @return void + */ + public function getfilterAction() + { + if ($this->getRequest()->isXmlHttpRequest() && ($data = $this->getRequest()->getParams())) { + try { + $this->loadLayout(); + + /** @var $attrFilterBlock Mage_ImportExport_Block_Adminhtml_Export_Filter */ + $attrFilterBlock = $this->getLayout()->getBlock('export.filter'); + /** @var $export Mage_ImportExport_Model_Export */ + $export = Mage::getModel('importexport/export'); + + $export->filterAttributeCollection( + $attrFilterBlock->prepareCollection( + $export->setData($data)->getEntityAttributeCollection() + ) + ); + return $this->renderLayout(); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + } else { + $this->_getSession()->addError($this->__('No valid data sent')); + } + return $this->_redirect('*/*/index'); + } + + /** + * Index action. + * + * @return void + */ + public function indexAction() + { + $this->_initAction() + ->_title($this->__('Export')) + ->_addBreadcrumb($this->__('Export'), $this->__('Export')); + + $this->renderLayout(); + } +} diff --git a/app/code/core/Mage/ImportExport/controllers/Adminhtml/FaqController.php b/app/code/core/Mage/ImportExport/controllers/Adminhtml/FaqController.php new file mode 100644 index 0000000000..99da5dd969 --- /dev/null +++ b/app/code/core/Mage/ImportExport/controllers/Adminhtml/FaqController.php @@ -0,0 +1,61 @@ + + */ +class Mage_ImportExport_Adminhtml_FaqController extends Mage_Adminhtml_Controller_Action +{ + /** + * Index action. + * + * @return void + */ + public function indexAction() + { + $this->_title($this->__('In/Out')) + ->_title($this->__('FAQ')) + ->loadLayout() + ->_setActiveMenu('system/importexport') + ->_addBreadcrumb($this->__('FAQ'), $this->__('FAQ')); + + $this->renderLayout(); + } + + /** + * Check access (in the ACL) for current user + * + * @return bool + */ + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('system/convert/importexport_faq'); + } +} diff --git a/app/code/core/Mage/ImportExport/controllers/Adminhtml/ImportController.php b/app/code/core/Mage/ImportExport/controllers/Adminhtml/ImportController.php new file mode 100644 index 0000000000..b564e2a67b --- /dev/null +++ b/app/code/core/Mage/ImportExport/controllers/Adminhtml/ImportController.php @@ -0,0 +1,206 @@ + + */ +class Mage_ImportExport_Adminhtml_ImportController extends Mage_Adminhtml_Controller_Action +{ + /** + * Custom constructor. + * + * @return void + */ + protected function _construct() + { + // Define module dependent translate + $this->setUsedModuleName('Mage_ImportExport'); + } + + /** + * Initialize layout. + * + * @return Mage_ImportExport_Adminhtml_ImportController + */ + protected function _initAction() + { + $this->_title($this->__('Import/Export')) + ->loadLayout() + ->_setActiveMenu('system/importexport'); + + return $this; + } + + /** + * Check access (in the ACL) for current user. + * + * @return bool + */ + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('system/convert/import'); + } + + /** + * Index action. + * + * @return void + */ + public function indexAction() + { + $maxUploadSize = Mage::helper('importexport')->getMaxUploadSize(); + $this->_getSession()->addNotice( + $this->__('Total size of uploadable files must not exceed %s', $maxUploadSize) + ); + $this->_initAction() + ->_title($this->__('Import')) + ->_addBreadcrumb($this->__('Import'), $this->__('Import')); + + $this->renderLayout(); + } + + /** + * Start import process action. + * + * @return void + */ + public function startAction() + { + if (($data = $this->getRequest()->getPost())) { + $this->loadLayout(false); + + /** @var $resultBlock Mage_ImportExport_Block_Adminhtml_Import_Frame_Result */ + $resultBlock = $this->getLayout()->getBlock('import.frame.result'); + + $importModel = Mage::getModel('importexport/import'); + + try { + $importModel->importSource(); + $importModel->invalidateIndex(); + $resultBlock->addAction('show', 'import_validation_container') + ->addAction('innerHTML', 'import_validation_container_header', $this->__('Status')); + } catch (Exception $e) { + $resultBlock->addError($e->getMessage()); + $this->renderLayout(); + return; + } + $resultBlock->addAction('hide', array('edit_form', 'upload_button', 'messages')) + ->addSuccess($this->__('Import successfully done.')); + $this->renderLayout(); + } else { + return $this->_redirect('*/*/index'); + } + } + + /** + * Validate uploaded files action. + * + * @return void + */ + public function validateAction() + { + if (($data = $this->getRequest()->getPost())) { + $this->loadLayout(false); + /** @var $resultBlock Mage_ImportExport_Block_Adminhtml_Import_Frame_Result */ + $resultBlock = $this->getLayout()->getBlock('import.frame.result'); + // common actions + $resultBlock->addAction('show', 'import_validation_container') + ->addAction('clear', array( + Mage_ImportExport_Model_Import::FIELD_NAME_SOURCE_FILE, + Mage_ImportExport_Model_Import::FIELD_NAME_IMG_ARCHIVE_FILE) + ); + + try { + /** @var $import Mage_ImportExport_Model_Import */ + $import = Mage::getModel('importexport/import'); + $validationResult = $import->validateSource($import->setData($data)->uploadSource()); + + if (!$import->getProcessedRowsCount()) { + $resultBlock->addError($this->__('File does not contain data. Please upload another one')); + } else { + if (!$validationResult) { + if ($import->getProcessedRowsCount() == $import->getInvalidRowsCount()) { + $resultBlock->addNotice( + $this->__('File is totally invalid. Please fix errors and re-upload file') + ); + } elseif ($import->getErrorsCount() >= $import->getErrorsLimit()) { + $resultBlock->addNotice( + $this->__( + 'Errors limit (%d) reached. Please fix errors and re-upload file', + $import->getErrorsLimit() + ) + ); + } else { + if ($import->isImportAllowed()) { + $resultBlock->addNotice( + $this->__('Please fix errors and re-upload file or simply press "Import" button to skip rows with errors'), + true + ); + } else { + $resultBlock->addNotice( + $this->__('File is partially valid, but import is not possible'), false + ); + } + } + // errors info + foreach ($import->getErrors() as $errorCode => $rows) { + $resultBlock->addError($errorCode . $this->__(' in rows: ') . implode(', ', $rows)); + } + } else { + if ($import->isImportAllowed()) { + $resultBlock->addSuccess( + $this->__('File is valid! To start import process press "Import" button'), true + ); + } else { + $resultBlock->addError( + $this->__('File is valid, but import is not possible'), false + ); + } + } + $resultBlock->addNotice($import->getNotices()); + $resultBlock->addNotice( + $this->__( + 'Checked rows: %d, checked entities: %d, invalid rows: %d, total errors: %d', + $import->getProcessedRowsCount(), $import->getProcessedEntitiesCount(), + $import->getInvalidRowsCount(), $import->getErrorsCount() + ) + ); + } + } catch (Exception $e) { + $resultBlock->addNotice($this->__('Please fix errors and re-upload file')) + ->addError($e->getMessage()); + } + $this->renderLayout(); + } else { + $this->_getSession()->addError($this->__('Data is invalid or file is not uploaded')); + return $this->_redirect('*/*/index'); + } + } +} diff --git a/app/code/core/Mage/ImportExport/etc/adminhtml.xml b/app/code/core/Mage/ImportExport/etc/adminhtml.xml new file mode 100644 index 0000000000..30a4a0335d --- /dev/null +++ b/app/code/core/Mage/ImportExport/etc/adminhtml.xml @@ -0,0 +1,78 @@ + + + + + + + + + + Import + adminhtml/import + 10 + + + Export + adminhtml/export + 20 + + + + + + + + + + + + + + + + + Import + 10 + + + Export + 20 + + + + + + + + + + diff --git a/app/code/core/Mage/ImportExport/etc/config.xml b/app/code/core/Mage/ImportExport/etc/config.xml new file mode 100644 index 0000000000..dc3a1720a1 --- /dev/null +++ b/app/code/core/Mage/ImportExport/etc/config.xml @@ -0,0 +1,140 @@ + + + + + + 0.1.0 + + + + + + Mage_ImportExport_Model + importexport_mysql4 + + + Mage_ImportExport_Model_Mysql4 + + + importexport_importdata
    +
    +
    +
    +
    + + + Mage_ImportExport_Block + + + + + Mage_ImportExport_Helper + + + + + + Mage_ImportExport + Mage_ImportExport_Model_Mysql4_Setup + + + + + + + importexport/import_entity_product + + + + importexport/import_entity_customer + + + + + + + importexport/export_entity_product + + + + importexport/export_entity_customer + + + + + + + importexport/export_adapter_csv + + + + + + importexport/import_entity_product_type_simple + importexport/import_entity_product_type_configurable + importexport/import_entity_product_type_simple + importexport/import_entity_product_type_grouped + + + importexport/export_entity_product_type_simple + importexport/export_entity_product_type_configurable + importexport/export_entity_product_type_simple + importexport/export_entity_product_type_grouped + + +
    + + + + + + Mage_ImportExport_Adminhtml + + + + + + + + + + importexport.xml + + + + +
    diff --git a/app/code/core/Mage/ImportExport/sql/importexport_setup/mysql4-install-0.1.0.php b/app/code/core/Mage/ImportExport/sql/importexport_setup/mysql4-install-0.1.0.php new file mode 100644 index 0000000000..e1ce5b41f9 --- /dev/null +++ b/app/code/core/Mage/ImportExport/sql/importexport_setup/mysql4-install-0.1.0.php @@ -0,0 +1,94 @@ +startSetup(); + +$installer->run(" +CREATE TABLE IF NOT EXISTS `{$installer->getTable('importexport_importdata')}` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `entity` VARCHAR(50) NOT NULL, + `behavior` SET('" . Mage_ImportExport_Model_Import::BEHAVIOR_APPEND. "','" + . Mage_ImportExport_Model_Import::BEHAVIOR_REPLACE . "','" . + Mage_ImportExport_Model_Import::BEHAVIOR_DELETE . "') NOT NULL DEFAULT '" . + Mage_ImportExport_Model_Import::BEHAVIOR_APPEND . "', + `data` MEDIUMTEXT NOT NULL DEFAULT '', + PRIMARY KEY (`id`) +) +COLLATE='utf8_general_ci' +ENGINE=MyISAM +ROW_FORMAT=DEFAULT +"); + +// add unique key for parent-child pairs which makes easier configurable products import +$installer->getConnection()->addKey( + $installer->getTable('catalog/product_super_link'), + 'UNQ_product_id_parent_id', + array('product_id', 'parent_id'), + 'unique' +); + +// add unique key for product-attribute pairs +$installer->getConnection()->addKey( + $installer->getTable('catalog/product_super_attribute'), + 'UNQ_product_id_attribute_id', + array('product_id', 'attribute_id'), + 'unique' +); + +// add unique key for product-value-website +$installer->getConnection()->addKey( + $installer->getTable('catalog/product_super_attribute_pricing'), + 'UNQ_product_super_attribute_id_value_index_website_id', + array('product_super_attribute_id', 'value_index', 'website_id'), + 'unique' +); + +$installer->getConnection()->addConstraint( + 'FK_INT_PRODUCT_LINK', + $installer->getTable('catalog/product_link_attribute_int'), + 'link_id', + $installer->getTable('catalog/product_link'), + 'link_id' +); + +$installer->getConnection()->addConstraint( + 'FK_INT_PRODUCT_LINK_ATTRIBUTE', + $installer->getTable('catalog/product_link_attribute_int'), + 'product_link_attribute_id', + $installer->getTable('catalog/product_link_attribute'), + 'product_link_attribute_id' +); + +$installer->getConnection()->addKey( + $installer->getTable('catalog/product_link_attribute_int'), + 'UNQ_product_link_attribute_id_link_id', + array('product_link_attribute_id', 'link_id'), + 'unique' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Index/Model/Mysql4/Abstract.php b/app/code/core/Mage/Index/Model/Mysql4/Abstract.php index 205d39902d..9a5b0e2b3c 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Abstract.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Abstract.php @@ -39,6 +39,13 @@ abstract class Mage_Index_Model_Mysql4_Abstract extends Mage_Core_Model_Mysql4_A */ protected $_isNeedUseIdxTable = false; + /** + * Flag that defines if need to disable keys during data inserting + * + * @var bool + */ + protected $_isDisableKeys = true; + public function reindexAll() { $this->useIdxTable(true); @@ -153,7 +160,11 @@ public function insertFromSelect($select, $destTable, array $columns, $readToInd $from = $this->_getIndexAdapter(); $to = $this->_getWriteAdapter(); } - $to->query("ALTER TABLE {$destTable} DISABLE KEYS"); + + if ($this->useDisableKeys()) { + $to->query("ALTER TABLE {$destTable} DISABLE KEYS"); + } + if ($from === $to) { $sql = 'INSERT INTO ' . $destTable . ' ' . $select; $to->query($sql); @@ -174,7 +185,11 @@ public function insertFromSelect($select, $destTable, array $columns, $readToInd $to->insertArray($destTable, $columns, $data); } } - $to->query("ALTER TABLE {$destTable} ENABLE KEYS"); + + if ($this->useDisableKeys()) { + $to->query("ALTER TABLE {$destTable} ENABLE KEYS"); + } + return $this; } @@ -192,6 +207,21 @@ public function useIdxTable($value = null) return $this->_isNeedUseIdxTable; } + /** + * Set or get flag that defines if need to disable keys during data inserting + * + * @param bool $value + * @return Mage_Index_Model_Mysql4_Abstract + */ + public function useDisableKeys($value = null) + { + if (!is_null($value)) { + $this->_isDisableKeys = (bool)$value; + } + return $this->_isDisableKeys; + } + + /** * Clean up temporary index table */ diff --git a/app/code/core/Mage/Newsletter/Model/Subscriber.php b/app/code/core/Mage/Newsletter/Model/Subscriber.php index b50b147472..926ba2ce2a 100644 --- a/app/code/core/Mage/Newsletter/Model/Subscriber.php +++ b/app/code/core/Mage/Newsletter/Model/Subscriber.php @@ -372,12 +372,18 @@ public function subscribeCustomer($customer) $confirmation = $customer->getConfirmation(); } - $subscribed_on_confirm = false; - if($customer->hasIsSubscribed()) { + $sendInformationEmail = false; + if ($customer->hasIsSubscribed()) { $status = $customer->getIsSubscribed() ? (!is_null($confirmation) ? self::STATUS_UNCONFIRMED : self::STATUS_SUBSCRIBED) : self::STATUS_UNSUBSCRIBED; + /** + * If subscription status has been changed then send email to the customer + */ + if ($status != self::STATUS_UNCONFIRMED && $status != $this->getStatus()) { + $sendInformationEmail = true; + } } elseif (($this->getStatus() == self::STATUS_UNCONFIRMED) && (is_null($confirmation))) { $status = self::STATUS_SUBSCRIBED; - $subscribed_on_confirm = true; + $sendInformationEmail = true; } else { $status = ($this->getStatus() == self::STATUS_NOT_ACTIVE ? self::STATUS_UNSUBSCRIBED : $this->getStatus()); } @@ -397,7 +403,7 @@ public function subscribeCustomer($customer) } $this->save(); - $sendSubscription = $customer->getData('sendSubscription') || $subscribed_on_confirm; + $sendSubscription = $customer->getData('sendSubscription') || $sendInformationEmail; if (is_null($sendSubscription) xor $sendSubscription) { if ($this->getIsStatusChanged() && $status == self::STATUS_UNSUBSCRIBED) { $this->sendUnsubscriptionEmail(); diff --git a/app/code/core/Mage/Page/Block/Html/Head.php b/app/code/core/Mage/Page/Block/Html/Head.php index 3a0c7371c5..140631a739 100644 --- a/app/code/core/Mage/Page/Block/Html/Head.php +++ b/app/code/core/Mage/Page/Block/Html/Head.php @@ -492,11 +492,24 @@ protected function _getFaviconFile() $faviconFile = Mage::getBaseUrl('media') . $folderName . '/' . $storeConfig; $absolutePath = Mage::getBaseDir('media') . '/' . $folderName . '/' . $storeConfig; - if(!is_null($storeConfig) && is_file($absolutePath)) { + if(!is_null($storeConfig) && $this->_isFile($absolutePath)) { $url = $faviconFile; } else { $url = $this->getSkinUrl('favicon.ico'); } return $url; } + + /** + * If DB file storage is on - find there, otherwise - just file_exists + * + * @param string $filename + * @return bool + */ + protected function _isFile($filename) { + if (Mage::helper('core/file_storage_database')->checkDbUsage() && !is_file($filename)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($filename); + } + return is_file($filename); + } } diff --git a/app/code/core/Mage/Paygate/Block/Authorizenet/Form/Cc.php b/app/code/core/Mage/Paygate/Block/Authorizenet/Form/Cc.php new file mode 100644 index 0000000000..8eddeed5ff --- /dev/null +++ b/app/code/core/Mage/Paygate/Block/Authorizenet/Form/Cc.php @@ -0,0 +1,178 @@ +setTemplate('paygate/form/cc.phtml'); + } + + /** + * Retreive payment method form html + * + * @return string + */ + public function getMethodFormBlock() + { + return $this->getLayout()->createBlock('payment/form_cc') + ->setMethod($this->getMethod()); + } + + /** + * Cards info block + * + * @return string + */ + public function getCardsBlock() + { + return $this->getLayout()->createBlock('paygate/authorizenet_info_cc') + ->setMethod($this->getMethod()) + ->setInfo($this->getMethod()->getInfoInstance()) + ->setCheckoutProgressBlock(false) + ->setHideTitle(true); + } + + /** + * Return url to cancel controller + * + * @return string + */ + public function getCancelUrl() + { + return $this->getUrl('paygate/authorizenet_payment/cancel', array('_secure' => true)); + } + + /** + * Return url to admin cancel controller from admin url model + * + * @return string + */ + public function getAdminCancelUrl() + { + return Mage::getModel('adminhtml/url')->getUrl('adminhtml/paygate_authorizenet_payment/cancel'); + } + + /** + * Render block HTML + * + * @return string + */ + protected function _toHtml() + { + $this->setChild('cards', $this->getCardsBlock()); + $this->setChild('method_form_block', $this->getMethodFormBlock()); + return parent::_toHtml(); + } + + /** + * Get notice message + * + * @return string + */ + public function showNoticeMessage($message) + { + return $this->getLayout()->getMessagesBlock() + ->addNotice($this->__($message)) + ->getGroupedHtml(); + } + + /** + * Return partial authorization confirmation message and unset it in payment model + * + * @return string + */ + public function getPartialAuthorizationConfirmationMessage() + { + $lastActionState = $this->getMethod()->getPartialAuthorizationLastActionState(); + if ($lastActionState == Mage_Paygate_Model_Authorizenet::PARTIAL_AUTH_LAST_SUCCESS) { + $this->getMethod()->unsetPartialAuthorizationLastActionState(); + return Mage::helper('paygate')->__('The amount on your credit card is insufficient to complete your purchase. The available amount has been put on hold. To complete your purchase click OK and specify additional credit card number. To cancel the purchase and release the amount on hold, click Cancel.'); + } elseif ($lastActionState == Mage_Paygate_Model_Authorizenet::PARTIAL_AUTH_LAST_DECLINED) { + $this->getMethod()->unsetPartialAuthorizationLastActionState(); + return Mage::helper('paygate')->__('Your credit card has been declined. Click OK to specify another credit card to complete your purchase. Click Cancel to release the amount on hold and select another payment method.'); + } + return false;; + } + + /** + * Return partial authorization form message and unset it in payment model + * + * @return string + */ + public function getPartialAuthorizationFormMessage() + { + $lastActionState = $this->getMethod()->getPartialAuthorizationLastActionState(); + if ($lastActionState == Mage_Paygate_Model_Authorizenet::PARTIAL_AUTH_ALL_CANCELED) { + $this->getMethod()->unsetPartialAuthorizationLastActionState(); + return Mage::helper('paygate')->__('Your payment has been cancelled. All authorized amounts have been released.'); + } elseif ($lastActionState == Mage_Paygate_Model_Authorizenet::PARTIAL_AUTH_CARDS_LIMIT_EXCEEDED) { + $this->getMethod()->unsetPartialAuthorizationLastActionState(); + return Mage::helper('paygate')->__('You have reached the maximum number of credit cards that can be used for one payment. The available amounts on all used cards were insufficient to complete payment. The payment has been cancelled and amounts on hold have been released.'); + } + return false;; + } + + /** + * Return cancel confirmation message + * + * @return string + */ + public function getCancelConfirmationMessage() + { + return $this->__('Are you sure you want to cancel your payment? Click OK to cancel your payment and release the amount on hold. Click Cancel to enter another credit card and continue with your payment.'); + } + + /** + * Return flag - is partial authorization process started + * + * @return string + */ + public function isPartialAuthorization() + { + return $this->getMethod()->isPartialAuthorization(); + } + + /** + * Return HTML content for creating admin panel`s button + * + * @return string + */ + public function getCancelButtonHtml() + { + $cancelButton = $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'id' => 'payment_cancel', + 'label' => Mage::helper('paygate')->__('Cancel'), + 'onclick' => 'cancelPaymentAuthorizations()' + )); + return $cancelButton->toHtml(); + } +} diff --git a/app/code/core/Mage/Paygate/Block/Authorizenet/Info/Cc.php b/app/code/core/Mage/Paygate/Block/Authorizenet/Info/Cc.php new file mode 100644 index 0000000000..fc2fce2a72 --- /dev/null +++ b/app/code/core/Mage/Paygate/Block/Authorizenet/Info/Cc.php @@ -0,0 +1,114 @@ +setTemplate('paygate/info/cc.phtml'); + } + + /** + * Render as PDF + * + * @return string + */ + public function toPdf() + { + $this->setTemplate('paygate/info/pdf.phtml'); + return $this->toHtml(); + } + + /** + * Retrieve card info object + * + * @return mixed + */ + public function getInfo() + { + if ($this->hasCardInfoObject()) { + return $this->getCardInfoObject(); + } + return parent::getInfo(); + } + + /** + * Set checkout progress information block flag + * to avoid showing credit card information from payment quote + * in Previously used card information block + * + * @param bool $flag + * @return Mage_Paygate_Block_Authorizenet_Info_Cc + */ + public function setCheckoutProgressBlock($flag) + { + $this->_isCheckoutProgressBlockFlag = $flag; + return $this; + } + + /** + * Retrieve credit cards info + * + * @return array + */ + public function getCards() + { + $cardsData = $this->getMethod()->getCardsStorage()->getCards(); + $cards = array(); + + if (is_array($cardsData)) { + foreach ($cardsData as $cardInfo) { + $data = array(); + if ($cardInfo->getProcessedAmount()) { + $amount = Mage::helper('core')->currency($cardInfo->getProcessedAmount(), true, false); + $data[Mage::helper('paygate')->__('Processed Amount')] = $amount; + } + if ($cardInfo->getBalanceOnCard() && is_numeric($cardInfo->getBalanceOnCard())) { + $balance = Mage::helper('core')->currency($cardInfo->getBalanceOnCard(), true, false); + $data[Mage::helper('paygate')->__('Remaining Balance')] = $balance; + } + $this->setCardInfoObject($cardInfo); + $cards[] = array_merge($this->getSpecificInformation(), $data); + $this->unsCardInfoObject(); + $this->_paymentSpecificInformation = null; + } + } + if ($this->getInfo()->getCcType() && $this->_isCheckoutProgressBlockFlag) { + $cards[] = $this->getSpecificInformation(); + } + return $cards; + } +} diff --git a/app/code/core/Mage/Paygate/Helper/Data.php b/app/code/core/Mage/Paygate/Helper/Data.php index 9f50394829..c376901d5b 100644 --- a/app/code/core/Mage/Paygate/Helper/Data.php +++ b/app/code/core/Mage/Paygate/Helper/Data.php @@ -29,5 +29,84 @@ */ class Mage_Paygate_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * Converts a lot of messages to message + * + * @param array $messages + * @return string + */ + public function convertMessagesToMessage($messages) + { + return implode(' | ', $messages); + } + /** + * Return message for gateway transaction request + * + * @param Mage_Payment_Model_Info $payment + * @param string $requestType + * @param string $lastTransactionId + * @param Varien_Object $card + * @param float $amount + * @param string $exception + * @return bool|string + */ + public function getTransactionMessage($payment, $requestType, $lastTransactionId, $card, $amount = false, $exception = false) + { + $operation = $this->_getOperation($requestType); + + if (!$operation) { + return false; + } + + if ($amount) { + $amount = $this->__('amount %s', $this->_formatPrice($payment, $amount)); + } + + if ($exception) { + $result = $this->__('failed'); + } else { + $result = $this->__('successful'); + } + + $card = $this->__('Credit Card: xxxx-%s', $card->getCcLast4()); + $transaction = $this->__('Authorize.Net Transaction ID %s', $lastTransactionId); + + return $this->__('%s %s %s - %s. %s. %s', $card, $amount, $operation, $result, $transaction, $exception ); + } + + /** + * Return operation name for request type + * + * @param string $requestType + * @return bool|string + */ + protected function _getOperation($requestType) + { + switch ($requestType) { + case Mage_Paygate_Model_Authorizenet::REQUEST_TYPE_AUTH_ONLY: + return $this->__('authorize'); + case Mage_Paygate_Model_Authorizenet::REQUEST_TYPE_AUTH_CAPTURE: + return $this->__('authorize and capture'); + case Mage_Paygate_Model_Authorizenet::REQUEST_TYPE_PRIOR_AUTH_CAPTURE: + return $this->__('capture'); + case Mage_Paygate_Model_Authorizenet::REQUEST_TYPE_CREDIT: + return $this->__('refund'); + case Mage_Paygate_Model_Authorizenet::REQUEST_TYPE_VOID: + return $this->__('void'); + default: + return false; + } + } + + /** + * Format price with currency sign + * @param Mage_Payment_Model_Info $payment + * @param float $amount + * @return string + */ + protected function _formatPrice($payment, $amount) + { + return $payment->getOrder()->getBaseCurrency()->formatTxt($amount); + } } diff --git a/app/code/core/Mage/Paygate/Model/Authorizenet.php b/app/code/core/Mage/Paygate/Model/Authorizenet.php index 00a98b9961..fedb3a4f30 100644 --- a/app/code/core/Mage/Paygate/Model/Authorizenet.php +++ b/app/code/core/Mage/Paygate/Model/Authorizenet.php @@ -55,7 +55,28 @@ class Mage_Paygate_Model_Authorizenet extends Mage_Payment_Model_Method_Cc const RESPONSE_CODE_ERROR = 3; const RESPONSE_CODE_HELD = 4; - protected $_code = 'authorizenet'; + const RESPONSE_REASON_CODE_PARTIAL_APPROVE = 295; + + const PARTIAL_AUTH_CARDS_LIMIT = 5; + + const PARTIAL_AUTH_LAST_SUCCESS = 'success'; + const PARTIAL_AUTH_LAST_DECLINED = 'declined'; + const PARTIAL_AUTH_ALL_CANCELED = 'canceled'; + const PARTIAL_AUTH_CARDS_LIMIT_EXCEEDED = 'exceeded'; + + const METHOD_CODE = 'authorizenet'; + + protected $_code = self::METHOD_CODE; + + /** + * Form block type + */ + protected $_formBlockType = 'paygate/authorizenet_form_cc'; + + /** + * Info block type + */ + protected $_infoBlockType = 'paygate/authorizenet_info_cc'; /** * Availability options @@ -86,6 +107,40 @@ class Mage_Paygate_Model_Authorizenet extends Mage_Payment_Model_Method_Cc 'x_bank_acct_type','x_bank_acct_name', 'x_echeck_type'); + /** + * Key for storing transaction id in additional information of payment model + * @var string + */ + protected $_realTransactionIdKey = 'real_transaction_id'; + + /** + * Key for storing split tender id in additional information of payment model + * @var string + */ + protected $_splitTenderIdKey = 'split_tender_id'; + + /** + * Key for storing locking gateway actions flag in additional information of payment model + * @var string + */ + protected $_isGatewayActionsLockedKey = 'is_gateway_actions_locked'; + + /** + * Key for storing partial authorization last action state in session + * @var string + */ + protected $_partialAuthorizationLastActionStateSessionKey = 'paygate_authorizenet_last_action_state'; + + /** + * Centinel cardinal fields map + * + * @var array + */ + protected $_centinelFieldMap = array( + 'centinel_cavv' => 'x_cardholder_authentication_value', + 'centinel_eci' => 'x_authentication_indicator' + ); + /** * Check method for processing with base currency * @@ -115,143 +170,793 @@ public function getAcceptedCurrencyCodes() return $this->_getData('_accepted_currency'); } + /** + * Check capture availability + * + * @return bool + */ + public function canCapture() + { + if ($this->_isGatewayActionsLocked($this->getInfoInstance())) { + return false; + } + if ($this->_isPreauthorizeCapture($this->getInfoInstance())) { + return true; + } + + /** + * If there are not transactions it is placing order and capturing is available + */ + foreach($this->getCardsStorage()->getCards() as $card) { + $lastTransaction = $this->getInfoInstance()->getTransaction($card->getLastTransId()); + if ($lastTransaction) { + return false; + } + } + return true; + } + + /** + * Check refund availability + * + * @return bool + */ + public function canRefund() + { + if ($this->_isGatewayActionsLocked($this->getInfoInstance()) + || $this->getCardsStorage()->getCardsCount() <= 0) { + return false; + } + foreach($this->getCardsStorage()->getCards() as $card) { + $lastTransaction = $this->getInfoInstance()->getTransaction($card->getLastTransId()); + if ($lastTransaction + && $lastTransaction->getTxnType() == Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE + && !$lastTransaction->getIsClosed()) { + return true; + } + } + return false; + } + + /** + * Check void availability + * + * @param Varien_Object $invoicePayment + * @return bool + */ + public function canVoid(Varien_Object $payment) + { + if ($this->_isGatewayActionsLocked($this->getInfoInstance())) { + return false; + } + return $this->_isPreauthorizeCapture($this->getInfoInstance()); + } + + /** + * Set partial authorization last action state into session + * + * @param string $message + * @return Mage_Paygate_Model_Authorizenet + */ + public function setPartialAuthorizationLastActionState($state) + { + $this->_getSession()->setData($this->_partialAuthorizationLastActionStateSessionKey, $state); + return $this; + } + + /** + * Return partial authorization last action state from session + * + * @return string + */ + public function getPartialAuthorizationLastActionState() + { + return $this->_getSession()->getData($this->_partialAuthorizationLastActionStateSessionKey); + } + + /** + * Unset partial authorization last action state in session + * + * @return Mage_Paygate_Model_Authorizenet + */ + public function unsetPartialAuthorizationLastActionState() + { + $this->_getSession()->setData($this->_partialAuthorizationLastActionStateSessionKey, false); + return $this; + } + /** * Send authorize request to gateway * - * @param Varien_Object $payment + * @param Mage_Payment_Model_Info $payment * @param decimal $amount * @return Mage_Paygate_Model_Authorizenet - * @throws Mage_Core_Exception */ public function authorize(Varien_Object $payment, $amount) { if ($amount <= 0) { Mage::throwException(Mage::helper('paygate')->__('Invalid amount for authorization.')); } - $payment->setAnetTransType(self::REQUEST_TYPE_AUTH_ONLY); - $payment->setAmount($amount); + $this->_initCardsStorage($payment); + + if ($this->isPartialAuthorization($payment)) { + $this->_partialAuthorization($payment, $amount, self::REQUEST_TYPE_AUTH_ONLY); + $payment->setSkipTransactionCreation(true); + return $this; + } + + $this->_place($payment, $amount, self::REQUEST_TYPE_AUTH_ONLY); + $payment->setSkipTransactionCreation(true); + return $this; + } + + /** + * Send capture request to gateway + * + * @param Mage_Payment_Model_Info $payment + * @param decimal $amount + * @return Mage_Paygate_Model_Authorizenet + */ + public function capture(Varien_Object $payment, $amount) + { + if ($amount <= 0) { + Mage::throwException(Mage::helper('paygate')->__('Invalid amount for capture.')); + } + $this->_initCardsStorage($payment); + if ($this->_isPreauthorizeCapture($payment)) { + $this->_preauthorizeCapture($payment, $amount); + } else if ($this->isPartialAuthorization($payment)) { + $this->_partialAuthorization($payment, $amount, self::REQUEST_TYPE_AUTH_CAPTURE); + } else { + $this->_place($payment, $amount, self::REQUEST_TYPE_AUTH_CAPTURE); + } + $payment->setSkipTransactionCreation(true); + return $this; + } + + /** + * Void the payment through gateway + * + * @param Mage_Payment_Model_Info $payment + * @return Mage_Paygate_Model_Authorizenet + */ + public function void(Varien_Object $payment) + { + $cardsStorage = $this->getCardsStorage($payment); + + $messages = array(); + $isSuccessful = false; + $isFiled = false; + foreach($cardsStorage->getCards() as $card) { + try { + $newTransaction = $this->_voidCardTransaction($payment, $card); + $messages[] = $newTransaction->getMessage(); + $isSuccessful = true; + } catch (Exception $e) { + $messages[] = $e->getMessage(); + $isFiled = true; + continue; + } + $cardsStorage->updateCard($card); + } + + if ($isFiled) { + $this->_processFailureMultitransactionAction($payment, $messages, $isSuccessful); + } + + $payment->setSkipTransactionCreation(true); + return $this; + } + + /** + * Cancel the payment through gateway + * + * @param Mage_Payment_Model_Info $payment + * @return Mage_Paygate_Model_Authorizenet + */ + public function cancel(Varien_Object $payment) + { + return $this->void($payment); + } + + /** + * Refund the amount with transaction id + * + * @param Mage_Payment_Model_Info $payment + * @param decimal $amount + * @return Mage_Paygate_Model_Authorizenet + * @throws Mage_Core_Exception + */ + public function refund(Varien_Object $payment, $requestedAmount) + { + $cardsStorage = $this->getCardsStorage($payment); + + if ($this->_formatAmount($cardsStorage->getCapturedAmount() - $cardsStorage->getRefundedAmount()) < $requestedAmount) { + Mage::throwException(Mage::helper('paygate')->__('Invalid amount for refund.')); + } + + $messages = array(); + $isSuccessful = false; + $isFiled = false; + foreach($cardsStorage->getCards() as $card) { + if ($requestedAmount > 0) { + $cardAmountForRefund = $this->_formatAmount($card->getCapturedAmount() - $card->getRefundedAmount()); + if ($cardAmountForRefund <= 0) { + continue; + } + if ($cardAmountForRefund > $requestedAmount) { + $cardAmountForRefund = $requestedAmount; + } + try { + $newTransaction = $this->_refundCardTransaction($payment, $cardAmountForRefund, $card); + $messages[] = $newTransaction->getMessage(); + $isSuccessful = true; + } catch (Exception $e) { + $messages[] = $e->getMessage(); + $isFiled = true; + continue; + } + $card->setRefundedAmount($this->_formatAmount($card->getRefundedAmount() + $cardAmountForRefund)); + $cardsStorage->updateCard($card); + $requestedAmount = $this->_formatAmount($requestedAmount - $cardAmountForRefund); + } else { + $payment->setSkipTransactionCreation(true); + return $this; + } + } + + if ($isFiled) { + $this->_processFailureMultitransactionAction($payment, $messages, $isSuccessful); + } + + $payment->setSkipTransactionCreation(true); + return $this; + } + + /** + * Cancel partial authorizations and flush current split_tender_id record + * + * @param Mage_Payment_Model_Info $payment + */ + public function cancelPartialAuthorization(Mage_Payment_Model_Info $payment) { + if (!$payment->getAdditionalInformation($this->_splitTenderIdKey)) { + Mage::throwException(Mage::helper('paygate')->__('Invalid split tenderId ID.')); + } + + $request = $this->_getRequest(); + $request->setXSplitTenderId($payment->getAdditionalInformation($this->_splitTenderIdKey)); + + $request + ->setXType(self::REQUEST_TYPE_VOID) + ->setXMethod(self::REQUEST_METHOD_CC); + $result = $this->_postRequest($request); + + switch ($result->getResponseCode()) { + case self::RESPONSE_CODE_APPROVED: + $payment->setAdditionalInformation($this->_splitTenderIdKey, null); + $this->getCardsStorage($payment)->flushCards(); + $this->setPartialAuthorizationLastActionState(self::PARTIAL_AUTH_ALL_CANCELED); + return; + default: + Mage::throwException(Mage::helper('paygate')->__('Payment canceling error.')); + } + + } + + /** + * Send request with new payment to gateway + * + * @param Mage_Payment_Model_Info $payment + * @param decimal $amount + * @param string $requestType + * @return Mage_Paygate_Model_Authorizenet + * @throws Mage_Core_Exception + */ + protected function _place($payment, $amount, $requestType) + { + $payment->setAnetTransType($requestType); + $payment->setAmount($amount); $request= $this->_buildRequest($payment); $result = $this->_postRequest($request); - $payment->setCcApproval($result->getApprovalCode()) - ->setLastTransId($result->getTransactionId()) - ->setTransactionId($result->getTransactionId()) - ->setIsTransactionClosed(0) - ->setCcTransId($result->getTransactionId()) - ->setCcAvsStatus($result->getAvsResultCode()) - ->setCcCidStatus($result->getCardCodeResponseCode()); + switch ($requestType) { + case self::REQUEST_TYPE_AUTH_ONLY: + $newTransactionType = Mage_Sales_Model_Order_Payment_Transaction::TYPE_AUTH; + $defaultExceptionMessage = Mage::helper('paygate')->__('Payment authorization error.'); + break; + case self::REQUEST_TYPE_AUTH_CAPTURE: + $newTransactionType = Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE; + $defaultExceptionMessage = Mage::helper('paygate')->__('Payment capturing error.'); + break; + } switch ($result->getResponseCode()) { case self::RESPONSE_CODE_APPROVED: - $payment->setStatus(self::STATUS_APPROVED); + $card = $this->_registerCard($result, $payment); + $this->_addTransaction( + $payment, + $card->getLastTransId(), + $newTransactionType, + array('is_transaction_closed' => 0), + array($this->_realTransactionIdKey => $card->getLastTransId()), + Mage::helper('paygate')->getTransactionMessage( + $payment, $requestType, $card->getLastTransId(), $card, $amount + ) + ); + if ($requestType == self::REQUEST_TYPE_AUTH_CAPTURE) { + $card->setCapturedAmount($card->getProcessedAmount()); + $this->getCardsStorage()->updateCard($card); + } return $this; + case self::RESPONSE_CODE_HELD: + if ($this->_processPartialAuthorizationResponse($result, $payment)) { + return $this; + } + Mage::throwException($defaultExceptionMessage); case self::RESPONSE_CODE_DECLINED: - Mage::throwException(Mage::helper('paygate')->__('Payment authorization transaction has been declined.')); + case self::RESPONSE_CODE_ERROR: + Mage::throwException($this->_wrapGatewayError($result->getResponseReasonText())); default: - Mage::throwException(Mage::helper('paygate')->__('Payment authorization error.')); + Mage::throwException($defaultExceptionMessage); } + return $this; } /** - * Send capture request to gateway + * Send request with new payment to gateway during partial authorization process * - * @param Varien_Object $payment + * @param Mage_Payment_Model_Info $payment * @param decimal $amount + * @param string $requestType * @return Mage_Paygate_Model_Authorizenet - * @throws Mage_Core_Exception */ - public function capture(Varien_Object $payment, $amount) + protected function _partialAuthorization($payment, $amount, $requestType) { - if ($payment->getCcTransId()) { - $payment->setAnetTransType(self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE); - } else { - $payment->setAnetTransType(self::REQUEST_TYPE_AUTH_CAPTURE); + $amount = $amount - $this->getCardsStorage()->getProcessedAmount(); + if ($amount <= 0) { + Mage::throwException(Mage::helper('paygate')->__('Invalid amount for partial authorization.')); } + $payment->setAnetTransType($requestType); $payment->setAmount($amount); - $request= $this->_buildRequest($payment); $result = $this->_postRequest($request); - if ($result->getResponseCode() == self::RESPONSE_CODE_APPROVED) { - $payment->setStatus(self::STATUS_APPROVED); - //$payment->setCcTransId($result->getTransactionId()); - $payment->setLastTransId($result->getTransactionId()); - if (!$payment->getParentTransactionId() || $result->getTransactionId() != $payment->getParentTransactionId()) { - $payment->setTransactionId($result->getTransactionId()); - } - return $this; + $this->_processPartialAuthorizationResponse($result, $payment); + + switch ($requestType) { + case self::REQUEST_TYPE_AUTH_ONLY: + $newTransactionType = Mage_Sales_Model_Order_Payment_Transaction::TYPE_AUTH; + break; + case self::REQUEST_TYPE_AUTH_CAPTURE: + $newTransactionType = Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE; + break; } - if ($result->getResponseReasonText()) { - Mage::throwException($this->_wrapGatewayError($result->getResponseReasonText())); + + foreach ($this->getCardsStorage()->getCards() as $card) { + $this->_addTransaction( + $payment, + $card->getLastTransId(), + $newTransactionType, + array('is_transaction_closed' => 0), + array($this->_realTransactionIdKey => $card->getLastTransId()), + Mage::helper('paygate')->getTransactionMessage( + $payment, $requestType, $card->getLastTransId(), $card, $amount + ) + ); + if ($requestType == self::REQUEST_TYPE_AUTH_CAPTURE) { + $card->setCapturedAmount($card->getProcessedAmount()); + $this->getCardsStorage()->updateCard($card); + } } - Mage::throwException(Mage::helper('paygate')->__('Error in capturing the payment.')); + return $this; } + /** + * Return true if there are authorized transactions + * + * @param Mage_Payment_Model_Info $payment + * @return bool + */ + protected function _isPreauthorizeCapture($payment) + { + if ($this->getCardsStorage()->getCardsCount() <= 0) { + return false; + } + foreach($this->getCardsStorage()->getCards() as $card) { + $lastTransaction = $payment->getTransaction($card->getLastTransId()); + if (!$lastTransaction || $lastTransaction->getTxnType() != Mage_Sales_Model_Order_Payment_Transaction::TYPE_AUTH) { + return false; + } + } + return true; + } /** - * Void the payment through gateway + * Send capture request to gateway for capture authorized transactions * - * @param Varien_Object $payment + * @param Mage_Payment_Model_Info $payment + * @param decimal $amount * @return Mage_Paygate_Model_Authorizenet - * @throws Mage_Core_Exception */ - public function void(Varien_Object $payment) + protected function _preauthorizeCapture($payment, $requestedAmount) { - if ($payment->getParentTransactionId()) { - $payment->setAnetTransType(self::REQUEST_TYPE_VOID); - $request = $this->_buildRequest($payment); - $request->setXTransId($payment->getParentTransactionId()); - $result = $this->_postRequest($request); - if ($result->getResponseCode()==self::RESPONSE_CODE_APPROVED) { - $payment->setStatus(self::STATUS_SUCCESS ); - return $this; + $cardsStorage = $this->getCardsStorage($payment); + + if ($this->_formatAmount($cardsStorage->getProcessedAmount() - $cardsStorage->getCapturedAmount()) < $requestedAmount) { + Mage::throwException(Mage::helper('paygate')->__('Invalid amount for capture.')); + } + + $messages = array(); + $isSuccessful = false; + $isFiled = false; + foreach($cardsStorage->getCards() as $card) { + if ($requestedAmount > 0) { + $cardAmountForCapture = $card->getProcessedAmount(); + if ($cardAmountForCapture > $requestedAmount) { + $cardAmountForCapture = $requestedAmount; + } + try { + $newTransaction = $this->_preauthorizeCaptureCardTransaction($payment, $cardAmountForCapture , $card); + $messages[] = $newTransaction->getMessage(); + $isSuccessful = true; + } catch (Exception $e) { + $messages[] = $e->getMessage(); + $isFiled = true; + continue; + } + $card->setCapturedAmount($cardAmountForCapture); + $cardsStorage->updateCard($card); + $requestedAmount = $this->_formatAmount($requestedAmount - $cardAmountForCapture); + } else { + /** + * This functional is commented because partial capture is disable. See self::_canCapturePartial. + */ + //$this->_voidCardTransaction($payment, $card); } - $payment->setStatus(self::STATUS_ERROR); - Mage::throwException($this->_wrapGatewayError($result->getResponseReasonText())); } - $payment->setStatus(self::STATUS_ERROR); - Mage::throwException(Mage::helper('paygate')->__('Invalid transaction ID.')); + + if ($isFiled) { + $this->_processFailureMultitransactionAction($payment, $messages, $isSuccessful); + } + return $this; } /** - * refund the amount with transaction id + * Send capture request to gateway for capture authorized transactions of card * - * @param string $payment Varien_Object object - * @return Mage_Paygate_Model_Authorizenet - * @throws Mage_Core_Exception + * @param Mage_Payment_Model_Info $payment + * @param decimal $amount + * @param Varien_Object $card + * @return Mage_Sales_Model_Order_Payment_Transaction */ - public function refund(Varien_Object $payment, $amount) + protected function _preauthorizeCaptureCardTransaction($payment, $amount, $card) { - if ($payment->getRefundTransactionId() && $amount > 0) { - $payment->setAnetTransType(self::REQUEST_TYPE_CREDIT); - $request = $this->_buildRequest($payment); - $request->setXTransId($payment->getRefundTransactionId()); + $authTransactionId = $card->getLastTransId(); + $authTransaction = $payment->getTransaction($authTransactionId); + $realAuthTransactionId = $authTransaction->getAdditionalInformation($this->_realTransactionIdKey); - /** - * need to send last 4 digit credit card number to authorize.net - * otherwise it will give an error - */ - $request->setXCardNum($payment->getCcLast4()); + $payment->setAnetTransType(self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE); + $payment->setXTransId($realAuthTransactionId); + $payment->setAmount($amount); + + $request= $this->_buildRequest($payment); + $result = $this->_postRequest($request); - $result = $this->_postRequest($request); + switch ($result->getResponseCode()) { + case self::RESPONSE_CODE_APPROVED: + $captureTransactionId = $result->getTransactionId() . '-capture'; + $card->setLastTransId($captureTransactionId); + return $this->_addTransaction( + $payment, + $captureTransactionId, + Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE, + array( + 'is_transaction_closed' => 0, + 'parent_transaction_id' => $authTransactionId + ), + array($this->_realTransactionIdKey => $result->getTransactionId()), + Mage::helper('paygate')->getTransactionMessage( + $payment, self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE, $result->getTransactionId(), $card, $amount + ) + ); + case self::RESPONSE_CODE_HELD: + case self::RESPONSE_CODE_DECLINED: + case self::RESPONSE_CODE_ERROR: + $exceptionMessage = $this->_wrapGatewayError($result->getResponseReasonText()); + break; + default: + $exceptionMessage = Mage::helper('paygate')->__('Payment capturing error.'); + break; + } - if ($result->getResponseCode()==self::RESPONSE_CODE_APPROVED) { - $payment->setStatus(self::STATUS_SUCCESS); - return $this; + $exceptionMessage = Mage::helper('paygate')->getTransactionMessage( + $payment, self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE, $realAuthTransactionId, $card, $amount, $exceptionMessage + ); + Mage::throwException($exceptionMessage); + } + + /** + * Void the card transaction through gateway + * + * @param Mage_Payment_Model_Info $payment + * @param Varien_Object $card + * @return Mage_Sales_Model_Order_Payment_Transaction + */ + protected function _voidCardTransaction($payment, $card) + { + $authTransactionId = $card->getLastTransId(); + $authTransaction = $payment->getTransaction($authTransactionId); + $realAuthTransactionId = $authTransaction->getAdditionalInformation($this->_realTransactionIdKey); + + $payment->setAnetTransType(self::REQUEST_TYPE_VOID); + $payment->setXTransId($realAuthTransactionId); + + $request= $this->_buildRequest($payment); + $result = $this->_postRequest($request); + + switch ($result->getResponseCode()) { + case self::RESPONSE_CODE_APPROVED: + $voidTransactionId = $result->getTransactionId() . '-void'; + $card->setLastTransId($voidTransactionId); + return $this->_addTransaction( + $payment, + $voidTransactionId, + Mage_Sales_Model_Order_Payment_Transaction::TYPE_VOID, + array( + 'is_transaction_closed' => 1, + 'should_close_parent_transaction' => 1, + 'parent_transaction_id' => $authTransactionId + ), + array($this->_realTransactionIdKey => $result->getTransactionId()), + Mage::helper('paygate')->getTransactionMessage( + $payment, self::REQUEST_TYPE_VOID, $result->getTransactionId(), $card + ) + ); + case self::RESPONSE_CODE_DECLINED: + case self::RESPONSE_CODE_ERROR: + $exceptionMessage = $this->_wrapGatewayError($result->getResponseReasonText()); + break; + default: + $exceptionMessage = Mage::helper('paygate')->__('Payment voiding error.'); + break; + } + + $exceptionMessage = Mage::helper('paygate')->getTransactionMessage( + $payment, self::REQUEST_TYPE_VOID, $realAuthTransactionId, $card, false, $exceptionMessage + ); + Mage::throwException($exceptionMessage); + } + + /** + * Refund the card transaction through gateway + * + * @param Mage_Payment_Model_Info $payment + * @param Varien_Object $card + * @return Mage_Sales_Model_Order_Payment_Transaction + */ + protected function _refundCardTransaction($payment, $amount, $card) + { + /** + * Card has last transaction with type "refund" when all captured amount is refunded. + * Until this moment card has last transaction with type "capture". + */ + $captureTransactionId = $card->getLastTransId(); + $captureTransaction = $payment->getTransaction($captureTransactionId); + $realCaptureTransactionId = $captureTransaction->getAdditionalInformation($this->_realTransactionIdKey); + + $payment->setAnetTransType(self::REQUEST_TYPE_CREDIT); + $payment->setXTransId($realCaptureTransactionId); + $payment->setAmount($amount); + + $request = $this->_buildRequest($payment); + $request->setXCardNum($card->getCcLast4()); + $result = $this->_postRequest($request); + + switch ($result->getResponseCode()) { + case self::RESPONSE_CODE_APPROVED: + $refundTransactionId = $result->getTransactionId() . '-refund'; + $shouldCloseCaptureTransaction = 0; + /** + * If it is last amount for refund, transaction with type "capture" will be closed + * and card will has last transaction with type "refund" + */ + if ($this->_formatAmount($card->getCapturedAmount() - $card->getRefundedAmount()) == $amount) { + $card->setLastTransId($refundTransactionId); + $shouldCloseCaptureTransaction = 1; + } + return $this->_addTransaction( + $payment, + $refundTransactionId, + Mage_Sales_Model_Order_Payment_Transaction::TYPE_REFUND, + array( + 'is_transaction_closed' => 1, + 'should_close_parent_transaction' => $shouldCloseCaptureTransaction, + 'parent_transaction_id' => $captureTransactionId + ), + array($this->_realTransactionIdKey => $result->getTransactionId()), + Mage::helper('paygate')->getTransactionMessage( + $payment, self::REQUEST_TYPE_CREDIT, $result->getTransactionId(), $card, $amount + ) + ); + case self::RESPONSE_CODE_DECLINED: + case self::RESPONSE_CODE_ERROR: + $exceptionMessage = $this->_wrapGatewayError($result->getResponseReasonText()); + break; + default: + $exceptionMessage = Mage::helper('paygate')->__('Payment refunding error.'); + break; + } + + $exceptionMessage = Mage::helper('paygate')->getTransactionMessage( + $payment, self::REQUEST_TYPE_CREDIT, $realCaptureTransactionId, $card, $amount, $exceptionMessage + ); + Mage::throwException($exceptionMessage); + } + + /** + * Init cards storage model + * + * @param Mage_Payment_Model_Info $payment + */ + protected function _initCardsStorage($payment) + { + $this->_cardsStorage = Mage::getModel('paygate/authorizenet_cards')->setPayment($payment); + } + + /** + * Return cards storage model + * + * @param Mage_Payment_Model_Info $payment + * @return Mage_Paygate_Model_Authorizenet_Cards + */ + public function getCardsStorage($payment = null) + { + if (is_null($payment)) { + $payment = $this->getInfoInstance(); + } + if (is_null($this->_cardsStorage)) { + $this->_initCardsStorage($payment); + } + return $this->_cardsStorage; + } + + /** + * If parial authorization is started method will returne true + * + * @param Mage_Payment_Model_Info $payment + * @return bool + */ + public function isPartialAuthorization($payment = null) + { + if (is_null($payment)) { + $payment = $this->getInfoInstance(); + } + return $payment->getAdditionalInformation($this->_splitTenderIdKey); + } + + /** + * Mock capture transaction id in invoice + * + * @param Mage_Sales_Model_Order_Invoice $invoice + * @param Mage_Sales_Model_Order_Payment $payment + * @return Mage_Payment_Model_Method_Abstract + */ + public function processInvoice($invoice, $payment) + { + $invoice->setTransactionId(1); + return $this; + } + + /** + * Set transaction ID into creditmemo for informational purposes + * @param Mage_Sales_Model_Order_Creditmemo $creditmemo + * @param Mage_Sales_Model_Order_Payment $payment + * @return Mage_Payment_Model_Method_Abstract + */ + public function processCreditmemo($creditmemo, $payment) + { + $creditmemo->setTransactionId(1); + return $this; + } + + /** + * Set split_tender_id to quote payment if neeeded + * + * @param Varien_Object $response + * @param Mage_Sales_Model_Order_Payment $payment + * @return bool + */ + protected function _processPartialAuthorizationResponse($response, $orderPayment) { + if (!$response->getSplitTenderId()) { + return false; + } + + $exceptionMessage = null; + $isPartialAuthorizationProcessCompleted = false; + $isLastPartialAuthorizationSuccessful = false; + $isCreditCardsLimitExceed = false; + + try { + $orderPayment->setAdditionalInformation($this->_splitTenderIdKey, $response->getSplitTenderId()); + switch ($response->getResponseCode()) { + case self::RESPONSE_CODE_HELD: + if ($response->getResponseReasonCode() != self::RESPONSE_REASON_CODE_PARTIAL_APPROVE) { + return false; + } + $this->_registerCard($response, $orderPayment); + if ($this->getCardsStorage($orderPayment)->getCardsCount() + >= self::PARTIAL_AUTH_CARDS_LIMIT) { + $isCreditCardsLimitExceed = true; + } + $isLastPartialAuthorizationSuccessful = true; + break; + case self::RESPONSE_CODE_APPROVED: + $this->_registerCard($response, $orderPayment); + $isLastPartialAuthorizationSuccessful = true; + $isPartialAuthorizationProcessCompleted = true; + break; + case self::RESPONSE_CODE_DECLINED: + case self::RESPONSE_CODE_ERROR: + $exceptionMessage = $this->_wrapGatewayError($response->getResponseReasonText()); + break; + default: + $exceptionMessage = $this->_wrapGatewayError( + Mage::helper('paygate')->__('Payment partial authorization error.') + ); + } + } catch (Exception $e) { + $exceptionMessage = $e->getMessage(); + } + + if (!$isPartialAuthorizationProcessCompleted) { + $quotePayment = $orderPayment->getOrder()->getQuote()->getPayment(); + if ($isCreditCardsLimitExceed) { + $this->cancelPartialAuthorization($orderPayment); + $this->_clearAssignedData($quotePayment); + $this->setPartialAuthorizationLastActionState(self::PARTIAL_AUTH_CARDS_LIMIT_EXCEEDED); + $exceptionMessage = Mage::helper('paygate')->__('You have reached the maximum number of credit card allowed to be used for the payment.'); + } else { + if ($isLastPartialAuthorizationSuccessful) { + $this->_clearAssignedData($quotePayment); + $this->setPartialAuthorizationLastActionState(self::PARTIAL_AUTH_LAST_SUCCESS); + } else { + $this->setPartialAuthorizationLastActionState(self::PARTIAL_AUTH_LAST_DECLINED); + } } - Mage::throwException($this->_wrapGatewayError($result->getResponseReasonText())); + $quotePayment->setAdditionalInformation($orderPayment->getAdditionalInformation()); + throw new Mage_Payment_Model_Info_Exception($exceptionMessage); } - Mage::throwException(Mage::helper('paygate')->__('Error in refunding the payment.')); + + return true; + } + + /** + * Return authorize payment request + * + * @return Mage_Paygate_Model_Authorizenet_Request + */ + protected function _getRequest() + { + $request = Mage::getModel('paygate/authorizenet_request') + ->setXVersion(3.1) + ->setXDelimData('True') + ->setXDelimChar(self::RESPONSE_DELIM_CHAR) + ->setXRelayResponse('False') + ->setXTestRequest($this->getConfigData('test') ? 'TRUE' : 'FALSE') + ->setXLogin($this->getConfigData('login')) + ->setXTranKey($this->getConfigData('trans_key')); + + return $request; } /** * Prepare request to gateway * * @link http://www.authorize.net/support/AIM_guide.pdf - * @param Mage_Sales_Model_Document $order - * @return unknown + * @param Mage_Payment_Model_Info $payment + * @return Mage_Paygate_Model_Authorizenet_Request */ protected function _buildRequest(Varien_Object $payment) { @@ -263,39 +968,56 @@ protected function _buildRequest(Varien_Object $payment) $payment->setAnetTransMethod(self::REQUEST_METHOD_CC); } - $request = Mage::getModel('paygate/authorizenet_request') - ->setXVersion(3.1) - ->setXDelimData('True') - ->setXDelimChar(self::RESPONSE_DELIM_CHAR) - ->setXRelayResponse('False'); + $request = $this->_getRequest() + ->setXType($payment->getAnetTransType()) + ->setXMethod($payment->getAnetTransMethod()); if ($order && $order->getIncrementId()) { $request->setXInvoiceNum($order->getIncrementId()); } - $request->setXTestRequest($this->getConfigData('test') ? 'TRUE' : 'FALSE'); - - $request->setXLogin($this->getConfigData('login')) - ->setXTranKey($this->getConfigData('trans_key')) - ->setXType($payment->getAnetTransType()) - ->setXMethod($payment->getAnetTransMethod()); - if($payment->getAmount()){ $request->setXAmount($payment->getAmount(),2); $request->setXCurrencyCode($order->getBaseCurrencyCode()); } + switch ($payment->getAnetTransType()) { + case self::REQUEST_TYPE_AUTH_CAPTURE: + $request->setXAllowPartialAuth($this->getConfigData('allow_partial_authorization') ? 'True' : 'False'); + if ($payment->getAdditionalInformation($this->_splitTenderIdKey)) { + $request->setXSplitTenderId($payment->getAdditionalInformation($this->_splitTenderIdKey)); + } + break; + case self::REQUEST_TYPE_AUTH_ONLY: + $request->setXAllowPartialAuth($this->getConfigData('allow_partial_authorization') ? 'True' : 'False'); + if ($payment->getAdditionalInformation($this->_splitTenderIdKey)) { + $request->setXSplitTenderId($payment->getAdditionalInformation($this->_splitTenderIdKey)); + } + break; case self::REQUEST_TYPE_CREDIT: + /** + * need to send last 4 digit credit card number to authorize.net + * otherwise it will give an error + */ + $request->setXCardNum($payment->getCcLast4()); + $request->setXTransId($payment->getXTransId()); + break; case self::REQUEST_TYPE_VOID: + $request->setXTransId($payment->getXTransId()); + break; case self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE: - $request->setXTransId($payment->getCcTransId()); + $request->setXTransId($payment->getXTransId()); break; - case self::REQUEST_TYPE_CAPTURE_ONLY: $request->setXAuthCode($payment->getCcAuthCode()); break; } + if ($this->getIsCentinelValidationEnabled()){ + $params = $this->getCentinelValidator()->exportCmpiData(array()); + $request = Varien_Object_Mapper::accumulateByMap($params, $request, $this->_centinelFieldMap); + } + if (!empty($order)) { $billing = $order->getBillingAddress(); if (!empty($billing)) { @@ -330,8 +1052,8 @@ protected function _buildRequest(Varien_Object $payment) } $request->setXPoNum($payment->getPoNumber()) - ->setXTax($order->getTaxAmount()) - ->setXFreight($order->getShippingAmount()); + ->setXTax($order->getBaseTaxAmount()) + ->setXFreight($order->getBaseShippingAmount()); } switch ($payment->getAnetTransMethod()) { @@ -341,6 +1063,13 @@ protected function _buildRequest(Varien_Object $payment) ->setXExpDate(sprintf('%02d-%04d', $payment->getCcExpMonth(), $payment->getCcExpYear())) ->setXCardCode($payment->getCcCid()); } + if ($this->getConfigData('allow_partial_authorization') && $order) { + $request->setXAllowPartialAuth('true'); + $splitTenderId = $order->getPayment()->getAdditionalInformation('split_tender_id'); + if ($splitTenderId) { + $request->setSplitTenderId($splitTenderId); + } + } break; case self::REQUEST_METHOD_ECHECK: @@ -356,6 +1085,12 @@ protected function _buildRequest(Varien_Object $payment) return $request; } + /** + * Post request to gateway and return responce + * + * @param Mage_Paygate_Model_Authorizenet_Request $request) + * @return Mage_Paygate_Model_Authorizenet_Result + */ protected function _postRequest(Varien_Object $request) { $debugData = array('request' => $request->getData()); @@ -376,8 +1111,7 @@ protected function _postRequest(Varien_Object $request) try { $response = $client->request(); - } - catch (Exception $e) { + } catch (Exception $e) { $result->setResponseCode(-1) ->setResponseReasonCode($e->getCode()) ->setResponseReasonText($e->getMessage()); @@ -407,7 +1141,13 @@ protected function _postRequest(Varien_Object $request) ->setCustomerId($r[12]) ->setMd5Hash($r[37]) ->setCardCodeResponseCode($r[38]) - ->setCAVVResponseCode( (isset($r[39])) ? $r[39] : null); + ->setCAVVResponseCode( (isset($r[39])) ? $r[39] : null) + ->setSplitTenderId($r[52]) + ->setAccNumber($r[50]) + ->setCardType($r[51]) + ->setRequestedAmount($r[53]) + ->setBalanceOnCard($r[54]) + ; } else { Mage::throwException( @@ -431,4 +1171,157 @@ protected function _wrapGatewayError($text) { return Mage::helper('paygate')->__('Gateway error: %s', $text); } + + /** + * Retrieve session object + * + * @return Mage_Core_Model_Session_Abstract + */ + protected function _getSession() + { + if (Mage::app()->getStore()->isAdmin()) { + return Mage::getSingleton('adminhtml/session_quote'); + } else { + return Mage::getSingleton('checkout/session'); + } + } + + /** + * It sets card`s data into additional information of payment model + * + * @param Mage_Paygate_Model_Authorizenet_Result $response + * @param Mage_Sales_Model_Order_Payment $payment + * @return Varien_Object + */ + protected function _registerCard(Varien_Object $response, Mage_Sales_Model_Order_Payment $payment) + { + $cardsStorage = $this->getCardsStorage($payment); + $card = $cardsStorage->registerCard(); + $card + ->setRequestedAmount($response->getRequestedAmount()) + ->setBalanceOnCard($response->getBalanceOnCard()) + ->setLastTransId($response->getTransactionId()) + ->setProcessedAmount($response->getAmount()) + ->setCcType($payment->getCcType()) + ->setCcOwner($payment->getCcOwner()) + ->setCcLast4($payment->getCcLast4()) + ->setCcExpMonth($payment->getCcExpMonth()) + ->setCcExpYear($payment->getCcExpYear()) + ->setCcSsIssue($payment->getCcSsIssue()) + ->setCcSsStartMonth($payment->getCcSsStartMonth()) + ->setCcSsStartYear($payment->getCcSsStartYear()); + + $cardsStorage->updateCard($card); + $this->_clearAssignedData($payment); + return $card; + } + + /** + * Reset assigned data in payment info model + * + * @param Mage_Payment_Model_Info + * @return Mage_Paygate_Model_Authorizenet + */ + private function _clearAssignedData($payment) + { + $payment->setCcType(null) + ->setCcOwner(null) + ->setCcLast4(null) + ->setCcNumber(null) + ->setCcCid(null) + ->setCcExpMonth(null) + ->setCcExpYear(null) + ->setCcSsIssue(null) + ->setCcSsStartMonth(null) + ->setCcSsStartYear(null) + ; + return $this; + } + + /** + * Add payment transaction + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param string $transactionId + * @param string $transactionType + * @param array $transactionDetails + * @param array $transactionAdditionalInfo + * @return null|Mage_Sales_Model_Order_Payment_Transaction + */ + protected function _addTransaction(Mage_Sales_Model_Order_Payment $payment, $transactionId, $transactionType, + array $transactionDetails = array(), array $transactionAdditionalInfo = array(), $message = false + ) { + $payment->setTransactionId($transactionId); + $payment->resetTransactionAdditionalInfo(); + foreach ($transactionDetails as $key => $value) { + $payment->setData($key, $value); + } + foreach ($transactionAdditionalInfo as $key => $value) { + $payment->setTransactionAdditionalInfo($key, $value); + } + $transaction = $payment->addTransaction($transactionType, null, false , $message); + foreach ($transactionDetails as $key => $value) { + $payment->unsetData($key); + } + $payment->unsLastTransId(); + + /** + * It for self using + */ + $transaction->setMessage($message); + + return $transaction; + } + + /** + * Round up and cast specified amount to float or string + * + * @param string|float $amount + * @param bool $asFloat + * @return string|float + */ + protected function _formatAmount($amount, $asFloat = false) + { + $amount = sprintf('%.2F', $amount); // "f" depends on locale, "F" doesn't + return $asFloat ? (float)$amount : $amount; + } + + /** + * If gateway actions are locked return true + * + * @param Mage_Payment_Model_Info $payment + * @return bool + */ + protected function _isGatewayActionsLocked($payment) + { + return $payment->getAdditionalInformation($this->_isGatewayActionsLockedKey); + } + + /** + * Process exceptions for gateway action with a lot of transactions + * + * @param Mage_Payment_Model_Info $payment + * @param string $messages + * @param bool $isSuccessfulTransactions + */ + protected function _processFailureMultitransactionAction($payment, $messages, $isSuccessfulTransactions) + { + if ($isSuccessfulTransactions) { + $messages[] = Mage::helper('paygate')->__('Gateway actions are locked because the gateway cannot complete one or more of the transactions. Please log in to your Authorize.Net account to manually resolve the issue(s).'); + /** + * If there is successful transactions we can not to cancel order but + * have to save information about processed transactions in order`s comments and disable + * opportunity to voiding\capturing\refunding in future. Current order and payment will not be saved because we have to + * load new order object and set information into this object. + */ + $currentOrderId = $payment->getOrder()->getId(); + $copyOrder = Mage::getModel('sales/order')->load($currentOrderId); + $copyOrder->getPayment()->setAdditionalInformation($this->_isGatewayActionsLockedKey, 1); + foreach($messages as $message) { + $copyOrder->addStatusHistoryComment($message); + } + $copyOrder->save(); + } + Mage::throwException(Mage::helper('paygate')->convertMessagesToMessage($messages)); + } } diff --git a/app/code/core/Mage/Paygate/Model/Authorizenet/Cards.php b/app/code/core/Mage/Paygate/Model/Authorizenet/Cards.php new file mode 100644 index 0000000000..1247428b49 --- /dev/null +++ b/app/code/core/Mage/Paygate/Model/Authorizenet/Cards.php @@ -0,0 +1,207 @@ +_payment = $payment; + $paymentCardsInformation = $this->_payment->getAdditionalInformation(self::CARDS_NAMESPACE); + if ($paymentCardsInformation) { + $this->_cards = $paymentCardsInformation; + } + + return $this; + } + + /** + * Add based on $cardInfo card to payment and return Id of new item + * + * @param mixed $cardInfo + * @return string + */ + public function registerCard($cardInfo = array()) + { + $this->_isPaymentValid(); + $cardId = md5(microtime(1)); + $cardInfo[self::CARD_ID_KEY] = $cardId; + $this->_cards[$cardId] = $cardInfo; + $this->_payment->setAdditionalInformation(self::CARDS_NAMESPACE, $this->_cards); + return $this->getCard($cardId); + } + + /** + * Save data from card object in cards storage + * + * @param Varien_Object $card + * @return Mage_Paygate_Model_Authorizenet_Cards + */ + public function updateCard($card) + { + $cardId = $card->getData(self::CARD_ID_KEY); + if ($cardId && isset($this->_cards[$cardId])) { + $this->_cards[$cardId] = $card->getData(); + $this->_payment->setAdditionalInformation(self::CARDS_NAMESPACE, $this->_cards); + } + return $this; + } + + /** + * Return card for $cardId + * + * $param string $cardId + * @return Varien_Object + */ + public function getCard($cardId) + { + if (isset($this->_cards[$cardId])) { + $card = new Varien_Object($this->_cards[$cardId]); + return $card; + } + return false; + } + + /** + * Get all stored cards + * + * @return array + */ + public function getCards() + { + $this->_isPaymentValid(); + $_cards = array(); + foreach(array_keys($this->_cards) as $key) { + $_cards[$key] = $this->getCard($key); + } + return $_cards; + } + + /** + * Return count of saved cards + * + * @return int + */ + public function getCardsCount() + { + $this->_isPaymentValid(); + return count($this->_cards); + } + + /** + * Return processed amount for all cards + * + * @return float + */ + public function getProcessedAmount() + { + return $this->_getAmount(self::CARD_PROCESSED_AMOUNT_KEY); + } + + /** + * Return captured amount for all cards + * + * @return float + */ + public function getCapturedAmount() + { + return $this->_getAmount(self::CARD_CAPTURED_AMOUNT_KEY); + } + + /** + * Return refunded amount for all cards + * + * @return float + */ + public function getRefundedAmount() + { + return $this->_getAmount(self::CARD_REFUNDED_AMOUNT_KEY); + } + + /** + * Remove all cards from payment instance + * + * @return Mage_Paygate_Model_Authorizenet_Cart + */ + public function flushCards() + { + $this->_payment->setAdditionalInformation(self::CARDS_NAMESPACE, null); + return $this; + } + + /** + * Check for payment instace present + * + * @throws Exception + */ + protected function _isPaymentValid() + { + if (!$this->_payment) { + throw new Exception('Payment instance is not set'); + } + } + /** + * Return total for cards data fields + * + * $param string $key + * @return float + */ + public function _getAmount($key) + { + $amount = 0; + foreach ($this->_cards as $card) { + if (isset($card[$key])) { + $amount += $card[$key]; + } + } + return $amount; + } +} diff --git a/app/code/core/Mage/Paygate/controllers/Adminhtml/Paygate/Authorizenet/PaymentController.php b/app/code/core/Mage/Paygate/controllers/Adminhtml/Paygate/Authorizenet/PaymentController.php new file mode 100644 index 0000000000..d570171c46 --- /dev/null +++ b/app/code/core/Mage/Paygate/controllers/Adminhtml/Paygate/Authorizenet/PaymentController.php @@ -0,0 +1,78 @@ + + */ +class Mage_Paygate_Adminhtml_Paygate_Authorizenet_PaymentController extends Mage_Adminhtml_Controller_Action +{ + + /** + * Cancel active partail authorizations + */ + public function cancelAction() + { + $result['success'] = false; + try { + $paymentMethod = Mage::helper('payment')->getMethodInstance(Mage_Paygate_Model_Authorizenet::METHOD_CODE); + if ($paymentMethod) { + $paymentMethod->cancelPartialAuthorization(Mage::getSingleton('adminhtml/session_quote')->getQuote()->getPayment()); + } + + $result['success'] = true; + $result['update_html'] = $this->_getPaymentMethodsHtml(); + } catch (Mage_Core_Exception $e) { + Mage::logException($e); + $result['error_message'] = $e->getMessage(); + } catch (Exception $e) { + Mage::logException($e); + $result['error_message'] = $this->__('There was an error canceling transactions. Please contact us or try again later.'); + } + + Mage::getSingleton('adminhtml/session_quote')->getQuote()->getPayment()->save(); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + + /** + * Get payment method step html + * + * @return string + */ + protected function _getPaymentMethodsHtml() + { + $layout = $this->getLayout(); + $update = $layout->getUpdate(); + $update->load('checkout_onepage_paymentmethod'); + $layout->generateXml(); + $layout->generateBlocks(); + $output = $layout->getOutput(); + return $output; + } +} diff --git a/app/code/core/Mage/Paygate/controllers/Authorizenet/PaymentController.php b/app/code/core/Mage/Paygate/controllers/Authorizenet/PaymentController.php new file mode 100644 index 0000000000..1785f63a7e --- /dev/null +++ b/app/code/core/Mage/Paygate/controllers/Authorizenet/PaymentController.php @@ -0,0 +1,77 @@ + + */ +class Mage_Paygate_Authorizenet_PaymentController extends Mage_Core_Controller_Front_Action +{ + + /** + * Cancel active partail authorizations + */ + public function cancelAction() + { + $result['success'] = false; + try { + $paymentMethod = Mage::helper('payment')->getMethodInstance(Mage_Paygate_Model_Authorizenet::METHOD_CODE); + if ($paymentMethod) { + $paymentMethod->cancelPartialAuthorization(Mage::getSingleton('checkout/session')->getQuote()->getPayment()); + } + $result['success'] = true; + $result['update_html'] = $this->_getPaymentMethodsHtml(); + } catch (Mage_Core_Exception $e) { + Mage::logException($e); + $result['error_message'] = $e->getMessage(); + } catch (Exception $e) { + Mage::logException($e); + $result['error_message'] = $this->__('There was an error canceling transactions. Please contact us or try again later.'); + } + + Mage::getSingleton('checkout/session')->getQuote()->getPayment()->save(); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + + /** + * Get payment method step html + * + * @return string + */ + protected function _getPaymentMethodsHtml() + { + $layout = $this->getLayout(); + $update = $layout->getUpdate(); + $update->load('checkout_onepage_paymentmethod'); + $layout->generateXml(); + $layout->generateBlocks(); + $output = $layout->getOutput(); + return $output; + } +} diff --git a/app/code/core/Mage/Paygate/etc/config.xml b/app/code/core/Mage/Paygate/etc/config.xml index ec7c46e397..bfc19c95a3 100644 --- a/app/code/core/Mage/Paygate/etc/config.xml +++ b/app/code/core/Mage/Paygate/etc/config.xml @@ -76,7 +76,27 @@ + + + standard + + Mage_Paygate + paygate + + +
    + + + + + + Mage_Paygate_Adminhtml + + + + + @@ -89,7 +109,7 @@ paygate/authorizenet - 1 + processing authorize 1 Credit Card (Authorize.net) diff --git a/app/code/core/Mage/Paygate/etc/system.xml b/app/code/core/Mage/Paygate/etc/system.xml index acc4827582..e09934e26f 100644 --- a/app/code/core/Mage/Paygate/etc/system.xml +++ b/app/code/core/Mage/Paygate/etc/system.xml @@ -202,6 +202,50 @@ 1 0 + + + select + adminhtml/system_config_source_yesno + 110 + 1 + 1 + 0 + + + + adminhtml/system_config_form_field_heading + 120 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 125 + 1 + 1 + + + + Severe Validation Removes Chargeback Liability on Merchant + select + adminhtml/system_config_source_yesno + 130 + 1 + 1 + 1 + + + + If empty, a default value will be used. Custom URL may be provided by CardinalCommerce agreement. + text + adminhtml/system_config_source_yesno + 135 + 1 + 1 + 1 + diff --git a/app/code/core/Mage/Payment/Block/Form/Container.php b/app/code/core/Mage/Payment/Block/Form/Container.php index 8520648fd8..9b505bb4ae 100644 --- a/app/code/core/Mage/Payment/Block/Form/Container.php +++ b/app/code/core/Mage/Payment/Block/Form/Container.php @@ -116,15 +116,14 @@ public function getMethods() $quote = $this->getQuote(); $store = $quote ? $quote->getStoreId() : null; $methods = $this->helper('payment')->getStoreMethods($store, $quote); - $total = $quote->getBaseGrandTotal(); + $total = $quote->getBaseSubtotal(); foreach ($methods as $key => $method) { if ($this->_canUseMethod($method) && ($total != 0 || $method->getCode() == 'free' || ($quote->hasRecurringItems() && $method->canManageRecurringProfiles()))) { $this->_assignMethod($method); - } - else { + } else { unset($methods[$key]); } } diff --git a/app/code/core/Mage/Payment/Helper/Data.php b/app/code/core/Mage/Payment/Helper/Data.php index cab2dfe6c9..b1ddcdb278 100644 --- a/app/code/core/Mage/Payment/Helper/Data.php +++ b/app/code/core/Mage/Payment/Helper/Data.php @@ -38,15 +38,12 @@ class Mage_Payment_Helper_Data extends Mage_Core_Helper_Abstract * Retrieve method model object * * @param string $code - * @return Mage_Payment_Model_Method_Abstract + * @return Mage_Payment_Model_Method_Abstract|false */ public function getMethodInstance($code) { $key = self::XML_PATH_PAYMENT_METHODS.'/'.$code.'/model'; $class = Mage::getStoreConfig($key); - if (!$class) { - Mage::throwException($this->__('Cannot load configuration for payment method "%s"', $code)); - } return Mage::getModel($class); } @@ -160,7 +157,7 @@ public function getRecurringProfileMethods($store = null) $result = array(); foreach ($this->getPaymentMethods($store) as $code => $data) { $method = $this->getMethodInstance($code); - if ($method->canManageRecurringProfiles()) { + if ($method && $method->canManageRecurringProfiles()) { $result[] = $method; } } @@ -209,7 +206,9 @@ public function getPaymentMethodList($sorted = true, $asLabelValue = false, $wit if ((isset($data['title']))) { $methods[$code] = $data['title']; } else { - $methods[$code] = $this->getMethodInstance($code)->getConfigData('title', $store); + if ($this->getMethodInstance($code)) { + $methods[$code] = $this->getMethodInstance($code)->getConfigData('title', $store); + } } if ($asLabelValue && $withGroups && isset($data['group'])) { $groupRelations[$code] = $data['group']; diff --git a/app/code/core/Mage/Payment/Model/Billing/AgreementAbstract.php b/app/code/core/Mage/Payment/Model/Billing/AgreementAbstract.php index eda3d72162..c1cd73244a 100644 --- a/app/code/core/Mage/Payment/Model/Billing/AgreementAbstract.php +++ b/app/code/core/Mage/Payment/Model/Billing/AgreementAbstract.php @@ -79,7 +79,9 @@ public function getPaymentMethodInstance() if (is_null($this->_paymentMethodInstance)) { $this->_paymentMethodInstance = Mage::helper('payment')->getMethodInstance($this->getMethodCode()); } - $this->_paymentMethodInstance->setStore($this->getStoreId()); + if ($this->_paymentMethodInstance) { + $this->_paymentMethodInstance->setStore($this->getStoreId()); + } return $this->_paymentMethodInstance; } diff --git a/app/code/core/Mage/Payment/Model/Info/Exception.php b/app/code/core/Mage/Payment/Model/Info/Exception.php new file mode 100644 index 0000000000..fc87f66314 --- /dev/null +++ b/app/code/core/Mage/Payment/Model/Info/Exception.php @@ -0,0 +1,38 @@ + + */ +class Mage_Payment_Model_Info_Exception extends Mage_Core_Exception +{} diff --git a/app/code/core/Mage/Paypal/Block/Adminhtml/System/Config/Fieldset/Store.php b/app/code/core/Mage/Paypal/Block/Adminhtml/System/Config/Fieldset/Store.php new file mode 100644 index 0000000000..e2de313329 --- /dev/null +++ b/app/code/core/Mage/Paypal/Block/Adminhtml/System/Config/Fieldset/Store.php @@ -0,0 +1,85 @@ + + */ +class Mage_Paypal_Block_Adminhtml_System_Config_Fieldset_Store + extends Mage_Adminhtml_Block_Abstract + implements Varien_Data_Form_Element_Renderer_Interface +{ + /** + * Path to template file + * + * @var string + */ + protected $_template = 'paypal/system/config/fieldset/store.phtml'; + + /** + * Render service JavaScript code + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + public function render(Varien_Data_Form_Element_Abstract $element) + { + return $this->toHtml(); + } + + /** + * Returns list of disabled (in the Default or the Website Scope) paypal methods + * + * @return array + */ + protected function getPaypalDisabledMethods() + { + // Assoc array that contains info about paypal methods (their IDs and corresponding Config Paths) + $methods = array( + 'express' => 'payment/paypal_express/active', + 'wps' => 'payment/paypal_standard/active', + 'wpp' => 'payment/paypal_direct/active', + 'wpppe' => 'payment/paypaluk_direct/active', + 'verisign' => 'payment/verisign/active', + 'expresspe' => 'payment/paypaluk_express/active' + ); + // Retrieve a code of the current website + $website = $this->getRequest()->getParam('website'); + + $configRoot = Mage::getConfig()->getNode(null, 'website', $website); + + $disabledMethods = array(); + foreach ($methods as $methodId => $methodPath) { + $isEnabled = (int) $configRoot->descend($methodPath); + if ($isEnabled === 0) { + $disabledMethods[$methodId] = $isEnabled; + } + } + + return $disabledMethods; + } +} diff --git a/app/code/core/Mage/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php b/app/code/core/Mage/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php new file mode 100644 index 0000000000..4e9695e1b7 --- /dev/null +++ b/app/code/core/Mage/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php @@ -0,0 +1,83 @@ + + */ + class Mage_Paypal_Block_Adminhtml_System_Config_Payflowlink_Info + extends Mage_Adminhtml_Block_Abstract + implements Varien_Data_Form_Element_Renderer_Interface +{ + /** + * Template path + * + * @var string + */ + protected $_template = 'paypal/system/config/payflowlink/info.phtml'; + + /** + * Render fieldset html + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + public function render(Varien_Data_Form_Element_Abstract $element) + { + return $this->toHtml(); + } + + /** + * Get frontend url + * + * @param string $routePath + * @return strting + */ + public function getFrontendUrl($routePath) + { + if ($this->getRequest()->getParam('website')) { + $website = Mage::getModel('core/website')->load($this->getRequest()->getParam('website')); + $secure = Mage::getStoreConfigFlag( + Mage_Core_Model_Url::XML_PATH_SECURE_IN_FRONT, + $website->getDefaultStore() + ); + $path = $secure ? + Mage_Core_Model_Store::XML_PATH_SECURE_BASE_LINK_URL : + Mage_Core_Model_Store::XML_PATH_UNSECURE_BASE_LINK_URL; + $websiteUrl = Mage::getStoreConfig($path, $website->getDefaultStore()); + } else { + $secure = Mage::getStoreConfigFlag( + Mage_Core_Model_Url::XML_PATH_SECURE_IN_FRONT + ); + $websiteUrl = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, $secure); + } + + return $websiteUrl . $routePath; + } +} diff --git a/app/code/core/Mage/Paypal/Block/Express/Shortcut.php b/app/code/core/Mage/Paypal/Block/Express/Shortcut.php index 8ccf8baf9d..5fc4322f20 100644 --- a/app/code/core/Mage/Paypal/Block/Express/Shortcut.php +++ b/app/code/core/Mage/Paypal/Block/Express/Shortcut.php @@ -72,6 +72,17 @@ protected function _beforeToHtml() return $result; } + if ($isInCatalog) { + // Show PayPal shortcut on a product view page only if product has nonzero price + $currentProduct = Mage::registry('current_product'); + if (!is_null($currentProduct)) { + $productPrice = (float)$currentProduct->getFinalPrice(); + if (empty($productPrice)) { + $this->_shouldRender = false; + return $result; + } + } + } // validate minimum quote amount and validate quote for zero grandtotal if (null !== $quote && (!$quote->validateMinimumAmount() || (!$quote->getGrandTotal() && !$quote->hasNominalItems()))) { @@ -81,7 +92,7 @@ protected function _beforeToHtml() // check payment method availability $methodInstance = Mage::helper('payment')->getMethodInstance($this->_paymentMethodCode); - if (!$methodInstance->isAvailable($quote)) { + if (!$methodInstance || !$methodInstance->isAvailable($quote)) { $this->_shouldRender = false; return $result; } diff --git a/app/code/core/Mage/Paypal/Block/Payflow/Link/Form.php b/app/code/core/Mage/Paypal/Block/Payflow/Link/Form.php new file mode 100644 index 0000000000..376484d443 --- /dev/null +++ b/app/code/core/Mage/Paypal/Block/Payflow/Link/Form.php @@ -0,0 +1,46 @@ + + */ +class Mage_Paypal_Block_Payflow_Link_Form extends Mage_Payment_Block_Form +{ + /** + * Internal constructor + * Set info template for payment step + * + */ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('paypal/payflow/link/info.phtml'); + } +} diff --git a/app/code/core/Mage/Paypal/Block/Payflow/Link/Iframe.php b/app/code/core/Mage/Paypal/Block/Payflow/Link/Iframe.php new file mode 100644 index 0000000000..9ee78c0079 --- /dev/null +++ b/app/code/core/Mage/Paypal/Block/Payflow/Link/Iframe.php @@ -0,0 +1,204 @@ + + */ +class Mage_Paypal_Block_Payflow_Link_Iframe extends Mage_Payment_Block_Form +{ + /** + * Payment method code + * + * @var string + */ + protected $_paymentMethodCode = Mage_Paypal_Model_Config::METHOD_PAYFLOWLINK; + + /** + * Whether the block should be eventually rendered + * + * @var bool + */ + protected $_shouldRender = false; + + /** + * Order object + * + * @var Mage_Sales_Model_Order + */ + protected $_order; + + /** + * Internal constructor + * Set info template for payment step + * + */ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('paypal/payflow/link/iframe.phtml'); + } + + /** + * Get frame action URL + * + * @return string + */ + public function getFrameActionUrl() + { + return $this->getUrl('paypal/payflow/form'); + } + + /** + * Get secure token + * + * @return string + */ + public function getSecureToken() + { + return $this->_getOrder() + ->getPayment() + ->getAdditionalInformation('secure_token'); + } + + /** + * Get secure token ID + * + * @return string + */ + public function getSecureTokenId() + { + return $this->_getOrder() + ->getPayment() + ->getAdditionalInformation('secure_token_id'); + } + + /** + * Get payflow transaction URL + * + * @return string + */ + public function getTransactionUrl() + { + return Mage_Paypal_Model_Payflowlink::TRANSACTION_PAYFLOW_URL; + } + + /** + * Check sandbox mode + * + * @return bool + */ + public function isTestMode() + { + $mode = Mage::helper('payment') + ->getMethodInstance($this->_paymentMethodCode) + ->getConfigData('sandbox_flag'); + return (bool) $mode; + } + + /** + * Get order object + * + * @return Mage_Sales_Model_Order + */ + protected function _getOrder() + { + if (!$this->_order) { + $incrementId = $this->_getCheckout()->getLastRealOrderId(); + $this->_order = Mage::getModel('sales/order') + ->loadByIncrementId($incrementId); + } + return $this->_order; + } + + /** + * Get frontend checkout session object + * + * @return Mage_Checkout_Model_Session + */ + protected function _getCheckout() + { + return Mage::getSingleton('checkout/session'); + } + + /** + * Before rendering html, check if is block rendering needed + * + * @return Mage_Core_Block_Abstract + */ + protected function _beforeToHtml() + { + if ($this->_getOrder()->getId() && + $this->_getOrder()->getQuoteId() == $this->_getCheckout()->getQuoteId() && + $this->_getOrder()->getPayment()->getMethodInstance()->getCode() == $this->_paymentMethodCode) { + $this->_shouldRender = true; + } + + if ($this->getGotoSection() || $this->getGotoSuccessPage()) { + $this->_shouldRender = true; + } + + return parent::_beforeToHtml(); + } + + /** + * Render the block if needed + * + * @return string + */ + protected function _toHtml() + { + if ($this->_isAfterPaymentSave()) { + $this->setTemplate('paypal/payflow/link/js.phtml'); + return parent::_toHtml(); + } + if (!$this->_shouldRender) { + return ''; + } + return parent::_toHtml(); + } + + /** + * Check whether block is rendering after save payment + * + * @return bool + */ + protected function _isAfterPaymentSave() + { + $quote = $this->_getCheckout()->getQuote(); + if ($quote->getPayment()->getMethod() == $this->_paymentMethodCode && + $quote->getIsActive() && + $this->getTemplate() && + $this->getRequest()->getActionName() == 'savePayment') { + return true; + } + + return false; + } +} diff --git a/app/code/core/Mage/Paypal/Block/Payflow/Link/Info.php b/app/code/core/Mage/Paypal/Block/Payflow/Link/Info.php new file mode 100644 index 0000000000..aa311ec739 --- /dev/null +++ b/app/code/core/Mage/Paypal/Block/Payflow/Link/Info.php @@ -0,0 +1,60 @@ + + */ +class Mage_Paypal_Block_Payflow_Link_Info extends Mage_Paypal_Block_Payment_Info +{ + /** + * Don't show CC type for non-CC methods + * Do not show credit card type on fronend + * + * @return string|null + */ + public function getCcTypeName() + { + if (Mage_Paypal_Model_Config::getIsCreditCardMethod($this->getInfo()->getMethod()) && + !$this->getIsSecureMode()) { + $_types = Mage::getConfig()->getNode('global/payment/payflow/cc/types')->asArray(); + $types = array(); + foreach ($_types as $data) { + if (isset($data['code']) && isset($data['name'])) { + $types[$data['code']] = $data['name']; + } + } + $ccType = $this->getInfo()->getCcType(); + if (isset($types[$ccType])) { + return $types[$ccType]; + } + return (empty($ccType)) ? Mage::helper('payment')->__('N/A') : $ccType; + } + } +} diff --git a/app/code/core/Mage/Paypal/Helper/Payflow.php b/app/code/core/Mage/Paypal/Helper/Payflow.php new file mode 100644 index 0000000000..dade3d0f14 --- /dev/null +++ b/app/code/core/Mage/Paypal/Helper/Payflow.php @@ -0,0 +1,55 @@ +getQuote(); + if ($quote) { + $payment = $quote->getPayment(); + if ($payment && $payment->getMethod() == Mage_Paypal_Model_Config::METHOD_PAYFLOWLINK) { + return $name; + } + } + + if ($blockObject = Mage::getSingleton('core/layout')->getBlock($block)) { + return $blockObject->getTemplate(); + } + + return ''; + } +} diff --git a/app/code/core/Mage/Paypal/Model/Api/Abstract.php b/app/code/core/Mage/Paypal/Model/Api/Abstract.php index 0f56f1d39b..28f2ec8f38 100644 --- a/app/code/core/Mage/Paypal/Model/Api/Abstract.php +++ b/app/code/core/Mage/Paypal/Model/Api/Abstract.php @@ -122,6 +122,16 @@ public function getApiSignature() return $this->_config->apiSignature; } + /** + * Return Paypal Api certificate based on config data + * + * @return string + */ + public function getApiCertificate() + { + return $this->_config->getApiCertificate(); + } + /** * BN code getter * @@ -526,9 +536,22 @@ protected function _importStreetFromAddress(Varien_Object $address, array &$to) if (!$keys || !$street || !is_array($street)) { return; } - foreach ($keys as $key) { - if ($value = array_pop($street)) { - $to[$key] = $value; + + if (count($keys) >= count($street)) { + foreach ($keys as $key) { + if ($value = array_shift($street)) { + $to[$key] = $value; + } + } + } else { + $countArgs = ceil(count($street)/count($keys)); + $offset = 0; + foreach ($keys as $key) { + $values = array_slice($street, $offset, $countArgs); + if (is_array($values)) { + $to[$key] = implode(' ', $values); + $offset += $countArgs; + } } } } @@ -579,4 +602,14 @@ public function getDebugFlag() { return $this->_config->debug; } + + /** + * Check whether API certificate authentication should be used + * + * @return bool + */ + public function getUseCertAuthentication() + { + return (bool)$this->_config->apiAuthentication; + } } diff --git a/app/code/core/Mage/Paypal/Model/Api/Nvp.php b/app/code/core/Mage/Paypal/Model/Api/Nvp.php index 09ef1d7c9d..c4dcf8015e 100644 --- a/app/code/core/Mage/Paypal/Model/Api/Nvp.php +++ b/app/code/core/Mage/Paypal/Model/Api/Nvp.php @@ -482,6 +482,15 @@ class Mage_Paypal_Model_Api_Nvp extends Mage_Paypal_Model_Api_Abstract protected $_supportedCcTypes = array( 'VI' => 'Visa', 'MC' => 'MasterCard', 'DI' => 'Discover', 'AE' => 'Amex', 'SM' => 'Maestro', 'SO' => 'Solo'); + /** + * Required fields in the response + * + * @var array + */ + protected $_requiredResponseParams = array( + self::DO_DIRECT_PAYMENT => array('ACK', 'CORRELATIONID', 'AMT') + ); + /** * Warning codes recollected after each API call * @@ -510,7 +519,8 @@ class Mage_Paypal_Model_Api_Nvp extends Mage_Paypal_Model_Api_Abstract */ public function getApiEndpoint() { - return sprintf('https://api-3t%s.paypal.com/nvp', $this->_config->sandboxFlag ? '.sandbox' : ''); + $url = $this->getUseCertAuthentication() ? 'https://api%s.paypal.com/nvp' : 'https://api-3t%s.paypal.com/nvp'; + return sprintf($url, $this->_config->sandboxFlag ? '.sandbox' : ''); } /** @@ -549,7 +559,7 @@ public function callSetExpressCheckout() if ($this->getAddress()) { $request = $this->_importAddresses($request); $request['ADDROVERRIDE'] = 1; - } elseif ($options && (count($options) < 10)) { // doesn't support more than 10 shipping options + } elseif ($options && (count($options) <= 10)) { // doesn't support more than 10 shipping options $request['CALLBACK'] = $this->getShippingOptionsCallbackUrl(); $request['CALLBACKTIMEOUT'] = 6; // max value $request['MAXAMT'] = $request['AMT'] + 999.00; // it is impossible to calculate max amount @@ -868,8 +878,12 @@ public function call($methodName, array $request) { $request = $this->_addMethodToRequest($methodName, $request); $eachCallRequest = $this->_prepareEachCallRequest($methodName); + if ($this->getUseCertAuthentication()) { + if ($key = array_search('SIGNATURE', $eachCallRequest)) { + unset($eachCallRequest[$key]); + } + } $request = $this->_exportToRequest($eachCallRequest, $request); - $debugData = array('url' => $this->getApiEndpoint(), $methodName => $request); try { @@ -878,6 +892,9 @@ public function call($methodName, array $request) if ($this->getUseProxy()) { $config['proxy'] = $this->getProxyHost(). ':' . $this->getProxyPort(); } + if ($this->getUseCertAuthentication()) { + $config['ssl_cert'] = $this->getApiCertificate(); + } $http->setConfig($config); $http->write(Zend_Http_Client::POST, $this->getApiEndpoint(), '1.1', array(), $this->_buildQuery($request)); $response = $http->read(); @@ -904,6 +921,14 @@ public function call($methodName, array $request) Mage::throwException(Mage::helper('paypal')->__('Unable to communicate with the PayPal gateway.')); } + + if (!$this->_validateResponse($methodName, $response)) { + Mage::logException(new Exception( + Mage::helper('paypal')->__("PayPal response hasn't required fields.") + )); + Mage::throwException(Mage::helper('paypal')->__('There was an error processing your order. Please contact us or try again later.')); + } + $this->_callErrors = array(); if ($this->_isCallSuccessful($response)) { if ($this->_rawResponseNeeded) { @@ -977,6 +1002,25 @@ protected function _isCallSuccessful($response) } return false; } + + /** + * Validate response array. + * + * @param string $method + * @param array $response + * @return bool + */ + protected function _validateResponse($method, $response) + { + if (isset($this->_requiredResponseParams[$method])) { + foreach ($this->_requiredResponseParams[$method] as $param) { + if (!isset($response[$param])) { + return false; + } + } + } + return true; + } /** * Parse an NVP response string into an associative array diff --git a/app/code/core/Mage/Paypal/Model/Cert.php b/app/code/core/Mage/Paypal/Model/Cert.php new file mode 100644 index 0000000000..86e5a6a790 --- /dev/null +++ b/app/code/core/Mage/Paypal/Model/Cert.php @@ -0,0 +1,135 @@ +_init('paypal/cert'); + } + + /** + * Load model by website id + * + * @param int $websiteId + * @param bool $strictLoad + * @return Mage_Paypal_Model_Cert + */ + public function loadByWebsite($websiteId, $strictLoad = true) + { + $this->setWebsiteId($websiteId); + $this->_getResource()->loadByWebsite($this, $strictLoad); + return $this; + } + + /** + * Get path to PayPal certificate file, if file does not exist try to create it + * + * @return string + */ + public function getCertPath() + { + if (!$this->getContent()) { + Mage::throwException(Mage::helper('paypal')->__('PayPal certificate does not exist.')); + } + + $certFileName = sprintf('cert_%s_%s.pem', $this->getWebsiteId(), strtotime($this->getUpdatedAt())); + $certFile = $this->_getBaseDir() . DS . $certFileName; + + if (!file_exists($certFile)) { + $this->_createCertFile($certFile); + } + return $certFile; + } + + /** + * Create physical certificate file based on DB data + * + * @param string $file + */ + protected function _createCertFile($file) + { + $certDir = $this->_getBaseDir(); + if (!is_dir($certDir)) { + $ioAdapter = new Varien_Io_File(); + $ioAdapter->checkAndCreateFolder($certDir); + } else { + $this->_removeOutdatedCertFile(); + } + + file_put_contents($file, Mage::helper('core')->decrypt($this->getContent())); + } + + /** + * Check and remove outdated certificate file by website + * + * @return void + */ + protected function _removeOutdatedCertFile() + { + $certDir = $this->_getBaseDir(); + if (is_dir($certDir)) { + $entries = scandir($certDir); + foreach ($entries as $entry) { + if ($entry != '.' && $entry != '..' && strpos($entry, 'cert_' . $this->getWebsiteId()) !== false) { + unlink($certDir . DS . $entry); + } + } + } + } + + /** + * Retrieve base directory for certificate + * + * @return string + */ + protected function _getBaseDir() + { + return Mage::getBaseDir('var') . DS . self::BASEPATH_PAYPAL_CERT; + } + + /** + * Delete assigned certificate file after delete object + * + * @return Mage_Paypal_Model_Cert + */ + protected function _afterDelete() + { + $this->_removeOutdatedCertFile(); + return $this; + } +} diff --git a/app/code/core/Mage/Paypal/Model/Config.php b/app/code/core/Mage/Paypal/Model/Config.php index e0fe325624..a22bbed247 100644 --- a/app/code/core/Mage/Paypal/Model/Config.php +++ b/app/code/core/Mage/Paypal/Model/Config.php @@ -66,6 +66,8 @@ class Mage_Paypal_Model_Config */ const METHOD_PAYFLOWPRO = 'verisign'; + const METHOD_PAYFLOWLINK = 'payflow_link'; + const METHOD_BILLING_AGREEMENT = 'paypal_billing_agreement'; /** @@ -415,7 +417,7 @@ public function getMerchantCountry() { $countryCode = Mage::getStoreConfig($this->_mapGeneralFieldset('merchant_country'), $this->_storeId); if (!$countryCode) { - $countryCode = Mage::getStoreConfig('general/country/default', $this->_storeId); + $countryCode = Mage::helper('core')->getDefaultCountry($this->_storeId); } return $countryCode; } @@ -457,12 +459,14 @@ public function getCountryMethods($countryCode = null) self::METHOD_WPP_PE_DIRECT, self::METHOD_WPP_PE_EXPRESS, self::METHOD_PAYFLOWPRO, + self::METHOD_PAYFLOWLINK, ), 'CA' => array( self::METHOD_WPS, self::METHOD_WPP_DIRECT, self::METHOD_WPP_EXPRESS, self::METHOD_PAYFLOWPRO, + self::METHOD_PAYFLOWLINK, ), 'GB' => array( self::METHOD_WPS, @@ -475,11 +479,13 @@ public function getCountryMethods($countryCode = null) self::METHOD_WPS, self::METHOD_WPP_EXPRESS, self::METHOD_PAYFLOWPRO, + self::METHOD_PAYFLOWLINK, ), 'NZ' => array( self::METHOD_WPS, self::METHOD_WPP_EXPRESS, self::METHOD_PAYFLOWPRO, + self::METHOD_PAYFLOWLINK, ), 'DE' => array( self::METHOD_WPS, @@ -940,6 +946,7 @@ public static function getIsCreditCardMethod($code) case self::METHOD_WPP_DIRECT: case self::METHOD_WPP_PE_DIRECT: case self::METHOD_PAYFLOWPRO: + case self::METHOD_PAYFLOWLINK: return true; } return false; @@ -1146,6 +1153,7 @@ protected function _mapWppFieldset($fieldName) { switch ($fieldName) { + case 'api_authentication': case 'api_username': case 'api_password': case 'api_signature': @@ -1254,5 +1262,29 @@ protected function _mapMethodFieldset($fieldName) return null; } } + + /** + * Payment API authentication methods source getter + * + * @return array + */ + public function getApiAuthenticationMethods() + { + return array( + '0' => Mage::helper('paypal')->__('API Signature'), + '1' => Mage::helper('paypal')->__('API Certificate') + ); + } + + /** + * Api certificate getter + * + * @return string + */ + public function getApiCertificate() + { + $websiteId = Mage::app()->getStore($this->_storeId)->getWebsiteId(); + return Mage::getModel('paypal/cert')->loadByWebsite($websiteId, false)->getCertPath(); + } } diff --git a/app/code/core/Mage/Paypal/Model/Express/Checkout.php b/app/code/core/Mage/Paypal/Model/Express/Checkout.php index c8b27f8deb..b71722416c 100644 --- a/app/code/core/Mage/Paypal/Model/Express/Checkout.php +++ b/app/code/core/Mage/Paypal/Model/Express/Checkout.php @@ -619,7 +619,8 @@ protected function _getApi() */ protected function _prepareShippingOptions(Mage_Sales_Model_Quote_Address $address, $mayReturnEmpty = false) { - $options = array(); $i = 0; $iMin = false; $min = false; $iDefault = false; + $options = array(); $i = 0; $iMin = false; $min = false; + $userSelectedOption = null; foreach ($address->getGroupedAllShippingRates() as $group) { foreach ($group as $rate) { @@ -628,15 +629,16 @@ protected function _prepareShippingOptions(Mage_Sales_Model_Quote_Address $addre continue; } $isDefault = $address->getShippingMethod() === $rate->getCode(); - if ($isDefault) { - $iDefault = $i; - } + $options[$i] = new Varien_Object(array( 'is_default' => $isDefault, - 'name' => "{$rate->getCarrierTitle()} - {$rate->getMethodTitle()}", + 'name' => trim("{$rate->getCarrierTitle()} - {$rate->getMethodTitle()}", ' -'), 'code' => $rate->getCode(), 'amount' => $amount, )); + if ($isDefault) { + $userSelectedOption = $options[$i]; + } if (false === $min || $amount < $min) { $min = $amount; $iMin = $i; } @@ -644,20 +646,48 @@ protected function _prepareShippingOptions(Mage_Sales_Model_Quote_Address $addre } } - if ($mayReturnEmpty) { + if ($mayReturnEmpty && is_null($userSelectedOption)) { $options[] = new Varien_Object(array( - 'is_default' => (false === $iDefault ? true : false), - 'name' => 'N/A', + 'is_default' => true, + 'name' => Mage::helper('paypal')->__('N/A'), 'code' => 'no_rate', 'amount' => 0.00, )); - } elseif (false === $iDefault && isset($options[$iMin])) { + } elseif (is_null($userSelectedOption) && isset($options[$iMin])) { $options[$iMin]->setIsDefault(true); } + // Magento will transfer only first 10 cheapest shipping options if there are more than 10 available. + if (count($options) > 10) { + usort($options, array(get_class($this),'cmpShippingOptions')); + array_splice($options, 10); + // User selected option will be always included in options list + if (!is_null($userSelectedOption) && !in_array($userSelectedOption, $options)) { + $options[9] = $userSelectedOption; + } + } + return $options; } + /** + * Compare two shipping options based on their amounts + * + * This function is used as a callback comparison function in shipping options sorting process + * @see self::_prepareShippingOptions() + * + * @param Varien_Object $option1 + * @param Varien_Object $option2 + * @return integer + */ + protected static function cmpShippingOptions(Varien_Object $option1, Varien_Object $option2) + { + if ($option1->getAmount() == $option2->getAmount()) { + return 0; + } + return ($option1->getAmount() < $option2->getAmount()) ? -1 : 1; + } + /** * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates * This method was created only because PayPal has issues with returning the selected code. diff --git a/app/code/core/Mage/Paypal/Model/Info.php b/app/code/core/Mage/Paypal/Model/Info.php index 57c58c0a60..c02e184f8b 100644 --- a/app/code/core/Mage/Paypal/Model/Info.php +++ b/app/code/core/Mage/Paypal/Model/Info.php @@ -474,6 +474,7 @@ protected function _getAvsLabel($value) switch ($value) { // Visa, MasterCard, Discover and American Express case 'A': + case 'YN': return Mage::helper('paypal')->__('Matched Address only (no ZIP)'); case 'B': // international "A" return Mage::helper('paypal')->__('Matched Address only (no ZIP). International'); @@ -494,8 +495,10 @@ protected function _getAvsLabel($value) case 'I': return Mage::helper('paypal')->__('N/A. International Unavailable'); case 'Z': + case 'NY': return Mage::helper('paypal')->__('Matched five-digit ZIP only (no Address)'); case 'P': // international "Z" + case 'NY': return Mage::helper('paypal')->__('Matched Postal Code only (no Address)'); case 'R': return Mage::helper('paypal')->__('N/A. Retry'); diff --git a/app/code/core/Mage/Paypal/Model/Method/Agreement.php b/app/code/core/Mage/Paypal/Model/Method/Agreement.php index 9ff5458380..8b7f6d1a64 100644 --- a/app/code/core/Mage/Paypal/Model/Method/Agreement.php +++ b/app/code/core/Mage/Paypal/Model/Method/Agreement.php @@ -50,6 +50,7 @@ class Mage_Paypal_Model_Method_Agreement extends Mage_Sales_Model_Payment_Method protected $_canRefundInvoicePartial = true; protected $_canVoid = true; protected $_canUseCheckout = false; + protected $_canUseInternal = false; protected $_canFetchTransactionInfo = true; protected $_canReviewPayment = true; diff --git a/app/code/core/Mage/Paypal/Model/Mysql4/Cert.php b/app/code/core/Mage/Paypal/Model/Mysql4/Cert.php new file mode 100644 index 0000000000..7704fb320a --- /dev/null +++ b/app/code/core/Mage/Paypal/Model/Mysql4/Cert.php @@ -0,0 +1,78 @@ +_init('paypal/cert', 'cert_id'); + } + + /** + * Set date of last update + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Mysql4_Abstract + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + $object->setUpdatedAt(Mage::getSingleton('core/date')->gmtDate()); + return parent::_beforeSave($object); + } + + /** + * Load model by website id + * + * @param Mage_Paypal_Model_Cert $object + * @param bool $strictLoad + * @return Mage_Paypal_Model_Cert + */ + public function loadByWebsite($object, $strictLoad = true) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select()->from(array('main_table' => $this->getMainTable())); + + if ($strictLoad) { + $select->where('main_table.website_id =?', $object->getWebsiteId()); + } else { + $select->where('main_table.website_id IN(0, ?)', $object->getWebsiteId()) + ->order('main_table.website_id DESC') + ->limit(1); + } + + $data = $adapter->fetchRow($select); + if ($data) { + $object->setData($data); + } + return $object; + } +} diff --git a/app/code/core/Mage/Paypal/Model/Observer.php b/app/code/core/Mage/Paypal/Model/Observer.php index f0c7f0cd71..c1bebac9f6 100644 --- a/app/code/core/Mage/Paypal/Model/Observer.php +++ b/app/code/core/Mage/Paypal/Model/Observer.php @@ -53,4 +53,61 @@ public function fetchReports() Mage::logException($e); } } + + /** + * Save order into registry to use it in the overloaded controller. + * + * @param Varien_Event_Observer $observer + * @return Mage_Paypal_Model_Observer + */ + public function saveOrderAfterSubmit(Varien_Event_Observer $observer) + { + /* @var $order Mage_Sales_Model_Order */ + $order = $observer->getEvent()->getData('order'); + Mage::register('payflowlink_order', $order, true); + + return $this; + } + + /** + * Set data for response of frontend saveOrder action + * + * @param Varien_Event_Observer $observer + * @return Mage_Paypal_Model_Observer + */ + public function setResponseAfterSaveOrder(Varien_Event_Observer $observer) + { + /* @var $order Mage_Sales_Model_Order */ + $order = Mage::registry('payflowlink_order'); + + if ($order && $order->getId()) { + $payment = $order->getPayment(); + if ($payment && $payment->getMethod() == Mage::getModel('paypal/payflowlink')->getCode()) { + /* @var $controller Mage_Core_Controller_Varien_Action */ + $controller = $observer->getEvent()->getData('controller_action'); + $result = Mage::helper('core')->jsonDecode( + $controller->getResponse()->getBody('default'), + Zend_Json::TYPE_ARRAY + ); + + if (empty($result['error'])) { + $controller->loadLayout('checkout_onepage_review'); + $html = $controller->getLayout()->getBlock('root')->toHtml(); + /*$formBlock = $controller->getLayout() + ->createBlock('paypal/payflow_link_iframe'); + $html .= $formBlock->toHtml();*/ + $result['update_section'] = array( + 'name' => 'review', + 'html' => $html + ); + $result['redirect'] = false; + $result['success'] = false; + $controller->getResponse()->clearHeader('Location'); + $controller->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + } + } + + return $this; + } } diff --git a/app/code/core/Mage/Paypal/Model/Payflow/Request.php b/app/code/core/Mage/Paypal/Model/Payflow/Request.php new file mode 100644 index 0000000000..5f0fec644d --- /dev/null +++ b/app/code/core/Mage/Paypal/Model/Payflow/Request.php @@ -0,0 +1,77 @@ + + */ + +class Mage_Paypal_Model_Payflow_Request extends Varien_Object +{ + /** + * Set/Get attribute wrapper + * Also add length path if key contains = or & + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + $key = $this->_underscore(substr($method,3)); + if (isset($args[0]) && (strstr($args[0], '=') || strstr($args[0], '&'))) { + $key .= '[' . strlen($args[0]) . ']'; + } + switch (substr($method, 0, 3)) { + case 'get' : + //Varien_Profiler::start('GETTER: '.get_class($this).'::'.$method); + $data = $this->getData($key, isset($args[0]) ? $args[0] : null); + //Varien_Profiler::stop('GETTER: '.get_class($this).'::'.$method); + return $data; + + case 'set' : + //Varien_Profiler::start('SETTER: '.get_class($this).'::'.$method); + $result = $this->setData($key, isset($args[0]) ? $args[0] : null); + //Varien_Profiler::stop('SETTER: '.get_class($this).'::'.$method); + return $result; + + case 'uns' : + //Varien_Profiler::start('UNS: '.get_class($this).'::'.$method); + $result = $this->unsetData($key); + //Varien_Profiler::stop('UNS: '.get_class($this).'::'.$method); + return $result; + + case 'has' : + //Varien_Profiler::start('HAS: '.get_class($this).'::'.$method); + //Varien_Profiler::stop('HAS: '.get_class($this).'::'.$method); + return isset($this->_data[$key]); + } + throw new Varien_Exception("Invalid method ".get_class($this)."::".$method."(".print_r($args,1).")"); + } +} diff --git a/app/code/core/Mage/Paypal/Model/Payflowlink.php b/app/code/core/Mage/Paypal/Model/Payflowlink.php new file mode 100644 index 0000000000..54e80d52b6 --- /dev/null +++ b/app/code/core/Mage/Paypal/Model/Payflowlink.php @@ -0,0 +1,432 @@ + + */ + +class Mage_Paypal_Model_Payflowlink extends Mage_Paypal_Model_Payflowpro +{ + /** + * Payment method code + */ + protected $_code = Mage_Paypal_Model_Config::METHOD_PAYFLOWLINK; + + protected $_formBlockType = 'paypal/payflow_link_form'; + protected $_infoBlockType = 'paypal/payflow_link_info'; + + /** + * Availability options + */ + protected $_canUseInternal = false; + protected $_canUseForMultishipping = false; + protected $_isInitializeNeeded = true; + + /** + * Request & response model + * @var Mage_Paypal_Model_Payflow_Request + */ + protected $_response; + + /** + * Gateway request URL + * @var string + */ + const TRANSACTION_PAYFLOW_URL = 'https://payflowlink.paypal.com/'; + + /** + * Error message + * @var string + */ + const RESPONSE_ERROR_MSG = 'Payment error. %s was not found.'; + + /** + * Do not validate payment form using server methods + * + * @return bool + */ + public function validate() + { + return true; + } + + /** + * Check whether payment method can be used + * + * @param Mage_Sales_Model_Quote + * @return bool + */ + public function isAvailable($quote = null) + { + $storeId = Mage::app()->getStore($this->getStore())->getId(); + $config = Mage::getModel('paypal/config')->setStoreId($storeId); + if ($config->isMethodAvailable($this->getCode()) && + Mage_Payment_Model_Method_Abstract::isAvailable($quote)) { + return true; + } + return false; + } + + /** + * Instantiate state and set it to state object + * + * @param string $paymentAction + * @param Varien_Object $stateObject + */ + public function initialize($paymentAction, $stateObject) + { + switch ($paymentAction) { + case Mage_Paypal_Model_Config::PAYMENT_ACTION_AUTH: + case Mage_Paypal_Model_Config::PAYMENT_ACTION_SALE: + $payment = $this->getInfoInstance(); + $order = $payment->getOrder(); + $order->setCanSendNewEmailFlag(false); + $payment->setAmountAuthorized($order->getTotalDue()); + $payment->setBaseAmountAuthorized($order->getBaseTotalDue()); + + $request = $this->_buildTokenRequest($payment); + $response = $this->_postRequest($request); + $this->_processTokenErrors($response, $payment); + + $order = $payment->getOrder(); + $order->setCanSendNewEmailFlag(false); + + $stateObject->setState(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT); + $stateObject->setStatus('pending_payment'); + $stateObject->setIsNotified(false); + break; + default: + break; + } + } + + /** + * Return response model. + * + * @return Mage_Mage_Paypal_Model_Payflow_Request + */ + public function getResponse() + { + if (!$this->_response) { + $this->_response = Mage::getModel('paypal/payflow_request'); + } + + return $this->_response; + } + + /** + * Fill response with data. + * + * @param array $postData + * @return Mage_Paypal_Model_Payflowlink + */ + public function setResponseData(array $postData) + { + foreach ($postData as $key => $val) { + $this->getResponse()->setData(strtolower($key), $val); + } + return $this; + } + + /** + * Operate with order using data from $_POST which came from Silent Post Url. + * + * @param array $responseData + * @throws Mage_Core_Exception in case of validation error or order creation error + */ + public function process($responseData) + { + $debugData = array( + 'response' => $responseData + ); + $this->_debug($debugData); + + $this->setResponseData($responseData); + + $order = $this->_getOrderFromResponse(); + $this->_processOrder($order); + } + + /** + * Operate with order using information from silent post + * + * @param Mage_Sales_Model_Order $order + */ + protected function _processOrder(Mage_Sales_Model_Order $order) + { + $response = $this->getResponse(); + $payment = $order->getPayment(); + $payment->setTransactionId($response->getPnref()) + ->setIsTransactionClosed(0); + $canSendNewOrderEmail = true; + + if ($response->getResult() == self::RESPONSE_CODE_FRAUDSERVICE_FILTER || + $response->getResult() == self::RESPONSE_CODE_DECLINED_BY_FILTER) { + $canSendNewOrderEmail = false; + $fraudMessage = $this->_getFraudMessage() ? + $response->getFraudMessage() : $response->getRespmsg(); + $payment->setIsTransactionPending(true) + ->setIsFraudDetected(true) + ->setAdditionalInformation('paypal_fraud_filters', $fraudMessage); + } + + if ($response->getAvsdata() && strstr(substr($response->getAvsdata(), 0, 2), 'N')) { + $payment->setAdditionalInformation('paypal_avs_code', substr($response->getAvsdata(), 0, 2)); + } + if ($response->getCvv2match() && $response->getCvv2match() != 'Y') { + $payment->setAdditionalInformation('paypal_cvv2_match', $response->getCvv2match()); + } + + switch ($response->getType()){ + case self::TRXTYPE_AUTH_ONLY: + $payment->registerAuthorizationNotification($payment->getBaseAmountAuthorized()); + break; + case self::TRXTYPE_SALE: + $payment->registerCaptureNotification($payment->getBaseAmountAuthorized()); + break; + } + $order->save(); + + try { + if ($canSendNewOrderEmail) { + $order->sendNewOrderEmail(); + } + Mage::getModel('sales/quote') + ->load($order->getQuoteId()) + ->setIsActive(false) + ->save(); + } catch (Exception $e) { + Mage::throwException(Mage::helper('paypal')->__('Can not send new order email.')); + } + } + + /** + * Get fraud message from response + * + * @return string|bool + */ + protected function _getFraudMessage() + { + if ($this->getResponse()->getFpsPrexmldata()) { + $xml = new SimpleXMLElement($this->getResponse()->getFpsPrexmldata()); + $this->getResponse()->setFraudMessage((string) $xml->rule->triggeredMessage); + return $this->getResponse()->getFraudMessage(); + } + + return false; + } + + /** + * Check response from Payflow gateway. + * + * @return Mage_Sales_Model_Order in case of validation passed + * @throws Mage_Core_Exception in other cases + */ + protected function _getOrderFromResponse() + { + $response = $this->getResponse(); + + $order = Mage::getModel('sales/order') + ->loadByIncrementId($response->getInvoice()); + + if ($response->getResult() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER && + $response->getResult() != self::RESPONSE_CODE_DECLINED_BY_FILTER && + $response->getResult() != self::RESPONSE_CODE_APPROVED) { + if ($order->getState() != Mage_Sales_Model_Order::STATE_CANCELED) { + $order->registerCancellation($response->getRespmsg())->save(); + } + Mage::throwException($response->getRespmsg()); + } + + $amountCompared = ($response->getAmount() == + $order->getPayment()->getBaseAmountAuthorized()) ? true : false; + if (!$order->getId() || + $order->getState() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT || + !$amountCompared) { + Mage::throwException($this->_formatStr(self::RESPONSE_ERROR_MSG, 'Order')); + } + + $fetchData = $this->fetchTransactionInfo($order->getPayment(), $response->getPnref()); + if (!isset($fetchData['custref']) || $fetchData['custref'] != $order->getIncrementId()) { + Mage::throwException($this->_formatStr(self::RESPONSE_ERROR_MSG, 'Transaction')); + } + if (isset($fetchData['cardtype'])) { + $order->getPayment()->setCcType($fetchData['cardtype']); + } + + return $order; + } + + /** + * Build request for getting token + * + * @param Mage_Sales_Model_Order_Payment $payment + * @return Varien_Object + */ + protected function _buildTokenRequest(Varien_Object $payment) + { + $request = $this->_buildBasicRequest($payment); + $request->setCreatesecuretoken('Y') + ->setSecuretokenid($this->_generateSecureTokenId()) + ->setTrxtype($this->_getTrxTokenType()) + ->setAmt($this->_formatStr('%.2F', $payment->getOrder()->getBaseTotalDue())) + ->setCurrency($payment->getOrder()->getBaseCurrencyCode()) + ->setInvnum($payment->getOrder()->getIncrementId()) + ->setCustref($payment->getOrder()->getIncrementId()) + ->setPonum($payment->getOrder()->getId()) + ->setSubtotal($payment->getOrder()->getBaseSubtotal()) + ->setTaxamt($this->_formatStr('%.2F', $payment->getOrder()->getBaseTaxAmount())) + ->setFreightamt($this->_formatStr('%.2F', $payment->getOrder()->getBaseShippingAmount())); + + $order = $payment->getOrder(); + if (empty($order)) { + return $request; + } + + $billing = $order->getBillingAddress(); + if (!empty($billing)) { + $request->setFirstname($billing->getFirstname()) + ->setLastname($billing->getLastname()) + ->setStreet(implode(' ', $billing->getStreet())) + ->setCity($billing->getCity()) + ->setState($billing->getRegionCode()) + ->setZip($billing->getPostcode()) + ->setCountry($billing->getCountry()) + ->setEmail($payment->getOrder()->getCustomerEmail()); + } + $shipping = $order->getShippingAddress(); + if (!empty($shipping)) { + $this->_applyCountryWorkarounds($shipping); + $request->setShiptofirstname($shipping->getFirstname()) + ->setShiptolastname($shipping->getLastname()) + ->setShiptostreet(implode(' ', $shipping->getStreet())) + ->setShiptocity($shipping->getCity()) + ->setShiptostate($shipping->getRegionCode()) + ->setShiptozip($shipping->getPostcode()) + ->setShiptocountry($shipping->getCountry()); + } + //pass store Id to request + $request->setUser1($order->getStoreId()); + + return $request; + } + + /** + * Get store id from response if exists + * or default + * + * @return int + */ + protected function _getStoreId() + { + $response = $this->getResponse(); + if ($response->getUser1()) { + return (int) $response->getUser1(); + } + + return Mage::app()->getStore($this->getStore())->getId(); + } + + /** + * Return request object with basic information for gateway request + * + * @param Varien_Object $payment + * @return Mage_Paypal_Model_Payflow_Request + */ + protected function _buildBasicRequest(Varien_Object $payment) + { + $request = Mage::getModel('paypal/payflow_request'); + $request + ->setUser($this->getConfigData('user', $this->_getStoreId())) + ->setVendor($this->getConfigData('vendor', $this->_getStoreId())) + ->setPartner($this->getConfigData('partner', $this->_getStoreId())) + ->setPwd($this->getConfigData('pwd', $this->_getStoreId())) + ->setVerbosity($this->getConfigData('verbosity', $this->_getStoreId())) + ->setTender(self::TENDER_CC); + return $request; + } + + /** + * Get payment action code + * + * @return string + */ + protected function _getTrxTokenType() + { + switch ($this->getConfigData('payment_action')) { + case Mage_Paypal_Model_Config::PAYMENT_ACTION_AUTH: + return self::TRXTYPE_AUTH_ONLY; + case Mage_Paypal_Model_Config::PAYMENT_ACTION_SALE: + return self::TRXTYPE_SALE; + } + } + + /** + * Return unique value for secure token id + * + * @return string + */ + protected function _generateSecureTokenId() + { + return Mage::helper('core')->uniqHash(); + } + + /** + * Format values + * + * @param mixed $format + * @param mixed $string + * @return mixed + */ + protected function _formatStr($format, $string) + { + return sprintf($format, $string); + } + + /** + * If response is failed throw exception + * Set token data in payment object + * + * @param Varien_Object $response + * @param Mage_Sales_Model_Order_Payment $payment + * @throws Mage_Core_Exception + */ + protected function _processTokenErrors($response, $payment) + { + if (!$response->getSecuretoken() && + $response->getResult() != self::RESPONSE_CODE_APPROVED + && $response->getResult() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER) { + Mage::throwException($response->getRespmsg()); + } else { + $payment->setAdditionalInformation('secure_token_id', $response->getSecuretokenid()) + ->setAdditionalInformation('secure_token', $response->getSecuretoken()); + } + } +} diff --git a/app/code/core/Mage/Paypal/Model/System/Config/Backend/Cert.php b/app/code/core/Mage/Paypal/Model/System/Config/Backend/Cert.php new file mode 100644 index 0000000000..0e22065037 --- /dev/null +++ b/app/code/core/Mage/Paypal/Model/System/Config/Backend/Cert.php @@ -0,0 +1,69 @@ +getValue(); + if (is_array($value) && !empty($value['delete'])) { + $this->setValue(''); + Mage::getModel('paypal/cert')->loadByWebsite($this->getScopeId())->delete(); + } + + $tmpPath = $_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']; + if ($tmpPath && file_exists($tmpPath)) { + if (!filesize($tmpPath)) { + Mage::throwException(Mage::helper('paypal')->__('PayPal certificate file is empty.')); + } + $this->setValue($_FILES['groups']['name'][$this->getGroupId()]['fields'][$this->getField()]['value']); + $content = Mage::helper('core')->encrypt(file_get_contents($tmpPath)); + Mage::getModel('paypal/cert')->loadByWebsite($this->getScopeId()) + ->setContent($content) + ->save(); + } + return $this; + } + + /** + * Process object after delete data + * + * @return Mage_Paypal_Model_System_Config_Backend_Cert + */ + protected function _afterDelete() + { + Mage::getModel('paypal/cert')->loadByWebsite($this->getScopeId())->delete(); + return $this; + } +} diff --git a/app/code/core/Mage/Paypal/Model/System/Config/Backend/MerchantCountry.php b/app/code/core/Mage/Paypal/Model/System/Config/Backend/MerchantCountry.php index b601be5871..433e073410 100644 --- a/app/code/core/Mage/Paypal/Model/System/Config/Backend/MerchantCountry.php +++ b/app/code/core/Mage/Paypal/Model/System/Config/Backend/MerchantCountry.php @@ -31,6 +31,7 @@ class Mage_Paypal_Model_System_Config_Backend_MerchantCountry extends Mage_Core_ { /** * Config path to default country + * @deprecated since 1.4.1.0 * @var string */ const XML_PATH_COUNTRY_DEFAULT = 'general/country/default'; @@ -43,9 +44,10 @@ protected function _afterLoad() $value = (string)$this->getValue(); if (empty($value)) { if ($this->getWebsite()) { - $defaultCountry = Mage::app()->getWebsite($this->getWebsite())->getConfig(self::XML_PATH_COUNTRY_DEFAULT); + $defaultCountry = Mage::app()->getWebsite($this->getWebsite()) + ->getConfig(Mage_Core_Helper_Data::XML_PATH_DEFAULT_COUNTRY); } else { - $defaultCountry = Mage::getStoreConfig(self::XML_PATH_COUNTRY_DEFAULT, $this->getStore()); + $defaultCountry = Mage::helper('core')->getDefaultCountry($this->getStore()); } $this->setValue($defaultCountry); } diff --git a/app/code/core/Mage/Paypal/controllers/PayflowController.php b/app/code/core/Mage/Paypal/controllers/PayflowController.php new file mode 100644 index 0000000000..be926ee115 --- /dev/null +++ b/app/code/core/Mage/Paypal/controllers/PayflowController.php @@ -0,0 +1,157 @@ + + */ +class Mage_Paypal_PayflowController extends Mage_Core_Controller_Front_Action +{ + /** + * When a customer cancel payment from payflow gateway. + */ + public function cancelPaymentAction() + { + $gotoSection = $this->_cancelPayment(); + $redirectBlock = $this->_getIframeBlock() + ->setGotoSection($gotoSection) + ->setTemplate('paypal/payflow/link/redirect.phtml'); + $this->getResponse()->setBody($redirectBlock->toHtml()); + } + + /** + * When a customer return to website from payflow gateway. + */ + public function returnUrlAction() + { + $redirectBlock = $this->_getIframeBlock() + ->setTemplate('paypal/payflow/link/redirect.phtml'); + + $session = $this->_getCheckout(); + if ($session->getLastRealOrderId()) { + $order = Mage::getModel('sales/order')->loadByIncrementId($session->getLastRealOrderId()); + + if ($order && $order->getIncrementId() == $session->getLastRealOrderId()) { + if ($order->getState() == Mage_Sales_Model_Order::STATE_PROCESSING) { + $session->unsLastRealOrderId(); + $redirectBlock->setGotoSuccessPage(true); + } else { + $gotoSection = $this->_cancelPayment(strval($this->getRequest()->getParam('RESPMSG'))); + $redirectBlock->setGotoSection($gotoSection); + $redirectBlock->setErrorMsg($this->__('Payment has been declined. Please try again.')); + } + } + } + + $this->getResponse()->setBody($redirectBlock->toHtml()); + } + + /** + * Cancel order, return quote to customer + * + * @param string $errorMsg + * @return mixed + */ + protected function _cancelPayment($errorMsg = '') + { + $gotoSection = false; + $session = $this->_getCheckout(); + if ($session->getLastRealOrderId()) { + $order = Mage::getModel('sales/order')->loadByIncrementId($session->getLastRealOrderId()); + if ($order->getId()) { + //Cancel order + if ($order->getState() != Mage_Sales_Model_Order::STATE_CANCELED) { + $order->registerCancellation($errorMsg)->save(); + } + $quote = Mage::getModel('sales/quote') + ->load($order->getQuoteId()); + //Return quote + if ($quote->getId()) { + $quote->setIsActive(1) + ->setReservedOrderId(NULL) + ->save(); + $session->replaceQuote($quote); + } + //Unset data + $session->unsLastRealOrderId(); + //Redirect to payment step + $gotoSection = 'payment'; + } + } + + return $gotoSection; + } + + /** + * Submit transaction to Payflow getaway into iframe + */ + public function formAction() + { + $this->getResponse() + ->setBody($this->_getIframeBlock()->toHtml()); + } + + /** + * Get response from PayPal by silent post method + */ + public function silentPostAction() + { + $data = $this->getRequest()->getPost(); + if (isset($data['INVOICE'])) { + $paymentModel = Mage::getModel('paypal/payflowlink'); + try { + $paymentModel->process($data); + } catch (Exception $e) { + Mage::logException($e); + } + } + } + + /** + * Get frontend checkout session object + * + * @return Mage_Checkout_Model_Session + */ + protected function _getCheckout() + { + return Mage::getSingleton('checkout/session'); + } + + /** + * Get iframe block + * + * @return Mage_Paypal_Block_Payflow_Link_Iframe + */ + protected function _getIframeBlock() + { + $this->loadLayout('paypal_payflow_link_iframe'); + return $this->getLayout() + ->getBlock('payflow.link.iframe'); + } +} diff --git a/app/code/core/Mage/Paypal/etc/config.xml b/app/code/core/Mage/Paypal/etc/config.xml index 096258cf33..42d47fa4da 100644 --- a/app/code/core/Mage/Paypal/etc/config.xml +++ b/app/code/core/Mage/Paypal/etc/config.xml @@ -28,7 +28,7 @@ - 1.4.0.1 + 1.4.0.2 @@ -43,6 +43,7 @@ paypal_api_debug
    paypal_settlement_report
    paypal_settlement_report_row
    + paypal_cert
    @@ -86,6 +87,46 @@ PayPal
    + + + + + + 0 + Visa + + + 1 + MasterCard + + + 2 + Discover + + + 3 + American Express + + + 4 + Diners Club + + + 5 + JCB + + + 8 + Other + + + S + Solo + + + + + @@ -120,6 +161,24 @@ paypal/express/callbackshippingoptions + + + + + paypal/observer + saveOrderAfterSubmit + + + + + + + paypal/observer + setResponseAfterSaveOrder + + + + @@ -141,6 +200,7 @@ + 0 dynamic @@ -215,6 +275,14 @@ PayPal Billing Agreement paypal + + paypal/payflowlink + Payflow Link + Authorization + HIGH + + paypal +
    diff --git a/app/code/core/Mage/Paypal/etc/system.xml b/app/code/core/Mage/Paypal/etc/system.xml index a53ba3ba94..cae84bee84 100644 --- a/app/code/core/Mage/Paypal/etc/system.xml +++ b/app/code/core/Mage/Paypal/etc/system.xml @@ -153,9 +153,23 @@ 1 1 + + + Quick set-up service lets your customers securely complete transactions. + + payment/payflow_link/active + checkbox + 50 + 1 + 1 + - + + paypal/adminhtml_system_config_fieldset_store + 5 + 1 + paypal-config @@ -164,6 +178,15 @@ 1 1 + + + paypal/wpp/api_authentication + select + paypal/config::getApiAuthenticationMethods + 1 + 1 + 1 + paypal/wpp/api_username @@ -190,7 +213,18 @@ 15 1 1 + 0 + + + paypal/wpp/api_cert + file + paypal/system_config_backend_cert + 17 + 1 + 1 + 1 + Get Credentials from PayPal @@ -319,7 +353,7 @@ payment/paypal_express/transfer_shipping_options If this option is enabled, customer can change shipping address and shipping method on PayPal website. In live mode works via HTTPS protocol only. - Does not work if there are more than 10 shipping options available. + Notice that PayPal can handle up to 10 shipping options. That is why Magento will transfer only first 10 cheapest shipping options if there are more than 10 available. select adminhtml/system_config_source_yesno 40 @@ -931,7 +965,7 @@ 1 - + @@ -1407,6 +1441,156 @@ + + + + text + 80 + 1 + 1 + 1 + + + paypal/adminhtml_system_config_payflowlink_info + 1 + 1 + 1 + 22 + + + <label>Title</label> + <comment>It is recommended to set this value to "Debit or Credit Card" per store views.</comment> + <config_path>payment/payflow_link/title</config_path> + <frontend_type>text</frontend_type> + <sort_order>5</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + + + + payment/payflow_link/sort_order + text + 10 + 1 + 1 + 1 + + + + payment/payflow_link/payment_action + select + paypal/config::getPaymentActions + 15 + 1 + 1 + + + + payment/payflow_link/allowspecific + select + adminhtml/system_config_source_payment_allspecificcountries + 20 + 1 + 1 + + + + payment/payflow_link/specificcountry + multiselect + paypal/system_config_source_buyerCountry + 25 + 1 + 1 + 1 + + + + payment/payflow_link/debug + select + adminhtml/system_config_source_yesno + 30 + 1 + 1 + + + + adminhtml/system_config_form_field_heading + 35 + 1 + 1 + + + + payment/payflow_link/partner + text + 40 + 1 + 1 + + + + text + payment/payflow_link/user + 45 + 1 + 1 + + + + payment/payflow_link/vendor + text + 50 + 1 + 1 + + + + payment/payflow_link/pwd + obscure + adminhtml/system_config_backend_encrypted + 55 + 1 + 1 + + + + payment/payflow_link/sandbox_flag + select + adminhtml/system_config_source_yesno + 60 + 1 + 1 + + + + payment/payflow_link/use_proxy + select + adminhtml/system_config_source_yesno + 65 + 1 + 1 + + + + payment/payflow_link/proxy_host + text + 70 + 1 + 1 + 1 + + + + payment/payflow_link/proxy_port + text + 75 + 1 + 1 + 1 + + + diff --git a/app/code/core/Mage/Paypal/sql/paypal_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php b/app/code/core/Mage/Paypal/sql/paypal_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php new file mode 100644 index 0000000000..131a494833 --- /dev/null +++ b/app/code/core/Mage/Paypal/sql/paypal_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php @@ -0,0 +1,47 @@ +run(" +CREATE TABLE `{$installer->getTable('paypal/cert')}` ( + `cert_id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT, + `website_id` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0', + `content` MEDIUMBLOB NOT NULL, + `updated_at` datetime default NULL, + PRIMARY KEY (`cert_id`), + KEY `IDX_PAYPAL_CERT_WEBSITE` (`website_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +"); + +$installer->getConnection()->addConstraint( + 'FK_PAYPAL_CERT_WEBSITE', + $this->getTable('paypal/cert'), + 'website_id', + $this->getTable('core/website'), + 'website_id' +); diff --git a/app/code/core/Mage/PaypalUk/Model/Api/Nvp.php b/app/code/core/Mage/PaypalUk/Model/Api/Nvp.php index 3d8db9dcc8..fda8583acd 100644 --- a/app/code/core/Mage/PaypalUk/Model/Api/Nvp.php +++ b/app/code/core/Mage/PaypalUk/Model/Api/Nvp.php @@ -280,6 +280,15 @@ class Mage_PaypalUk_Model_Api_Nvp extends Mage_Paypal_Model_Api_Nvp 'PAYMENTSTATUS', 'PENDINGREASON', 'PROTECTIONELIGIBILITY', 'EMAIL', ); + /** + * Required fields in the response + * + * @var array + */ + protected $_requiredResponseParams = array( + self::DO_DIRECT_PAYMENT => array('RESULT', 'PNREF', 'PPREF') + ); + /** * API endpoint getter * diff --git a/app/code/core/Mage/PaypalUk/Model/Express.php b/app/code/core/Mage/PaypalUk/Model/Express.php index 27bc796dc0..b24c6477c1 100644 --- a/app/code/core/Mage/PaypalUk/Model/Express.php +++ b/app/code/core/Mage/PaypalUk/Model/Express.php @@ -62,10 +62,10 @@ public function isAvailable($quote = null) if (!$this->_ecInstance) { $this->_ecInstance = Mage::helper('payment')->getMethodInstance(Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS); } - if ($quote) { + if ($quote && $this->_ecInstance) { $this->_ecInstance->setStore($quote->getStoreId()); } - return !$this->_ecInstance->isAvailable(); + return $this->_ecInstance ? !$this->_ecInstance->isAvailable() : false; } /** diff --git a/app/code/core/Mage/Reports/Block/Product/Abstract.php b/app/code/core/Mage/Reports/Block/Product/Abstract.php index d5f077e0e6..6256af0ac3 100644 --- a/app/code/core/Mage/Reports/Block/Product/Abstract.php +++ b/app/code/core/Mage/Reports/Block/Product/Abstract.php @@ -113,6 +113,10 @@ public function getItemsCollection() ->addUrlRewrite() ->setPageSize($this->getPageSize()) ->setCurPage(1); + + /* Price data is added to consider item stock status using price index */ + $this->_collection->addPriceData(); + $ids = $this->getProductIds(); if (empty($ids)) { $this->_collection->addIndexFilter(); diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Accounts/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Accounts/Collection.php index a9c02549f3..f00ec187bd 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Accounts/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Accounts/Collection.php @@ -51,13 +51,17 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter to collection + * + * @param array $setStoreIds + * @return Mage_Reports_Model_Mysql4_Accounts_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->addAttributeToFilter('store_id', array('in' => (array)$storeIds)); } - return $this; } } diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Customer/Orders/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Customer/Orders/Collection.php index f3f67d30ae..fec272e53f 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Customer/Orders/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Customer/Orders/Collection.php @@ -50,10 +50,15 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter to collection + * + * @param array $setStoreIds + * @return Mage_Reports_Model_Mysql4_Customer_Orders_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->addAttributeToFilter('store_id', array('in' => (array)$storeIds)); $this->addSumAvgTotals(1) ->orderByOrdersCount(); diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Customer/Totals/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Customer/Totals/Collection.php index 0024cecad0..a2b7a92a33 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Customer/Totals/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Customer/Totals/Collection.php @@ -50,10 +50,14 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter collection + * @param array $storeIds + * @return Mage_Reports_Model_Mysql4_Customer_Totals_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->addAttributeToFilter('store_id', array('in' => (array)$storeIds)); $this->addSumAvgTotals(1) ->orderByTotalAmount(); diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Invoiced/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Invoiced/Collection.php index 6319575301..e94ebd34ae 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Invoiced/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Invoiced/Collection.php @@ -52,10 +52,14 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter collection + * @param array $storeIds + * @return Mage_Reports_Model_Mysql4_Invoiced_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->addAttributeToFilter('store_id', array('in' => (array)$storeIds)) ->addExpressionAttributeToSelect( 'invoiced', diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php index dccbcc6b7a..e305b2a272 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Order/Collection.php @@ -423,10 +423,14 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter collection + * @param array $storeIds + * @return Mage_Reports_Model_Mysql4_Order_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->getSelect()->columns(array( 'subtotal' => 'SUM(main_table.base_subtotal)', 'tax' => 'SUM(main_table.base_tax_amount)', @@ -523,8 +527,17 @@ public function addSumAvgTotals($storeId = 0) * calculate average and total amount */ $expr = ($storeId == 0) - ? '(main_table.base_subtotal-IFNULL(main_table.base_subtotal_refunded,0)-IFNULL(main_table.base_subtotal_canceled,0))*main_table.base_to_global_rate' - : 'main_table.base_subtotal-IFNULL(main_table.base_subtotal_canceled,0)-IFNULL(main_table.base_subtotal_refunded,0)'; + ? '(main_table.base_subtotal + - IFNULL(main_table.base_subtotal_refunded, 0) + - IFNULL(main_table.base_subtotal_canceled, 0) + - ABS(main_table.base_discount_amount) + - IFNULL(main_table.base_discount_canceled, 0) + ) * main_table.base_to_global_rate' + : 'main_table.base_subtotal + - IFNULL(main_table.base_subtotal_canceled, 0) + - IFNULL(main_table.base_subtotal_refunded, 0) + - ABS(main_table.base_discount_amount) + - IFNULL(main_table.base_discount_canceled, 0)'; $this->getSelect() ->columns(array("orders_avg_amount" => "AVG({$expr})")) diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php index bd57e3f50a..356cec5764 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Product/Sold/Collection.php @@ -52,18 +52,40 @@ public function setDateRange($from, $to) } /** - * Set Store filter to collection + * Set store filter to collection * * @param array $storeIds * @return Mage_Reports_Model_Mysql4_Product_Sold_Collection */ public function setStoreIds($storeIds) { - if (!empty($storeIds[0])) { - $storeIds = array_values($storeIds); - $this->getSelect()->where('order_items.store_id IN(?)', $storeIds); + if ($storeIds) { + $this->getSelect()->where('order_items.store_id IN (?)', (array)$storeIds); } - + return $this; + } + + /** + * Add website product limitation + * + * @return Mage_Reports_Model_Mysql4_Product_Sold_Collection + */ + protected function _productLimitationJoinWebsite() + { + $filters = $this->_productLimitationFilters; + $conditions = array( + 'product_website.product_id=e.entity_id' + ); + if (isset($filters['website_ids'])) { + $conditions[] = $this->getConnection() + ->quoteInto('product_website.website_id IN(?)', $filters['website_ids']); + + $subQuery = $this->getConnection()->select() + ->from(array('product_website' => $this->getTable('catalog/product_website')), array('product_website.product_id')) + ->where(join(' AND ', $conditions)); + $this->getSelect()->where('e.entity_id IN( '.$subQuery.' )'); + } + return $this; } } diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Refunded/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Refunded/Collection.php index c0ba7a919e..1e81e9d2b7 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Refunded/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Refunded/Collection.php @@ -50,10 +50,15 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter to collection + * + * @param array $setStoreIds + * @return Mage_Reports_Model_Mysql4_Refunded_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->addAttributeToFilter('store_id', array('in' => (array)$storeIds)) ->addExpressionAttributeToSelect( 'refunded', diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Shipping/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Shipping/Collection.php index 3b11c55956..91f527d0a4 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Shipping/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Shipping/Collection.php @@ -49,10 +49,15 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter to collection + * + * @param array $setStoreIds + * @return Mage_Reports_Model_Mysql4_Shipping_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->addAttributeToFilter('store_id', array('in' => (array)$storeIds)); $this->addExpressionAttributeToSelect('total', 'SUM({{base_shipping_amount}})', diff --git a/app/code/core/Mage/Reports/Model/Mysql4/Tax/Collection.php b/app/code/core/Mage/Reports/Model/Mysql4/Tax/Collection.php index 5fe36d6eae..49e6b63c12 100644 --- a/app/code/core/Mage/Reports/Model/Mysql4/Tax/Collection.php +++ b/app/code/core/Mage/Reports/Model/Mysql4/Tax/Collection.php @@ -55,10 +55,15 @@ public function setDateRange($from, $to) return $this; } + /** + * Set store filter to collection + * + * @param array $setStoreIds + * @return Mage_Reports_Model_Mysql4_Tax_Collection + */ public function setStoreIds($storeIds) { - $vals = array_values($storeIds); - if (count($storeIds) >= 1 && $vals[0] != '') { + if ($storeIds) { $this->getSelect() ->where('e.store_id in (?)', (array)$storeIds) ->columns(array('tax'=>'SUM(tax_table.base_real_amount)')); diff --git a/app/code/core/Mage/Review/Block/Product/View.php b/app/code/core/Mage/Review/Block/Product/View.php index 1cb66ac719..79d6b15968 100644 --- a/app/code/core/Mage/Review/Block/Product/View.php +++ b/app/code/core/Mage/Review/Block/Product/View.php @@ -35,6 +35,11 @@ class Mage_Review_Block_Product_View extends Mage_Catalog_Block_Product_View { protected $_reviewsCollection; + /** + * Render block HTML + * + * @return string + */ protected function _toHtml() { $this->getProduct()->setShortDescription(null); diff --git a/app/code/core/Mage/Review/Model/Mysql4/Review/Product/Collection.php b/app/code/core/Mage/Review/Model/Mysql4/Review/Product/Collection.php index 45ed47e254..58ac97475e 100644 --- a/app/code/core/Mage/Review/Model/Mysql4/Review/Product/Collection.php +++ b/app/code/core/Mage/Review/Model/Mysql4/Review/Product/Collection.php @@ -237,18 +237,22 @@ public function addAttributeToFilter($attribute, $condition=null, $joinType='inn break; case 'type': if($condition == 1) { - $this->getSelect()->where('rdt.customer_id = 0'); + $this->getSelect() + ->where('rdt.customer_id IS NULL AND rdt.store_id = ?', Mage_Core_Model_App::ADMIN_STORE_ID); } elseif ($condition == 2) { $this->getSelect()->where('rdt.customer_id > 0'); } else { - $this->getSelect()->where('rdt.customer_id IS NULL'); + $this->getSelect() + ->where('rdt.customer_id IS NULL AND rdt.store_id <> ?', Mage_Core_Model_App::ADMIN_STORE_ID); } + return $this; break; default: parent::addAttributeToFilter($attribute, $condition, $joinType); } + return $this; } diff --git a/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php b/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php index fc6c61a0f1..206bc6bbfe 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php +++ b/app/code/core/Mage/Rss/Block/Catalog/NotifyStock.php @@ -58,16 +58,16 @@ protected function _toHtml() $rssObj = Mage::getModel('rss/rss'); $data = array('title' => $title, - 'description' => $title, - 'link' => $newurl, - 'charset' => 'UTF-8', - ); + 'description' => $title, + 'link' => $newurl, + 'charset' => 'UTF-8', + ); $rssObj->_addHeader($data); $_configManageStock = (int)Mage::getStoreConfigFlag(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_MANAGE_STOCK); $stockItemWhere = "({{table}}.low_stock_date is not null) " . " and ({{table}}.low_stock_date>'0000-00-00') " - . " and IF({{table}}.use_config_manage_stock=1," . $_configManageStock . ",{{table}}.manage_stock) <= 1"; + . " and IF({{table}}.use_config_manage_stock=1," . $_configManageStock . ",{{table}}.manage_stock) = 1"; $product = Mage::getModel('catalog/product'); $collection = $product->getCollection() @@ -76,6 +76,9 @@ protected function _toHtml() ->joinTable('cataloginventory/stock_item', 'product_id=entity_id', array('qty'=>'qty', 'notify_stock_qty'=>'notify_stock_qty', 'use_config' => 'use_config_notify_stock_qty','low_stock_date' => 'low_stock_date'), $stockItemWhere, 'inner') ->setOrder('low_stock_date') ; + $collection->addAttributeToFilter('status', array('in' => Mage::getSingleton('catalog/product_status')->getVisibleStatusIds())); + $collection->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); + $_globalNotifyStockQty = (float) Mage::getStoreConfig(Mage_CatalogInventory_Model_Stock_Item::XML_PATH_NOTIFY_STOCK_QTY); Mage::dispatchEvent('rss_catalog_notify_stock_collection_select', array('collection' => $collection)); diff --git a/app/code/core/Mage/Rss/Block/Catalog/Special.php b/app/code/core/Mage/Rss/Block/Catalog/Special.php index f07a7b0ffb..9511db1b36 100644 --- a/app/code/core/Mage/Rss/Block/Catalog/Special.php +++ b/app/code/core/Mage/Rss/Block/Catalog/Special.php @@ -45,7 +45,7 @@ protected function _construct() /* * setting cache to save the rss for 10 minutes */ - $this->setCacheKey('rss_catalog_special_'.$this->getStoreId().'_'.$this->_getCustomerGroupId()); + $this->setCacheKey('rss_catalog_special_'.$this->_getStoreId().'_'.$this->_getCustomerGroupId()); $this->setCacheLifetime(600); } diff --git a/app/code/core/Mage/Rss/Block/Order/Status.php b/app/code/core/Mage/Rss/Block/Order/Status.php index e52104163c..67cb5b7c02 100644 --- a/app/code/core/Mage/Rss/Block/Order/Status.php +++ b/app/code/core/Mage/Rss/Block/Order/Status.php @@ -63,7 +63,8 @@ protected function _toHtml() if($type && $type!='order'){ $urlAppend = $type; } - $title = Mage::helper('rss')->__('Details for %s #%s', ucwords($type), $result['increment_id']); + $type = Mage::helper('rss')->__(ucwords($type)); + $title = Mage::helper('rss')->__('Details for %s #%s', $type, $result['increment_id']); $description = '

    '. Mage::helper('rss')->__('Notified Date: %s
    ',$this->formatDate($result['created_at'])). diff --git a/app/code/core/Mage/Rule/Model/Condition/Abstract.php b/app/code/core/Mage/Rule/Model/Condition/Abstract.php index 54e872204c..a702b1ba93 100644 --- a/app/code/core/Mage/Rule/Model/Condition/Abstract.php +++ b/app/code/core/Mage/Rule/Model/Condition/Abstract.php @@ -80,7 +80,7 @@ public function getDefaultOperatorInputByType() 'date' => array('==', '>=', '<='), 'select' => array('==', '!='), 'boolean' => array('==', '!='), - 'multiselect' => array('==', '!=', '{}', '!{}'), + 'multiselect' => array('{}', '!{}', '()', '!()'), 'grid' => array('()', '!()'), ); } @@ -254,7 +254,7 @@ public function getValueParsed() $value = $this->getData('value'); $op = $this->getOperator(); - if (($op==='()' || $op==='!()') && is_string($value)) { + if (($op === '{}' || $op === '!{}' || $op==='()' || $op==='!()') && is_string($value)) { $value = preg_split('#\s*[,;]\s*#', $value, null, PREG_SPLIT_NO_EMPTY); $this->setValue($value); } @@ -511,8 +511,13 @@ public function validateAttribute($validatedValue) $op = $this->getOperator(); // if operator requires array and it is not, or on opposite, return false - if ((($op=='()' || $op=='!()') && !is_array($value)) - || (!($op=='()' || $op=='!()' || $op=='!=' || $op=='==' || $op=='{}' || $op=='!{}') && is_array($value))) { + if (( + ($op == '()' || $op == '!()' || $op == '{}' || $op == '!{}') + && !is_array($value) + ) || ( + !($op == '()' || $op == '!()' || $op == '{}' || $op == '!{}') + && is_array($value) + )) { return false; } @@ -520,19 +525,10 @@ public function validateAttribute($validatedValue) switch ($op) { case '==': case '!=': - if (is_array($value)) { - if (is_array($validatedValue)) { - $result = array_diff($validatedValue, $value); - $result = empty($result) && (sizeof($validatedValue) == sizeof($value)); - } else { - return false; - } + if (is_array($validatedValue)) { + $result = in_array($value, $validatedValue); } else { - if (is_array($validatedValue)) { - $result = in_array($value, $validatedValue); - } else { - $result = $validatedValue==$value; - } + $result = $validatedValue == $value; } break; @@ -555,8 +551,8 @@ public function validateAttribute($validatedValue) case '{}': case '!{}': if (is_array($value)) { if (is_array($validatedValue)) { - $result = array_diff($value, $validatedValue); - $result = empty($result); + $result = array_intersect($value, $validatedValue); + $result = !empty($result); } else { return false; } diff --git a/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php b/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php index 8820bb8407..c7ca136b3d 100644 --- a/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php +++ b/app/code/core/Mage/Sales/Block/Order/Item/Renderer/Default.php @@ -165,4 +165,14 @@ public function getSku() }*/ return $this->getItem()->getSku(); } + + /** + * Return product additional information block + * + * @return Mage_Core_Block_Abstract + */ + public function getProductAdditionalInformationBlock() + { + return $this->getLayout()->getBlock('additional.product.info'); + } } diff --git a/app/code/core/Mage/Sales/Block/Order/Print/Shipment.php b/app/code/core/Mage/Sales/Block/Order/Print/Shipment.php index 517250521d..2b6340793d 100644 --- a/app/code/core/Mage/Sales/Block/Order/Print/Shipment.php +++ b/app/code/core/Mage/Sales/Block/Order/Print/Shipment.php @@ -133,7 +133,7 @@ public function getShipmentsCollection() */ public function getShipmentTracks($shipment) { - return $this->_tracks[$shipment->getId()]; + return isset($this->_tracks[$shipment->getId()]) ? $this->_tracks[$shipment->getId()] : null; } /** diff --git a/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php b/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php index 08f4f80e12..34b4bb4d42 100644 --- a/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php +++ b/app/code/core/Mage/Sales/Block/Reorder/Sidebar.php @@ -63,6 +63,7 @@ public function getItems() $items = array(); $order = $this->getLastOrder(); $limit = 5; + if ($order) { $website = Mage::app()->getStore()->getWebsiteId(); foreach ($order->getParentItemsRandomCollection($limit) as $item) { @@ -71,6 +72,7 @@ public function getItems() } } } + return $items; } diff --git a/app/code/core/Mage/Sales/Model/Billing/Agreement.php b/app/code/core/Mage/Sales/Model/Billing/Agreement.php index ba73a38dbc..46dda00047 100644 --- a/app/code/core/Mage/Sales/Model/Billing/Agreement.php +++ b/app/code/core/Mage/Sales/Model/Billing/Agreement.php @@ -208,13 +208,14 @@ public function importOrderPayment(Mage_Sales_Model_Order_Payment $payment) $this->_paymentMethodInstance = (isset($baData['method_code'])) ? Mage::helper('payment')->getMethodInstance($baData['method_code']) - ->setStore($payment->getMethodInstance()->getStore()) : $payment->getMethodInstance(); - - $this->setCustomerId($payment->getOrder()->getCustomerId()) - ->setMethodCode($this->_paymentMethodInstance->getCode()) - ->setReferenceId($baData['billing_agreement_id']) - ->setStatus(self::STATUS_ACTIVE); + if ($this->_paymentMethodInstance) { + $this->_paymentMethodInstance->setStore($payment->getMethodInstance()->getStore()); + $this->setCustomerId($payment->getOrder()->getCustomerId()) + ->setMethodCode($this->_paymentMethodInstance->getCode()) + ->setReferenceId($baData['billing_agreement_id']) + ->setStatus(self::STATUS_ACTIVE); + } return $this; } diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Order/Grid/Collection.php b/app/code/core/Mage/Sales/Model/Mysql4/Order/Grid/Collection.php index e0fd7af9f3..5a84cd7839 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Order/Grid/Collection.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Order/Grid/Collection.php @@ -33,6 +33,13 @@ class Mage_Sales_Model_Mysql4_Order_Grid_Collection extends Mage_Sales_Model_Mys protected $_eventPrefix = 'sales_order_grid_collection'; protected $_eventObject = 'order_grid_collection'; + /** + * Customer mode flag + * + * @var bool + */ + protected $_customerModeFlag = false; + protected function _construct() { parent::_construct(); @@ -60,4 +67,26 @@ public function getSelectCountSql() return $countSelect; } + + /** + * Set customer mode flag value + * + * @param bool $value + * @return Mage_Sales_Model_Mysql4_Order_Grid_Collection + */ + public function setIsCustomerMode($value) + { + $this->_customerModeFlag = (bool)$value; + return $this; + } + + /** + * Get customer mode flag value + * + * @return bool + */ + public function getIsCustomerMode() + { + return $this->_customerModeFlag; + } } diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Order/Status.php b/app/code/core/Mage/Sales/Model/Mysql4/Order/Status.php new file mode 100644 index 0000000000..ea31a265c0 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Mysql4/Order/Status.php @@ -0,0 +1,203 @@ +_init('sales/order_status', 'status'); + $this->_isPkAutoIncrement = false; + $this->_labelsTable = $this->getTable('sales/order_status_label'); + $this->_stateTable = $this->getTable('sales/order_status_state'); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @return Zend_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + if ($field == 'default_state') { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->join(array('state_table'=>$this->_stateTable), $this->getMainTable().'.status=state_table.status', 'status') + ->where('state_table.state=?', $value) + ->order('state_table.is_default DESC') + ->limit(1); + } else { + $select = parent::_getLoadSelect($field, $value, $object); + } + return $select; + } + + /** + * Store labels getter + * + * @param Mage_Core_Model_Abstract $status + * @return array + */ + public function getStoreLabels(Mage_Core_Model_Abstract $status) + { + $select = $this->_getWriteAdapter()->select() + ->from($this->_labelsTable, array('store_id', 'label')) + ->where('status=?', $status->getStatus()); + return $this->_getReadAdapter()->fetchPairs($select); + } + + /** + * Save status labels per store + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Sales_Model_Mysql4_Order_Status + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + if ($object->hasStoreLabels()) { + $labels = $object->getStoreLabels(); + $this->_getWriteAdapter()->delete( + $this->_labelsTable, + array('status = ?' => $object->getStatus()) + ); + $data = array(); + foreach ($labels as $storeId => $label) { + if (empty($label)) { + continue; + } + $data[] = array( + 'status' => $object->getStatus(), + 'store_id' => $storeId, + 'label' => $label + ); + } + if (!empty($data)) { + $this->_getWriteAdapter()->insertMultiple($this->_labelsTable, $data); + } + } + return parent::_afterSave($object); + } + + /** + * Assign order status to order state + * + * @param string $status + * @param string $state + * @param bool $isDefault + * @return Mage_Sales_Model_Mysql4_Order_Status + */ + public function assignState($status, $state, $isDefault) + { + if ($isDefault) { + $this->_getWriteAdapter()->update( + $this->_stateTable, + array('is_default' => 0), + array('state=?' => $state) + ); + } + $this->_getWriteAdapter()->insertOnDuplicate( + $this->_stateTable, + array( + 'status' => $status, + 'state' => $state, + 'is_default'=> (int) $isDefault + ) + ); + return $this; + } + + /** + * Unassign order status from order state + * + * @param string $status + * @param string $state + * @return Mage_Sales_Model_Mysql4_Order_Status + */ + public function unassignState($status, $state) + { + $select = $this->_getWriteAdapter()->select() + ->from($this->_stateTable, 'count(*)') + ->where('state=?', $state); + if ($this->_getWriteAdapter()->fetchOne($select) == 1) { + throw new Mage_Core_Exception( + Mage::helper('sales')->__('Last status can\'t be unassigned from state.') + ); + } + $select = $this->_getWriteAdapter()->select() + ->from($this->_stateTable, 'is_default') + ->where('state=?', $state) + ->where('status=?', $status) + ->limit(1); + $isDefault = $this->_getWriteAdapter()->fetchOne($select); + $this->_getWriteAdapter()->delete( + $this->_stateTable, + array( + 'state=?' => $state, + 'status=?' => $status + ) + ); + + if ($isDefault) { + $select = $this->_getWriteAdapter()->select() + ->from($this->_stateTable, 'status') + ->where('state=?', $state) + ->limit(1); + $defaultStatus = $this->_getWriteAdapter()->fetchOne($select); + if ($defaultStatus) { + $this->_getWriteAdapter()->update( + $this->_stateTable, + array('is_default'=>1), + array( + 'state=?' => $state, + 'status=?' => $defaultStatus + ) + ); + } + } + return $this; + } +} diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Order/Status/Collection.php b/app/code/core/Mage/Sales/Model/Mysql4/Order/Status/Collection.php new file mode 100644 index 0000000000..a115a782d9 --- /dev/null +++ b/app/code/core/Mage/Sales/Model/Mysql4/Order/Status/Collection.php @@ -0,0 +1,100 @@ +_init('sales/order_status'); + } + + /** + * Get collection data as options array + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('status', 'label'); + } + + /** + * Get collection data as options hash + * + * @return array + */ + public function toOptionHash() + { + return $this->_toOptionHash('status', 'label'); + } + + /** + * Join order states table + */ + public function joinStates() + { + if (!$this->getFlag('states_joined')) { + $this->_idFieldName = 'status_state'; + $this->getSelect()->joinLeft( + array('state_table' => $this->getTable('sales/order_status_state')), + 'main_table.status=state_table.status', + array('state', 'is_default') + ); + $this->setFlag('states_joined', true); + } + return $this; + } + + /** + * add state code filter to collection + * + * @param string $state + */ + public function addStateFilter($state) + { + $this->joinStates(); + $this->getSelect()->where('state_table.state=?', $state); + return $this; + } + + /** + * Define label order + * + * @param string $dir + * @return Mage_Sales_Model_Mysql4_Order_Status_Collection + */ + public function orderByLabel($dir = 'ASC') + { + $this->getSelect()->order('main_table.label '.$dir); + return $this; + } +} diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Report/Order.php b/app/code/core/Mage/Sales/Model/Mysql4/Report/Order.php index c2d2493c38..2d789efa05 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Report/Order.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Report/Order.php @@ -99,6 +99,7 @@ public function aggregate($from = null, $to = null) 'total_qty_invoiced' => 'SUM(qty_invoiced)', ); $selectOrderItem->from($this->getTable('sales/order_item'), $cols) + ->where('parent_item_id IS NULL') ->group('order_id'); if ($subSelect !== null) { //$selectOrderItem->where($this->_makeConditionFromDateRangeSelect($subSelect, 'created_at')); @@ -108,8 +109,7 @@ public function aggregate($from = null, $to = null) ->join(array('oi' => $selectOrderItem), 'oi.order_id = o.entity_id', array()) ->where('o.state NOT IN (?)', array( Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, - Mage_Sales_Model_Order::STATE_NEW, - Mage_Sales_Model_Order::STATE_CANCELED, + Mage_Sales_Model_Order::STATE_NEW )); if ($subSelect !== null) { diff --git a/app/code/core/Mage/Sales/Model/Mysql4/Report/Order/Updatedat/Collection.php b/app/code/core/Mage/Sales/Model/Mysql4/Report/Order/Updatedat/Collection.php index 6a8c604b67..6f300b276d 100644 --- a/app/code/core/Mage/Sales/Model/Mysql4/Report/Order/Updatedat/Collection.php +++ b/app/code/core/Mage/Sales/Model/Mysql4/Report/Order/Updatedat/Collection.php @@ -157,6 +157,7 @@ protected function _initSelect() 'total_qty_ordered' => 'SUM(qty_ordered - IFNULL(qty_canceled, 0))', 'total_qty_invoiced' => 'SUM(qty_invoiced)', )) + ->where('parent_item_id IS NULL') ->group('order_id'); $select = $this->getSelect() @@ -164,8 +165,7 @@ protected function _initSelect() ->join(array('oi' => $selectOrderItem), 'oi.order_id = e.entity_id', array()) ->where('e.state NOT IN (?)', array( Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, - Mage_Sales_Model_Order::STATE_NEW, - Mage_Sales_Model_Order::STATE_CANCELED, + Mage_Sales_Model_Order::STATE_NEW )); $this->_applyStoresFilter(); diff --git a/app/code/core/Mage/Sales/Model/Order.php b/app/code/core/Mage/Sales/Model/Order.php index 5b12677b8a..ea9120b0e6 100644 --- a/app/code/core/Mage/Sales/Model/Order.php +++ b/app/code/core/Mage/Sales/Model/Order.php @@ -67,6 +67,11 @@ class Mage_Sales_Model_Order extends Mage_Sales_Model_Abstract const STATE_HOLDED = 'holded'; const STATE_PAYMENT_REVIEW = 'payment_review'; + /** + * Order statuses + */ + const STATUS_FRAUD = 'fraud'; + /** * Order flags */ @@ -109,6 +114,13 @@ class Mage_Sales_Model_Order extends Mage_Sales_Model_Abstract */ protected $_actionFlag = array(); + /** + * Flag: if after order placing we can send new email to the customer. + * + * @var bool + */ + protected $_canSendNewEmailFlag = true; + /** * Initialize resource model */ @@ -159,6 +171,28 @@ public function setActionFlag($action, $flag) return $this; } + /** + * Return flag for order if it can sends new email to customer. + * + * @return bool + */ + public function getCanSendNewEmailFlag() + { + return $this->_canSendNewEmailFlag; + } + + /** + * Set flag for order if it can sends new email to customer. + * + * @param bool $flag + * @return Mage_Sales_Model_Order + */ + public function setCanSendNewEmailFlag($flag) + { + $this->_canSendNewEmailFlag = (boolean) $flag; + return $this; + } + /** * Load order by system increment identifier * @@ -433,18 +467,35 @@ public function canReorder() foreach ($this->getItemsCollection() as $item) { $products[] = $item->getProductId(); } - $productsCollection = Mage::getModel('catalog/product')->getCollection() - ->setStoreId($this->getStoreId()); if (!empty($products)) { - $productsCollection->addIdFilter($products) + /* + * @TODO ACPAOC: Use product collection here, but ensure that product is loaded with order store id, otherwise there'll be problems with isSalable() + * for configurables, bundles and other composites + * + */ + /* + $productsCollection = Mage::getModel('catalog/product')->getCollection() + ->setStoreId($this->getStoreId()) + ->addIdFilter($products) ->addAttributeToSelect('status') ->load(); + foreach ($productsCollection as $product) { if (!$product->isSalable()) { return false; } } + */ + + foreach ($products as $productId) { + $product = Mage::getModel('catalog/product') + ->setStoreId($this->getStoreId()) + ->load($productId); + if (!$product->getId() || !$product->isSalable()) { + return false; + } + } } if ($this->getActionFlag(self::ACTION_FLAG_REORDER) === false) { @@ -1091,13 +1142,11 @@ protected function _getItemsRandomCollection($limit, $nonChildrenOnly = false) { $collection = Mage::getModel('sales/order_item')->getCollection() ->setOrderFilter($this) - ->setRandomOrder() - ->setPageSize($limit); + ->setRandomOrder(); if ($nonChildrenOnly) { $collection->filterByParent(); } - $products = array(); foreach ($collection as $item) { $products[] = $item->getProductId(); @@ -1106,11 +1155,17 @@ protected function _getItemsRandomCollection($limit, $nonChildrenOnly = false) $productsCollection = Mage::getModel('catalog/product') ->getCollection() ->addIdFilter($products) + ->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInSiteIds()) + /* Price data is added to consider item stock status using price index */ + ->addPriceData() + ->setPageSize($limit) ->load(); - Mage::getSingleton('catalog/product_visibility') - ->addVisibleInSiteFilterToCollection($productsCollection); + foreach ($collection as $item) { - $item->setProduct($productsCollection->getItemById($item->getProductId())); + $product = $productsCollection->getItemById($item->getProductId()); + if ($product) { + $item->setProduct($product); + } } return $collection; diff --git a/app/code/core/Mage/Sales/Model/Order/Address.php b/app/code/core/Mage/Sales/Model/Order/Address.php index 29744ed061..ccf75d53cc 100644 --- a/app/code/core/Mage/Sales/Model/Order/Address.php +++ b/app/code/core/Mage/Sales/Model/Order/Address.php @@ -45,6 +45,9 @@ public function setOrder(Mage_Sales_Model_Order $order) public function getOrder() { + if (!$this->_order) { + $this->_order = Mage::getModel('sales/order')->load($this->getParentId()); + } return $this->_order; } diff --git a/app/code/core/Mage/Sales/Model/Order/Api/V2.php b/app/code/core/Mage/Sales/Model/Order/Api/V2.php index 86ba322cc5..39b40d5995 100644 --- a/app/code/core/Mage/Sales/Model/Order/Api/V2.php +++ b/app/code/core/Mage/Sales/Model/Order/Api/V2.php @@ -74,15 +74,23 @@ public function items($filters = null) $preparedFilters = array(); if (isset($filters->filter)) { foreach ($filters->filter as $_filter) { - $preparedFilters[$_filter->key] = $_filter->value; + $preparedFilters[][$_filter->key] = $_filter->value; } } if (isset($filters->complex_filter)) { foreach ($filters->complex_filter as $_filter) { $_value = $_filter->value; - $preparedFilters[][$_filter->key] = array( - $_value->key => $_value->value - ); + if(is_object($_value)) { + $preparedFilters[][$_filter->key] = array( + $_value->key => $_value->value + ); + } elseif(is_array($_value)) { + $preparedFilters[][$_filter->key] = array( + $_value['key'] => $_value['value'] + ); + } else { + $preparedFilters[][$_filter->key] = $_value; + } } } diff --git a/app/code/core/Mage/Sales/Model/Order/Config.php b/app/code/core/Mage/Sales/Model/Order/Config.php index b4a7f8925e..f67295a9f4 100644 --- a/app/code/core/Mage/Sales/Model/Order/Config.php +++ b/app/code/core/Mage/Sales/Model/Order/Config.php @@ -33,6 +33,13 @@ */ class Mage_Sales_Model_Order_Config extends Mage_Core_Model_Config_Base { + /** + * Statuses per state array + * + * @var array + */ + protected $_stateStatuses; + private $_states; public function __construct() @@ -60,18 +67,9 @@ public function getStateDefaultStatus($state) { $status = false; if ($stateNode = $this->_getState($state)) { - if ($stateNode->statuses) { - foreach ($stateNode->statuses->children() as $statusNode) { - if (!$status) { - $status = $statusNode->getName(); - } - $attributes = $statusNode->attributes(); - // empty($attributes['default']) is for backwards compatibility - if (isset($attributes['default']) && (empty($attributes['default']) || $attributes['default'] == '1')) { - $status = $statusNode->getName(); - } - } - } + $status = Mage::getModel('sales/order_status') + ->loadDefaultByState($state); + $status = $status->getStatus(); } return $status; } @@ -79,18 +77,32 @@ public function getStateDefaultStatus($state) /** * Retrieve status label * - * @param string $status + * @param string $code + * @return string + */ + public function getStatusLabel($code) + { + $status = Mage::getModel('sales/order_status') + ->load($code); + return $status->getStoreLabel(); + } + + /** + * State label getter + * + * @param string $state * @return string */ - public function getStatusLabel($status) + public function getStateLabel($state) { - if ($statusNode = $this->_getStatus($status)) { - $status = (string) $statusNode->label; - return Mage::helper('sales')->__($status); + if ($stateNode = $this->_getState($state)) { + $state = (string) $stateNode->label; + return Mage::helper('sales')->__($state); } - return $status; + return $state; } + /** * Retrieve all statuses * @@ -98,14 +110,27 @@ public function getStatusLabel($status) */ public function getStatuses() { - $statuses = array(); - foreach ($this->getNode('statuses')->children() as $status) { - $label = (string) $status->label; - $statuses[$status->getName()] = Mage::helper('sales')->__($label); - } + $statuses = Mage::getResourceModel('sales/order_status_collection') + ->toOptionHash(); return $statuses; } + /** + * Order states getter + * + * @return array + */ + public function getStates() + { + $states = array(); + foreach ($this->getNode('states')->children() as $state) { + $label = (string) $state->label; + $states[$state->getName()] = Mage::helper('sales')->__($label); + } + return $states; + } + + /** * Retrieve statuses available for state * Get all possible statuses, or for specified state, or specified states array @@ -117,23 +142,30 @@ public function getStatuses() */ public function getStateStatuses($state, $addLabels = true) { + $key = $state . $addLabels; + if (isset($this->_stateStatuses[$key])) { + return $this->_stateStatuses[$key]; + } $statuses = array(); if (empty($state) || !is_array($state)) { $state = array($state); } foreach ($state as $_state) { if ($stateNode = $this->_getState($_state)) { - foreach ($stateNode->statuses->children() as $statusNode) { - $status = $statusNode->getName(); + $collection = Mage::getResourceModel('sales/order_status_collection') + ->addStateFilter($_state) + ->orderByLabel(); + foreach ($collection as $status) { + $code = $status->getStatus(); if ($addLabels) { - $statuses[$status] = $this->getStatusLabel($status); - } - else { - $statuses[] = $status; + $statuses[$code] = $status->getStoreLabel(); + } else { + $statuses[] = $code; } } } } + $this->_stateStatuses[$key] = $statuses; return $statuses; } diff --git a/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Tax.php b/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Tax.php index 4e91365868..20ffd60c46 100644 --- a/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Tax.php +++ b/app/code/core/Mage/Sales/Model/Order/Creditmemo/Total/Tax.php @@ -89,14 +89,18 @@ public function collect(Mage_Sales_Model_Order_Creditmemo $creditmemo) } if ($invoice = $creditmemo->getInvoice()) { - $totalTax += $invoice->getShippingTaxAmount(); - $baseTotalTax += $invoice->getBaseShippingTaxAmount(); - $totalHiddenTax += $invoice->getShippingHiddenTaxAmount(); - $baseTotalHiddenTax += $invoice->getBaseShippingHiddenTaxAmount(); - $shippingTaxAmount = $invoice->getShippingTaxAmount(); - $baseShippingTaxAmount = $invoice->getBaseShippingTaxAmount(); - $shippingHiddenTaxAmount = $invoice->getShippingHiddenTaxAmount(); - $baseShippingHiddenTaxAmount = $invoice->getBaseShippingHiddenTaxAmount(); + //recalculate tax amounts in case if refund shipping value was changed + if ($order->getBaseShippingAmount() && $creditmemo->getBaseShippingAmount()) { + $taxFactor = $creditmemo->getBaseShippingAmount()/$order->getBaseShippingAmount(); + $shippingTaxAmount = $invoice->getShippingTaxAmount()*$taxFactor; + $baseShippingTaxAmount = $invoice->getBaseShippingTaxAmount()*$taxFactor; + $totalHiddenTax += $invoice->getShippingHiddenTaxAmount()*$taxFactor; + $baseTotalHiddenTax += $invoice->getBaseShippingHiddenTaxAmount()*$taxFactor; + $shippingHiddenTaxAmount = $invoice->getShippingHiddenTaxAmount()*$taxFactor; + $baseShippingHiddenTaxAmount = $invoice->getBaseShippingHiddenTaxAmount()*$taxFactor; + $totalTax += $shippingTaxAmount; + $baseTotalTax += $baseShippingTaxAmount; + } } else { $orderShippingAmount = $order->getShippingAmount(); $baseOrderShippingAmount = $order->getBaseShippingAmount(); diff --git a/app/code/core/Mage/Sales/Model/Order/Invoice/Api/V2.php b/app/code/core/Mage/Sales/Model/Order/Invoice/Api/V2.php index 671fcb417f..a060d8c404 100644 --- a/app/code/core/Mage/Sales/Model/Order/Invoice/Api/V2.php +++ b/app/code/core/Mage/Sales/Model/Order/Invoice/Api/V2.php @@ -42,7 +42,7 @@ class Mage_Sales_Model_Order_Invoice_Api_V2 extends Mage_Sales_Model_Order_Invoi public function items($filters = null) { //TODO: add full name logic - $collection = Mage::getResourceModel('sales/order_invoice_collection') + $collection = Mage::getModel('sales/order_invoice')->getCollection() ->addAttributeToSelect('order_id') ->addAttributeToSelect('increment_id') ->addAttributeToSelect('created_at') @@ -57,26 +57,36 @@ public function items($filters = null) $preparedFilters = array(); if (isset($filters->filter)) { foreach ($filters->filter as $_filter) { - $preparedFilters[$_filter->key] = $_filter->value; + $preparedFilters[][$_filter->key] = $_filter->value; } } if (isset($filters->complex_filter)) { foreach ($filters->complex_filter as $_filter) { $_value = $_filter->value; - $preparedFilters[$_filter->key] = array( - $_value->key => $_value->value - ); + if(is_object($_value)) { + $preparedFilters[][$_filter->key] = array( + $_value->key => $_value->value + ); + } elseif(is_array($_value)) { + $preparedFilters[][$_filter->key] = array( + $_value['key'] => $_value['value'] + ); + } else { + $preparedFilters[][$_filter->key] = $_value; + } } } if (!empty($preparedFilters)) { try { - foreach ($preparedFilters as $field => $value) { - if (isset($this->_attributesMap['invoice'][$field])) { - $field = $this->_attributesMap['invoice'][$field]; - } + foreach ($preparedFilters as $preparedFilter) { + foreach ($preparedFilter as $field => $value) { + if (isset($this->_attributesMap['order'][$field])) { + $field = $this->_attributesMap['order'][$field]; + } - $collection->addFieldToFilter($field, $value); + $collection->addFieldToFilter($field, $value); + } } } catch (Mage_Core_Exception $e) { $this->_fault('filters_invalid', $e->getMessage()); diff --git a/app/code/core/Mage/Sales/Model/Order/Payment.php b/app/code/core/Mage/Sales/Model/Order/Payment.php index 42ac4e3e7c..d2a26176e6 100644 --- a/app/code/core/Mage/Sales/Model/Order/Payment.php +++ b/app/code/core/Mage/Sales/Model/Order/Payment.php @@ -277,7 +277,7 @@ public function capture($invoice) $message = Mage::helper('sales')->__('Capturing amount of %s is pending approval on gateway.', $this->_formatPrice($amountToCapture)); $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; if ($this->getIsFraudDetected()) { - $status = 'fraud'; + $status = Mage_Sales_Model_Order::STATUS_FRAUD; } $invoice->setIsPaid(false); } else { // normal online capture: invoice is marked as "paid" @@ -330,6 +330,7 @@ public function registerCaptureNotification($amount) $order->addRelatedObject($invoice); $this->setCreatedInvoice($invoice); } else { + $this->setIsFraudDetected(true); $this->_updateTotals(array('base_amount_paid_online' => $amount)); } } @@ -339,11 +340,17 @@ public function registerCaptureNotification($amount) $message = Mage::helper('sales')->__('Capturing amount of %s is pending approval on gateway.', $this->_formatPrice($amount)); $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; if ($this->getIsFraudDetected()) { - $status = 'fraud'; + $message = Mage::helper('sales')->__('Order is suspended as its capture amount %s is suspected to be fraudulent.', $this->_formatPrice($amount)); + $status = Mage_Sales_Model_Order::STATUS_FRAUD; } } else { $message = Mage::helper('sales')->__('Registered notification about captured amount of %s.', $this->_formatPrice($amount)); $state = Mage_Sales_Model_Order::STATE_PROCESSING; + if ($this->getIsFraudDetected()) { + $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; + $message = Mage::helper('sales')->__('Order is suspended as its capture amount %s is suspected to be fraudulent.', $this->_formatPrice($amount)); + $status = Mage_Sales_Model_Order::STATUS_FRAUD; + } // register capture for an existing invoice if ($invoice && Mage_Sales_Model_Order_Invoice::STATE_OPEN == $invoice->getState()) { $invoice->pay(); @@ -803,22 +810,21 @@ protected function _authorize($isOnline, $amount) $state = Mage_Sales_Model_Order::STATE_PROCESSING; $status = true; if ($isOnline) { - // invoke authorization on gateway $this->getMethodInstance()->setStore($order->getStoreId())->authorize($this, $amount); + } else { + $message = Mage::helper('sales')->__('Registered notification about authorized amount of %s.', $this->_formatPrice($amount)); + } - // similar logic of "payment review" order as in capturing - if ($this->getIsTransactionPending()) { - $message = Mage::helper('sales')->__('Authorizing amount of %s is pending approval on gateway.', $this->_formatPrice($amount)); - $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; - if ($this->getIsFraudDetected()) { - $status = 'fraud'; - } - } else { - $message = Mage::helper('sales')->__('Authorized amount of %s.', $this->_formatPrice($amount)); + // similar logic of "payment review" order as in capturing + if ($this->getIsTransactionPending()) { + $message = Mage::helper('sales')->__('Authorizing amount of %s is pending approval on gateway.', $this->_formatPrice($amount)); + $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW; + if ($this->getIsFraudDetected()) { + $status = Mage_Sales_Model_Order::STATUS_FRAUD; } } else { - $message = Mage::helper('sales')->__('Registered notification about authorized amount of %s.', $this->_formatPrice($amount)); + $message = Mage::helper('sales')->__('Authorized amount of %s.', $this->_formatPrice($amount)); } // update transactions, order state and add comments @@ -926,6 +932,11 @@ protected function _void($isOnline, $amount = null, $gatewayCallback = 'void') */ protected function _addTransaction($type, $salesDocument = null, $failsafe = false) { + if ($this->getSkipTransactionCreation()) { + $this->unsTransactionId(); + return null; + } + // look for set transaction ids $transactionId = $this->getTransactionId(); if (null !== $transactionId) { @@ -985,11 +996,20 @@ protected function _addTransaction($type, $salesDocument = null, $failsafe = fal * @param string $type * @param Mage_Sales_Model_Abstract $salesDocument * @param bool $failsafe + * @param string $message * @return null|Mage_Sales_Model_Order_Payment_Transaction */ - public function addTransaction($type, $salesDocument = null, $failsafe = false) + public function addTransaction($type, $salesDocument = null, $failsafe = false, $message = false) { - return $this->_addTransaction($type, $salesDocument, $failsafe); + $transaction = $this->_addTransaction($type, $salesDocument, $failsafe); + + if ($message) { + $order = $this->getOrder(); + $message = $this->_appendTransactionToMessage($transaction, $message); + $order->addStatusHistoryComment($message); + } + + return $transaction; } /** @@ -1275,6 +1295,17 @@ public function setTransactionAdditionalInfo($key, $value) $this->_transactionAdditionalInfo[$key] = $value; } + /** + * Reset transaction additional info property + * + * @return Mage_Sales_Model_Order_Payment + */ + public function resetTransactionAdditionalInfo() + { + $this->_transactionAdditionalInfo = array(); + return $this; + } + /** * Return invoice model for transaction * diff --git a/app/code/core/Mage/Sales/Model/Order/Pdf/Total/Default.php b/app/code/core/Mage/Sales/Model/Order/Pdf/Total/Default.php index abe2abc34f..b4db890861 100644 --- a/app/code/core/Mage/Sales/Model/Order/Pdf/Total/Default.php +++ b/app/code/core/Mage/Sales/Model/Order/Pdf/Total/Default.php @@ -53,6 +53,47 @@ public function getTotalsForDisplay() return array($total); } + /** + * Get array of arrays with tax information for display in PDF + * array( + * $index => array( + * 'amount' => $amount, + * 'label' => $label, + * 'font_size'=> $font_size + * ) + * ) + * @return array + */ + public function getFullTaxInfo() + { + $rates = Mage::getResourceModel('sales/order_tax_collection')->loadByOrder($this->getOrder())->toArray(); + $fullInfo = Mage::getSingleton('tax/calculation')->reproduceProcess($rates['items']); + $fontSize = $this->getFontSize() ? $this->getFontSize() : 7; + $tax_info = array(); + + if ($fullInfo) { + foreach ($fullInfo as $info) { + if (isset($info['hidden']) && $info['hidden']) { + continue; + } + + $_amount = $info['amount']; + + foreach ($info['rates'] as $rate) { + $percent = $rate['percent'] ? ' (' . $rate['percent']. '%)' : ''; + + $tax_info[] = array( + 'amount' => $this->getAmountPrefix().$this->getOrder()->formatPriceTxt($_amount), + 'label' => Mage::helper('tax')->__($rate['title']) . $percent . ':', + 'font_size' => $fontSize + ); + } + } + } + + return $tax_info; + } + /** * Check if we can display total information in PDF * diff --git a/app/code/core/Mage/Sales/Model/Order/Status.php b/app/code/core/Mage/Sales/Model/Order/Status.php index 640abd1b98..d48cb94de0 100644 --- a/app/code/core/Mage/Sales/Model/Order/Status.php +++ b/app/code/core/Mage/Sales/Model/Order/Status.php @@ -34,20 +34,86 @@ protected function _construct() } /** - * Retrieve order status label + * Assign order status to particular state * - * @return string + * @param string $state + * @param boolean $isDefault make the status as default one for state + * @return Mage_Sales_Model_Order_Status + */ + public function assignState($state, $isDefault=false) + { + $this->_getResource()->beginTransaction(); + try { + $this->_getResource()->assignState($this->getStatus(), $state, $isDefault); + $this->_getResource()->commit(); + } catch (Exception $e) { + $this->_getResource()->rollBack(); + throw $e; + } + return $this; + } + + /** + * Unassigns order status from particular state + * + * @param string $state + * @return Mage_Sales_Model_Order_Status */ - public function getFrontendLabel() + public function unassignState($state) { - $label = ''; - if ($storeId = Mage::app()->getStore()->getId()) { - $label = Mage::app()->getStore()->getConfig('sales/order_statuses/status_' . $this->getId()); + $this->_getResource()->beginTransaction(); + try { + $this->_getResource()->unassignState($this->getStatus(), $state); + $this->_getResource()->commit(); + } catch (Exception $e) { + $this->_getResource()->rollBack(); + throw $e; } - if (! $label) { - $label = $this->getData('frontend_label'); + return $this; + } + + /** + * Getter for status labels per store + * + * @return array + */ + public function getStoreLabels() + { + if ($this->hasData('store_labels')) { + return $this->_getData('store_labels'); } - return $label; + $labels = $this->_getResource()->getStoreLabels($this); + $this->setData('store_labels', $labels); + return $labels; } + /** + * Get status label by store + * + * @param mixed $store + * @return string + */ + public function getStoreLabel($store=null) + { + $store = Mage::app()->getStore($store); + $label = false; + if (!$store->isAdmin()) { + $labels = $this->getStoreLabels(); + if (isset($labels[$store->getId()])) { + return $labels[$store->getId()]; + } + } + return Mage::helper('sales')->__($this->getLabel()); + } + + /** + * Load default status per state + * + * @param string $state + */ + public function loadDefaultByState($state) + { + $this->load($state, 'default_state'); + return $this; + } } diff --git a/app/code/core/Mage/Sales/Model/Payment/Method/Billing/AgreementAbstract.php b/app/code/core/Mage/Sales/Model/Payment/Method/Billing/AgreementAbstract.php index b1109e5a87..fc24e2cf93 100644 --- a/app/code/core/Mage/Sales/Model/Payment/Method/Billing/AgreementAbstract.php +++ b/app/code/core/Mage/Sales/Model/Payment/Method/Billing/AgreementAbstract.php @@ -61,10 +61,12 @@ public function isAvailable($quote = null) $availableBA = Mage::getModel('sales/billing_agreement')->getAvailableCustomerBillingAgreements( $quote->getCustomer()->getId() ); - $this->_canUseCheckout = count($availableBA) > 0; + $isAvailableBA = count($availableBA) > 0; + $this->_canUseCheckout = $this->_canUseInternal = $isAvailableBA; } $this->_isAvailable = parent::isAvailable($quote) && $this->_isAvailable($quote); $this->_canUseCheckout = ($this->_isAvailable && $this->_canUseCheckout); + $this->_canUseInternal = ($this->_isAvailable && $this->_canUseInternal); } return $this->_isAvailable; } diff --git a/app/code/core/Mage/Sales/Model/Quote.php b/app/code/core/Mage/Sales/Model/Quote.php index bb1a5c52cc..034c644229 100644 --- a/app/code/core/Mage/Sales/Model/Quote.php +++ b/app/code/core/Mage/Sales/Model/Quote.php @@ -623,6 +623,23 @@ public function hasItemsWithDecimalQty() return false; } + /** + * Checking product exist in Quote + * + * @param int $productId + * @return bool + */ + public function hasProductId($productId) + { + foreach ($this->getAllItems() as $item) { + if ($item->getProductId() == $productId) { + return true; + } + } + + return false; + } + /** * Retrieve item model object by item identifier * @@ -642,7 +659,8 @@ public function getItemById($itemId) */ public function removeItem($itemId) { - if ($item = $this->getItemById($itemId)) { + $item = $this->getItemById($itemId); + if ($item) { $item->setQuote($this); /** * If we remove item from quote - we can't use multishipping mode @@ -688,16 +706,16 @@ public function addItem(Mage_Sales_Model_Quote_Item $item) } /** - * Add product to quote + * Advanced func to add product to quote - processing mode can be specified there. + * Returns error message if product type instance can't prepare product. * - * return error message if product type instance can't prepare product - * - * @param mixed $product - * @return Mage_Sales_Model_Quote_Item || string + * @param mixed $product + * @param null|float|Varien_Object $request + * @param null|string $processMode + * @return Mage_Sales_Model_Quote_Item|string */ - public function addProduct(Mage_Catalog_Model_Product $product, $request=null) + public function addProductAdvanced(Mage_Catalog_Model_Product $product, $request = null, $processMode = null) { - if ($request === null) { $request = 1; } @@ -709,12 +727,11 @@ public function addProduct(Mage_Catalog_Model_Product $product, $request=null) } $cartCandidates = $product->getTypeInstance(true) - ->prepareForCart($request, $product); + ->prepareForCartAdvanced($request, $product, $processMode); /** * Error message */ - if (is_string($cartCandidates)) { return $cartCandidates; } @@ -726,9 +743,6 @@ public function addProduct(Mage_Catalog_Model_Product $product, $request=null) $cartCandidates = array($cartCandidates); } - - - $parentItem = null; $errors = array(); $items = array(); @@ -742,7 +756,7 @@ public function addProduct(Mage_Catalog_Model_Product $product, $request=null) if (!$parentItem) { $parentItem = $item; } - if ($parentItem && $candidate->getParentProductId()) { + if ($parentItem && $candidate->getParentProductId() && !$item->getId()) { $item->setParentItem($parentItem); } @@ -765,6 +779,21 @@ public function addProduct(Mage_Catalog_Model_Product $product, $request=null) return $item; } + + /** + * Add product to quote + * + * return error message if product type instance can't prepare product + * + * @param mixed $product + * @param null|float|Varien_Object $request + * @return Mage_Sales_Model_Quote_Item|string + */ + public function addProduct(Mage_Catalog_Model_Product $product, $request = null) + { + return $this->addProductAdvanced($product, $request, Mage_Catalog_Model_Product_Type_Abstract::PROCESS_MODE_FULL); + } + /** * Adding catalog product object data to quote * @@ -801,10 +830,66 @@ protected function _addCatalogProduct(Mage_Catalog_Model_Product $product, $qty return $item; } + /** + * Updates quote item with new configuration + * + * @param int $itemId + * @param Varien_Object $buyRequest + * @return Mage_Sales_Model_Quote_Item + */ + public function updateItem($itemId, $buyRequest) + { + $item = $this->getItemById($itemId); + if (!$item) { + Mage::throwException(Mage::helper('sales')->__('Wrong quote item id to update configuration.')); + } + $productId = $item->getProduct()->getId(); + + //We need to create new clear product instance with same $productId + //to set new option values from $buyRequest + $product = Mage::getModel('catalog/product') + ->setStoreId($this->getStore()->getId()) + ->load($productId); + + $resultItem = $this->addProduct($product, $buyRequest); + + if (is_string($resultItem)) { + Mage::throwException($resultItem); + } + + if ($resultItem->getParentItem()) { + $resultItem = $resultItem->getParentItem(); + } + + if ($resultItem->getId() != $itemId) { + /* + * Product configuration didn't stick to original quote item + * It either has same configuration as some other quote item's product or completely new configuration + */ + $this->removeItem($itemId); + + $items = $this->getAllItems(); + foreach ($items as $item) { + if (($item->getProductId() == $productId) && ($item->getId() != $resultItem->getId())) { + if ($resultItem->compare($item)) { + // Product configuration is same as in other quote item + $resultItem->setQty($resultItem->getQty() + $item->getQty()); + $this->removeItem($item->getId()); + break; + } + } + } + } else { + $resultItem->setQty($buyRequest->getQty()); + } + + return $resultItem; + } + /** * Retrieve quote item by product id * - * @param int $productId + * @param Mage_Catalog_Model_Product $product * @return Mage_Sales_Model_Quote_Item || false */ public function getItemByProduct($product) diff --git a/app/code/core/Mage/Sales/Model/Quote/Address/Total/Shipping.php b/app/code/core/Mage/Sales/Model/Quote/Address/Total/Shipping.php index e8eed9739b..6fd92aae57 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Address/Total/Shipping.php +++ b/app/code/core/Mage/Sales/Model/Quote/Address/Total/Shipping.php @@ -166,7 +166,7 @@ public function collect(Mage_Sales_Model_Quote_Address $address) $this->_setAmount($amountPrice); $this->_setBaseAmount($rate->getPrice()); $shippingDescription = $rate->getCarrierTitle() . ' - ' . $rate->getMethodTitle(); - $address->setShippingDescription(trim($shippingDescription, '-')); + $address->setShippingDescription(trim($shippingDescription, ' -')); break; } } diff --git a/app/code/core/Mage/Sales/Model/Quote/Item.php b/app/code/core/Mage/Sales/Model/Quote/Item.php index 205faafc67..edcfa1156d 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Item.php +++ b/app/code/core/Mage/Sales/Model/Quote/Item.php @@ -78,6 +78,12 @@ class Mage_Sales_Model_Quote_Item extends Mage_Sales_Model_Quote_Item_Abstract */ protected $_notRepresentOptions = array('info_buyRequest'); + /** + * Flag stating that options were successfully saved + * + */ + protected $_flagOptionsSaved = null; + /** * Initialize resource model * @@ -288,29 +294,6 @@ public function setProduct($product) return $this; } - /** - * Retrieve product model object associated with item - * - * @return Mage_Catalog_Model_Product - */ - public function getProduct() - { - $product = $this->_getData('product'); - if (($product === null) && $this->getProductId()) { - $product = Mage::getModel('catalog/product') - ->setStoreId($this->getQuote()->getStoreId()) - ->load($this->getProductId()); - $this->setProduct($product); - } - - /** - * Reset product final price because it related to custom options - */ - $product->setFinalPrice(null); - $product->setCustomOptions($this->_optionsByCode); - return $product; - } - /** * Check product representation in item * @@ -320,7 +303,7 @@ public function getProduct() public function representProduct($product) { $itemProduct = $this->getProduct(); - if ($itemProduct->getId() != $product->getId()) { + if (!$product || $itemProduct->getId() != $product->getId()) { return false; } @@ -597,12 +580,7 @@ protected function _hasModelChanged() return false; } - $result = $this->_getResource()->hasDataChanged($this); - if ($result === false) { - $this->_saveItemOptions(); - } - - return $result; + return $this->_getResource()->hasDataChanged($this); } /** @@ -622,9 +600,27 @@ protected function _saveItemOptions() } } + $this->_flagOptionsSaved = true; // Report to watchers that options were saved + return $this; } + /** + * Save model plus its options + * Ensures saving options in case when resource model was not changed + */ + public function save() + { + $hasDataChanges = $this->hasDataChanges(); + $this->_flagOptionsSaved = false; + + parent::save(); + + if ($hasDataChanges && !$this->_flagOptionsSaved) { + $this->_saveItemOptions(); + } + } + /** * Save item options after item saved * @@ -653,4 +649,22 @@ public function __clone() } return $this; } + + /** + * Returns formatted buy request - object, holding request received from + * product view page with keys and options for configured product + * + * @return Varien_Object + */ + public function getBuyRequest() + { + $option = $this->getOptionByCode('info_buyRequest'); + $buyRequest = new Varien_Object(unserialize($option->getValue())); + + // Owerwrite standard buy request qty, because item qty could have changed since adding to quote + $buyRequest->setOriginalQty($buyRequest); + $buyRequest->setQty($this->getQty()); + + return $buyRequest; + } } diff --git a/app/code/core/Mage/Sales/Model/Quote/Item/Abstract.php b/app/code/core/Mage/Sales/Model/Quote/Item/Abstract.php index 89da5b4e43..5cebc4001a 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Item/Abstract.php +++ b/app/code/core/Mage/Sales/Model/Quote/Item/Abstract.php @@ -39,6 +39,7 @@ * @author Magento Core Team */ abstract class Mage_Sales_Model_Quote_Item_Abstract extends Mage_Core_Model_Abstract + implements Mage_Catalog_Model_Product_Configuration_Item_Interface { protected $_parentItem = null; protected $_children = array(); @@ -51,6 +52,43 @@ abstract class Mage_Sales_Model_Quote_Item_Abstract extends Mage_Core_Model_Abst */ abstract function getQuote(); + /** + * Retrieve product model object associated with item + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + $product = $this->_getData('product'); + if (($product === null) && $this->getProductId()) { + $product = Mage::getModel('catalog/product') + ->setStoreId($this->getQuote()->getStoreId()) + ->load($this->getProductId()); + $this->setProduct($product); + } + + /** + * Reset product final price because it related to custom options + */ + $product->setFinalPrice(null); + if (is_array($this->_optionsByCode)) { + $product->setCustomOptions($this->_optionsByCode); + } + return $product; + } + + /** + * Returns special download params (if needed) for custom option with type = 'file' + * Needed to implement Mage_Catalog_Model_Product_Configuration_Item_Interface. + * Return null, as quote item needs no additional configuration. + * + * @return null|Varien_Object + */ + public function getFileDownloadParams() + { + return null; + } + /** * Specify parent item id before saving data * @@ -121,11 +159,14 @@ public function addChild($child) * @return Mage_Sales_Model_Quote_Item_Abstract */ public function setMessage($messages) { + $messagesExists = $this->getMessage(false); if (!is_array($messages)) { $messages = array($messages); } foreach ($messages as $message) { - $this->addMessage($message); + if (!in_array($message, $messagesExists)) { + $this->addMessage($message); + } } return $this; } @@ -195,7 +236,7 @@ public function checkData() $this->setMessage($e->getMessage()); $this->getQuote()->setHasError(true); $this->getQuote()->addMessage( - Mage::helper('sales')->__('Some of the products below do not have all the required options. Please remove them and add again with all the required options.') + Mage::helper('sales')->__('Some of the products below do not have all the required options. Please edit them and configure all the required options.') ); } catch (Exception $e) { $this->setHasError(true); diff --git a/app/code/core/Mage/Sales/Model/Quote/Item/Option.php b/app/code/core/Mage/Sales/Model/Quote/Item/Option.php index 6a0c7521ab..71113cde4b 100644 --- a/app/code/core/Mage/Sales/Model/Quote/Item/Option.php +++ b/app/code/core/Mage/Sales/Model/Quote/Item/Option.php @@ -32,6 +32,7 @@ * @author Magento Core Team */ class Mage_Sales_Model_Quote_Item_Option extends Mage_Core_Model_Abstract + implements Mage_Catalog_Model_Product_Configuration_Item_Option_Interface { protected $_item; protected $_product; @@ -104,6 +105,16 @@ public function getProduct() return $this->_product; } + /** + * Get option value + * + * @return mixed + */ + public function getValue() + { + return $this->_getData('value'); + } + /** * Initialize item identifier before save data * diff --git a/app/code/core/Mage/Sales/Model/Service/Order.php b/app/code/core/Mage/Sales/Model/Service/Order.php index 264157e43b..61dd04bed1 100644 --- a/app/code/core/Mage/Sales/Model/Service/Order.php +++ b/app/code/core/Mage/Sales/Model/Service/Order.php @@ -128,7 +128,7 @@ public function prepareShipment($qtys = array()) } $item = $this->_convertor->itemToShipmentItem($orderItem); - if ($orderItem->isDummy()) { + if ($orderItem->isDummy(true)) { $qty = 1; } else { if (isset($qtys[$orderItem->getId()])) { @@ -229,8 +229,16 @@ public function prepareInvoiceCreditmemo($invoice, $data = array()) $this->_initCreditmemoData($creditmemo, $data); if (!isset($data['shipping_amount'])) { $order = $invoice->getOrder(); - $baseAllowedAmount = $order->getBaseShippingAmount()-$order->getBaseShippingRefunded(); - $creditmemo->setBaseShippingAmount(min($baseAllowedAmount, $invoice->getBaseShippingAmount())); + $isShippingInclTax = Mage::getSingleton('tax/config')->displaySalesShippingInclTax($order->getStoreId()); + if ($isShippingInclTax) { + $baseAllowedAmount = $order->getBaseShippingInclTax() + - $order->getBaseShippingRefunded() + - $order->getBaseShippingTaxRefunded(); + } else { + $baseAllowedAmount = $order->getBaseShippingAmount() - $order->getBaseShippingRefunded(); + $baseAllowedAmount = min($baseAllowedAmount, $invoice->getBaseShippingAmount()); + } + $creditmemo->setBaseShippingAmount($baseAllowedAmount); } $creditmemo->collectTotals(); diff --git a/app/code/core/Mage/Sales/Model/Service/Quote.php b/app/code/core/Mage/Sales/Model/Service/Quote.php index 94fd6a6274..225be103a7 100644 --- a/app/code/core/Mage/Sales/Model/Service/Quote.php +++ b/app/code/core/Mage/Sales/Model/Service/Quote.php @@ -168,6 +168,8 @@ public function submitOrder() $order->addItem($orderItem); } + $order->setQuote($quote); + $transaction->addObject($order); $transaction->addCommitCallback(array($order, 'place')); $transaction->addCommitCallback(array($order, 'save')); diff --git a/app/code/core/Mage/Sales/controllers/DownloadController.php b/app/code/core/Mage/Sales/controllers/DownloadController.php index 1c023fb28a..19bd184fb9 100644 --- a/app/code/core/Mage/Sales/controllers/DownloadController.php +++ b/app/code/core/Mage/Sales/controllers/DownloadController.php @@ -49,33 +49,56 @@ protected function _downloadFileAction($info) } $filePath = Mage::getBaseDir() . $info['order_path']; - if (!is_file($filePath) || !is_readable($filePath)) { - // try get file from quote + if ((!is_file($filePath) || !is_readable($filePath)) && !$this->_processDatabaseFile($filePath)) { + //try get file from quote $filePath = Mage::getBaseDir() . $info['quote_path']; - if (!is_file($filePath) || !is_readable($filePath)) { + if ((!is_file($filePath) || !is_readable($filePath)) && !$this->_processDatabaseFile($filePath)) { throw new Exception(); } } + $this->_prepareDownloadResponse($info['title'], array( + 'value' => $filePath, + 'type' => 'filename' + )); + } catch (Exception $e) { + $this->_forward('noRoute'); + } + } - $this->getResponse() - ->setHttpResponseCode(200) - ->setHeader('Pragma', 'public', true) - ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) - ->setHeader('Content-type', $info['type'], true) - ->setHeader('Content-Length', $info['size']) - ->setHeader('Content-Disposition', 'inline' . '; filename='.$info['title']); - - $this->getResponse() - ->clearBody(); - $this->getResponse() - ->sendHeaders(); + /** + * Check file in database storage if needed and place it on file system + * + * @param string $filePath + * @return bool + */ + protected function _processDatabaseFile($filePath) + { + if (!Mage::helper('core/file_storage_database')->checkDbUsage()) { + return false; + } - readfile($filePath); + $relativePath = Mage::helper('core/file_storage_database')->getMediaRelativePath($filePath); + $file = Mage::getModel('core/file_storage_database')->loadByFilename($relativePath); - } catch (Exception $e) { - $this->_forward('noRoute'); + if (!$file->getId()) { + return false; } + + $directory = dirname($filePath); + @mkdir($directory, 0777, true); + + $io = new Varien_Io_File(); + $io->cd($directory); + + $io->streamOpen($filePath); + $io->streamLock(true); + $io->streamWrite($file->getContent()); + $io->streamUnlock(); + $io->streamClose(); + + return true; } + /** * Profile custom options download action */ diff --git a/app/code/core/Mage/Sales/etc/adminhtml.xml b/app/code/core/Mage/Sales/etc/adminhtml.xml index a2127ef37e..18ccf8d906 100644 --- a/app/code/core/Mage/Sales/etc/adminhtml.xml +++ b/app/code/core/Mage/Sales/etc/adminhtml.xml @@ -69,6 +69,15 @@ + + + + Order Statuses + adminhtml/sales_order_status + 105 + + + @@ -85,6 +94,7 @@ Create View + Send Order Email Reorder Edit Cancel @@ -153,6 +163,10 @@ + + Order Statuses + 15 + diff --git a/app/code/core/Mage/Sales/etc/config.xml b/app/code/core/Mage/Sales/etc/config.xml index 421d51eee2..e045e03fc8 100644 --- a/app/code/core/Mage/Sales/etc/config.xml +++ b/app/code/core/Mage/Sales/etc/config.xml @@ -28,7 +28,7 @@ - 1.4.0.21 + 1.4.0.23 @@ -378,6 +378,9 @@ sales_flat_order_address
    sales_flat_order_payment
    sales_flat_order_status_history
    + sales_order_status
    + sales_order_status_state
    + sales_order_status_label
    sales_flat_invoice
    sales_flat_invoice_grid
    @@ -630,6 +633,10 @@ + diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-1.4.0.21-1.4.0.22.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-1.4.0.21-1.4.0.22.php new file mode 100644 index 0000000000..7c16325469 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-1.4.0.21-1.4.0.22.php @@ -0,0 +1,37 @@ +getTable($table); + $installer->getConnection()->dropKey($tableName, 'IDX_INCREMENT_ID'); + $installer->getConnection()->addKey($tableName, 'UNQ_INCREMENT_ID', 'increment_id', 'unique'); +} diff --git a/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-1.4.0.22-1.4.0.23.php b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-1.4.0.22-1.4.0.23.php new file mode 100644 index 0000000000..24a90f0242 --- /dev/null +++ b/app/code/core/Mage/Sales/sql/sales_setup/mysql4-upgrade-1.4.0.22-1.4.0.23.php @@ -0,0 +1,93 @@ +getTable('sales/order_status'); +$statusStateTable = $installer->getTable('sales/order_status_state'); +$statusLabelTable = $installer->getTable('sales/order_status_label'); + +$installer->run(" +CREATE TABLE `{$statusTable}` ( + `status` varchar(32) NOT NULL, + `label` varchar(128) NOT NULL, + PRIMARY KEY (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +"); + +$statuses = Mage::getConfig()->getNode('global/sales/order/statuses')->asArray(); +$data = array(); +foreach ($statuses as $code => $info) { + $data[] = array( + 'status' => $code, + 'label' => $info['label'] + ); +} +$installer->getConnection()->insertArray($statusTable, array('status', 'label'), $data); + +$installer->run(" +CREATE TABLE `{$statusStateTable}` ( + `status` varchar(32) NOT NULL, + `state` varchar(32) NOT NULL, + `is_default` tinyint(1) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`status`,`state`), + CONSTRAINT `FK_SALES_ORDER_STATUS_STATE_STATUS` FOREIGN KEY (`status`) + REFERENCES `{$statusTable}` (`status`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +"); +$states = Mage::getConfig()->getNode('global/sales/order/states')->asArray(); +$data = array(); +foreach ($states as $code => $info) { + if (isset($info['statuses'])) { + foreach ($info['statuses'] as $status => $statusInfo) { + $data[] = array( + 'status' => $status, + 'state' => $code, + 'is_default'=> is_array($statusInfo) && isset($statusInfo['@']['default']) ? 1 : 0 + ); + } + } +} +$installer->getConnection()->insertArray( + $statusStateTable, + array('status', 'state', 'is_default'), + $data +); + +$installer->run(" +CREATE TABLE `{$statusLabelTable}` ( + `status` varchar(32) NOT NULL, + `store_id` smallint(5) unsigned NOT NULL, + `label` varchar(128) NOT NULL, + PRIMARY KEY (`status`,`store_id`), + KEY `FK_SALES_ORDER_STATUS_LABEL_STORE` (`store_id`), + CONSTRAINT `FK_SALES_ORDER_STATUS_LABEL_STATUS` FOREIGN KEY (`status`) + REFERENCES `{$statusTable}` (`status`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_SALES_ORDER_STATUS_LABEL_STORE` FOREIGN KEY (`store_id`) + REFERENCES `{$installer->getTable('core/store')}` (`store_id`)ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +"); diff --git a/app/code/core/Mage/SalesRule/Model/Quote/Discount.php b/app/code/core/Mage/SalesRule/Model/Quote/Discount.php index 63b24d5ba1..8e4c705f13 100644 --- a/app/code/core/Mage/SalesRule/Model/Quote/Discount.php +++ b/app/code/core/Mage/SalesRule/Model/Quote/Discount.php @@ -51,6 +51,7 @@ public function collect(Mage_Sales_Model_Quote_Address $address) parent::collect($address); $quote = $address->getQuote(); $store = Mage::app()->getStore($quote->getStoreId()); + $this->_calculator->reset($address); $items = $this->_getAddressItems($address); if (!count($items)) { diff --git a/app/code/core/Mage/SalesRule/Model/Validator.php b/app/code/core/Mage/SalesRule/Model/Validator.php index f3283be909..095805d2cc 100644 --- a/app/code/core/Mage/SalesRule/Model/Validator.php +++ b/app/code/core/Mage/SalesRule/Model/Validator.php @@ -50,16 +50,32 @@ class Mage_SalesRule_Model_Validator extends Mage_Core_Model_Abstract * Defines if method Mage_SalesRule_Model_Validator::process() was already called * Used for clearing applied rule ids in Quote and in Address * + * @deprecated since 1.4.2.0 * @var bool */ protected $_isFirstTimeProcessRun = false; + /** + * Defines if method Mage_SalesRule_Model_Validator::reset() wasn't called + * Used for clearing applied rule ids in Quote and in Address + * + * @var bool + */ + protected $_isFirstTimeResetRun = true; + /** * Information about item totals for rules. * @var array */ protected $_rulesItemTotals = array(); + /** + * Store information about addresses which cart fixed rule applied for + * + * @var array + */ + protected $_cartFixedRuleUsedForAddress = array(); + /** * Init validator * Init process load collection of rules for specific website, @@ -221,6 +237,23 @@ public function processFreeShipping(Mage_Sales_Model_Quote_Item_Abstract $item) return $this; } + /** + * Reset quote and address applied rules + * + * @param Mage_Sales_Model_Quote_Address $address + * @return Mage_SalesRule_Model_Validator + */ + public function reset(Mage_Sales_Model_Quote_Address $address) + { + if ($this->_isFirstTimeResetRun) { + $address->setAppliedRuleIds(''); + $address->getQuote()->setAppliedRuleIds(''); + $this->_isFirstTimeResetRun = false; + } + + return $this; + } + /** * Quote item discount calculation process * @@ -235,13 +268,6 @@ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) $quote = $item->getQuote(); $address = $this->_getAddress($item); - //Clearing applied rule ids for quote and address - if ($this->_isFirstTimeProcessRun !== true){ - $this->_isFirstTimeProcessRun = true; - $quote->setAppliedRuleIds(''); - $address->setAppliedRuleIds(''); - } - $itemPrice = $this->_getItemPrice($item); $baseItemPrice = $this->_getItemBasePrice($item); @@ -302,29 +328,42 @@ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) if (empty($this->_rulesItemTotals[$rule->getId()])) { Mage::throwException(Mage::helper('salesrule')->__('Item totals are not set for rule.')); } + + /** + * prevent applying whole cart discount for every shipping order, but only for first order + */ + if ($quote->getIsMultiShipping()) { + $usedForAddressId = $this->getCartFixedRuleUsedForAddress($rule->getId()); + if ($usedForAddressId && $usedForAddressId != $address->getId()) { + break; + } else { + $this->setCartFixedRuleUsedForAddress($rule->getId(), $address->getId()); + } + } $cartRules = $address->getCartFixedRules(); if (!isset($cartRules[$rule->getId()])) { $cartRules[$rule->getId()] = $rule->getDiscountAmount(); } if ($cartRules[$rule->getId()] > 0) { - if (1 >= $this->_rulesItemTotals[$rule->getId()]['items_count']) { + if ($this->_rulesItemTotals[$rule->getId()]['items_count'] <= 1) { $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]); - $discountAmount = min($itemPrice * $qty, $quoteAmount); $baseDiscountAmount = min($baseItemPrice * $qty, $cartRules[$rule->getId()]); } else { $discountRate = $baseItemPrice * $qty / $this->_rulesItemTotals[$rule->getId()]['base_items_price']; $maximumItemDiscount = $rule->getDiscountAmount() * $discountRate; $quoteAmount = $quote->getStore()->convertPrice($maximumItemDiscount); - $discountAmount = min($itemPrice * $qty, $quoteAmount); $baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount); $this->_rulesItemTotals[$rule->getId()]['items_count']--; } + + $discountAmount = min($itemPrice * $qty, $quoteAmount); $cartRules[$rule->getId()] -= $baseDiscountAmount; } $address->setCartFixedRules($cartRules); + break; case Mage_SalesRule_Model_Rule::BUY_X_GET_Y_ACTION: @@ -397,13 +436,16 @@ public function process(Mage_Sales_Model_Quote_Item_Abstract $item) $this->_maintainAddressCouponCode($address, $rule); $this->_addDiscountDescription($address, $rule); + if ($rule->getStopRulesProcessing()) { break; } } + $item->setAppliedRuleIds(join(',',$appliedRuleIds)); $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds)); $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds)); + return $this; } @@ -452,7 +494,6 @@ public function processShippingAmount(Mage_Sales_Model_Quote_Address $address) $discountAmount = $quoteAmount; $baseDiscountAmount = $rule->getDiscountAmount(); break; - case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION: $cartRules = $address->getCartFixedRules(); if (!isset($cartRules[$rule->getId()])) { @@ -460,10 +501,17 @@ public function processShippingAmount(Mage_Sales_Model_Quote_Address $address) } if ($cartRules[$rule->getId()] > 0) { $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]); - $discountAmount = min($shippingAmount-$address->getShippingDiscountAmount(), $quoteAmount); - $baseDiscountAmount = min($baseShippingAmount-$address->getBaseShippingDiscountAmount(), $cartRules[$rule->getId()]); + $discountAmount = min( + $shippingAmount-$address->getShippingDiscountAmount(), + $quoteAmount + ); + $baseDiscountAmount = min( + $baseShippingAmount-$address->getBaseShippingDiscountAmount(), + $cartRules[$rule->getId()] + ); $cartRules[$rule->getId()] -= $baseDiscountAmount; } + $address->setCartFixedRules($cartRules); break; } @@ -480,12 +528,22 @@ public function processShippingAmount(Mage_Sales_Model_Quote_Address $address) break; } } + $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds)); $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds)); + return $this; } - public function mergeIds($a1, $a2, $asString=true) + /** + * Merge two sets of ids + * + * @param array|string $a1 + * @param array|string $a2 + * @param bool $asString + * @return array + */ + public function mergeIds($a1, $a2, $asString = true) { if (!is_array($a1)) { $a1 = empty($a1) ? array() : explode(',', $a1); @@ -500,6 +558,32 @@ public function mergeIds($a1, $a2, $asString=true) return $a; } + /** + * Set information about usage cart fixed rule by quote address + * + * @param int $ruleId + * @param int $itemId + * @return void + */ + public function setCartFixedRuleUsedForAddress($ruleId, $itemId) + { + $this->_cartFixedRuleUsedForAddress[$ruleId] = $itemId; + } + + /** + * Retrieve information about usage cart fixed rule by quote address + * + * @param int $ruleId + * @return int|null + */ + public function getCartFixedRuleUsedForAddress($ruleId) + { + if (isset($this->_cartFixedRuleUsedForAddress[$ruleId])) { + return $this->_cartFixedRuleUsedForAddress[$ruleId]; + } + return null; + } + /** * Calculate quote totals for each rule and save results * @@ -524,6 +608,10 @@ public function initTotals($items, Mage_Sales_Model_Quote_Address $address) $validItemsCount = 0; foreach ($items as $item) { + // For complex product handle only its child items + if ($item->getHasChildren()) { + continue; + } if (!$rule->getActions()->validate($item)) { continue; } diff --git a/app/code/core/Mage/Sendfriend/controllers/ProductController.php b/app/code/core/Mage/Sendfriend/controllers/ProductController.php index 4a71dbdd7a..4aefb9de22 100644 --- a/app/code/core/Mage/Sendfriend/controllers/ProductController.php +++ b/app/code/core/Mage/Sendfriend/controllers/ProductController.php @@ -119,7 +119,7 @@ public function sendAction() return; } - if ($model->getMaxSendsToFriend()) { + if ($model->getMaxSendsToFriend() && $model->isExceedLimit()) { Mage::getSingleton('catalog/session')->addNotice( $this->__('The messages cannot be sent more than %d times in an hour', $model->getMaxSendsToFriend()) ); diff --git a/app/code/core/Mage/Shipping/Model/Carrier/Freeshipping.php b/app/code/core/Mage/Shipping/Model/Carrier/Freeshipping.php index 52e08b9670..ac806704e1 100644 --- a/app/code/core/Mage/Shipping/Model/Carrier/Freeshipping.php +++ b/app/code/core/Mage/Shipping/Model/Carrier/Freeshipping.php @@ -53,8 +53,7 @@ public function collectRates(Mage_Shipping_Model_Rate_Request $request) } $result = Mage::getModel('shipping/rate_result'); -// $packageValue = $request->getBaseCurrency()->convert($request->getPackageValueWithDiscount(), $request->getPackageCurrency()); - $packageValue = $request->getPackageValueWithDiscount(); + $packageValue = $request->getPackageValue(); $this->_updateFreeMethodQuote($request); diff --git a/app/code/core/Mage/Tax/Model/Calculation/Rate.php b/app/code/core/Mage/Tax/Model/Calculation/Rate.php index 5d321468b5..33157c6a00 100644 --- a/app/code/core/Mage/Tax/Model/Calculation/Rate.php +++ b/app/code/core/Mage/Tax/Model/Calculation/Rate.php @@ -91,6 +91,20 @@ protected function _afterSave() return parent::_afterSave(); } + /** + * Processing object before delete data + * + * @return Mage_Core_Model_Abstract + * @throws Mage_Core_Exception + */ + protected function _beforeDelete() + { + if ($this->_isInRule()) { + Mage::throwException(Mage::helper('tax')->__('Tax rate cannot be removed. It exists in tax rule')); + } + return parent::_beforeDelete(); + } + /** * After rate delete * redeclared for dispatch tax_settings_change_after event @@ -159,4 +173,15 @@ public function loadByCode($code) return $this; } + + /** + * Check if rate exists in tax rule + * + * @return array + */ + protected function _isInRule() + { + return $this->getResource()->isInRule($this->getId()); + } + } diff --git a/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php b/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php index c04931e2bb..9c671045ae 100644 --- a/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php +++ b/app/code/core/Mage/Tax/Model/Mysql4/Calculation.php @@ -173,7 +173,7 @@ protected function _createSearchPostCodeTemplates($postcode) $strlen = $len; } - $strArr = array($postcode); + $strArr = array($postcode, $postcode . '*'); if ($strlen > 1) { for ($i = 1; $i < $strlen; $i++) { $strArr[] = sprintf('%s*', substr($postcode, 0, - $i)); @@ -247,12 +247,16 @@ protected function _getRates($request) $selectClone = clone $select; $select - ->where("rate.zip_is_range IS NULL") - ->where("rate.tax_postcode in ('*', '', ?)", $this->_createSearchPostCodeTemplates($postcode)); - + ->where("rate.zip_is_range IS NULL"); $selectClone - ->where("rate.zip_is_range IS NOT NULL") - ->where("? BETWEEN rate.zip_from AND rate.zip_to", $postcode); + ->where("rate.zip_is_range IS NOT NULL"); + + if ($request->getPostcode() != '*') { + $select + ->where("rate.tax_postcode in ('*', '', ?)", $this->_createSearchPostCodeTemplates($postcode)); + $selectClone + ->where("? BETWEEN rate.zip_from AND rate.zip_to", $postcode); + } /** * @see ZF-7592 issue http://framework.zend.com/issues/browse/ZF-7592 diff --git a/app/code/core/Mage/Tax/Model/Mysql4/Calculation/Rate.php b/app/code/core/Mage/Tax/Model/Mysql4/Calculation/Rate.php index 8e8f9810ba..4ab1057f40 100644 --- a/app/code/core/Mage/Tax/Model/Mysql4/Calculation/Rate.php +++ b/app/code/core/Mage/Tax/Model/Mysql4/Calculation/Rate.php @@ -56,4 +56,20 @@ public function deleteAllRates() { $this->_getWriteAdapter()->delete($this->getMainTable()); } + + + /** + * Check if this rate exists in rule + * + * @param int $rateId + * @return array + */ + public function isInRule($rateId) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getTable('tax/tax_calculation'), array('tax_calculation_rate_id')) + ->where('tax_calculation_rate_id = ?', $rateId); + return $adapter->fetchCol($select); + } } diff --git a/app/code/core/Mage/Tax/Model/Mysql4/Report/Collection.php b/app/code/core/Mage/Tax/Model/Mysql4/Report/Collection.php index c9509d3740..8bad157710 100644 --- a/app/code/core/Mage/Tax/Model/Mysql4/Report/Collection.php +++ b/app/code/core/Mage/Tax/Model/Mysql4/Report/Collection.php @@ -89,10 +89,7 @@ protected function _initSelect() { $this->getSelect()->from($this->getResource()->getMainTable() , $this->_getSelectedColumns()); if (!$this->isTotals() && !$this->isSubTotals()) { - $this->getSelect()->group(array( - $this->_periodFormat, - 'code' - )); + $this->getSelect()->group(array($this->_periodFormat, 'code', 'percent')); } if ($this->isSubTotals()) { diff --git a/app/code/core/Mage/Tax/Model/Mysql4/Report/Tax.php b/app/code/core/Mage/Tax/Model/Mysql4/Report/Tax.php index 6ac7f87c0c..04a94a6d84 100644 --- a/app/code/core/Mage/Tax/Model/Mysql4/Report/Tax.php +++ b/app/code/core/Mage/Tax/Model/Mysql4/Report/Tax.php @@ -80,12 +80,7 @@ public function aggregate($from = null, $to = null) $select->where($this->_makeConditionFromDateRangeSelect($subSelect, 'e.created_at')); } - $select->group(array( - 'period', - 'store_id', - 'code', - 'order_status' - )); + $select->group(array('period', 'store_id', 'code', 'tax.percent', 'order_status')); $writeAdapter->query($select->insertFromSelect($this->getMainTable(), array_keys($columns))); @@ -109,11 +104,7 @@ public function aggregate($from = null, $to = null) $select->where($this->_makeConditionFromDateRangeSelect($subSelect, 'period')); } - $select->group(array( - 'period', - 'code', - 'order_status' - )); + $select->group(array('period', 'code', 'percent', 'order_status')); $writeAdapter->query($select->insertFromSelect($this->getMainTable(), array_keys($columns))); diff --git a/app/code/core/Mage/Tax/Model/Mysql4/Report/Updatedat/Collection.php b/app/code/core/Mage/Tax/Model/Mysql4/Report/Updatedat/Collection.php index 64a65d8403..f02bae9322 100644 --- a/app/code/core/Mage/Tax/Model/Mysql4/Report/Updatedat/Collection.php +++ b/app/code/core/Mage/Tax/Model/Mysql4/Report/Updatedat/Collection.php @@ -165,10 +165,7 @@ protected function _initSelect() } if (!$this->isTotals() && !$this->isSubTotals()) { - $select->group(array( - $this->_periodFormat, - 'code' - )); + $select->group(array($this->_periodFormat, 'code', 'percent')); } if ($this->isSubTotals()) { diff --git a/app/code/core/Mage/Tax/Model/Sales/Pdf/Grandtotal.php b/app/code/core/Mage/Tax/Model/Sales/Pdf/Grandtotal.php index 9b73d167c9..86edf598c9 100644 --- a/app/code/core/Mage/Tax/Model/Sales/Pdf/Grandtotal.php +++ b/app/code/core/Mage/Tax/Model/Sales/Pdf/Grandtotal.php @@ -50,22 +50,25 @@ public function getTotalsForDisplay() $tax = $this->getOrder()->formatPriceTxt($this->getSource()->getTaxAmount()); $fontSize = $this->getFontSize() ? $this->getFontSize() : 7; - $totals = array( - array( - 'amount' => $this->getAmountPrefix().$amountExclTax, - 'label' => Mage::helper('tax')->__('Grand Total (Excl. Tax)') . ':', - 'font_size' => $fontSize - ), - array( - 'amount' => $this->getAmountPrefix().$tax, - 'label' => Mage::helper('tax')->__('Tax') . ':', - 'font_size' => $fontSize - ), - array( - 'amount' => $this->getAmountPrefix().$amount, - 'label' => Mage::helper('tax')->__('Grand Total (Incl. Tax)') . ':', - 'font_size' => $fontSize - ), + $totals = array(array( + 'amount' => $this->getAmountPrefix().$amountExclTax, + 'label' => Mage::helper('tax')->__('Grand Total (Excl. Tax)') . ':', + 'font_size' => $fontSize + )); + + if ($config->displaySalesFullSummary($store)) { + $totals = array_merge($totals, $this->getFullTaxInfo()); + } + + $totals[] = array( + 'amount' => $this->getAmountPrefix().$tax, + 'label' => Mage::helper('tax')->__('Tax') . ':', + 'font_size' => $fontSize + ); + $totals[] = array( + 'amount' => $this->getAmountPrefix().$amount, + 'label' => Mage::helper('tax')->__('Grand Total (Incl. Tax)') . ':', + 'font_size' => $fontSize ); return $totals; } diff --git a/app/code/core/Mage/Tax/Model/Sales/Pdf/Tax.php b/app/code/core/Mage/Tax/Model/Sales/Pdf/Tax.php index 91fe56ff00..7c216d8ef8 100644 --- a/app/code/core/Mage/Tax/Model/Sales/Pdf/Tax.php +++ b/app/code/core/Mage/Tax/Model/Sales/Pdf/Tax.php @@ -44,6 +44,18 @@ public function getTotalsForDisplay() if ($config->displaySalesTaxWithGrandTotal($store)) { return array(); } - return parent::getTotalsForDisplay(); + + $fontSize = $this->getFontSize() ? $this->getFontSize() : 7; + $totals = array(); + + if ($config->displaySalesFullSummary($store)) { + $totals = $this->getFullTaxInfo(); + } + + $totals = array_merge($totals, parent::getTotalsForDisplay()); + + return $totals; } + + } diff --git a/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php b/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php index 40ea620b97..a3451d52a3 100644 --- a/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php +++ b/app/code/core/Mage/Tax/Model/Sales/Total/Quote/Subtotal.php @@ -128,7 +128,7 @@ public function collect(Mage_Sales_Model_Quote_Address $address) } foreach ($items as $item) { - if ($item->getParentItemId()) { + if ($item->getParentItem()) { continue; } if ($item->getHasChildren() && $item->isChildrenCalculated()) { @@ -577,12 +577,12 @@ protected function _recalculateParent(Mage_Sales_Model_Quote_Item_Abstract $item $rowTotalInclTax = 0; $baseRowTotalInclTax= 0; foreach ($item->getChildren() as $child) { - $price += $child->getOriginalPrice() * $child->getQty(); - $basePrice += $child->getBaseOriginalPrice() * $child->getQty(); + $price += $child->getPrice() * $child->getQty(); + $basePrice += $child->getBasePrice() * $child->getQty(); $rowTotal += $child->getRowTotal(); $baseRowTotal += $child->getBaseRowTotal(); - $priceInclTax += $child->getPriceInclTax(); - $basePriceInclTax += $child->getBasePriceInclTax(); + $priceInclTax += $child->getPriceInclTax() * $child->getQty(); + $basePriceInclTax += $child->getBasePriceInclTax() * $child->getQty(); $rowTotalInclTax += $child->getRowTotalInclTax(); $baseRowTotalInclTax+= $child->getBaseRowTotalInclTax(); } diff --git a/app/code/core/Mage/Tax/etc/config.xml b/app/code/core/Mage/Tax/etc/config.xml index 04f489fa8a..483687d669 100644 --- a/app/code/core/Mage/Tax/etc/config.xml +++ b/app/code/core/Mage/Tax/etc/config.xml @@ -28,7 +28,7 @@ - 1.4.0.0 + 1.4.0.1 diff --git a/app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php b/app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php new file mode 100644 index 0000000000..2f78820bd8 --- /dev/null +++ b/app/code/core/Mage/Tax/sql/tax_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php @@ -0,0 +1,39 @@ +getTable('tax/tax_order_aggregated_created'); +$installer->getConnection()->truncate($table); +$installer->getConnection()->addKey($table, 'UNQ_PERIOD_STORE_CODE_ORDER_STATUS', + array('period', 'store_id', 'code', 'percent', 'order_status'), 'unique' +); diff --git a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php index 44a249cb8e..c8cec58129 100644 --- a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php @@ -599,7 +599,7 @@ public function getCurrencyCode () 'CLP' => 'CHP', // Chilean Pesos 'TWD' => 'NTD', // New Taiwan Dollars ); - $currencyCode = Mage::app()->getBaseCurrencyCode(); + $currencyCode = Mage::app()->getStore()->getBaseCurrencyCode(); return isset($codes[$currencyCode]) ? $codes[$currencyCode] : $currencyCode; } diff --git a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php index da8111a10f..d5712ca099 100644 --- a/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php +++ b/app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php @@ -47,6 +47,13 @@ class Mage_Usa_Model_Shipping_Carrier_Ups protected $_defaultCgiGatewayUrl = 'http://www.ups.com:80/using/services/rave/qcostcgi.cgi'; + /** + * Base currency rate + * + * @var double + */ + protected $_baseCurrencyRate; + public function collectRates(Mage_Shipping_Model_Rate_Request $request) { if (!$this->getConfigFlag('active')) { @@ -681,6 +688,23 @@ protected function _getXmlQuotes() return $this->_parseXmlResponse($xmlResponse); } + /** + * Get base currency rate + * + * @param string $code + * @return double + */ + protected function _getBaseCurrencyRate($code) + { + if (!$this->_baseCurrencyRate) { + $this->_baseCurrencyRate = Mage::getModel('directory/currency') + ->load($code) + ->getAnyRate($this->_request->getBaseCurrency()->getCode()); + } + + return $this->_baseCurrencyRate; + } + protected function _parseXmlResponse($xmlResponse) { $costArr = array(); @@ -690,7 +714,7 @@ protected function _parseXmlResponse($xmlResponse) $xml->loadString($xmlResponse); $arr = $xml->getXpath("//RatingServiceSelectionResponse/Response/ResponseStatusCode/text()"); $success = (int)$arr[0]; - if($success===1){ + if ($success===1) { $arr = $xml->getXpath("//RatingServiceSelectionResponse/RatedShipment"); $allowedMethods = explode(",", $this->getConfigData('allowed_methods')); @@ -700,6 +724,8 @@ protected function _parseXmlResponse($xmlResponse) && $this->getConfigData('shipper_number') && !empty($negotiatedArr); + $allowedCurrencies = Mage::getModel('directory/currency')->getConfigAllowCurrencies(); + foreach ($arr as $shipElement){ $code = (string)$shipElement->Service->Code; #$shipment = $this->getShipmentByCode($code); @@ -711,8 +737,29 @@ protected function _parseXmlResponse($xmlResponse) $cost = $shipElement->TotalCharges->MonetaryValue; } - $costArr[$code] = $cost; - $priceArr[$code] = $this->getMethodPrice(floatval($cost),$code); + //convert price with Origin country currency code to base currency code + $successConversion = true; + $responseCurrencyCode = (string) $shipElement->TotalCharges->CurrencyCode; + if ($responseCurrencyCode) { + if (in_array($responseCurrencyCode, $allowedCurrencies)) { + $cost *= $this->_getBaseCurrencyRate($responseCurrencyCode); + } else { + $errorTitle = Mage::helper('directory') + ->__('Can\'t convert rate from "%s-%s".', + $responseCurrencyCode, + $this->_request->getPackageCurrency()->getCode()); + $error = Mage::getModel('shipping/rate_result_error'); + $error->setCarrier('ups'); + $error->setCarrierTitle($this->getConfigData('title')); + $error->setErrorMessage($errorTitle); + $successConversion = false; + } + } + + if ($successConversion) { + $costArr[$code] = $cost; + $priceArr[$code] = $this->getMethodPrice(floatval($cost),$code); + } } } } else { @@ -721,7 +768,7 @@ protected function _parseXmlResponse($xmlResponse) $error = Mage::getModel('shipping/rate_result_error'); $error->setCarrier('ups'); $error->setCarrierTitle($this->getConfigData('title')); - //$error->setErrorMessage($errorTitle); + Mage::log($errorTitle); $error->setErrorMessage($this->getConfigData('specificerrmsg')); } } @@ -735,7 +782,7 @@ protected function _parseXmlResponse($xmlResponse) if(!isset($errorTitle)){ $errorTitle = Mage::helper('usa')->__('Cannot retrieve shipping rates'); } - //$error->setErrorMessage($errorTitle); + Mage::log($errorTitle); $error->setErrorMessage($this->getConfigData('specificerrmsg')); $result->append($error); } else { diff --git a/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php b/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php index 45e20a2e36..d450cdd37b 100644 --- a/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php +++ b/app/code/core/Mage/Weee/Model/Total/Quote/Weee.php @@ -35,6 +35,13 @@ class Mage_Weee_Model_Total_Quote_Weee extends Mage_Tax_Model_Sales_Total_Quote_ protected $_helper; protected $_store; + /** + * Tax configuration object + * + * @var Mage_Tax_Model_Config + */ + protected $_config; + /** * Flag which notify what tax amount can be affected by fixed porduct tax * @@ -49,6 +56,7 @@ public function __construct() { $this->setCode('weee'); $this->_helper = Mage::helper('weee'); + $this->_config = Mage::getSingleton('tax/config'); } /** @@ -215,11 +223,13 @@ protected function _processDiscountSettings($item, $value, $baseValue) protected function _processTaxSettings($item, $value, $baseValue, $rowValue, $baseRowValue) { if ($this->_helper->isTaxable($this->_store) && $rowValue) { - $item->setExtraTaxableAmount($value) - ->setBaseExtraTaxableAmount($baseValue) - ->setExtraRowTaxableAmount($rowValue) - ->setBaseExtraRowTaxableAmount($baseRowValue) - ->unsRowTotalInclTax() + if ($this->_config->priceIncludesTax($this->_store)) { + $item->setExtraTaxableAmount($value) + ->setBaseExtraTaxableAmount($baseValue) + ->setExtraRowTaxableAmount($rowValue) + ->setBaseExtraRowTaxableAmount($baseRowValue); + } + $item->unsRowTotalInclTax() ->unsBaseRowTotalInclTax() ->unsPriceInclTax() ->unsBasePriceInclTax(); diff --git a/app/code/core/Mage/Wishlist/Block/Abstract.php b/app/code/core/Mage/Wishlist/Block/Abstract.php index a7aa5955f1..ffc6a3bf32 100644 --- a/app/code/core/Mage/Wishlist/Block/Abstract.php +++ b/app/code/core/Mage/Wishlist/Block/Abstract.php @@ -37,7 +37,7 @@ abstract class Mage_Wishlist_Block_Abstract extends Mage_Catalog_Block_Product_A /** * Wishlist Product Items Collection * - * @var Mage_Wishlist_Model_Mysql4_Product_Collection + * @var Mage_Wishlist_Model_Mysql4_Item_Collection */ protected $_collection; @@ -95,7 +95,7 @@ protected function _getWishlist() /** * Prepare additional conditions to collection * - * @param Mage_Wishlist_Model_Mysql4_Product_Collection $collection + * @param Mage_Wishlist_Model_Mysql4_Item_Collection $collection * @return Mage_Wishlist_Block_Customer_Wishlist */ protected function _prepareCollection($collection) @@ -106,20 +106,14 @@ protected function _prepareCollection($collection) /** * Retrieve Wishlist Product Items collection * - * @return Mage_Wishlist_Model_Mysql4_Product_Collection + * @return Mage_Wishlist_Model_Mysql4_Item_Collection */ public function getWishlistItems() { if (is_null($this->_collection)) { - $attributes = Mage::getSingleton('catalog/config')->getProductAttributes(); $this->_collection = $this->_getWishlist() - ->getProductCollection() - ->addAttributeToSelect($attributes) - ->addStoreFilter() - ->addUrlRewrite(); - - Mage::getSingleton('catalog/product_visibility') - ->addVisibleInSiteFilterToCollection($this->_collection); + ->getItemCollection() + ->addStoreFilter(); $this->_prepareCollection($this->_collection); } @@ -130,8 +124,8 @@ public function getWishlistItems() /** * Back compatibility retrieve wishlist product items * - * @deprecated - * @return Mage_Wishlist_Model_Mysql4_Product_Collection + * @deprecated after 1.4.2.0 + * @return Mage_Wishlist_Model_Mysql4_Item_Collection */ public function getWishlist() { @@ -141,7 +135,7 @@ public function getWishlist() /** * Retrieve URL for Removing item from wishlist * - * @param Mage_Catalog_Model_Product $item + * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $item * @return string */ public function getItemRemoveUrl($product) @@ -152,12 +146,12 @@ public function getItemRemoveUrl($product) /** * Retrieve Add Item to shopping cart URL * - * @param Mage_Catalog_Model_Product $product + * @param string|Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $item * @return string */ - public function getItemAddToCartUrl($product) + public function getItemAddToCartUrl($item) { - return $this->_getHelper()->getAddToCartUrl($product); + return $this->_getHelper()->getAddToCartUrl($item); } /** @@ -171,6 +165,26 @@ public function getAddToWishlistUrl($product) return $this->_getHelper()->getAddUrl($product); } + /** + * Returns item configure url in wishlist + * + * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $product + * + * @return string + */ + public function getItemConfigureUrl($product) + { + if ($product instanceof Mage_Catalog_Model_Product) { + $id = $product->getWishlistItemId(); + } else { + $id = $product->getId(); + } + $params = array('id' => $id); + + return $this->getUrl('wishlist/index/configure/', $params); + } + + /** * Retrieve Escaped Description for Wishlist Item * @@ -179,8 +193,8 @@ public function getAddToWishlistUrl($product) */ public function getEscapedDescription($item) { - if ($item->getWishlistItemDescription()) { - return $this->htmlEscape($item->getWishlistItemDescription()); + if ($item->getDescription()) { + return $this->htmlEscape($item->getDescription()); } return ' '; } @@ -193,7 +207,7 @@ public function getEscapedDescription($item) */ public function hasDescription($item) { - return trim($item->getWishlistItemDescription()) != ''; + return trim($item->getDescription()) != ''; } /** @@ -215,7 +229,7 @@ public function getFormatedDate($date) public function isSaleable() { foreach ($this->getWishlistItems() as $item) { - if ($item->isSaleable()) { + if ($item->getProduct()->isSaleable()) { return true; } } @@ -233,6 +247,21 @@ public function getWishlistItemsCount() return $this->getWishlistItems()->count(); } + /** + * Retrieve Qty from item + * + * @param Mage_Wishlist_Model_Item|Mage_Catalog_Model_Product $item + * @return float + */ + public function getQty($item) + { + $qty = $item->getQty() * 1; + if (!$qty) { + $qty = 1; + } + return $qty; + } + /** * Check is the wishlist has items * diff --git a/app/code/core/Mage/Wishlist/Block/Customer/Sidebar.php b/app/code/core/Mage/Wishlist/Block/Customer/Sidebar.php index f73647e712..e590db7a41 100644 --- a/app/code/core/Mage/Wishlist/Block/Customer/Sidebar.php +++ b/app/code/core/Mage/Wishlist/Block/Customer/Sidebar.php @@ -37,13 +37,15 @@ class Mage_Wishlist_Block_Customer_Sidebar extends Mage_Wishlist_Block_Abstract /** * Add sidebar conditions to collection * - * @param Mage_Wishlist_Model_Mysql4_Product_Collection $collection + * @param Mage_Wishlist_Model_Mysql4_Item_Collection $collection * @return Mage_Wishlist_Block_Customer_Wishlist */ protected function _prepareCollection($collection) { - $collection->setPage(1, 3); - $collection->addAttributeToSort('added_at', 'desc'); + $collection->setCurPage(1) + ->setPageSize(3) + ->setInStockFilter(true) + ->addWishListSortOrder('added_at', 'desc'); return $this; } @@ -76,7 +78,7 @@ public function getCanDisplayWishlist() * Retrieve URL for removing item from wishlist * * @deprecated back compatibility alias for getItemRemoveUrl - * @param Mage_Wishlist_Model_Item $item + * @param Mage_Wishlist_Model_Item $item * @return string */ public function getRemoveItemUrl($item) @@ -88,7 +90,7 @@ public function getRemoveItemUrl($item) * Retrieve URL for adding product to shopping cart and remove item from wishlist * * @deprecated - * @param Mage_Catalog_Model_Product $product + * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $product * @return string */ public function getAddToCartItemUrl($product) diff --git a/app/code/core/Mage/Wishlist/Block/Customer/Wishlist.php b/app/code/core/Mage/Wishlist/Block/Customer/Wishlist.php index 01fc7d8984..ff92a26675 100644 --- a/app/code/core/Mage/Wishlist/Block/Customer/Wishlist.php +++ b/app/code/core/Mage/Wishlist/Block/Customer/Wishlist.php @@ -34,6 +34,32 @@ */ class Mage_Wishlist_Block_Customer_Wishlist extends Mage_Wishlist_Block_Abstract { + /* + * List of product type configuration to render options list + */ + protected $_optionsCfg = array(); + + /* + * Constructor of block - adds default renderer for product configuration + */ + public function _construct() + { + parent::_construct(); + $this->addOptionsRenderCfg('default', 'catalog/product_configuration', 'wishlist/options_list.phtml'); + } + + /** + * Add wishlist conditions to collection + * + * @param Mage_Wishlist_Model_Mysql4_Item_Collection $collection + * @return Mage_Wishlist_Block_Customer_Wishlist + */ + protected function _prepareCollection($collection) + { + $collection->setInStockFilter(true)->setOrder('added_at', 'ASC'); + return $this; + } + /** * Preparing global layout * @@ -60,4 +86,119 @@ public function getBackUrl() } return $this->getUrl('customer/account/'); } + + /** + * Sets all options render configurations + * + * @param null|array $optionCfg + * @return Mage_Wishlist_Block_Customer_Wishlist + */ + public function setOptionsRenderCfgs($optionCfg) + { + $this->_optionsCfg = $optionCfg; + return $this; + } + + /** + * Returns all options render configurations + * + * @return array + */ + public function getOptionsRenderCfgs() + { + return $this->_optionsCfg; + } + + /* + * Adds config for rendering product type options + * If template is null - later default will be used + * + * @param string $productType + * @param string $helperName + * @param null|string $template + * @return Mage_Wishlist_Block_Customer_Wishlist + */ + public function addOptionsRenderCfg($productType, $helperName, $template = null) + { + $this->_optionsCfg[$productType] = array('helper' => $helperName, 'template' => $template); + return $this; + } + + /** + * Returns html for showing item options + * + * @param string $productType + * @return array|null + */ + public function getOptionsRenderCfg($productType) + { + if (isset($this->_optionsCfg[$productType])) { + return $this->_optionsCfg[$productType]; + } elseif (isset($this->_optionsCfg['default'])) { + return $this->_optionsCfg['default']; + } else { + return null; + } + } + + /** + * Returns html for showing item options + * + * @param Mage_Wishlist_Model_Item $item + * @return string + */ + public function getDetailsHtml(Mage_Wishlist_Model_Item $item) + { + $cfg = $this->getOptionsRenderCfg($item->getProduct()->getTypeId()); + if (!$cfg) { + return ''; + } + + $helper = Mage::helper($cfg['helper']); + if (!($helper instanceof Mage_Catalog_Helper_Product_Configuration_Interface)) { + Mage::throwException($this->__("Helper for wishlist options rendering doesn't implement required interface.")); + } + + $block = $this->getChild('item_options'); + if (!$block) { + return ''; + } + + if ($cfg['template']) { + $template = $cfg['template']; + } else { + $cfgDefault = $this->getOptionsRenderCfg('default'); + if (!$cfgDefault) { + return ''; + } + $template = $cfgDefault['template']; + } + + return $block->setTemplate($template) + ->setOptionList($helper->getOptions($item)) + ->toHtml(); + } + + /** + * Returns default description to show in textarea field + * + * @param Mage_Wishlist_Model_Item $item + * @return string + */ + public function getCommentValue(Mage_Wishlist_Model_Item $item) + { + return $this->hasDescription($item) ? $this->getEscapedDescription($item) : Mage::helper('wishlist')->defaultCommentString(); + } + + /** + * Returns qty to show visually to user + * + * @param Mage_Wishlist_Model_Item $item + * @return float + */ + public function getAddToCartQty(Mage_Wishlist_Model_Item $item) + { + $qty = $this->getQty($item); + return $qty ? $qty : 1; + } } diff --git a/app/code/core/Mage/Wishlist/Block/Customer/Wishlist/Item/Options.php b/app/code/core/Mage/Wishlist/Block/Customer/Wishlist/Item/Options.php new file mode 100644 index 0000000000..6770c96015 --- /dev/null +++ b/app/code/core/Mage/Wishlist/Block/Customer/Wishlist/Item/Options.php @@ -0,0 +1,36 @@ + + */ +class Mage_Wishlist_Block_Customer_Wishlist_Item_Options extends Mage_Wishlist_Block_Abstract +{ +} diff --git a/app/code/core/Mage/Wishlist/Block/Item/Configure.php b/app/code/core/Mage/Wishlist/Block/Item/Configure.php new file mode 100644 index 0000000000..2f1032b381 --- /dev/null +++ b/app/code/core/Mage/Wishlist/Block/Item/Configure.php @@ -0,0 +1,56 @@ +_localFilter) { + $this->_localFilter = new Zend_Filter_LocalizedToNormalized(array('locale' => Mage::app()->getLocale()->getLocaleCode())); + } + $qty = $this->_localFilter->filter($qty); + if ($qty < 0) { + $qty = null; + } + return $qty; + } + /** * Retrieve current wishlist instance * @@ -58,14 +82,26 @@ public function allcartAction() $addedItems = array(); $notSalable = array(); $hasOptions = array(); - $isGrouped = array(); $cart = Mage::getSingleton('checkout/cart'); - $collection = $wishlist->getItemCollection(); + $collection = $wishlist->getItemCollection() + ->setVisibilityFilter(); + $qtys = $this->getRequest()->getParam('qty'); foreach ($collection as $item) { /** @var Mage_Wishlist_Model_Item */ try { + $item->unsProduct(); + + // Set qty + if (isset($qtys[$item->getId()])) { + $qty = $this->_processLocalizedQty($qtys[$item->getId()]); + if ($qty) { + $item->setQty($qty); + } + } + + // Add to cart if ($item->addToCart($cart, $isOwner)) { $addedItems[] = $item->getProduct(); } @@ -75,10 +111,8 @@ public function allcartAction() $notSalable[] = $item; } else if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_HAS_REQUIRED_OPTIONS) { $hasOptions[] = $item; - } else if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_IS_GROUPED_PRODUCT) { - $isGrouped[] = $item; } else { - $messages[] = $e->getMessage(); + $messages[] = $this->__('%s for "%s".', trim($e->getMessage(), '.'), $item->getProduct()->getName()); } } catch (Exception $e) { Mage::logException($e); @@ -107,14 +141,6 @@ public function allcartAction() $messages[] = Mage::helper('wishlist')->__('Unable to add the following product(s) to shopping cart: %s.', join(', ', $products)); } - if ($isGrouped) { - $products = array(); - foreach ($isGrouped as $item) { - $products[] = '"' . $item->getProduct()->getName() . '"'; - } - $messages[] = Mage::helper('wishlist')->__('Product(s) %s are grouped. Each of them can be added to cart separately only.', join(', ', $products)); - } - if ($hasOptions) { $products = array(); foreach ($hasOptions as $item) { @@ -131,12 +157,6 @@ public function allcartAction() $item->delete(); } $redirectUrl = $item->getProductUrl(); - } elseif ($isMessageSole && count($isGrouped) == 1) { - $item = $isGrouped[0]; - if ($isOwner) { - $item->delete(); - } - $redirectUrl = $item->getProductUrl(); } else { $wishlistSession = Mage::getSingleton('wishlist/session'); foreach ($messages as $message) { diff --git a/app/code/core/Mage/Wishlist/Helper/Data.php b/app/code/core/Mage/Wishlist/Helper/Data.php index 757e411c32..778d7da9fa 100644 --- a/app/code/core/Mage/Wishlist/Helper/Data.php +++ b/app/code/core/Mage/Wishlist/Helper/Data.php @@ -48,6 +48,13 @@ class Mage_Wishlist_Helper_Data extends Mage_Core_Helper_Abstract */ protected $_productCollection = null; + /** + * Wishlist Items Collection + * + * @var Mage_Wishlist_Model_Mysql4_Item_Collection + */ + protected $_wishlistItemCollection = null; + /** * Retrieve customer login status * @@ -111,6 +118,9 @@ public function getItemCount() * * alias for getProductCollection * + * @deprecated after 1.4.2.0 + * @see Mage_Wishlist_Model_Wishlist::getItemCollection() + * * @return Mage_Wishlist_Model_Mysql4_Product_Collection */ public function getItemCollection() @@ -118,9 +128,28 @@ public function getItemCollection() return $this->getProductCollection(); } + + /** + * Retrieve wishlist items collection + * + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function getWishlistItemCollection() + { + if (is_null($this->_wishlistItemCollection)) { + $this->_wishlistItemCollection = $this->getWishlist() + ->getItemCollection(); + } + return $this->_wishlistItemCollection; + } + + /** * Retrieve wishlist product items collection * + * @deprecated after 1.4.2.0 + * @see Mage_Wishlist_Model_Wishlist::getItemCollection() + * * @return Mage_Wishlist_Model_Mysql4_Product_Collection */ public function getProductCollection() @@ -144,12 +173,18 @@ public function getProductCollection() protected function _getUrlStore($item) { $storeId = null; - if ($item instanceof Mage_Catalog_Model_Product) { - if ($item->isVisibleInSiteVisibility()) { - $storeId = $item->getStoreId(); + $product = null; + if ($item instanceof Mage_Wishlist_Model_Item) { + $product = $item->getProduct(); + } elseif ($item instanceof Mage_Catalog_Model_Product) { + $product = $item; + } + if ($product) { + if ($product->isVisibleInSiteVisibility()) { + $storeId = $product->getStoreId(); } - else if ($item->hasUrlDataObject()) { - $storeId = $item->getUrlDataObject()->getStoreId(); + else if ($product->hasUrlDataObject()) { + $storeId = $product->getUrlDataObject()->getStoreId(); } } return Mage::app()->getStore($storeId); @@ -168,6 +203,19 @@ public function getRemoveUrl($item) )); } + /** + * Retrieve URL for removing item from wishlist + * + * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $item + * @return string + */ + public function getConfigureUrl($item) + { + return $this->_getUrl('wishlist/index/configure', array( + 'item' => $item->getWishlistItemId() + )); + } + /** * Retrieve url for adding product to wishlist * @@ -179,6 +227,30 @@ public function getAddUrl($item) return $this->getAddUrlWithParams($item); } + /** + * Retrieve url for updating product in wishlist + * + * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $product + * @return string|boolean + */ + public function getUpdateUrl($item) + { + $itemId = null; + if ($item instanceof Mage_Catalog_Model_Product) { + $itemId = $item->getWishlistItemId(); + } + if ($item instanceof Mage_Wishlist_Model_Item) { + $itemId = $item->getId(); + } + + if ($itemId) { + $params['id'] = $itemId; + return $this->_getUrlStore($item)->getUrl('wishlist/index/updateItemOptions', $params); + } + + return false; + } + /** * Retrieve url for adding product to wishlist with params * @@ -207,22 +279,23 @@ public function getAddUrlWithParams($item, array $params = array()) /** * Retrieve URL for adding item to shoping cart * - * @param Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $item + * @param string|Mage_Catalog_Model_Product|Mage_Wishlist_Model_Item $item * @return string */ public function getAddToCartUrl($item) { - $urlParamName = Mage_Core_Controller_Front_Action::PARAM_NAME_URL_ENCODED; $continueUrl = Mage::helper('core')->urlEncode(Mage::getUrl('*/*/*', array( '_current' => true, '_use_rewrite' => true, '_store_to_url' => true, ))); - return $this->_getUrlStore($item)->getUrl('wishlist/index/cart', array( - 'item' => $item->getWishlistItemId(), - $urlParamName => $continueUrl - )); + $urlParamName = Mage_Core_Controller_Front_Action::PARAM_NAME_URL_ENCODED; + $params = array( + 'item' => is_string($item) ? $item : $item->getWishlistItemId(), + $urlParamName => $continueUrl + ); + return $this->_getUrlStore($item)->getUrl('wishlist/index/cart', $params); } /** @@ -327,7 +400,9 @@ public function calculate() $count = 0; } else { - $count = $this->getProductCollection() + $count = $this->getWishlistItemCollection() + /* Price data is added to consider item stock status using price index */ +// ->addPriceData() ->getSize(); } Mage::getSingleton('customer/session')->setWishlistItemCount($count); diff --git a/app/code/core/Mage/Wishlist/Model/Item.php b/app/code/core/Mage/Wishlist/Model/Item.php index 7c7631c4d3..6fb7206da4 100644 --- a/app/code/core/Mage/Wishlist/Model/Item.php +++ b/app/code/core/Mage/Wishlist/Model/Item.php @@ -33,10 +33,17 @@ * @author Magento Core Team */ class Mage_Wishlist_Model_Item extends Mage_Core_Model_Abstract + implements Mage_Catalog_Model_Product_Configuration_Item_Interface { const EXCEPTION_CODE_NOT_SALABLE = 901; const EXCEPTION_CODE_HAS_REQUIRED_OPTIONS = 902; - const EXCEPTION_CODE_IS_GROUPED_PRODUCT = 903; + const EXCEPTION_CODE_IS_GROUPED_PRODUCT = 903; // deprecated after 1.4.2.0, because we can store product configuration and add grouped products + + /** + * Custom path to download attached file + * @var string + */ + protected $_customOptionDownloadUrl = 'wishlist/index/downloadCustomOption'; /** * Prefix of model events names @@ -54,6 +61,33 @@ class Mage_Wishlist_Model_Item extends Mage_Core_Model_Abstract */ protected $_eventObject = 'item'; + /** + * Item options array + * + * @var array + */ + protected $_options = array(); + + /** + * Item options by code cache + * + * @var array + */ + protected $_optionsByCode = array(); + + /** + * Not Represent options + * + * @var array + */ + protected $_notRepresentOptions = array('info_buyRequest'); + + /** + * Flag stating that options were successfully saved + * + */ + protected $_flagOptionsSaved = null; + /** * Initialize resource model * @@ -73,6 +107,108 @@ protected function _getResource() return parent::_getResource(); } + /** + * Check if two options array are identical + * + * @param array $options1 + * @param array $options2 + * @return bool + */ + protected function _compareOptions($options1, $options2) + { + $skipOptions = array('id', 'qty', 'return_url'); + foreach ($options1 as $code => $value) { + if (in_array($code, $skipOptions)) { + continue; + } + if (!isset($options2[$code]) || $options2[$code] != $value) { + return false; + } + } + return true; + } + + /** + * Register option code + * + * @param Mage_Wishlist_Model_Item_Option $option + * @return Mage_Wishlist_Model_Item + */ + protected function _addOptionCode($option) + { + if (!isset($this->_optionsByCode[$option->getCode()])) { + $this->_optionsByCode[$option->getCode()] = $option; + } + else { + Mage::throwException(Mage::helper('sales')->__('An item option with code %s already exists.', $option->getCode())); + } + return $this; + } + + /** + * Checks that item model has data changes. + * Call save item options if model isn't need to save in DB + * + * @return boolean + */ + protected function _hasModelChanged() + { + if (!$this->hasDataChanges()) { + return false; + } + + return $this->_getResource()->hasDataChanged($this); + } + + /** + * Save item options + * + * @return Mage_Wishlist_Model_Item + */ + protected function _saveItemOptions() + { + foreach ($this->_options as $index => $option) { + if ($option->isDeleted()) { + $option->delete(); + unset($this->_options[$index]); + unset($this->_optionsByCode[$option->getCode()]); + } else { + $option->save(); + } + } + + $this->_flagOptionsSaved = true; // Report to watchers that options were saved + + return $this; + } + + /** + * Save model plus its options + * Ensures saving options in case when resource model was not changed + */ + public function save() + { + $hasDataChanges = $this->hasDataChanges(); + $this->_flagOptionsSaved = false; + + parent::save(); + + if ($hasDataChanges && !$this->_flagOptionsSaved) { + $this->_saveItemOptions(); + } + } + + /** + * Save item options after item saved + * + * @return Mage_Wishlist_Model_Item + */ + protected function _afterSave() + { + $this->_saveItemOptions(); + return parent::_afterSave(); + } + /** * Validate wish list item data * @@ -166,10 +302,17 @@ public function getProduct() } $product = Mage::getModel('catalog/product') + ->setStoreId($this->getStoreId()) ->load($this->getProductId()); $this->setData('product', $product); } + + /** + * Reset product final price because it related to custom options + */ + $product->setFinalPrice(null); + $product->setCustomOptions($this->_optionsByCode); return $product; } @@ -188,11 +331,6 @@ public function addToCart(Mage_Checkout_Model_Cart $cart, $delete = false) { $product = $this->getProduct(); - if (Mage_Catalog_Model_Product_Type::TYPE_GROUPED == $product->getTypeId()) { - throw new Mage_Core_Exception(null, self::EXCEPTION_CODE_IS_GROUPED_PRODUCT); - } - - $product->setQty(1); $storeId = $this->getStoreId(); if ($product->getStatus() != Mage_Catalog_Model_Product_Status::STATUS_ENABLED) { @@ -219,11 +357,9 @@ public function addToCart(Mage_Checkout_Model_Cart $cart, $delete = false) throw new Mage_Core_Exception(null, self::EXCEPTION_CODE_NOT_SALABLE); } - if ($product->getTypeInstance(true)->hasRequiredOptions($product)) { - throw new Mage_Core_Exception(null, self::EXCEPTION_CODE_HAS_REQUIRED_OPTIONS); - } + $buyRequest = $this->getBuyRequest(); - $cart->addProduct($product); + $cart->addProduct($product, $buyRequest); if (!$product->isVisibleInSiteVisibility()) { $cart->getQuote()->getItemByProduct($product)->setStoreId($storeId); } @@ -253,4 +389,261 @@ public function getProductUrl() return $product->getUrlModel()->getUrl($product, array('_query' => $query)); } + + /** + * Returns formatted buy request - object, holding request received from + * product view page with keys and options for configured product + * + * @return Varien_Object + */ + public function getBuyRequest() + { + $option = $this->getOptionByCode('info_buyRequest'); + if ($option) { + $buyRequest = new Varien_Object(unserialize($option->getValue())); + } else { + $buyRequest = new Varien_Object(); + } + + $buyRequest->setQty($this->getQty()*1); + return $buyRequest; + } + + /** + * Set buy request - object, holding request received from + * product view page with keys and options for configured product + * @param Varien_Object $buyRequest + * @return Mage_Wishlist_Model_Item + */ + public function setBuyRequest($buyRequest) + { + $buyRequest->setId($this->getId()); + + $_buyRequest = serialize($buyRequest->getData()); + $this->setData('buy_request', $_buyRequest); + return $this; + } + + /** + * Check product representation in item + * + * @param Mage_Catalog_Model_Product $product + * @param Varien_Object $buyRequest + * @return bool + */ + public function isRepresent($product, $buyRequest) + { + if ($this->getProductId() != $product->getId()) { + return false; + } + + $selfOptions = $this->getBuyRequest()->getData(); + + if (empty($buyRequest) && !empty($selfOptions)) { + return false; + } + if (empty($selfOptions) && !empty($buyRequest)) { + if (!$product->isComposite()){ + return true; + } else { + return false; + } + } + + $requestArray = $buyRequest->getData(); + + if(!$this->_compareOptions($requestArray, $selfOptions)){ + return false; + } + if(!$this->_compareOptions($selfOptions, $requestArray)){ + return false; + } + return true; + } + + /** + * Check product representation in item + * + * @param Mage_Catalog_Model_Product $product + * @return bool + */ + public function representProduct($product) + { + $itemProduct = $this->getProduct(); + if ($itemProduct->getId() != $product->getId()) { + return false; + } + + $itemOptions = $this->getOptionsByCode(); + $productOptions = $product->getCustomOptions(); + + if(!$this->compareOptions($itemOptions, $productOptions)){ + return false; + } + if(!$this->compareOptions($productOptions, $itemOptions)){ + return false; + } + return true; + } + + /** + * Check if two options array are identical + * First options array is prerogative + * Second options array checked against first one + * + * @param array $options1 + * @param array $options2 + * @return bool + */ + public function compareOptions($options1, $options2) + { + foreach ($options1 as $option) { + $code = $option->getCode(); + if (in_array($code, $this->_notRepresentOptions )) { + continue; + } + if ( !isset($options2[$code]) + || ($options2[$code]->getValue() === null) + || $options2[$code]->getValue() != $option->getValue()) { + return false; + } + } + return true; + } + + /** + * Initialize item options + * + * @param array $options + * @return Mage_Wishlist_Model_Item + */ + public function setOptions($options) + { + foreach ($options as $option) { + $this->addOption($option); + } + return $this; + } + + /** + * Get all item options + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Get all item options as array with codes in array key + * + * @return array + */ + public function getOptionsByCode() + { + return $this->_optionsByCode; + } + + /** + * Add option to item + * + * @param Mage_Wishlist_Model_Item_Option $option + * @return Mage_Wishlist_Model_Item + */ + public function addOption($option) + { + if (is_array($option)) { + $option = Mage::getModel('wishlist/item_option')->setData($option) + ->setItem($this); + } else if ($option instanceof Mage_Wishlist_Model_Item_Option) { + $option->setItem($this); + } else if ($option instanceof Varien_Object) { + $option = Mage::getModel('wishlist/item_option')->setData($option->getData()) + ->setProduct($option->getProduct()) + ->setItem($this); + } else { + Mage::throwException(Mage::helper('sales')->__('Invalid item option format.')); + } + + $exOption = $this->getOptionByCode($option->getCode()); + if ($exOption) { + $exOption->addData($option->getData()); + } else { + $this->_addOptionCode($option); + $this->_options[] = $option; + } + return $this; + } + + /** + *Remove option from item options + * + * @param string $code + * @return Mage_Wishlist_Model_Item + */ + public function removeOption($code) + { + $option = $this->getOptionByCode($code); + if ($option) { + $option->isDeleted(true); + } + return $this; + } + + /** + * Get item option by code + * + * @param string $code + * @return Mage_Wishlist_Model_Item_Option || null + */ + public function getOptionByCode($code) + { + if (isset($this->_optionsByCode[$code]) && !$this->_optionsByCode[$code]->isDeleted()) { + return $this->_optionsByCode[$code]; + } + return null; + } + + /** + * Returns whether Qty field is valid for this item + * + * @return bool + */ + public function canHaveQty() + { + $product = $this->getProduct(); + return $product->getTypeId() != Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE; + } + + /** + * Get current custom option download url + */ + public function getCustomDownloadUrl() + { + return $this->_customOptionDownloadUrl; + } + + /** + * Sets custom option download url + */ + public function setCustomDownloadUrl($url) + { + $this->_customOptionDownloadUrl = $url; + } + + /** + * Returns special download params (if needed) for custom option with type = 'file'. + * Needed to implement Mage_Catalog_Model_Product_Configuration_Item_Interface. + * + * We have to customize only controller url, so return it. + * + * @return null|Varien_Object + */ + public function getFileDownloadParams() + { + $params = new Varien_Object(); + $params->setUrl($this->_customOptionDownloadUrl); + return $params; + } } diff --git a/app/code/core/Mage/Wishlist/Model/Item/Option.php b/app/code/core/Mage/Wishlist/Model/Item/Option.php new file mode 100644 index 0000000000..bcf210cd63 --- /dev/null +++ b/app/code/core/Mage/Wishlist/Model/Item/Option.php @@ -0,0 +1,142 @@ + + */ +class Mage_Wishlist_Model_Item_Option extends Mage_Core_Model_Abstract + implements Mage_Catalog_Model_Product_Configuration_Item_Option_Interface +{ + protected $_item; + protected $_product; + + /** + * Initialize resource model + */ + protected function _construct() + { + $this->_init('wishlist/item_option'); + } + + /** + * Checks that item option model has data changes + * + * @return boolean + */ + protected function _hasModelChanged() + { + if (!$this->hasDataChanges()) { + return false; + } + + return $this->_getResource()->hasDataChanged($this); + } + + /** + * Set quote item + * + * @param Mage_Wishlist_Model_Item $item + * @return Mage_Wishlist_Model_Item_Option + */ + public function setItem($item) + { + $this->setWishlistItemId($item->getId()); + $this->_item = $item; + return $this; + } + + /** + * Get option item + * + * @return Mage_Wishlist_Model_Item + */ + public function getItem() + { + return $this->_item; + } + + /** + * Set option product + * + * @param Mage_Catalog_Model_Product $product + * @return Mage_Wishlist_Model_Item_Option + */ + public function setProduct($product) + { + $this->setProductId($product->getId()); + $this->_product = $product; + return $this; + } + + /** + * Get option product + * + * @return Mage_Catalog_Model_Product + */ + public function getProduct() + { + return $this->_product; + } + + /** + * Get option value + * + * @return mixed + */ + public function getValue() + { + return $this->_getData('value'); + } + + /** + * Initialize item identifier before save data + * + * @return Mage_Wishlist_Model_Item_Option + */ + protected function _beforeSave() + { + if ($this->getItem()) { + $this->setWishlistItemId($this->getItem()->getId()); + } + return parent::_beforeSave(); + } + + /** + * Clone option object + * + * @return Mage_Wishlist_Model_Item_Option + */ + public function __clone() + { + $this->setId(null); + $this->_item = null; + return $this; + } +} diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Collection.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Collection.php index c81f0573d4..271b7aa853 100644 --- a/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Collection.php +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Collection.php @@ -34,6 +34,48 @@ */ class Mage_Wishlist_Model_Mysql4_Item_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { + /** + * Product Visibility Filter to product collection flag + * + * @var bool + */ + protected $_productVisible = false; + + /** + * Product Salable Filter to product collection flag + * + * @var bool + */ + protected $_productSalable = false; + + /** + * If product out of stock, its item will be removed after load + * + * @var bool + */ + protected $_productInStock = false; + + /** + * Product Ids array + * + * @var array + */ + protected $_productIds = array(); + + /** + * Store Ids array + * + * @var array + */ + protected $_storeIds = array(); + + /** + * Add days in whishlist filter of product collection + * + * @var boolean + */ + protected $_addDaysInWishlist = false; + /** * Initialize resource model for collection * @@ -43,6 +85,108 @@ public function _construct() $this->_init('wishlist/item'); } + /** + * After load processing + * + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + protected function _afterLoad() + { + parent::_afterLoad(); + + /** + * Assign products + */ + $this->_assignOptions(); + $this->_assignProducts(); + $this->resetItemsDataChanged(); + + + $this->getPageSize(); + + return $this; + } + + /** + * Add options to items + * + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + protected function _assignOptions() + { + $itemIds = array_keys($this->_items); + $optionCollection = Mage::getModel('wishlist/item_option')->getCollection() + ->addItemFilter($itemIds); + foreach ($this as $item) { + $item->setOptions($optionCollection->getOptionsByItem($item)); + } + $productIds = $optionCollection->getProductIds(); + $this->_productIds = array_merge($this->_productIds, $productIds); + + return $this; + } + + /** + * Add products to items and item options + * + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + protected function _assignProducts() + { + Varien_Profiler::start('WISHLIST:'.__METHOD__); + $productIds = array(); + foreach ($this as $item) { + $productIds[$item->getProductId()]=1; + } + $this->_productIds = array_merge($this->_productIds, array_keys($productIds)); + $attributes = Mage::getSingleton('catalog/config')->getProductAttributes(); + + $productCollection = Mage::getModel('catalog/product')->getCollection() + ->addIdFilter($this->_productIds) + ->addAttributeToSelect($attributes) + ->addOptionsToResult() + ->addPriceData() + ->addUrlRewrite(); + + if ($this->_productVisible) { + Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($productCollection); + } + if ($this->_productSalable) { + $productCollection = Mage::helper('adminhtml/sales')->applySalableProductTypesFilter($productCollection); + } + + foreach ($this->_storeIds as $id) { + $productCollection->addStoreFilter($id); + } + + Mage::dispatchEvent('wishlist_item_collection_products_after_load', array( + 'product_collection' => $productCollection + )); + + foreach ($this as $item) { + $product = $productCollection->getItemById($item->getProductId()); + if ($product) { + if ($this->_productInStock && + !$product->isSalable() && + !Mage::helper('cataloginventory')->isShowOutOfStock()) { + $this->removeItemByKey($item->getId()); + } else { + $product->setCustomOptions(array()); + $item->setProduct($product); + $item->setProductName($product->getName()); + $item->setName($product->getName()); + $item->setPrice($product->getPrice()); + } + } else { + $item->isDeleted(true); + } + } + + Varien_Profiler::stop('WISHLIST:'.__METHOD__); + + return $this; + } + /** * Add filter by wishlist object * @@ -62,21 +206,103 @@ public function addWishlistFilter(Mage_Wishlist_Model_Wishlist $wishlist) * @param int|array $store * @return Mage_Wishlist_Model_Mysql4_Item_Collection */ - public function addStoreFilter($store) + public function addStoreFilter($store = null) { - $this->addFieldToFilter('store_id', array('in' => $store)); + if (!is_array($store)) { + $store = array($store); + } + $this->_storeIds = $store; return $this; } /** - * This method will be not supported anymore + * Add items store data to collection * - * @deprecated since 1.4.0.0 * @return Mage_Wishlist_Model_Mysql4_Item_Collection */ public function addStoreData() { + $storeTable = Mage::getSingleton('core/resource')->getTableName('core/store'); + $this->getSelect()->join(array('store'=>$storeTable), 'main_table.store_id=store.store_id', array( + 'store_name'=>'name', + 'item_store_id' => 'store_id' + )); + return $this; + } + + /** + * Add wishlist sort order + * + * @param string $attribute + * @param string $dir + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function addWishListSortOrder($attribute = 'added_at', $dir = 'desc') + { + $this->setOrder($attribute, $dir); + return $this; + } + + /** + * Reset sort order + * + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function resetSortOrder() + { + $this->getSelect()->reset(Zend_Db_Select::ORDER); + return $this; + } + + /** + * Set product Visibility Filter to product collection flag + * + * @param bool $flag + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function setVisibilityFilter($flag = true) + { + $this->_productVisible = (bool)$flag; + return $this; + } + + /** + * Set Salable Filter. + * This filter apply Salable Product Types Filter to product collection. + * + * @param bool $flag + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function setSalableFilter($flag = true) + { + $this->_productSalable = (bool)$flag; + return $this; + } + + /** + * Set In Stock Filter. + * This filter remove items with no salable product. + * + * @param bool $flag + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function setInStockFilter($flag = true) + { + $this->_productInStock = (bool)$flag; + return $this; + } + + /** + * Set add days in whishlist + * + * @return Mage_Wishlist_Model_Mysql4_Item_Collection + */ + public function addDaysInWishlist($flag = null) + { + $this->getSelect()->columns(array('days_in_wishlist' => + "(TO_DAYS('" . (substr(Mage::getSingleton('core/date')->date(), 0, -2) . '00') . "') ". + "- TO_DAYS(DATE_ADD(added_at, INTERVAL " .(int) Mage::getSingleton('core/date')->getGmtOffset() . " SECOND)))")); return $this; } } diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Option.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Option.php new file mode 100644 index 0000000000..47fec2571f --- /dev/null +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Option.php @@ -0,0 +1,40 @@ + + */ +class Mage_Wishlist_Model_Mysql4_Item_Option extends Mage_Core_Model_Mysql4_Abstract +{ + protected function _construct() + { + $this->_init('wishlist/item_option', 'option_id'); + } +} diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Option/Collection.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Option/Collection.php new file mode 100644 index 0000000000..b69da1838e --- /dev/null +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Item/Option/Collection.php @@ -0,0 +1,175 @@ + + */ +class Mage_Wishlist_Model_Mysql4_Item_Option_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + /** + * Array of option ids grouped by item id + * + * @var array + */ + protected $_optionsByItem = array(); + + /** + * Array of option ids grouped by product id + * + * @var array + */ + protected $_optionsByProduct = array(); + + /** + * Define resource model for collection + * + * @return void + */ + protected function _construct() + { + $this->_init('wishlist/item_option'); + } + + /** + * Fill array of options by item and product + * + * @return Mage_Wishlist_Model_Mysql4_Item_Option_Collection + */ + protected function _afterLoad() + { + parent::_afterLoad(); + + foreach ($this as $option) { + $optionId = $option->getId(); + $itemId = $option->getWishlistItemId(); + $productId = $option->getProductId(); + if (isset($this->_optionsByItem[$itemId])) { + $this->_optionsByItem[$itemId][] = $optionId; + } else { + $this->_optionsByItem[$itemId] = array($optionId); + } + if (isset($this->_optionsByProduct[$productId])) { + $this->_optionsByProduct[$productId][] = $optionId; + } else { + $this->_optionsByProduct[$productId] = array($optionId); + } + } + + return $this; + } + + /** + * Apply quote item(s) filter to collection + * + * @param int | array $item + * @return Mage_Wishlist_Model_Mysql4_Item_Option_Collection + */ + public function addItemFilter($item) + { + if (empty($item)) { + $this->_totalRecords = 0; + $this->_setIsLoaded(true); + //$this->addFieldToFilter('item_id', ''); + } else if (is_array($item)) { + $this->addFieldToFilter('wishlist_item_id', array('in'=>$item)); + } else if ($item instanceof Mage_Wishlist_Model_Item) { + $this->addFieldToFilter('wishlist_item_id', $item->getId()); + } else { + $this->addFieldToFilter('wishlist_item_id', $item); + } + + return $this; + } + + /** + * Get array of all product ids + * + * @return array + */ + public function getProductIds() + { + $this->load(); + + return array_keys($this->_optionsByProduct); + } + + /** + * Get all option for item + * + * @param mixed $item + * @return array + */ + public function getOptionsByItem($item) + { + if ($item instanceof Mage_Wishlist_Model_Item) { + $itemId = $item->getId(); + } else { + $itemId = $item; + } + + $this->load(); + + $options = array(); + if (isset($this->_optionsByItem[$itemId])) { + foreach ($this->_optionsByItem[$itemId] as $optionId) { + $options[] = $this->_items[$optionId]; + } + } + + return $options; + } + + /** + * Get all option for item + * + * @param mixed $item + * @return array + */ + public function getOptionsByProduct($product) + { + if ($product instanceof Mage_Catalog_Model_Product) { + $productId = $product->getId(); + } else { + $productId = $product; + } + + $this->load(); + + $options = array(); + if (isset($this->_optionsByProduct[$productId])) { + foreach ($this->_optionsByProduct[$productId] as $optionId) { + $options[] = $this->_items[$optionId]; + } + } + + return $options; + } +} diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php index fb6dee0572..93a537342b 100644 --- a/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Product/Collection.php @@ -27,7 +27,10 @@ /** * Wishlist Product collection + * Deprecated because after Magento 1.4.2.0 it's impossible + * to use product collection in wishlist * + * @deprecated after 1.4.2.0 * @category Mage * @package Mage_Wishlist * @author Magento Core Team @@ -35,7 +38,6 @@ class Mage_Wishlist_Model_Mysql4_Product_Collection extends Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection { - /** * Add days in whishlist filter of product collection * @@ -43,6 +45,8 @@ class Mage_Wishlist_Model_Mysql4_Product_Collection */ protected $_addDaysInWishlist = false; + + /** * Get add days in whishlist filter of product collection flag * @@ -159,4 +163,15 @@ protected function _getAttributeFieldName($attributeCode) } return parent::_getAttributeFieldName($attributeCode); } + + /** + * Prevent loading collection because since Magento 1.4.2.0 it's impossible + * to use product collection in wishlist + * + * @return bool + */ + public function load($printQuery = false, $logQuery = false) + { + return $this; + } } diff --git a/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist.php b/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist.php index e5686e8c4d..94365f0677 100644 --- a/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist.php +++ b/app/code/core/Mage/Wishlist/Model/Mysql4/Wishlist.php @@ -58,12 +58,9 @@ public function setCustomerIdFieldName($fieldName) public function fetchItemsCount(Mage_Wishlist_Model_Wishlist $wishlist) { if (is_null($this->_itemsCount)) { - $collection = $wishlist->getProductCollection() - //->addAttributeToFilter('store_id', array('in'=>$wishlist->getSharedStoreIds())) - ->addStoreFilter(); - - Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection); - Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($collection); + $collection = $wishlist->getItemCollection() + ->addStoreFilter() + ->setVisibilityFilter(); $this->_itemsCount = $collection->getSize(); } diff --git a/app/code/core/Mage/Wishlist/Model/Observer.php b/app/code/core/Mage/Wishlist/Model/Observer.php index 198c3cf343..9062f875b0 100644 --- a/app/code/core/Mage/Wishlist/Model/Observer.php +++ b/app/code/core/Mage/Wishlist/Model/Observer.php @@ -69,6 +69,9 @@ public function processCartUpdateBefore($observer) if (!empty($itemInfo['wishlist'])) { if ($item = $cart->getQuote()->getItemById($itemId)) { $productId = $item->getProductId(); + + $wishlist->addNewItem($productId, $item->getBuyRequest()); + $productIds[] = $productId; $cart->getQuote()->removeItem($itemId); } @@ -76,9 +79,6 @@ public function processCartUpdateBefore($observer) } if (!empty($productIds)) { - foreach ($productIds as $productId) { - $wishlist->addNewItem($productId); - } $wishlist->save(); Mage::helper('wishlist')->calculate(); } diff --git a/app/code/core/Mage/Wishlist/Model/Wishlist.php b/app/code/core/Mage/Wishlist/Model/Wishlist.php index fc53222d4a..b2e512aebe 100644 --- a/app/code/core/Mage/Wishlist/Model/Wishlist.php +++ b/app/code/core/Mage/Wishlist/Model/Wishlist.php @@ -126,6 +126,58 @@ protected function _beforeSave() return $this; } + /** + * Save related items + * + * @return Mage_Sales_Model_Quote + */ + protected function _afterSave() + { + parent::_afterSave(); + + if (null !== $this->_itemCollection) { + $this->getItemCollection()->save(); + } + return $this; + } + + /** + * Adding catalog product object data to wishlist + * + * @param Mage_Catalog_Model_Product $product + * @return Mage_Wishlist_Model_Item + */ + protected function _addCatalogProduct(Mage_Catalog_Model_Product $product, $qty = 1) + { + $item = null; + foreach ($this->getItemCollection() as $_item) { + if ($_item->representProduct($product)) { + $item = $_item; + break; + } + } + + if ($item === null) { + $storeId = $product->hasWishlistStoreId() ? $product->getWishlistStoreId() : $this->getStore()->getId(); + $item = Mage::getModel('wishlist/item'); + $item->setProductId($product->getId()) + ->setWishlistId($this->getId()) + ->setAddedAt(now()) + ->setStoreId($storeId) + ->setOptions($product->getCustomOptions()) + ->setProduct($product) + ->setQty($qty) + ->save(); + } else { + $item->setQty($item->getQty() + $qty) + ->save(); + } + + $this->addItem($item); + + return $item; + } + /** * Retrieve wishlist item collection * @@ -141,43 +193,132 @@ public function getItemCollection() return $this->_itemCollection; } + /** + * Retrieve wishlist item collection + * + * @param int $itemId + * @return Mage_Wishlist_Model_Item + */ + public function getItem($itemId) + { + if (!$itemId) { + return false; + } + return $this->getItemCollection()->getItemById($itemId); + } + /** * Retrieve Product collection * + * @deprecated after 1.4.2.0 + * @see Mage_Wishlist_Model_Wishlist::getItemCollection() + * * @return Mage_Wishlist_Model_Mysql4_Product_Collection */ public function getProductCollection() { $collection = $this->getData('product_collection'); if (is_null($collection)) { - $collection = Mage::getResourceModel('wishlist/product_collection') - ->setStoreId($this->getStore()->getId()) - ->addWishlistFilter($this) - ->addWishListSortOrder(); + $collection = Mage::getResourceModel('wishlist/product_collection'); $this->setData('product_collection', $collection); } return $collection; } /** - * Add new item to wishlist + * Adding item to wishlist + * + * @param Mage_Wishlist_Model_Item $item + * @return Mage_Wishlist_Model_Wishlist + */ + public function addItem(Mage_Wishlist_Model_Item $item) + { + $item->setWishlist($this); + if (!$item->getId()) { + $this->getItemCollection()->addItem($item); + Mage::dispatchEvent('wishlist_add_item', array('item' => $item)); + } + return $this; + } + + /** + * Add new product to wishlist * - * @param int $productId + * @param int|Mage_Catalog_Model_Product $product + * @param mixed $buyRequest * @return Mage_Wishlist_Model_Item */ - public function addNewItem($productId) + public function addNewItem($product, $buyRequest = null) { - $item = Mage::getModel('wishlist/item'); - $item->loadByProductWishlist($this->getId(), $productId, $this->getSharedStoreIds()); + /* + * Always load product, to ensure: + * a) we have new instance and do not interfere with other products in wishlist + * b) product has full set of attributes + */ + if ($product instanceof Mage_Catalog_Model_Product) { + $productId = $product->getId(); + // Maybe force some store by wishlist internal properties + $storeId = $product->hasWishlistStoreId() ? $product->getWishlistStoreId() : $product->getStoreId(); + } else { + $productId = (int) $product; + if ($buyRequest->getStoreId()) { + $storeId = $buyRequest->getStoreId(); + } else { + $storeId = Mage::app()->getStore()->getId(); + } + } - if (!$item->getId()) { - $item->setProductId($productId) - ->setWishlistId($this->getId()) - ->setAddedAt(now()) - ->setStoreId($this->getStore()->getId()) - ->save(); + /* @var $product Mage_Catalog_Model_Product */ + $product = Mage::getModel('catalog/product') + ->setStoreId($storeId) + ->load($productId); + + if ($buyRequest instanceof Varien_Object) { + $_buyRequest = $buyRequest; + } elseif (is_string($buyRequest)) { + $_buyRequest = new Varien_Object(unserialize($buyRequest)); + } elseif (is_array($buyRequest)) { + $_buyRequest = new Varien_Object($buyRequest); + } else { + $_buyRequest = new Varien_Object(); + } + + $cartCandidates = $product->getTypeInstance(true) + ->processConfiguration($_buyRequest, $product); + + /** + * Error message + */ + if (is_string($cartCandidates)) { + return $cartCandidates; + } + + /** + * If prepare process return one object + */ + if (!is_array($cartCandidates)) { + $cartCandidates = array($cartCandidates); } + $errors = array(); + $items = array(); + + foreach ($cartCandidates as $candidate) { + if ($candidate->getParentProductId()) { + continue; + } + $candidate->setWishlistStoreId($storeId); + $item = $this->_addCatalogProduct($candidate, $candidate->getQty()); + $items[] = $item; + + // Collect errors instead of throwing first one + if ($item->getHasError()) { + $errors[] = $item->getMessage(); + } + } + + Mage::dispatchEvent('wishlist_product_add_after', array('items' => $items)); + return $item; } @@ -292,8 +433,8 @@ public function getItemsCount() */ public function isSalable() { - foreach ($this->getProductCollection() as $product) { - if ($product->getIsSalable()) { + foreach ($this->getItemCollection() as $item) { + if ($item->getProduct()->getIsSalable()) { return true; } } @@ -310,4 +451,53 @@ public function isOwner($customerId) { return $customerId == $this->getCustomerId(); } + + + /** + * Update wishlist Item and set data from request + * + * @param int $itemId + * @param Varien_Object $buyRequest + * @return Mage_Wishlist_Model_Wishlist + */ + public function updateItem($itemId, $buyRequest) { + $item = $this->getItem((int)$itemId); + if (!$item) { + Mage::throwException(Mage::helper('wishlist')->__('Cannot specify wishlist item.')); + } + + $product = $item->getProduct(); + $productId = $product->getId(); + if ($productId) { + $product->setWishlistStoreId($item->getStoreId()); + $resultItem = $this->addNewItem($product, $buyRequest); + /** + * Error message + */ + if (is_string($resultItem)) { + Mage::throwException(Mage::helper('checkout')->__($resultItem)); + } + + if ($resultItem->getId() != $itemId) { + $item->isDeleted(true); + $this->setDataChanges(true); + + $items = $this->getItemCollection(); + $eqItems = array(); + foreach ($items as $_item) { + if ($_item->getProductId() == $productId && $_item->getId() != $resultItem->getId()) { + if ($resultItem->compareOptions($resultItem->getOptions(), $_item->getOptions())) { + $resultItem->setQty($resultItem->getQty() + $_item->getQty()); + $_item->isDeleted(true); + } + } + } + } else { + $resultItem->setQty($buyRequest->getQty()*1); + } + } else { + Mage::throwException(Mage::helper('checkout')->__('The product does not exist.')); + } + return $this; + } } diff --git a/app/code/core/Mage/Wishlist/controllers/IndexController.php b/app/code/core/Mage/Wishlist/controllers/IndexController.php index 0ff88f6ade..9216ce94a6 100644 --- a/app/code/core/Mage/Wishlist/controllers/IndexController.php +++ b/app/code/core/Mage/Wishlist/controllers/IndexController.php @@ -87,6 +87,7 @@ public function indexAction() $this->_getWishlist(); $this->loadLayout(); + $session = Mage::getSingleton('customer/session'); $block = $this->getLayout()->getBlock('customer.wishlist'); $referer = $session->getAddActionReferer(true); @@ -131,15 +132,27 @@ public function addAction() } try { - $wishlist->addNewItem($product->getId()); + $buyRequest = new Varien_Object($this->getRequest()->getParams()); + + $result = $wishlist->addNewItem($product, $buyRequest); + if (is_string($result)) { + Mage::throwException($result); + } $wishlist->save(); - Mage::dispatchEvent('wishlist_add_product', array('wishlist'=>$wishlist, 'product'=>$product)); + Mage::dispatchEvent( + 'wishlist_add_product', + array( + 'wishlist' => $wishlist, + 'product' => $product, + 'item' => $result + ) + ); - if ($referer = $session->getBeforeWishlistUrl()) { + $referer = $session->getBeforeWishlistUrl(); + if ($referer) { $session->setBeforeWishlistUrl(null); - } - else { + } else { $referer = $this->_getRefererUrl(); } @@ -157,11 +170,104 @@ public function addAction() $session->addError($this->__('An error occurred while adding item to wishlist: %s', $e->getMessage())); } catch (Exception $e) { + mage::log($e->getMessage()); $session->addError($this->__('An error occurred while adding item to wishlist.')); } + $this->_redirect('*'); } + /** + * Action to reconfigure wishlist item + */ + public function configureAction() + { + $id = (int) $this->getRequest()->getParam('id'); + $wishlist = $this->_getWishlist(); + /* @var $item Mage_Wishlist_Model_Item */ + $item = $wishlist->getItem($id); + + try { + if (!$item) { + throw new Exception($this->__('Cannot load wishlist item')); + } + + Mage::register('wishlist_item', $item); + + $params = new Varien_Object(); + $params->setCategoryId(false); + $params->setConfigureMode(true); + + $buyRequest = $item->getBuyRequest(); + if (!$buyRequest->getQty() && $item->getQty()) { + $buyRequest->setQty($item->getQty()); + } + if ($buyRequest->getQty() && !$item->getQty()) { + $item->setQty($buyRequest->getQty()); + } + $params->setBuyRequest($buyRequest); + + Mage::helper('catalog/product_view')->prepareAndRender($item->getProductId(), $this, $params); + } catch (Mage_Core_Exception $e) { + Mage::getSingleton('customer/session')->addError($e->getMessage()); + $this->_redirect('*'); + return; + } catch (Exception $e) { + Mage::getSingleton('customer/session')->addError($this->__('Cannot configure product')); + Mage::logException($e); + $this->_redirect('*'); + return; + } + } + + /** + * Action to accept new configuration for a wishlist item + */ + public function updateItemOptionsAction() + { + $session = Mage::getSingleton('customer/session'); + $wishlist = $this->_getWishlist(); + if (!$wishlist) { + $this->_redirect('*/'); + return; + } + + $productId = (int) $this->getRequest()->getParam('product'); + if (!$productId) { + $this->_redirect('*/'); + return; + } + + $product = Mage::getModel('catalog/product')->load($productId); + if (!$product->getId() || !$product->isVisibleInCatalog()) { + $session->addError($this->__('Cannot specify product.')); + $this->_redirect('*/'); + return; + } + + try { + $id = (int) $this->getRequest()->getParam('id'); + $buyRequest = new Varien_Object($this->getRequest()->getParams()); + + /* @var $item Mage_Wishlist_Model_Item */ + $item = $wishlist->updateItem($id, $buyRequest) + ->save(); + + Mage::dispatchEvent('wishlist_update_item', array('wishlist' => $wishlist, 'product' => $product, 'item' => $item)); + + Mage::helper('wishlist')->calculate(); + + $message = $this->__('%1$s has been updated in your wishlist.', $product->getName()); + $session->addSuccess($message); + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()); + } catch (Exception $e) { + $session->addError($this->__('An error occurred while updating wishlist.')); + Mage::logException($e); + } + $this->_redirect('*/*'); + } + /** * Update wishlist item comments */ @@ -177,12 +283,43 @@ public function updateAction() foreach ($post['description'] as $itemId => $description) { $item = Mage::getModel('wishlist/item')->load($itemId); + if ($item->getWishlistId() != $wishlist->getId()) { + continue; + } + + // Extract new values $description = (string) $description; - if(!strlen($description) || $item->getWishlistId()!=$wishlist->getId()) { + if (!strlen($description)) { + $description = $item->getDescription(); + } + + $qty = null; + if (isset($post['qty'][$itemId])) { + $qty = $this->_processLocalizedQty($post['qty'][$itemId]); + } + if (is_null($qty)) { + $qty = $item->getQty(); + if (!$qty) { + $qty = 1; + } + } elseif (0 == $qty) { + try { + $item->delete(); + } catch (Exception $e) { + Mage::logException($e); + Mage::getSingleton('customer/session')->addError( + $this->__('Can\'t delete item from wishlist') + ); + } + } + + // Check that we need to save + if (($item->getDescription() == $description) && ($item->getQty() == $qty)) { continue; } try { $item->setDescription($description) + ->setQty($qty) ->save(); $updatedItems++; } @@ -256,14 +393,24 @@ public function cartAction() return $this->_redirect('*/*'); } - $itemId = (int)$this->getRequest()->getParam('item'); + $itemId = (int) $this->getRequest()->getParam('item'); + /* @var $item Mage_Wishlist_Model_Item */ - $item = Mage::getModel('wishlist/item')->load($itemId); + $item = Mage::getModel('wishlist/item')->load($itemId); if (!$item->getId() || $item->getWishlistId() != $wishlist->getId()) { return $this->_redirect('*/*'); } + // Set qty + $qtys = $this->getRequest()->getParam('qty'); + if (isset($qtys[$itemId])) { + $qty = $this->_processLocalizedQty($qtys[$itemId]); + if ($qty) { + $item->setQty($qty); + } + } + /* @var $session Mage_Wishlist_Model_Session */ $session = Mage::getSingleton('wishlist/session'); $cart = Mage::getSingleton('checkout/cart'); @@ -271,8 +418,12 @@ public function cartAction() $redirectUrl = Mage::getUrl('*/*'); try { + $options = Mage::getModel('wishlist/item_option')->getCollection() + ->addItemFilter(array($itemId)); + $item->setOptions($options->getOptionsByItem($itemId)); + $item->addToCart($cart, true); - $cart->save()-> getQuote()->collectTotals(); + $cart->save()->getQuote()->collectTotals(); $wishlist->save(); Mage::helper('wishlist')->calculate(); @@ -286,13 +437,11 @@ public function cartAction() if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_NOT_SALABLE) { $session->addError(Mage::helper('wishlist')->__('This product(s) is currently out of stock')); } else if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_HAS_REQUIRED_OPTIONS) { - $redirectUrl = $item->getProductUrl(); - $item->delete(); - } else if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_IS_GROUPED_PRODUCT) { - $redirectUrl = $item->getProductUrl(); - $item->delete(); + Mage::getSingleton('catalog/session')->addNotice($e->getMessage()); + $redirectUrl = Mage::getUrl('*/*/configure/', array('id' => $item->getId())); } else { - $session->addError($e->getMessage()); + Mage::getSingleton('catalog/session')->addNotice($e->getMessage()); + $redirectUrl = Mage::getUrl('*/*/configure/', array('id' => $item->getId())); } } catch (Exception $e) { $session->addException($e, Mage::helper('wishlist')->__('Cannot add item to shopping cart')); @@ -394,4 +543,33 @@ public function sendAction() $this->_redirect('*/*/share'); } } + + /** + * Custom options download action + * + * @return void + */ + public function downloadCustomOptionAction() + { + try { + $optionId = $this->getRequest()->getParam('id'); + $option = Mage::getModel('wishlist/item_option')->load($optionId); + $hasError = false; + + if ($option->getId()) { + $info = unserialize($option->getValue()); + $filePath = Mage::getBaseDir() . $info['quote_path']; + $secretKey = $this->getRequest()->getParam('key'); + + if ($secretKey == $info['secret_key']) { + $this->_prepareDownloadResponse($info['title'], array( + 'value' => $filePath, + 'type' => 'filename' + )); + } + } + } catch(Exception $e) { + } + $this->_forward('noRoute'); + } } diff --git a/app/code/core/Mage/Wishlist/etc/config.xml b/app/code/core/Mage/Wishlist/etc/config.xml index 119fde80f2..192f7a1669 100644 --- a/app/code/core/Mage/Wishlist/etc/config.xml +++ b/app/code/core/Mage/Wishlist/etc/config.xml @@ -28,7 +28,7 @@ - 0.7.8 + 0.7.9 @@ -51,6 +51,10 @@ wishlist_item
    + + wishlist_item
    +
    + wishlist_item_option
    diff --git a/app/code/core/Mage/Wishlist/sql/wishlist_setup/mysql4-upgrade-0.7.8-0.7.9.php b/app/code/core/Mage/Wishlist/sql/wishlist_setup/mysql4-upgrade-0.7.8-0.7.9.php new file mode 100644 index 0000000000..003838869f --- /dev/null +++ b/app/code/core/Mage/Wishlist/sql/wishlist_setup/mysql4-upgrade-0.7.8-0.7.9.php @@ -0,0 +1,51 @@ +startSetup(); +$installer->getConnection()->addColumn($this->getTable('wishlist/item'), 'qty', 'DECIMAL( 12, 4 ) NOT NULL'); + +$installer->run(" +CREATE TABLE `{$this->getTable('wishlist/item_option')}` ( + `option_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `wishlist_item_id` int(10) unsigned NOT NULL, + `product_id` int(10) unsigned NOT NULL, + `code` varchar(255) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`option_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Additional options for wishlist item'; +"); + +$installer->getConnection()->addConstraint( + 'FK_WISHLIST_ITEM_OPTION_ITEM_ID', + $this->getTable('wishlist/item_option'), + 'wishlist_item_id', + $this->getTable('wishlist/item'), + 'wishlist_item_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/History.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/History.php new file mode 100644 index 0000000000..65e9244054 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/History.php @@ -0,0 +1,50 @@ + + */ + + +class Mage_XmlConnect_Block_Adminhtml_History extends Mage_Adminhtml_Block_Widget_Grid_Container +{ + /** + * Class constructor + */ + public function __construct() + { + $this->_blockGroup = 'xmlconnect'; + $this->_controller = 'adminhtml_history'; + $this->_headerText = Mage::helper('xmlconnect')->__('App Submission History'); + + parent::__construct(); + $this->removeButton('add'); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/History/Grid.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/History/Grid.php new file mode 100644 index 0000000000..b7c8b5d46d --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/History/Grid.php @@ -0,0 +1,107 @@ + + */ +class Mage_XmlConnect_Block_Adminhtml_History_Grid extends Mage_Adminhtml_Block_Widget_Grid +{ + + /** + * Constructor + * + * Setting grid_id, sort order and sort direction + */ + public function __construct() + { + parent::__construct(); + $this->setId('app_history_grid'); + $this->setDefaultSort('created_at'); + $this->setDefaultDir('ASC'); + } + + /** + * Setting collection to show + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareCollection() + { + $collection = Mage::getModel('xmlconnect/history')->getCollection(); + $this->setCollection($collection); + return parent::_prepareCollection(); + } + + /** + * Configuration of grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('title', array( + 'header' => Mage::helper('xmlconnect')->__('App Title'), + 'align' => 'left', + 'index' => 'title', + 'type' => 'text', + )); + + $this->addColumn('code', array( + 'header' => Mage::helper('xmlconnect')->__('App Code'), + 'align' => 'left', + 'index' => 'code', + )); + + $this->addColumn('created_at', array( + 'header' => Mage::helper('xmlconnect')->__('Date Submitted'), + 'align' => 'left', + 'index' => 'created_at', + 'type' => 'datetime' + )); + + $this->addColumn('activation_key', array( + 'header' => Mage::helper('xmlconnect')->__('Activation Key'), + 'align' => 'left', + 'index' => 'activation_key', + )); + return parent::_prepareColumns(); + } + + /** + * Remove row click url + * + * @param Mage_Catalog_Model_Product|Varien_Object $row + * @return string + */ + public function getRowUrl($row) + { + return ''; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile.php new file mode 100644 index 0000000000..0434fd2a15 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile.php @@ -0,0 +1,44 @@ +_controller = 'adminhtml_mobile'; + $this->_blockGroup = 'xmlconnect'; + $this->_headerText = Mage::helper('xmlconnect')->__('Manage Apps'); + $this->_addButtonLabel = Mage::helper('xmlconnect')->__('Add App'); + + parent::__construct(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit.php new file mode 100644 index 0000000000..ce2041d0ca --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit.php @@ -0,0 +1,97 @@ +_objectId = 'application_id'; + $this->_controller = 'adminhtml_mobile'; + $this->_blockGroup = 'xmlconnect'; + parent::__construct(); + $model = Mage::registry('current_app'); + + $this->_updateButton('save', 'label', Mage::helper('xmlconnect')->__('Save')); + $this->_updateButton('save', 'onclick', 'if (editForm.submit()) {disableElements(\'save\')}'); + + $this->_addButton('save_and_continue', array( + 'label' => Mage::helper('xmlconnect')->__('Save and Continue Edit'), + 'onclick' => 'saveAndContinueEdit()', + 'class' => 'save', + ), -5); + + if (Mage::registry('current_app')->getId()) { + $this->_addButton('submit_application_button', array( + 'label' => Mage::helper('xmlconnect')->__('Save and Submit App'), + 'onclick' => 'saveAndSubmitApp()', + 'class' => 'save' + ), -10); + } + + $this->_formScripts[] = 'function saveAndContinueEdit() {' + .'if (editForm.submit($(\'edit_form\').action + \'back/edit/\')) {disableElements(\'save\')};}'; + if ($model->getId()) { + $this->_formScripts[] = 'function saveAndSubmitApp() {' + .'if (editForm.submit($(\'edit_form\').action+\'submitapp/' . $model->getId() . '\')) {' + .'disableElements(\'save\')};}'; + } + + if (Mage::registry('current_app')->getIsSubmitted()) { + $this->removeButton('delete'); + } + $this->removeButton('reset'); + } + + /** + * Adding JS scripts to block + * + * @return Mage_Adminhtml_Block_Widget_Form_Container + */ + protected function _prepareLayout() + { + $this->getLayout()->getBlock('head')->addJs('jscolor/jscolor.js'); + $this->getLayout()->getBlock('head')->addJs('scriptaculous/scriptaculous.js'); + return parent::_prepareLayout(); + } + + /** + * Get form header title + * + * @return string + */ + public function getHeaderText() + { + $app = Mage::registry('current_app'); + if ($app && $app->getId()) { + return Mage::helper('xmlconnect')->__('Edit App "%s"', $this->htmlEscape($app->getName())); + } else { + return Mage::helper('xmlconnect')->__('New App'); + } + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Form.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Form.php new file mode 100644 index 0000000000..4af1501076 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Form.php @@ -0,0 +1,42 @@ + 'edit_form', 'action' => $this->getUrl('*/mobile/save'), 'method' => 'post', 'enctype' => 'multipart/form-data')); + $form->setUseContainer(true); + $this->setForm($form); + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Submission.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Submission.php new file mode 100644 index 0000000000..5813c679e3 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Submission.php @@ -0,0 +1,41 @@ +setId('mobile_app_submit'); + $this->setDestElementId('content'); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Content.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Content.php new file mode 100644 index 0000000000..7d8e63dad9 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Content.php @@ -0,0 +1,138 @@ +setShowGlobalIcon(true); + } + + /** + * Add page input to fieldset + * + * @param Varien_Data_Form_Element_Fieldset $fieldset + * @param string $fieldPrefix + */ + protected function _addPage($fieldset, $fieldPrefix) + { + $el = $fieldset->addField($fieldPrefix, 'page', array( + 'name' => $fieldPrefix, + )); + $el->initFields(array( + 'name' => $fieldPrefix, + 'values' => $this->_pages, + )); + } + + /** + * Prepare form before rendering HTML + * Setting Form Fieldsets and fields + * + * @return Mage_Adminhtml_Block_Widget_Form + */ + protected function _prepareForm() + { + $model = Mage::registry('current_app'); + $conf = $model->getConf(); + $form = new Varien_Data_Form(); + $this->setForm($form); + + $pages = Mage::getResourceModel('xmlconnect/cms_page_collection')->toOptionIdArray(); + $dummy = array(array( 'value' => '', 'label' => '' )); + $this->_pages = array_merge($dummy, $pages); + + $fieldset = $form->addFieldset('cms_pages', array('legend' => Mage::helper('xmlconnect')->__('Pages'))); + $this->_addElementTypes($fieldset); + + $fieldset->addField('page_row_add', 'addrow', array( + 'onclick' => 'insertNewTableRow(this)', + 'options' => $this->_pages, + 'class' => ' scalable save ', + 'label' => Mage::helper('xmlconnect')->__('Label'), + 'before_element_html' => Mage::helper('xmlconnect')->__('Get Content from CMS Page').'', + )); + + if (isset($conf['native']['pages'])) { + foreach ($conf['native']['pages'] as $key=>$dummy) { + $this->_addPage($fieldset, 'conf[native][pages]['.$key.']'); + } + } + + $data = $model->getFormData(); + $data['page_row_add'] = Mage::helper('xmlconnect')->__('Add Page'); + $form->setValues($data); + return parent::_prepareForm(); + } + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('Content'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('Content'); + } + + /** + * Returns status flag about this tab can be showen or not + * + * @return true + */ + public function canShowTab() + { + return true; + } + + /** + * Returns status flag about this tab hidden or not + * + * @return false + */ + public function isHidden() + { + return false; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design.php new file mode 100644 index 0000000000..4d43ea3b5a --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design.php @@ -0,0 +1,79 @@ +setShowGlobalIcon(true); + $this->setTemplate('xmlconnect/edit/tab/design.phtml'); + } + + /** + * Tab label getter + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('Design'); + } + + /** + * Tab title getter + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('Design'); + } + + /** + * Check if tab can be shown + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Check if tab hidden + * + * @return bool + */ + public function isHidden() + { + return false; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion.php new file mode 100644 index 0000000000..25f9874f74 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion.php @@ -0,0 +1,48 @@ +getLayout()->createBlock($block); + } else { + $block = $this->getLayout()->getBlock($block); + } + + $this->addItem($itemId, array( + 'title' => $block->getTitle(), + 'content' => $block->toHtml(), + 'open' => $block->getIsOpen(), + )); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Images.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Images.php new file mode 100644 index 0000000000..4af6291d69 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Images.php @@ -0,0 +1,96 @@ +__('Images'); + } + + /** + * Getter for accordion item is open flag + * + * @return bool + */ + public function getIsOpen() + { + return true; + } + + /** + * Prepare form + * + * @return Mage_XmlConnect_Block_Adminhtml_Mobile_Widget_Form + */ + protected function _prepareForm() + { + $form = new Varien_Data_Form(); + + $fieldset = $form->addFieldset('field_logo', array()); + $this->_addElementTypes($fieldset); + $this->addImage($fieldset, + 'conf[native][navigationBar][icon]', + Mage::helper('xmlconnect')->__('Logo in Header'), + Mage::helper('xmlconnect')->__('Recommended size 35px x 35px.'), + $this->_getDesignPreviewImageUrl(Mage::helper('xmlconnect/image')->getInterfaceImagesPaths('conf/native/navigationBar/icon')), + true + ); + $this->addImage($fieldset, + 'conf[native][body][bannerImage]', + Mage::helper('xmlconnect')->__('Banner on Home Screen'), + Mage::helper('xmlconnect')->__('Recommended size 320px x 230px. Note: Image size affects the performance of your app. Keep your image size below 50 KB for optimal performance.'), + $this->_getDesignPreviewImageUrl(Mage::helper('xmlconnect/image')->getInterfaceImagesPaths('conf/native/body/bannerImage')), + true + ); + $this->addImage($fieldset, + 'conf[native][body][backgroundImage]', + Mage::helper('xmlconnect')->__('App Background'), + Mage::helper('xmlconnect')->__('Recommended size 320px x 367px. Note: Image size affects the performance of your app. Keep your image size below 75 KB for optimal performance.'), + $this->_getDesignPreviewImageUrl(Mage::helper('xmlconnect/image')->getInterfaceImagesPaths('conf/native/body/backgroundImage')), + true + ); + + $form->setValues($this->getApplication()->getFormData()); + $this->setForm($form); + return parent::_prepareForm(); + } + + /** + * Retrieve url for images in the skin folder + * + * @param string $name - path to file name relative to the skin dir + * @return string + */ + protected function _getDesignPreviewImageUrl($name) + { + return Mage::helper('xmlconnect/image')->getSkinImagesUrl('design_default/' . $name); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Tabs.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Tabs.php new file mode 100644 index 0000000000..19dc140666 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Tabs.php @@ -0,0 +1,65 @@ +__('Tabs'); + } + + /** + * Getter for accordion item is open flag + * + * @return bool + */ + public function getIsOpen() + { + return true; + } + + /** + * Prepare form + * + * @return Mage_XmlConnect_Block_Adminhtml_Mobile_Widget_Form + */ + protected function _prepareForm() + { + $form = new Varien_Data_Form(); + + $fieldset = $form->addFieldset('field_tabs', array()); + $this->_addElementTypes($fieldset); + $fieldset->addField('conf[extra][tabs]', 'tabs', array('name' => 'conf[extra][tabs]')); + + $form->setValues($this->getApplication()->getFormData()); + $this->setForm($form); + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Themes.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Themes.php new file mode 100644 index 0000000000..3e307e1106 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Accordion/Themes.php @@ -0,0 +1,68 @@ +__('Color Themes'); + } + + /** + * Getter for accordion item is open flag + * + * @return bool + */ + public function getIsOpen() + { + return true; + } + + /** + * Add theme field + * + * @return Mage_XmlConnect_Block_Adminhtml_Mobile_Widget_Form + */ + protected function _prepareForm() + { + $form = new Varien_Data_Form(); + + $fieldset = $form->addFieldset('field_colors', array()); + $this->_addElementTypes($fieldset); + $fieldset->addField('theme', 'theme', array( + 'name' => 'theme', + 'themes' => Mage::helper('xmlconnect/theme')->getAllThemes(), + )); + $form->setValues($this->getApplication()->getFormData()); + $this->setForm($form); + + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Preview.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Preview.php new file mode 100644 index 0000000000..88b768a51c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Preview.php @@ -0,0 +1,47 @@ +setTemplate('xmlconnect/edit/tab/design/preview.phtml'); + } + + /** + * Retieve preview action url + * + * @param string $page + * @return string + */ + public function getPreviewActionUrl($page = 'home') + { + return $this->getUrl('*/*/preview' . $page, array('application_id' => Mage::registry('current_app')->getId())); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Theme.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Theme.php new file mode 100644 index 0000000000..646e712ec0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Theme.php @@ -0,0 +1,33 @@ +setTemplate('xmlconnect/theme.phtml'); + } +} \ No newline at end of file diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Themes.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Themes.php new file mode 100644 index 0000000000..9724ca5279 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Design/Themes.php @@ -0,0 +1,148 @@ +setTemplate('xmlconnect/form/element/themes.phtml'); + + $data = $model->getFormData(); + $this->setColorFieldset (array ( + array ( 'id' => 'field_colors', 'label' => Mage::helper('xmlconnect')->__('Colors'), 'fields' => array ( + $this->_addColorBox('conf[native][navigationBar][tintColor]', Mage::helper('xmlconnect')->__('Header Background Color'), $data), + $this->_addColorBox('conf[native][body][primaryColor]', Mage::helper('xmlconnect')->__('Primary Color'), $data), + $this->_addColorBox('conf[native][body][secondaryColor]', Mage::helper('xmlconnect')->__('Secondary Color'), $data), + $this->_addColorBox('conf[native][categoryItem][backgroundColor]', Mage::helper('xmlconnect')->__('Category Item Background Color'), $data), + $this->_addColorBox('conf[native][categoryItem][tintColor]', Mage::helper('xmlconnect')->__('Category Button Color'), $data), + )), + array ( 'id' => 'field_fonts', 'label' => Mage::helper('xmlconnect')->__('Fonts'), 'fields' => array ( + $this->_addColorBox('conf[extra][fontColors][header]', Mage::helper('xmlconnect')->__('Header Font Color'), $data), + $this->_addColorBox('conf[extra][fontColors][primary]', Mage::helper('xmlconnect')->__('Primary Font Color'), $data), + $this->_addColorBox('conf[extra][fontColors][secondary]', Mage::helper('xmlconnect')->__('Secondary Font Color'), $data), + $this->_addColorBox('conf[extra][fontColors][price]', Mage::helper('xmlconnect')->__('Price Font Color'), $data), + )), + array ( 'id' => 'field_advanced', 'label' => Mage::helper('xmlconnect')->__('Advanced Settings'), 'fields' => array ( + $this->_addColorBox('conf[native][body][backgroundColor]', Mage::helper('xmlconnect')->__('Background Color'), $data), + $this->_addColorBox('conf[native][body][scrollBackgroundColor]', Mage::helper('xmlconnect')->__('Scroll Background Color'), $data), + $this->_addColorBox('conf[native][itemActions][relatedProductBackgroundColor]', Mage::helper('xmlconnect')->__('Related Product Background Color'), $data), + )), + )); + } + + /** + * Themes array getter + * + * @return array + */ + public function getAllThemes() + { + $result = array(); + foreach ($this->getThemes() as $theme) { + $result[$theme->getName()] = $theme->getFormData(); + } + return $result; + } + + /** + * Create color field params + * + * @param id $id + * @param string $label + * @param array $data + * @return array + */ + protected function _addColorBox($id, $label, $data) + { + return array( + 'id' => $id, + 'name' => $id, + 'label' => $label, + 'value' => isset($data[$id]) ? $data[$id] : '' + ); + } + + /** + * Getter, check if it's needed to load default theme config + * + * @return bool + */ + public function getDefaultThemeLoaded() + { + return $this->getApplication()->getDefaultThemeLoaded(); + } + + /** + * Check if adding new Application + * + * @return bool + */ + public function isNewApplication() + { + return $this->getApplication()->getId() ? false : true; + } + + /** + * Save theme action url getter + * + * @return string + */ + public function getSaveThemeActionUrl() + { + return $this->getUrl('*/*/saveTheme'); + } + + /** + * Reset theme action url getter + * + * @return string + */ + public function getResetThemeActionUrl() + { + return $this->getUrl('*/*/resetTheme'); + } + + /** + * Getter for current loaded application model + * + * @return Mage_XmlConnect_Model_Application + */ + public function getApplication() + { + $model = Mage::registry('current_app'); + if (!($model instanceof Mage_XmlConnect_Model_Application)) { + Mage::throwException(Mage::helper('xmlconnect')->__('App model not loaded.')); + } + + return $model; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/General.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/General.php new file mode 100644 index 0000000000..3a7e3f83ae --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/General.php @@ -0,0 +1,147 @@ +setHtmlIdPrefix('app_'); + $fieldset = $form->addFieldset('base_fieldset', array('legend' => Mage::helper('xmlconnect')->__('App Information'))); + + if ($model->getId()) { + $fieldset->addField('application_id', 'hidden', array( + 'name' => 'application_id', + )); + } + + $fieldset->addField('name', 'text', array( + 'name' => 'name', + 'label' => Mage::helper('xmlconnect')->__('App Name'), + 'title' => Mage::helper('xmlconnect')->__('App Name'), + 'required' => true, + )); + + if ($model->getId()) { + $field = $fieldset->addField('code', 'label', array( + 'label' => Mage::helper('xmlconnect')->__('App Code'), + 'title' => Mage::helper('xmlconnect')->__('App Code'), + )); + } + + /** + * Check is single store mode + */ + if (!Mage::app()->isSingleStoreMode()) { + $storeElement = $fieldset->addField('store_id', 'select', array( + 'name' => 'store_id', + 'label' => Mage::helper('xmlconnect')->__('Store View'), + 'title' => Mage::helper('xmlconnect')->__('Store View'), + 'required' => true, + 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(true, false), + )); + } else { + $storeElement = $fieldset->addField('store_id', 'hidden', array( + 'name' => 'store_id', + 'value' => Mage::app()->getStore(true)->getId() + )); + $model->setStoreId(Mage::app()->getStore(true)->getId()); + } + + if ($model->getId()) { + $storeElement->setDisabled(true); + } + + $fieldset->addField('type', 'select', array( + 'name' => 'type', + 'label' => Mage::helper('xmlconnect')->__('Device Type'), + 'title' => Mage::helper('xmlconnect')->__('Device Type'), + 'disabled' => $model->getId() ? true : false, + 'values' => Mage::helper('xmlconnect')->getDeviceTypeOptions(), + )); + + $yesNoValues = Mage::getModel('adminhtml/system_config_source_yesno')->toOptionArray(); + + $fieldset->addField('browsing_mode', 'select', array( + 'label' => Mage::helper('xmlconnect')->__('Catalog Only App?'), + 'name' => 'browsing_mode', + 'note' => Mage::helper('xmlconnect')->__('A Catalog Only App will not support functions such as add to cart, add to wishlist, or login.'), + 'values' => $yesNoValues + )); + + $form->setValues($model->getFormData()); + $this->setForm($form); + return parent::_prepareForm(); + } + + /** + * Tab label getter + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('General'); + } + + /** + * Tab title getter + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('General'); + } + + /** + * Check if tab can be shown + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Check if tab hidden + * + * @return bool + */ + public function isHidden() + { + return false; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Payment.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Payment.php new file mode 100644 index 0000000000..6068cfb4a0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Payment.php @@ -0,0 +1,153 @@ +setShowGlobalIcon(true); + } + + /** + * Prepare form before rendering HTML + * Setting Form Fieldsets and fields + * + * @return Mage_Adminhtml_Block_Widget_Form + */ + protected function _prepareForm() + { + + $form = new Varien_Data_Form(); + + $this->setForm($form); + + $data = $this->getApplication()->getFormData(); + $yesNoValues = Mage::getModel('adminhtml/system_config_source_yesno')->toOptionArray(); + + $fieldset = $form->addFieldset('onepage_checkout', array('legend' => Mage::helper('xmlconnect')->__('Standard Checkout'))); + + $fieldset->addField('conf/native/defaultCheckout/isActive', 'select', array( + 'label' => Mage::helper('xmlconnect')->__('Enable Standard Checkout'), + 'name' => 'conf[native][defaultCheckout][isActive]', + 'values' => $yesNoValues, + 'note' => Mage::helper('xmlconnect')->__('Standard Checkout uses the checkout methods provided by Magento. Only inline payment methods are supported. (e.g PayPal Direct, Authorize.Net, etc.)'), + 'value' => (isset($data['conf[native][defaultCheckout][isActive]']) ? $data['conf[native][defaultCheckout][isActive]'] : '1') + )); + + + /** + * PayPal MEP management + */ + $isExpressCheckoutAvaliable = Mage::getModel('xmlconnect/payment_method_paypal_mep')->isAvailable(); + + $paypalActive = 0; + if (isset($data['conf[native][paypal][isActive]'])) { + $paypalActive = (int)($data['conf[native][paypal][isActive]'] && $isExpressCheckoutAvaliable); + } + $fieldsetPaypal = $form->addFieldset('paypal_mep_checkout', array('legend' => Mage::helper('xmlconnect')->__('PayPal Mobile Embedded Payment (MEP)'))); + + $activateMepMethodNote = Mage::helper('xmlconnect')->__('To activate PayPal MEP payment method activate Express checkout first. '); + $paypalConfigurationUrl = $this->escapeHtml($this->getUrl('adminhtml/system_config/edit', array('section' => 'paypal'))); + $businessAccountNote = Mage::helper('xmlconnect')->__('MEP is PayPal`s native checkout experience for the iPhone. You can choose to use MEP alongside standard checkout, or use it as your only checkout method for Magento mobile. PayPal MEP requires a PayPal business account', $paypalConfigurationUrl); + + $paypalActiveField = $fieldsetPaypal->addField('conf/native/paypal/isActive', 'select', array( + 'label' => Mage::helper('xmlconnect')->__('Activate PayPal Checkout'), + 'name' => 'conf[native][paypal][isActive]', + 'note' => (!$isExpressCheckoutAvaliable ? $activateMepMethodNote : $businessAccountNote), + 'values' => $yesNoValues, + 'value' => $paypalActive, + 'disabled' => !$isExpressCheckoutAvaliable + )); + + $merchantlabelField = $fieldsetPaypal->addField('conf/special/merchantLabel', 'text', array( + 'name' => 'conf[special][merchantLabel]', + 'label' => Mage::helper('xmlconnect')->__('Merchant Label'), + 'title' => Mage::helper('xmlconnect')->__('Merchant Label'), + 'required' => true, + 'value' => (isset($data['conf[special][merchantLabel]']) ? $data['conf[special][merchantLabel]'] : '') + )); + + // field dependencies + $this->setChild('form_after', $this->getLayout()->createBlock('adminhtml/widget_form_element_dependence') + ->addFieldMap($merchantlabelField->getHtmlId(), $merchantlabelField->getName()) + ->addFieldMap($paypalActiveField->getHtmlId(), $paypalActiveField->getName()) + ->addFieldDependence( + $merchantlabelField->getName(), + $paypalActiveField->getName(), + 1) + ); + + return parent::_prepareForm(); + } + + /** + * Tab label getter + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('Payment Methods'); + } + + /** + * Tab title getter + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('Payment Methods'); + } + + /** + * Check if tab can be shown + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Check if tab hidden + * + * @return bool + */ + public function isHidden() + { + return false; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Submission/History.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Submission/History.php new file mode 100644 index 0000000000..1980e1a9da --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tab/Submission/History.php @@ -0,0 +1,162 @@ + + */ +class Mage_XmlConnect_Block_Adminhtml_Mobile_Edit_Tab_Submission_History extends Mage_Adminhtml_Block_Widget_Grid + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + + /** + * Set order by column and order direction + * Set grid ID + * Set use AJAX for grid + * + */ + public function __construct() + { + parent::__construct(); + $this->setId('history_grid'); + $this->setDefaultSort('created_at'); + $this->setDefaultDir('ASC'); + $this->setUseAjax(true); + $this->setSaveParametersInSession(true); + $this->setVarNameFilter('history_filter'); + } + + /** + * Tab label getter + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('Submission History'); + } + + /** + * Tab title getter + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('Submission History'); + } + + /** + * Check if tab can be shown + * + * @return bool + */ + public function canShowTab() + { + return true; + } + + /** + * Check if tab hidden + * + * @return bool + */ + public function isHidden() + { + return false; + } + + /** + * Initialize history colelction + * Set aaplication filter + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareCollection() + { + $collection = Mage::getResourceModel('xmlconnect/history_collection') + ->addApplicationFilter($this->_getApplication()->getId()); + $this->setCollection($collection); + return parent::_prepareCollection(); + } + + /** + * Configuration of grid + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('activation_key', array( + 'header' => Mage::helper('xmlconnect')->__('Activation Key'), + 'align' => 'left', + 'index' => 'activation_key', + 'type' => 'text' + )); + + $this->addColumn('created_at', array( + 'header' => Mage::helper('xmlconnect')->__('Date Submitted'), + 'align' => 'left', + 'index' => 'created_at', + 'type' => 'datetime' + )); + + return parent::_prepareColumns(); + } + + /** + * Ajax grid URL getter + * @return string + */ + public function getGridUrl() + { + return $this->getUrl('*/*/submissionHistoryGrid', array('_current'=>true)); + } + + /** + * Get current application model + * + * @return Mage_XmlConnect_Model_Application + */ + protected function _getApplication() + { + return Mage::registry('current_app'); + } + + /** + * Remove row click url + * + * @param Mage_Catalog_Model_Product|Varien_Object $row + * @return string + */ + public function getRowUrl($row) + { + return ''; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tabs.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tabs.php new file mode 100644 index 0000000000..1acec8fcc1 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Edit/Tabs.php @@ -0,0 +1,41 @@ +setId('mobile_app_tabs'); + $this->setDestElementId('edit_form'); + $this->setTitle(Mage::helper('xmlconnect')->__('Manage Mobile App')); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Addrow.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Addrow.php new file mode 100644 index 0000000000..d8a9d1d7c1 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Addrow.php @@ -0,0 +1,84 @@ +getBeforeElementHtml() . ''.$this->getAfterElementHtml(); + return $html; + } + + /** + * Getter for "before_element_html" + * + * @return string + */ + public function getBeforeElementHtml() + { + return $this->getData('before_element_html'); + } + + /** + * Return label html code + * + * @param string $idSuffix + * @return string + */ + public function getLabelHtml($idSuffix = '') + { + if (!is_null($this->getLabel())) { + $html = ''; + } else { + $html = ''; + } + return $html; + } + + /** + * Overriding toHtml parent method + * Adding addrow Block to element renderer + * + * @return string + */ + public function toHtml() + { + $blockClassName = Mage::getConfig()->getBlockClassName('adminhtml/template'); + $jsBlock = new $blockClassName; + $jsBlock->setTemplate('xmlconnect/form/element/addrow.phtml'); + $jsBlock->setOptions($this->getOptions()); + return parent::toHtml() . $jsBlock->toHtml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Color.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Color.php new file mode 100644 index 0000000000..41ad94b739 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Color.php @@ -0,0 +1,39 @@ +addClass('color {required:false,hash:true}'); + return parent::getHtml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Font.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Font.php new file mode 100644 index 0000000000..63324848c0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Font.php @@ -0,0 +1,94 @@ +setType('font'); + } + + /** + * Setting stored data to Font element + * + * @param array $conf + */ + public function initFields($conf) + { + $name = $conf['name']; + + $this->addElement(new Varien_Data_Form_Element_Select(array( + 'name' => $name . '[name]', + 'values' => $conf['fontNames'], + 'style' => 'width: 206px; margin: 0', + ))); + + $this->addElement(new Varien_Data_Form_Element_Select(array( + 'name' => $name . '[size]', + 'values' => $conf['fontSizes'], + 'style' => 'width: 70px; margin: 0', + ))); + + $this->addElement(new Mage_XmlConnect_Block_Adminhtml_Mobile_Form_Element_Color(array( + 'name' => $name . '[color]', + 'style' => 'width: 60px; margin: 0' + ))); + } + + /** + * Add form element + * + * @param Varien_Data_Form_Element_Abstract $element + * @param boolean|'^'|string $after + * @return Varien_Data_Form + */ + public function addElement(Varien_Data_Form_Element_Abstract $element, $after=false) + { + $element->setId($element->getData('name')); + $element->setNoSpan(TRUE); + parent::addElement($element, $after); + } + + /** + * Get Rendered Element Html + * + * @return string + */ + public function getElementHtml() + { + $el = array(); + foreach ($this->getElements() as $element) { + $el[] .= $element->toHtml(); + } + return $el[0] . $el[1] . '' . $el[2]; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Image.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Image.php new file mode 100644 index 0000000000..77cdff62ad --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Image.php @@ -0,0 +1,88 @@ +getValue()) { + if (strpos($this->getValue(), '://') === FALSE ) { + $url = Mage::getBaseUrl('media') . 'xmlconnect/' . Mage::helper('xmlconnect/image')->getFileDefaultSizeSuffixAsUrl($this->getValue()); + } else { + $url = $this->getValue(); + } + } else { + $url = $this->getDefaultValue(); + } + return $url; + } + + /** + * Get "clear" filename from element + * + * @return string + */ + public function getUploadName() + { + /** + * Ugly hack to avoid $_FILES[..]['name'][..][..] + */ + $name = $this->getName(); + $name = strtr($name, array('[' => '/', ']' => '')); + return $name; + } + + /** + * Compose output html for element + * + * @return string + */ + public function getElementHtml() + { + $html = '

    '; + + $url = $this->_getUrl(); + $html .= ''.$this->getValue().' '; + + $html .= ''; + + $this->setClass('input-file'); + $html .= 'serialize($this->getHtmlAttributes()).'/>'."\n"; + $html.= $this->getAfterElementHtml(); + + $html.= '
    '; + + return $html; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Page.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Page.php new file mode 100644 index 0000000000..d333b4ce22 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Page.php @@ -0,0 +1,98 @@ +setType('page'); + } + + /** + * Setting stored data to page element + * + * @param array $conf + */ + public function initFields($conf) + { + $this->addElement(new Varien_Data_Form_Element_Text(array( + 'name' => $conf['name'] . '[label]', + 'class' => 'label onclick_text', + ))); + + $this->addElement(new Varien_Data_Form_Element_Select(array( + 'name' => $conf['name'] . '[id]', + 'values' => $conf['values'], + ))); + } + + /** + * Add form element + * + * @param Varien_Data_Form_Element_Abstract $element + * @param boolean|'^'|string $after + * @return Varien_Data_Form + */ + public function addElement(Varien_Data_Form_Element_Abstract $element, $after=false) + { + $element->setId($element->getData('name')); + parent::addElement($element, $after); + } + + /** + * Getter for Label field + * fetching first element as label + * + * @param string $idSuffix + * @return string + */ + public function getLabelHtml($idSuffix = '') + { + list($label, $element) = $this->getElements(); + return $label->toHtml(); + } + + /** + * Enter description here... + * gettter for second part of rendered field ("selectbox" and "delete button") + * fetching second element as + * + * @return string + */ + public function getElementHtml() + { + list($label, $element) = $this->getElements(); + return $element->toHtml() . '' . + ''; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Tabs.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Tabs.php new file mode 100644 index 0000000000..edeaae8d18 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Tabs.php @@ -0,0 +1,44 @@ +getBlockClassName('adminhtml/template'); + $block = new $blockClassName; + $block->setTemplate('xmlconnect/form/element/app_tabs.phtml'); + $tabs = Mage::getModel('xmlconnect/tabs', $this->getValue()); + $block->setTabs($tabs); + $block->setName($this->getName()); + return $block->toHtml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Theme.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Theme.php new file mode 100644 index 0000000000..35d5bbeee0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Form/Element/Theme.php @@ -0,0 +1,43 @@ +getBlockClassName('xmlconnect/adminhtml_mobile_edit_tab_design_themes'); + $block = new $blockClassName; + $block->setThemes($this->getThemes()); + $block->setName($this->getName()); + $block->setValue($this->getValue()); + return $block->toHtml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid.php new file mode 100644 index 0000000000..2b6a26c24a --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid.php @@ -0,0 +1,115 @@ +setId('mobile_apps_grid'); + $this->setDefaultSort('application_id'); + $this->setDefaultDir('ASC'); + } + + /** + * Initialize grid data collection + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareCollection() + { + $collection = Mage::getModel('xmlconnect/application')->getCollection(); + $this->setCollection($collection); + return parent::_prepareCollection(); + } + + /** + * Declare grid columns + * + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _prepareColumns() + { + $this->addColumn('name', array( + 'header' => Mage::helper('xmlconnect')->__('App Name'), + 'align' => 'left', + 'index' => 'name', + )); + + $this->addColumn('code', array( + 'header' => Mage::helper('xmlconnect')->__('Code'), + 'align' => 'left', + 'index' => 'code', + 'width' => '200', + )); + + if (!Mage::app()->isSingleStoreMode()) { + $this->addColumn('store_id', array( + 'header' => Mage::helper('xmlconnect')->__('Store View'), + 'index' => 'store_id', + 'type' => 'store', + 'store_view' => true, + 'sortable' => false, + 'width' => '250', + )); + } + + $this->addColumn('type', array( + 'header' => Mage::helper('xmlconnect')->__('Device'), + 'type' => 'text', + 'index' => 'type', + 'align' => 'center', + 'filter' => 'adminhtml/widget_grid_column_filter_select', + 'options' => Mage::helper('xmlconnect')->getSupportedDevices(), + 'renderer' => 'xmlconnect/adminhtml_mobile_grid_renderer_type', + )); + + $this->addColumn('status', array( + 'header' => Mage::helper('xmlconnect')->__('Status'), + 'index' => 'status', + 'renderer' => 'xmlconnect/adminhtml_mobile_grid_renderer_bool', + 'align' => 'center', + 'filter' => 'adminhtml/widget_grid_column_filter_select', + 'options' => Mage::helper('xmlconnect')->getStatusOptions(), + + )); + + return parent::_prepareColumns(); + } + + /** + * Row click url + * + * @param Mage_Catalog_Model_Product|Varien_Object $row + * @return string + */ + public function getRowUrl($row) + { + return $this->getUrl('*/*/edit', array('application_id' => $row->getId())); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid/Renderer/Bool.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid/Renderer/Bool.php new file mode 100644 index 0000000000..3d55817be3 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid/Renderer/Bool.php @@ -0,0 +1,56 @@ + + */ +class Mage_XmlConnect_Block_Adminhtml_Mobile_Grid_Renderer_Bool extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +{ + /** + * Render application status image + * + * @param Varien_Object $row + * @return string + */ + public function render(Varien_Object $row) + { + $result = ''; + $status = (int) $row->getData($this->getColumn()->getIndex()); + $options = Mage::helper('xmlconnect')->getStatusOptions(); + if ($status == Mage_XmlConnect_Model_Application::APP_STATUS_SUCCESS) { + $result = ' ' . (isset($options[$status]) ? $options[$status] : ''); + } else if ($status == Mage_XmlConnect_Model_Application::APP_STATUS_INACTIVE) { + $result = ' ' . (isset($options[$status]) ? $options[$status] : ''); + } + return $result; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid/Renderer/Type.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid/Renderer/Type.php new file mode 100644 index 0000000000..4b15440113 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Grid/Renderer/Type.php @@ -0,0 +1,52 @@ + + */ +class Mage_XmlConnect_Block_Adminhtml_Mobile_Grid_Renderer_Type extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +{ + /** + * Renders grid column + * + * @param Varien_Object $row + * @return string + */ + public function render(Varien_Object $row) + { + $type = $row->getData($this->getColumn()->getIndex()); + $devices = Mage::helper('xmlconnect')->getSupportedDevices(); + if (isset($devices[$type])) { + return $devices[$type]; + } else { + return $type; + } + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Preview/Content.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Preview/Content.php new file mode 100644 index 0000000000..4966b47f5e --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Preview/Content.php @@ -0,0 +1,96 @@ +getEnabledTabs() as $tab) { + $conf['tabBar'][$tab->action]['label'] = $tab->label; + $conf['tabBar'][$tab->action]['image'] = + Mage::helper('xmlconnect/image')->getSkinImagesUrl('mobile_preview/' . $tab->image); + } + } + $this->setData('conf', $conf); + } + + /** + * Get preview images url + * + * @param string $name - file name + * @return string + */ + public function getPreviewImagesUrl($name = '') + { + return Mage::helper('xmlconnect/image')->getSkinImagesUrl('mobile_preview/' . $name); + } + + + /** + * Retrieve url for images in the skin folder + * + * @param string $name - path to file name relative to the skin dir + * @return string + */ + public function getDesignPreviewImageUrl($name) + { + return Mage::helper('xmlconnect/image')->getSkinImagesUrl('design_default/' . $name); + } + + /** + * Expose function getInterfaceImagesPaths from xmlconnect/images + * Converts Data path(conf/submision/zzzz) to config path (conf/native/submission/zzzzz) + * + * @param string $path + * @return array + */ + public function getInterfaceImagesPaths($path) + { + $path = preg_replace('/^conf\/(.*)$/', 'conf/native/${1}', $path); + return Mage::helper('xmlconnect/image')->getInterfaceImagesPaths($path); + } + + /** + * Get xmlconnect css url + * + * @param string $name - file name + * @return string + */ + public function getPreviewCssUrl($name = '') + { + return Mage::getDesign()->getSkinUrl('xmlconnect/' . $name); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Preview/Tabitems.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Preview/Tabitems.php new file mode 100644 index 0000000000..489e96487c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Preview/Tabitems.php @@ -0,0 +1,60 @@ +setTemplate('xmlconnect/edit/tab/design/preview/tab_items.phtml'); + } + + /** + * Collect tab items array + * + * @return array + */ + public function getTabItems() + { + $items = array(); + $model = Mage::registry('current_app'); + $tabs = $model->getEnabledTabsArray(); + + $tabLimit = (int) Mage::getStoreConfig('xmlconnect/devices/'.strtolower($model->getType()).'/tab_limit'); + $showedTabs = 0; + foreach ($tabs as $tab) { + if (++$showedTabs > $tabLimit) { + break; + } + $items[] = array( + 'label' => Mage::helper('xmlconnect')->getTabLabel($tab->action), + 'image' => $tab->image); + } + return $items; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission.php new file mode 100644 index 0000000000..4d5f37a4d4 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission.php @@ -0,0 +1,79 @@ +_objectId = 'application_id'; + $this->_controller = 'adminhtml_mobile'; + $this->_blockGroup = 'xmlconnect'; + $this->_mode = 'submission'; + parent::__construct(); + + $this->removeButton('delete'); + $this->removeButton('save'); + $this->removeButton('reset'); + + $app = Mage::registry('current_app'); + if ($app && $app->getIsResubmitAction()) { + $label = Mage::helper('xmlconnect')->__('Resubmit App'); + } else { + $label = Mage::helper('xmlconnect')->__('Submit App'); + } + + $this->_addButton('submission_post', array( + 'class' => 'save', + 'label' => $label, + 'onclick' => "submitApplication()", + )); + + $this->_updateButton('back', 'label', Mage::helper('xmlconnect')->__('Back to App Edit')); + $this->_updateButton('back', 'onclick', 'setLocation(\''. $this->getUrl('*/*/edit', + array('application_id' => Mage::registry('current_app')->getId())) . '\')'); + + } + + /** + * Get form header title + * + * @return string + */ + public function getHeaderText() + { + $app = Mage::registry('current_app'); + if ($app && $app->getId()) { + return Mage::helper('xmlconnect')->__('Submit App "%s"', $this->htmlEscape($app->getName())); + } + return ''; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Form.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Form.php new file mode 100644 index 0000000000..df1653f0a8 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Form.php @@ -0,0 +1,40 @@ + 'edit_form', 'action' => $this->getUrl('*/mobile/submission'), 'method' => 'post', 'enctype' => 'multipart/form-data')); + $form->setUseContainer(true); + $this->setForm($form); + return parent::_prepareForm(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Container.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Container.php new file mode 100644 index 0000000000..a53484c75d --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Container.php @@ -0,0 +1,89 @@ +setShowGlobalIcon(true); + $this->setTemplate('xmlconnect/submission/container.phtml'); + } + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('Submission'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('Submission'); + } + + /** + * Returns status flag about this tab can be showen or not + * + * @return true + */ + public function canShowTab() + { + return true; + } + + /** + * Returns status flag about this tab hidden or not + * + * @return true + */ + public function isHidden() + { + return false; + } + + /** + * Retrive submission action url + * + * @return string + */ + public function getActionUrl() + { + return $this->getUrl('*/*/submissionPost', array('key' => Mage::registry('current_app')->getId())); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Container/Submission.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Container/Submission.php new file mode 100644 index 0000000000..45e7e4aac0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Container/Submission.php @@ -0,0 +1,274 @@ +setShowGlobalIcon(true); + } + + /** + * Adding preview for images if application was submitted(so we have saved images) + * + * @return Mage_Core_Block_Abstract + */ + protected function _prepareLayout() + { + $block = $this->getLayout()->createBlock('adminhtml/template') + ->setTemplate('xmlconnect/submission/app_icons_preview.phtml') + ->setImages($this->getApplication()->getImages()); + $this->setChild('images', $block); + parent::_prepareLayout(); + } + + /** + * Add image uploader to fieldset + * + * @param Varien_Data_Form_Element_Fieldset $fieldset + * @param string $fieldName + * @param string $title + * @param string $note + * @param string $default + * @param boolean $required + */ + public function addImage($fieldset, $fieldName, $title, $note = '', $default = '', $required = false) + { + $fieldset->addField($fieldName, 'image', array( + 'name' => $fieldName, + 'label' => $title, + 'note' => !empty($note) ? $note : null, + 'required' => $required, + )); + } + + /** + * Prepare form before rendering HTML + * + * @return Mage_Adminhtml_Block_Widget_Form + */ + protected function _prepareForm() + { + $form = new Varien_Data_Form(); + $this->setForm($form); + + $form->setAction($this->getUrl('*/mobile/submission')); + $isResubmit = $this->getApplication()->getIsResubmitAction(); + $formData = $this->getApplication()->getFormData(); + + $url = Mage::getStoreConfig('xmlconnect/mobile_application/activation_key_url'); + $afterElementHtml = Mage::helper('xmlconnect')->__('In order to submit your app, you need to first purchase a %s from MagentoCommerce', $url, Mage::helper('xmlconnect')->__('Activation Key')); + $fieldset = $form->addFieldset('submit_keys', array('legend' => Mage::helper('xmlconnect')->__('Key'))); + $field = $fieldset->addField('conf[submit_text][key]', 'text', array( + 'name' => 'conf[submit_text][key]', + 'label' => Mage::helper('xmlconnect')->__('Activation Key'), + 'value' => isset($formData['conf[submit_text][key]']) ? $formData['conf[submit_text][key]'] : null, + 'after_element_html' => $afterElementHtml, + )); + if (!$isResubmit) { + $field->setRequired(true); + } else { + $field->setDisabled('disabled'); + $fieldset->addField('conf[submit_text][key]_hidden', 'hidden', array( + 'name' => 'conf[submit_text][key]', + 'value' => isset($formData['conf[submit_text][key]']) ? $formData['conf[submit_text][key]'] : null, + )); + } + + if ($isResubmit) { + $url = Mage::getStoreConfig('xmlconnect/mobile_application/resubmission_key_url'); + $afterElementHtml = Mage::helper('xmlconnect')->__('In order to resubmit your app, you need to first purchase a %s from MagentoCommerce', $url, Mage::helper('xmlconnect')->__('Resubmission Key')); + + $fieldset->addField('conf[submit_text][resubmission_activation_key]', 'text', array( + 'name' => 'conf[submit_text][resubmission_activation_key]', + 'label' => Mage::helper('xmlconnect')->__('Resubmission Key'), + 'value' => isset($formData['conf[submit_text][resubmission_activation_key]']) ? $formData['conf[submit_text][resubmission_activation_key]'] : null, + 'required' => true, + 'after_element_html' => $afterElementHtml, + )); + } + + $fieldset = $form->addFieldset('submit_general', array('legend' => Mage::helper('xmlconnect')->__('Submission Fields'))); + + $fieldset->addField('submission_action', 'hidden', array( + 'name' => 'submission_action', + 'value' => '1', + )); + $fieldset->addField('conf/submit_text/title', 'text', array( + 'name' => 'conf[submit_text][title]', + 'label' => Mage::helper('xmlconnect')->__('Title'), + 'maxlength' => '200', + 'value' => isset($formData['conf[submit_text][title]']) ? $formData['conf[submit_text][title]'] : null, + 'note' => Mage::helper('xmlconnect')->__('Name that appears beneath your app when users install it to their device. We recommend choosing a name that is 10-12 characters and that your customers will recognize.'), + 'required' => true, + )); + + $field = $fieldset->addField('conf/submit_text/description', 'textarea', array( + 'name' => 'conf[submit_text][description]', + 'label' => Mage::helper('xmlconnect')->__('Description'), + 'maxlength' => '500', + 'value' => isset($formData['conf[submit_text][description]']) ? $formData['conf[submit_text][description]'] : null, + 'note' => Mage::helper('xmlconnect')->__('Description that appears in the iTunes App Store. 4000 chars maximum. '), + 'required' => true, + )); + $field->setRows(15); + + $fieldset->addField('conf/submit_text/contact_email', 'text', array( + 'name' => 'conf[submit_text][email]', + 'label' => Mage::helper('xmlconnect')->__('Contact Email'), + 'class' => 'email', + 'maxlength' => '40', + 'value' => isset($formData['conf[submit_text][email]']) ? $formData['conf[submit_text][email]'] : null, + 'note' => Mage::helper('xmlconnect')->__('Administrative contact for this app and for app submission issues.'), + 'required' => true, + )); + + $fieldset->addField('conf/submit_text/price_free_label', 'label', array( + 'name' => 'conf[submit_text][price_free_label]', + 'label' => Mage::helper('xmlconnect')->__('Price'), + 'value' => Mage::helper('xmlconnect')->__('Free'), + 'maxlength' => '40', + 'checked' => 'checked', + 'note' => Mage::helper('xmlconnect')->__('Only free apps are allowed in this version.'), + )); + + $fieldset->addField('conf/submit_text/price_free', 'hidden', array( + 'name' => 'conf[submit_text][price_free]', + 'value' => '1', + )); + + $selected = isset($formData['conf[submit_text][country]']) ? explode(',', $formData['conf[submit_text][country]']) : null; + $fieldset->addField('conf/submit_text/country', 'multiselect', array( + 'name' => 'conf[submit_text][country][]', + 'label' => Mage::helper('xmlconnect')->__('Country'), + 'values' => Mage::helper('xmlconnect')->getCountryOptionsArray(), + 'value' => $selected, + 'note' => Mage::helper('xmlconnect')->__('Make this app available in the following territories'), + 'required' => true, + )); + + $fieldset->addField('conf/submit_text/copyright', 'text', array( + 'name' => 'conf[submit_text][copyright]', + 'label' => Mage::helper('xmlconnect')->__('Copyright'), + 'maxlength' => '200', + 'value' => isset($formData['conf[submit_text][copyright]']) ? $formData['conf[submit_text][copyright]'] : null, + 'note' => Mage::helper('xmlconnect')->__('Appears in the info section of your app (example: Copyright 2010 – Your Company, Inc.)'), + 'required' => true, + )); + + $fieldset->addField('conf/submit_text/keywords', 'text', array( + 'name' => 'conf[submit_text][keywords]', + 'label' => Mage::helper('xmlconnect')->__('Keywords'), + 'maxlength' => '100', + 'value' => isset($formData['conf[submit_text][keywords]']) ? $formData['conf[submit_text][keywords]'] : null, + 'note' => Mage::helper('xmlconnect')->__('One or more keywords that describe your app. Keywords are matched to users` searches in the App Store and help return accurate search results. Separate multiple keywords with commas. 100 chars is maximum.'), + )); + + $fieldset = $form->addFieldset('submit_icons', array('legend' => Mage::helper('xmlconnect')->__('Icons'))); + $this->addImage($fieldset, 'conf/submit/icon', Mage::helper('xmlconnect')->__('Large iTunes Icon'), + Mage::helper('xmlconnect')->__('Large icon that appears in the iTunes App Store. You do not need to apply a gradient or soft edges (this is done automatically by Apple). Required size: 512px x 512px.'), '', true); + $this->addImage($fieldset, 'conf/submit/loader_image', Mage::helper('xmlconnect')->__('Loader Splash Screen'), + Mage::helper('xmlconnect')->__('Image that appears on first screen while your app is loading. Required size: 320px x 460px.'), '', true); + + $this->addImage($fieldset, 'conf/submit/logo', Mage::helper('xmlconnect')->__('Custom App Icon'), + Mage::helper('xmlconnect')->__('Icon that will appear on the user’s phone after they download your app. You do not need to apply a gradient or soft edges (this is done automatically by Apple). Recommended size: 57px x 57px at 72 dpi.'), '', true); + $this->addImage($fieldset, 'conf/submit/big_logo', Mage::helper('xmlconnect')->__('Copyright Page Logo'), + Mage::helper('xmlconnect')->__('Store logo that is displayed on copyright page of app. Preferred size: 100px x 100px.'), '', true); + + return parent::_prepareForm(); + } + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return Mage::helper('xmlconnect')->__('Submission'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return Mage::helper('xmlconnect')->__('Submission'); + } + + /** + * Returns status flag about this tab can be showen or not + * + * @return true + */ + public function canShowTab() + { + return true; + } + + /** + * Returns status flag about this tab hidden or not + * + * @return false + */ + public function isHidden() + { + return false; + } + + /** + * Configure image element type + * + * @return array + */ + protected function _getAdditionalElementTypes() + { + return array( + 'image' => Mage::getConfig()->getBlockClassName('xmlconnect/adminhtml_mobile_helper_image'), + ); + } + + /** + * Prepare html output + * Adding preview for images if application was submitted(so we have saved images) + * + * @return string + */ + protected function _toHtml() + { + return parent::_toHtml() . $this->getChildHtml('images'); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Submission.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Submission.php new file mode 100644 index 0000000000..5f627fdce7 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tab/Submission.php @@ -0,0 +1,285 @@ +setShowGlobalIcon(true); + + } + + protected function _prepareLayout() + { + $application = Mage::registry('current_app'); + + $actionUrl = $this->getUrl('*/*/submissionPost', array('key' => $application->getId())); + $this->setChild('submission_scripts', + $this->getLayout()->createBlock('adminhtml/template') + ->setActionUrl($actionUrl) + ->setTemplate('xmlconnect/submission_scripts.phtml')); + if ($application->getIsResubmitAction()) { + $block = $this->getLayout()->createBlock('adminhtml/template') + ->setTemplate('xmlconnect/resubmit.phtml') + ->setActionUrl($actionUrl) + ->setActivationKey($application->getActivationKey()) + ->setResubmissionName('conf[submit_text][resubmission_activation_key]'); + $this->setChild('resubmit', $block); + + $block = $this->getLayout()->createBlock('adminhtml/template') + ->setTemplate('xmlconnect/images.phtml') + ->setImages($application->getImages()); + $this->setChild('images', $block); + } + parent::_prepareLayout(); + } + + + /** + * Add image uploader to fieldset + * + * @param Varien_Data_Form_Element_Fieldset $fieldset + * @param string $fieldName + * @param string $title + */ + protected function addImage($fieldset, $fieldName, $title, $note = '') + { + $fieldset->addField($fieldName, 'image', array( + 'name' => $fieldName, + 'label' => $title, + 'note' => !empty($note) ? $note : null, + )); + } + + protected function _prepareForm() + { + $form = new Varien_Data_Form(); + $this->setForm($form); + + $form->setAction($this->getUrl('*/mobile/submission')); + $application = Mage::registry('current_app'); + $isResubmit = $application->getIsResubmitAction(); + $formData = $application->getFormData(); + + $fieldset = $form->addFieldset('submit0', array('legend' => $this->__('Submission Fields'))); + + $fieldset->addField('conf/submit_text/title', 'text', array( + 'name' => 'conf[submit_text][title]', + 'label' => $this->__('Title'), + 'maxlength' => '200', + 'value' => isset($formData['conf[submit_text][title]']) ? $formData['conf[submit_text][title]'] : null, + 'note' => $this->__('This is the name that will appear beneath your app when users install it to their device. . We recommend choosing a name that is 10-12 characters in length, and that your customers will recognize.'), + 'required' => true, + )); + + $field = $fieldset->addField('conf/submit_text/description', 'textarea', array( + 'name' => 'conf[submit_text][description]', + 'label' => $this->__('Description'), + 'maxlength' => '500', + 'value' => isset($formData['conf[submit_text][description]']) ? $formData['conf[submit_text][description]'] : null, + 'note' => $this->__('This is the description that will appear in the iTunes marketplace. '), + 'required' => true, + )); + $field->setRows(15); + + $fieldset->addField('conf/submit_text/username/', 'text', array( + 'name' => 'conf[submit_text][username]', + 'label' => $this->__('Username'), + 'maxlength' => '40', + 'value' => isset($formData['conf[submit_text][username]']) ? $formData['conf[submit_text][username]'] : null, + 'note' => $this->__('Paypal Merchant Account Username.'), + 'required' => true, + )); + + $fieldset->addField('conf/submit_text/email', 'text', array( + 'name' => 'conf[submit_text][email]', + 'label' => $this->__('Email'), + 'class' => 'email', + 'maxlength' => '40', + 'value' => isset($formData['conf[submit_text][email]']) ? $formData['conf[submit_text][email]'] : null, + 'note' => $this->__('Paypal Merchant Account Email.'), + 'required' => true, + )); + + $fieldset->addField('conf/submit_text/price_free', 'radio', array( + 'name' => 'conf[submit_text][price_free]', + 'label' => $this->__('Price'), + 'value' => '1', + 'maxlength' => '40', + 'after_element_html' => $this->__('Free'), + 'onclick' => "$('conf/submit_text/price').setValue('')", + )); + + $fieldset->addField('conf/submit_text/price', 'text', array( + 'name' => 'conf[submit_text][price]', + 'label' => $this->__(' '), + 'maxlength' => '40', + 'value' => isset($formData['conf[submit_text][price]']) ? $formData['conf[submit_text][price]'] : null, + 'note' => $this->__('You can set any price you want for your app, or you can give it away for free. Most apps range from $0.99 - $4.99'), + 'onchange' => "$('conf/submit_text/price_free').checked = false", + + )); + + $selected = isset($formData['conf[submit_text][country]']) ? json_decode($formData['conf[submit_text][country]']) : null; + $fieldset->addField('conf/submit_text/country', 'multiselect', array( + 'name' => 'conf[submit_text][country][]', + 'label' => $this->__('Country'), + 'values' => Mage::helper('xmlconnect')->getCountryOptionsArray(), + 'value' => $selected, + 'note' => $this->__('Make this app available in the following territories'), + )); + + $fieldset->addField('conf/submit_text/country_additional', 'text', array( + 'name' => 'conf[submit_text][country_additional]', + 'label' => $this->__('Additional Countries'), + 'maxlength' => '200', + 'value' => isset($formData['conf[submit_text][country_additional]']) ? $formData['conf[submit_text][country_additional]'] : null, + 'note' => $this->__('You can set any additional countries added by Apple Store.'), + + )); + + $fieldset->addField('conf/submit_text/copyright', 'textarea', array( + 'name' => 'conf[submit_text][copyright]', + 'label' => $this->__('Copyright'), + 'maxlength' => '200', + 'value' => isset($formData['conf[submit_text][copyright]']) ? $formData['conf[submit_text][copyright]'] : null, + 'note' => $this->__('This will appear in the info section of your App (example: Copyright 2010 – Your Company, Inc.)'), + 'size' => '30', + 'required' => true, + )); + + $fieldset->addField('conf/submit_text/push_notification', 'checkbox', array( + 'name' => 'conf[submit_text][push_notification]', + 'label' => $this->__('Push Notification'), + 'checked' => isset($formData['conf[submit_text][push_notification]']), + 'value' => '1', + )); + + $fieldset = $form->addFieldset('submit1', array('legend' => $this->__('Icons'))); + $this->addImage($fieldset, 'conf/submit/icon', 'Application Icon', + $this->__('Apply will automatically resize this image for display in the App Store and on users’ devices. A gloss (i.e. gradient) will also be applied, so you do not need to apply a gradient. Image must be at least 512x512')); + $this->addImage($fieldset, 'conf/submit/loader_image', 'Loader Splash Screen', + $this->__('Users will see this image as the first screen while your application is loading. It is a 320x460 image.')); + + $this->addImage($fieldset, 'conf/submit/logo', $this->__('Custom application icon'), + $this->__('This icon will be used for users’ devices in case if different than AppSore icon needed. ')); + $this->addImage($fieldset, 'conf/submit/big_logo', $this->__('Copyright page logo'), + $this->__('Store logo that will be displayed on copyright page of application ')); + + $fieldset = $form->addFieldset('submit2', array('legend' => $this->__('Key'))); + $fieldset->addField('conf[submit_text][key]', 'text', array( + 'name' => 'conf[submit_text][key]', + 'label' => $this->__('Activation Key'), + 'value' => isset($formData['conf[submit_text][key]']) ? $formData['conf[submit_text][key]'] : null, + 'disabled' => $isResubmit, + 'after_element_html' => '' + . $this->__('Get Activation Key'). '', + )); + + if (!$isResubmit) { + $fieldset->addField('submit_application', 'button', array( + 'name' => 'submit_application', + 'label'=>$this->__('Submit'), + 'value' => $this->__('Submit Application'), + 'onclick' => 'submitApplication()', + )); + } else { + $fieldset->addField('submit_application', 'button', array( + 'name' => 'submit_application', + 'label'=> $this->__('Resubmit'), + 'value' => $this->__('Resubmit Application'), + 'onclick' => 'resubmitAction(); return false;', + )); + } + + return parent::_prepareForm(); + } + + /** + * Prepare label for tab + * + * @return string + */ + public function getTabLabel() + { + return $this->__('Submission'); + } + + /** + * Prepare title for tab + * + * @return string + */ + public function getTabTitle() + { + return $this->__('Submission'); + } + + /** + * Returns status flag about this tab can be showen or not + * + * @return true + */ + public function canShowTab() + { + return true; + } + + /** + * Returns status flag about this tab hidden or not + * + * @return false + */ + public function isHidden() + { + return false; + } + + /** + * Configure image element type + * + */ + protected function _getAdditionalElementTypes() + { + return array( + 'image' => Mage::getConfig()->getBlockClassName('xmlconnect/adminhtml_mobile_helper_image'), + ); + } + + protected function _toHtml() + { + return parent::_toHtml() + . $this->getChildHtml('submission_scripts') + . (!Mage::registry('current_app')->getIsResubmitAction() ? '' : + ($this->getChildHtml('mobile_edit_tab_submission_history') + . $this->getChildHtml('resubmit') + . $this->getChildHtml('images'))); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tabs.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tabs.php new file mode 100644 index 0000000000..d3961b8545 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Submission/Tabs.php @@ -0,0 +1,40 @@ +setId('mobile_app_tabs'); + $this->setDestElementId('edit_form'); + $this->setTitle(Mage::helper('xmlconnect')->__('Manage Mobile App')); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Widget/Form.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Widget/Form.php new file mode 100644 index 0000000000..a6103b351a --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Adminhtml/Mobile/Widget/Form.php @@ -0,0 +1,117 @@ +addField($fieldName, 'color', array( + 'name' => $fieldName, + 'label' => $title, + )); + } + + /** + * Add image uploader to fieldset + * + * @param Varien_Data_Form_Element_Fieldset $fieldset + * @param string $fieldName + * @param string $title + * @param string|null $note + * @param string $default + * @param boolean $required + */ + public function addImage($fieldset, $fieldName, $title, $note = null, $default = '', $required = false) + { + $fieldset->addField($fieldName, 'image', array( + 'name' => $fieldName, + 'label' => $title, + 'note' => $note, + 'default_value' => $default, + 'required' => $required, + )); + } + + /** + * Add font selector to fieldset + * + * @param Varien_Data_Form_Element_Fieldset $fieldset + * @param string $fieldPrefix + * @param string $title + */ + public function addFont($fieldset, $fieldPrefix, $title) + { + $el = $fieldset->addField($fieldPrefix, 'font', array( + 'name' => $fieldPrefix, + 'label' => $title, + )); + $el->initFields(array( + 'name' => $fieldPrefix, + 'fontNames' => Mage::helper('xmlconnect/iphone')->getFontList(), + 'fontSizes' => Mage::helper('xmlconnect/iphone')->getFontSizes(), + )); + } + + /** + * Configure image element type + * + * @return array + */ + protected function _getAdditionalElementTypes() + { + $config = Mage::getConfig(); + return array( + 'image' => $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_image'), + 'font' => $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_font'), + 'color' => $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_color'), + 'tabs' => $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_tabs'), + 'theme' => $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_theme'), + 'page' => $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_page'), + 'addrow'=> $config->getBlockClassName('xmlconnect/adminhtml_mobile_form_element_addrow'), + ); + } + + /** + * Getter for current loaded application model + * + * @return Mage_XmlConnect_Model_Application + */ + public function getApplication() + { + $model = Mage::registry('current_app'); + if (!($model instanceof Mage_XmlConnect_Model_Application)) { + Mage::throwException(Mage::helper('xmlconnect')->__('App model not loaded.')); + } + + return $model; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart.php new file mode 100644 index 0000000000..5b08d67d99 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart.php @@ -0,0 +1,225 @@ + + */ +class Mage_XmlConnect_Block_Cart extends Mage_Checkout_Block_Cart_Abstract +{ + /** + * Render shopping cart xml + * + * @return string + */ + protected function _toHtml() + { + $cartMessages = $this->getMessages(); + $quote = $this->getQuote(); + $xmlObject = new Mage_XmlConnect_Model_Simplexml_Element(''); + $xmlObject->addAttribute('is_virtual', (int)$this->helper('checkout/cart')->getIsVirtualQuote()); + $xmlObject->addAttribute('summary_qty', (int)$this->helper('checkout/cart')->getSummaryCount()); + if (strlen($quote->getCouponCode())) { + $xmlObject->addAttribute('has_coupon_code', 1); + } + $products = $xmlObject->addChild('products'); + + /* @var $item Mage_Sales_Model_Quote_Item */ + foreach ($this->getItems() as $item) { + $type = $item->getProductType(); + $renderer = $this->getItemRenderer($type)->setItem($item); + + /** + * General information + */ + $itemXml = $products->addChild('item'); + $itemXml->addChild('entity_id', $item->getProduct()->getId()); + $itemXml->addChild('entity_type', $type); + $itemXml->addChild('item_id', $item->getId()); + $itemXml->addChild('name', $xmlObject->xmlentities(strip_tags($renderer->getProductName()))); + $itemXml->addChild('code', 'cart[' . $item->getId() . '][qty]'); + $itemXml->addChild('qty', $renderer->getQty()); + $icon = $renderer->getProductThumbnail()->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('product_small')); + + $iconXml = $itemXml->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + + /** + * Price + */ + $exclPrice = $inclPrice = 0.00; + if ($this->helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()) { + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $exclPrice = $item->getCalculationPrice() + $item->getWeeeTaxAppliedAmount() + $item->getWeeeTaxDisposition(); + } else { + $exclPrice = $item->getCalculationPrice(); + } + } + + if ($this->helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()) { + $_incl = $this->helper('checkout')->getPriceInclTax($item); + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $inclPrice = $_incl + $item->getWeeeTaxAppliedAmount(); + } else { + $inclPrice = $_incl - $item->getWeeeTaxDisposition(); + } + } + + $exclPrice = Mage::helper('xmlconnect')->formatPriceForXml($exclPrice); + $formatedExclPrice = $quote->getStore()->formatPrice($exclPrice, false); + + $inclPrice = Mage::helper('xmlconnect')->formatPriceForXml($inclPrice); + $formatedInclPrice = $quote->getStore()->formatPrice($inclPrice, false); + + $priceXmlObj = $itemXml->addChild('price'); + $formatedPriceXmlObj = $itemXml->addChild('formated_price'); + + if ($this->helper('tax')->displayCartBothPrices()) { + $priceXmlObj->addAttribute('excluding_tax', $exclPrice); + $priceXmlObj->addAttribute('including_tax', $inclPrice); + + $formatedPriceXmlObj->addAttribute('excluding_tax', $formatedExclPrice); + $formatedPriceXmlObj->addAttribute('including_tax', $formatedInclPrice); + } else { + if ($this->helper('tax')->displayCartPriceExclTax()) { + $priceXmlObj->addAttribute('regular', $exclPrice); + $formatedPriceXmlObj->addAttribute('regular', $formatedExclPrice); + } + if ($this->helper('tax')->displayCartPriceInclTax()) { + $priceXmlObj->addAttribute('regular', $inclPrice); + $formatedPriceXmlObj->addAttribute('regular', $formatedInclPrice); + } + } + + + /** + * Subtotal + */ + $exclPrice = $inclPrice = 0.00; + if ($this->helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()) { + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $exclPrice = $item->getRowTotal() + $item->getWeeeTaxAppliedRowAmount() + $item->getWeeeTaxRowDisposition(); + } else { + $exclPrice = $item->getRowTotal(); + } + } + if ($this->helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()) { + $_incl = $this->helper('checkout')->getSubtotalInclTax($item); + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $inclPrice = $_incl + $item->getWeeeTaxAppliedRowAmount(); + } else { + $inclPrice = $_incl - $item->getWeeeTaxRowDisposition(); + } + } + + $exclPrice = Mage::helper('xmlconnect')->formatPriceForXml($exclPrice); + $formatedExclPrice = $quote->getStore()->formatPrice($exclPrice, false); + + $inclPrice = Mage::helper('xmlconnect')->formatPriceForXml($inclPrice); + $formatedInclPrice = $quote->getStore()->formatPrice($inclPrice, false); + + $subtotalPriceXmlObj = $itemXml->addChild('subtotal'); + $subtotalFormatedPriceXmlObj = $itemXml->addChild('formated_subtotal'); + + if ($this->helper('tax')->displayCartBothPrices()) { + $subtotalPriceXmlObj->addAttribute('excluding_tax', $exclPrice); + $subtotalPriceXmlObj->addAttribute('including_tax', $inclPrice); + + $subtotalFormatedPriceXmlObj->addAttribute('excluding_tax', $formatedExclPrice); + $subtotalFormatedPriceXmlObj->addAttribute('including_tax', $formatedInclPrice); + } else { + if ($this->helper('tax')->displayCartPriceExclTax()) { + $subtotalPriceXmlObj->addAttribute('regular', $exclPrice); + $subtotalFormatedPriceXmlObj->addAttribute('regular', $formatedExclPrice); + } + if ($this->helper('tax')->displayCartPriceInclTax()) { + $subtotalPriceXmlObj->addAttribute('regular', $inclPrice); + $subtotalFormatedPriceXmlObj->addAttribute('regular', $formatedInclPrice); + } + } + + /** + * Options list + */ + if ($_options = $renderer->getOptionList()) { + $itemOptionsXml = $itemXml->addChild('options'); + foreach ($_options as $_option) { + $_formatedOptionValue = $renderer->getFormatedOptionValue($_option); + $optionXml = $itemOptionsXml->addChild('option'); + $optionXml->addAttribute('label', $xmlObject->xmlentities(strip_tags($_option['label']))); + $optionXml->addAttribute('text', $xmlObject->xmlentities(strip_tags($_formatedOptionValue['value']))); +// if (isset($_formatedOptionValue['full_view'])) { +// $label = strip_tags($_option['label']); +// $value = strip_tags($_formatedOptionValue['full_view']); +// } + } + } + + /** + * Item messages + */ + if ($messages = $renderer->getMessages()) { + $itemMessagesXml = $itemXml->addChild('messages'); + foreach ($messages as $message) { + $messageXml = $itemMessagesXml->addChild('option'); + $messageXml->addChild('type', $message['type']); + $messageXml->addChild('text', $xmlObject->xmlentities(strip_tags($message['text']))); + } + } + } + + /** + * Cart messages + */ + if ($cartMessages) { + $messagesXml = $xmlObject->addChild('messages'); + foreach ($cartMessages as $status => $messages) { + foreach ($messages as $message) { + $messageXml = $messagesXml->addChild('message'); + $messageXml->addChild('status', $status); + $messageXml->addChild('text', strip_tags($message)); + } + } + } + + /** + * Cross Sell Products + */ + $crossSellXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($this->getChildHtml('crosssell')); + $xmlObject->appendChild($crossSellXmlObj); + + return $xmlObject->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Crosssell.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Crosssell.php new file mode 100644 index 0000000000..29bf967e48 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Crosssell.php @@ -0,0 +1,84 @@ + + */ +class Mage_XmlConnect_Block_Cart_Crosssell extends Mage_Checkout_Block_Cart_Crosssell +{ + /** + * Render cross sell items xml + * + * @return string + */ + protected function _toHtml() + { + $crossSellXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + if (!$this->getItemCount()) { + return $crossSellXmlObj->asNiceXml(); + } + + foreach ($this->getItems() as $_item) { + $itemXmlObj = $crossSellXmlObj->addChild('item'); + $itemXmlObj->addChild('name', $crossSellXmlObj->xmlentities(strip_tags($_item->getName()))); + $icon = $this->helper('catalog/image')->init($_item, 'thumbnail') + ->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('product_small')); + + $iconXml = $itemXmlObj->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + + $itemXmlObj->addChild('entity_id', $_item->getId()); + $itemXmlObj->addChild('entity_type', $_item->getTypeId()); + $itemXmlObj->addChild('has_options', (int)$_item->getHasOptions()); + + if ($this->getChild('product_price')) { + $this->getChild('product_price')->setProduct($_item) + ->setProductXmlObj($itemXmlObj) + ->collectProductPrices(); + } + + if (!$_item->getRatingSummary()) { + Mage::getModel('review/review') + ->getEntitySummary($_item, Mage::app()->getStore()->getId()); + } + + $itemXmlObj->addChild('rating_summary', round((int)$_item->getRatingSummary()->getRatingSummary() / 10)); + $itemXmlObj->addChild('reviews_count', $_item->getRatingSummary()->getReviewsCount()); + } + + return $crossSellXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Info.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Info.php new file mode 100644 index 0000000000..133dbef863 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Info.php @@ -0,0 +1,61 @@ + + */ +class Mage_XmlConnect_Block_Cart_Info extends Mage_XmlConnect_Block_Cart +{ + /** + * Render cart summary xml + * + * @return string + */ + protected function _toHtml() + { + $quote = $this->getQuote(); + $xmlObject = new Mage_XmlConnect_Model_Simplexml_Element(''); + $xmlObject->addChild('is_virtual', (int)$this->helper('checkout/cart')->getIsVirtualQuote()); + $xmlObject->addChild('summary_qty', (int)$this->helper('checkout/cart')->getSummaryCount()); + $xmlObject->addChild('virtual_qty', (int)$quote->getItemVirtualQty()); + if (strlen($quote->getCouponCode())) { + $xmlObject->addChild('has_coupon_code', 1); + } + + $totalsXml = $this->getChildHtml('totals'); + if ($totalsXml) { + $totalsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($totalsXml); + $xmlObject->appendChild($totalsXmlObj); + } + + return $xmlObject->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Paypal/Mep/Totals.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Paypal/Mep/Totals.php new file mode 100644 index 0000000000..59dc468cab --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Paypal/Mep/Totals.php @@ -0,0 +1,51 @@ + + */ +class Mage_XmlConnect_Block_Cart_Paypal_Mep_Totals extends Mage_Checkout_Block_Cart_Totals +{ + /** + * Render cart totals xml + * + * @return string + */ + protected function _toHtml() + { + $paypalCart = Mage::getModel('paypal/cart', array($this->getQuote())); + $totalsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + foreach ($paypalCart->getTotals(true) as $code => $amount) { + $currencyAmount = $this->helper('core')->currency($amount, false, false); + $totalsXmlObj->addChild($code, Mage::helper('xmlconnect')->formatPriceForXml($currencyAmount)); + } + return $totalsXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Totals.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Totals.php new file mode 100644 index 0000000000..9fd03bafa3 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cart/Totals.php @@ -0,0 +1,123 @@ + + */ +class Mage_XmlConnect_Block_Cart_Totals extends Mage_Checkout_Block_Cart_Totals +{ + /** + * Render cart totals xml + * + * @return string + */ + protected function _toHtml() + { + $totalsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $taxConfig = Mage::getSingleton('tax/config'); + $displayInclTax = $displayBoth = false; + + foreach ($this->getQuote()->getTotals() as $total) { + $code = $total->getCode(); + if ($code == 'giftcardaccount') { + continue; + } + $title = ''; + $value = null; + $renderer = $this->_getTotalRenderer($code)->setTotal($total); + switch ($code) { + case 'subtotal': + if ($renderer->displayBoth()) { + $title = $this->helper('xmlconnect')->__('Subtotal (Excl. Tax)'); + $this->_addTotalDataToXmlObj($totalsXmlObj, $code . '_excl_tax', $title, $total->getValueExclTax()); + + $code = $code . '_incl_tax'; + $title = $this->helper('xmlconnect')->__('Subtotal (Incl. Tax)'); + $value = $total->getValueInclTax(); + } + break; + case 'shipping': + if ($renderer->displayBoth()) { + $title = $renderer->getExcludeTaxLabel(); + $this->_addTotalDataToXmlObj($totalsXmlObj, $code . '_excl_tax', $title, $renderer->getShippingExcludeTax()); + + $code = $code . '_incl_tax'; + $title = $renderer->getIncludeTaxLabel(); + $value = $renderer->getShippingIncludeTax(); + } else if ($renderer->displayIncludeTax()) { + $value = $renderer->getShippingIncludeTax(); + } else { + $value = $renderer->getShippingExcludeTax(); + } + break; + case 'grand_total': + $grandTotalExlTax = $renderer->getTotalExclTax(); + $displayBoth = $renderer->includeTax() && $grandTotalExlTax >= 0; + if ($displayBoth) { + $title = $this->helper('xmlconnect')->__('Grand Total (Excl. Tax)'); + $this->_addTotalDataToXmlObj($totalsXmlObj, $code . '_excl_tax', $title, $grandTotalExlTax); + + $code = $code . '_incl_tax'; + $title = $this->helper('xmlconnect')->__('Grand Total (Incl. Tax)'); + } + break; + default: + break; + } + if ($title == '') { + $title = $total->getTitle(); + } + if (is_null($value)) { + $value = $total->getValue(); + } + $this->_addTotalDataToXmlObj($totalsXmlObj, $code, $title, $value); + } + + return $totalsXmlObj->asNiceXml(); + } + + /** + * Add total data to totals xml object + * + * @param Mage_XmlConnect_Model_Simplexml_Element $totalsXmlObj + * @param string $code + * @param string $title + * @param float $value + */ + protected function _addTotalDataToXmlObj($totalsXmlObj, $code, $title, $value) + { + $value = Mage::helper('xmlconnect')->formatPriceForXml($value); + $totalXmlObj = $totalsXmlObj->addChild($code); + $totalXmlObj->addChild('title', $totalsXmlObj->xmlentities(strip_tags($title))); + $formatedValue = $this->getQuote()->getStore()->formatPrice($value, false); + $totalXmlObj->addChild('value', $value); + $totalXmlObj->addChild('formated_value', $formatedValue); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog.php new file mode 100644 index 0000000000..72c3920046 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog.php @@ -0,0 +1,65 @@ +'); + $sortOptions = Mage::getModel('catalog/category')->getAvailableSortByOptions(); + $sortOptions = array_slice($sortOptions, 0, self::PRODUCT_SORT_FIELDS_NUMBER); + foreach ($sortOptions as $code => $name) { + $item = $ordersXmlObject->addChild('item'); + $item->addChild('code', $code); + $item->addChild('name', $ordersXmlObject->xmlentities(strip_tags($name))); + } + + return $ordersXmlObject; + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Category.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Category.php new file mode 100644 index 0000000000..af9e9010d6 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Category.php @@ -0,0 +1,112 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Category extends Mage_XmlConnect_Block_Catalog +{ + /** + * Render block HTML + * + * @return string + */ + protected function _toHtml() + { + $categoryXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $categoryId = $this->getRequest()->getParam('id', null); + if ($categoryId === null) { + $categoryId = Mage::app()->getStore()->getRootCategoryId(); + } + $categoryModel = Mage::getModel('catalog/category')->load($categoryId); + if ($categoryModel->getId()) { + $hasMoreProductItems = 0; + $productsXmlObj = $productListBlock = null; + /** + * Return products list if there are no child categories + */ + if (!$categoryModel->hasChildren()) { + $productListBlock = $this->getChild('product_list'); + if ($productListBlock) { + $layer = Mage::getSingleton('catalog/layer'); + $productsXmlObj = $productListBlock->setCategory($categoryModel) + ->setLayer($layer) + ->getProductsXmlObject(); + $hasMoreProductItems = (int)$productListBlock->getHasProductItems(); + } + } + + $infoBlock = $this->getChild('category_info'); + if ($infoBlock) { + $categoryInfoXmlObj = $infoBlock->setCategory($categoryModel) + ->getCategoryInfoXmlObject(); + $categoryInfoXmlObj->addChild('has_more_items', $hasMoreProductItems); + $categoryXmlObj->appendChild($categoryInfoXmlObj); + } + + if ($productListBlock && $productsXmlObj) { + $categoryXmlObj->appendChild($productsXmlObj); + } + + } + + $categoryCollection = Mage::getResourceModel('xmlconnect/category_collection'); + $categoryCollection->setStoreId(Mage::app()->getStore()->getId()) + ->setOrder('position', 'ASC') + ->addParentIdFilter($categoryId); + + if (sizeof($categoryCollection)) { + $itemsXmlObj = $categoryXmlObj->addChild('items'); + } + + foreach ($categoryCollection->getItems() as $item) { + $itemXmlObj = $itemsXmlObj->addChild('item'); + $itemXmlObj->addChild('label', $categoryXmlObj->xmlentities(strip_tags($item->getName()))); + $itemXmlObj->addChild('entity_id', $item->getEntityId()); + $itemXmlObj->addChild('content_type', $item->hasChildren() ? 'categories' : 'products'); + if (!is_null($categoryId)) { + $itemXmlObj->addChild('parent_id', $item->getParentId()); + } + $icon = Mage::helper('xmlconnect/catalog_category_image')->initialize($item, 'thumbnail') + ->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('category')); + + $iconXml = $itemXmlObj->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + } + + return $categoryXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Category/Info.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Category/Info.php new file mode 100644 index 0000000000..50395a53a0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Category/Info.php @@ -0,0 +1,78 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Category_Info extends Mage_XmlConnect_Block_Catalog +{ + + /** + * Produce category info xml object + * + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function getCategoryInfoXmlObject() + { + $infoXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $category = $this->getCategory(); + if ($category && is_object($category) && $category->getId()) { + $title = $infoXmlObj->xmlentities(strip_tags($category->getParentCategory()->getName())); + if ($category->getParentCategory()->getLevel() == 1) { + /** + * @var string $title + * + * Copied data from "getDefaultApplicationDesignTabs()" method in "Mage_XmlConnect_Helper_Data" + */ + $title = Mage::helper('xmlconnect')->__('Shop'); + } + + $infoXmlObj->addChild('parent_title', $title); + $pId = $category->getParentId(); + if ($category->getLevel() == 1) { + $pId = 0; + } + $infoXmlObj->addChild('parent_id', $pId); + } + + return $infoXmlObj; + } + + /** + * Render category info xml + * + * @return string + */ + protected function _toHtml() + { + return $this->getCategoryInfoXmlObject()->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Filters.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Filters.php new file mode 100644 index 0000000000..90a6e172ee --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Filters.php @@ -0,0 +1,71 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Filters extends Mage_XmlConnect_Block_Catalog +{ + + /** + * Render filters list xml + * + * @return string + */ + protected function _toHtml() + { + $categoryId = $this->getRequest()->getParam('category_id', null); + $categoryXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $filtersCollection = Mage::getResourceModel('xmlconnect/filter_collection')->setCategoryId($categoryId); + + $filtersXmlObj = $categoryXmlObj->addChild('filters'); + foreach ($filtersCollection->getItems() as $item) { + if (!sizeof($item->getValues())) { + continue; + } + $itemXmlObj = $filtersXmlObj->addChild('item'); + $itemXmlObj->addChild('name', $categoryXmlObj->xmlentities(strip_tags($item->getName()))); + $itemXmlObj->addChild('code', $categoryXmlObj->xmlentities($item->getCode())); + + $valuesXmlObj = $itemXmlObj->addChild('values'); + foreach ($item->getValues() as $value) { + $valueXmlObj = $valuesXmlObj->addChild('value'); + $valueXmlObj->addChild('id', $categoryXmlObj->xmlentities($value->getValueString())); + $valueXmlObj->addChild('label', $categoryXmlObj->xmlentities(strip_tags($value->getLabel()))); + $valueXmlObj->addChild('count', (int)$value->getProductsCount()); + } + } + $categoryXmlObj->appendChild($this->getProductSortFeildsXmlObject()); + + return $categoryXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product.php new file mode 100644 index 0000000000..77a8eb9d9d --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product.php @@ -0,0 +1,159 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product extends Mage_XmlConnect_Block_Catalog +{ + + /** + * Retrieve product attributes as xml object + * + * @param Mage_Catalog_Model_Product $product + * @param string $itemNodeName + * + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function productToXmlObject(Mage_Catalog_Model_Product $product, $itemNodeName = 'item') + { + $item = new Mage_XmlConnect_Model_Simplexml_Element('<' . $itemNodeName . '>'); + if ($product && $product->getId()) { + $item->addChild('entity_id', $product->getId()); + $item->addChild('name', $item->xmlentities(strip_tags($product->getName()))); + $item->addChild('entity_type', $product->getTypeId()); + $item->addChild('short_description', $item->xmlentities(strip_tags($product->getShortDescription()))); + $item->addChild('description', Mage::helper('xmlconnect')->htmlize($item->xmlentities($product->getDescription()))); + + if ($itemNodeName == 'item') { + $imageToResize = Mage::helper('xmlconnect/image')->getImageSizeForContent('product_small'); + $propertyToResizeName = 'small_image'; + } else { + $imageToResize = Mage::helper('xmlconnect/image')->getImageSizeForContent('product_big'); + $propertyToResizeName = 'image'; + } + + + $icon = clone Mage::helper('catalog/image')->init($product, $propertyToResizeName) + ->resize($imageToResize); + + $iconXml = $item->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + + $item->addChild('in_stock', (int)$product->isSalable()); + $item->addChild('is_salable', (int)$product->isSalable()); + /** + * By default all products has gallery (because of collection not load gallery attribute) + */ + $hasGallery = 1; + if ($product->getMediaGalleryImages()) { + $hasGallery = sizeof($product->getMediaGalleryImages()) > 0 ? 1 : 0; + } + $item->addChild('has_gallery', $hasGallery); + /** + * If product type is grouped than it has options as its grouped items + */ + if ($product->getTypeId() == Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE) { + $product->setHasOptions(true); + } + $item->addChild('has_options', (int)$product->getHasOptions()); + + if ($minSaleQty = $this->_getMinimalQty($product)) { + $item->addChild('min_sale_qty', (int) $minSaleQty); + } + + if (!$product->getRatingSummary()) { + Mage::getModel('review/review') + ->getEntitySummary($product, Mage::app()->getStore()->getId()); + } + + $item->addChild('rating_summary', round((int)$product->getRatingSummary()->getRatingSummary() / 10)); + $item->addChild('reviews_count', $product->getRatingSummary()->getReviewsCount()); + + if ($this->getChild('product_price')) { + $this->getChild('product_price')->setProduct($product) + ->setProductXmlObj($item) + ->collectProductPrices(); + } + + if ($this->getChild('additional_info')) { + $this->getChild('additional_info')->addAdditionalData($product, $item); + } + } + + return $item; + } + + /** + * Get MinSaleQty for product + * + * @param Mage_Catalog_Model_Product $product + * @return int|null + */ + protected function _getMinimalQty($product) + { + if ($stockItem = $product->getStockItem()) { + return ($stockItem->getMinSaleQty() && $stockItem->getMinSaleQty() > 0 ? $stockItem->getMinSaleQty() * 1 : null); + } + return null; + } + /** + * Render product info xml + * + * @return string + */ + protected function _toHtml() + { + $product = Mage::getModel('catalog/product') + ->setStoreId(Mage::app()->getStore()->getId()) + ->load($this->getRequest()->getParam('id', 0)); + + if (!$product) { + throw new Mage_Core_Exception(Mage::helper('xmlconnect')->__('Selected product is unavailable.')); + } else { + $this->setProduct($product); + $productXmlObj = $this->productToXmlObject($product, 'product'); + + $relatedProductsBlock = $this->getChild('related_products'); + if ($relatedProductsBlock) { + $relatedXmlObj = $relatedProductsBlock->getRelatedProductsXmlObj(); + $productXmlObj->appendChild($relatedXmlObj); + } + } + return $productXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Attributes.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Attributes.php new file mode 100644 index 0000000000..ec1d5c17a2 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Attributes.php @@ -0,0 +1,61 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Attributes extends Mage_Catalog_Block_Product_View_Attributes +{ + + /** + * Add additional information (attributes) to current product xml object + * + * @param Mage_Catalog_Model_Product $product + * @param Mage_XmlConnect_Model_Simplexml_Element $productXmlObject + * + */ + public function addAdditionalData(Mage_Catalog_Model_Product $product, Mage_XmlConnect_Model_Simplexml_Element $productXmlObject) + { + if ($product && $productXmlObject && $product->getId()) { + $this->_product = $product; + $additionalData = $this->getAdditionalData(); + if (!empty($additionalData)) { + $attributesXmlObj = $productXmlObject->addChild('additional_attributes'); + foreach ($additionalData as $data) { + $_attrXmlObject = $attributesXmlObj->addChild('item'); + $_attrXmlObject->addChild('label', $this->htmlEscape(Mage::helper('xmlconnect')->__($data['label']))); + $_attrXmlObject->addChild('value', Mage::helper('catalog/output')->productAttribute($product, $data['value'], $data['code'])); + } + } + } + + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Gallery.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Gallery.php new file mode 100644 index 0000000000..09d083baae --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Gallery.php @@ -0,0 +1,91 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Gallery extends Mage_XmlConnect_Block_Catalog +{ + + /** + * Generate images gallery xml + * + * @return string + */ + protected function _toHtml() + { + $productId = $this->getRequest()->getParam('id', null); + $product = Mage::getModel('catalog/product') + ->setStoreId(Mage::app()->getStore()->getId()) + ->load($productId); + $collection = $product->getMediaGalleryImages(); + + $imagesNode = new Mage_XmlConnect_Model_Simplexml_Element(''); + $helper = $this->helper('catalog/image'); + + foreach ($collection as $item) { + $imageNode = $imagesNode->addChild('image'); + + /** + * Big image + */ + $bigImage = $helper->init($product, 'image', $item->getFile()) + ->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('product_gallery_big')); + + $fileNode = $imageNode->addChild('file'); + $fileNode->addAttribute('type', 'big'); + $fileNode->addAttribute('url', $bigImage); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $bigImage); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $fileNode->addAttribute('id', ($id = $item->getId()) ? (int) $id : 0); + $fileNode->addAttribute('modification_time', filemtime($file)); + + /** + * Small image + */ + $smallImage = $helper->init($product, 'thumbnail', $item->getFile()) + ->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('product_gallery_small')); + + $fileNode = $imageNode->addChild('file'); + $fileNode->addAttribute('type', 'small'); + $fileNode->addAttribute('url', $smallImage); + + $path = str_replace($baseUrl, '', $smallImage); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $fileNode->addAttribute('modification_time', filemtime($file)); + } + return $imagesNode->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/List.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/List.php new file mode 100644 index 0000000000..14071ebbfb --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/List.php @@ -0,0 +1,204 @@ + + */ +class Mage_XmlConnect_Block_Catalog_Product_List extends Mage_XmlConnect_Block_Catalog_Product +{ + + /** + * Store product collection + * + * @var Mage_Eav_Model_Entity_Collection_Abstract + */ + protected $_productCollection = null; + + /** + * Store collected layered navigation filters whike applying them + * + * @var array + */ + protected $_collectedFilters = array(); + + /** + * Produce products list xml object + * + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function getProductsXmlObject() + { + $productsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $collection = $this->_getProductCollection(); + + if (!$collection) { + return $productsXmlObj; + } + foreach ($collection->getItems() as $product) { + $productXmlObj = $this->productToXmlObject($product); + if ($productXmlObj) { + $productsXmlObj->appendChild($productXmlObj); + } + } + + return $productsXmlObj; + } + + /** + * Getter for collected layered navigation filters + * + * @return array + */ + public function getCollectedFilters() + { + return $this->_collectedFilters; + } + + /** + * Retrieve product collection with all prepared data and limitations + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _getProductCollection() + { + if (is_null($this->_productCollection)) { + $filters = array(); + $request = $this->getRequest(); + $requestParams = $request->getParams(); + $layer = $this->getLayer(); + if (!$layer) { + return null; + } + $category = $this->getCategory(); + if ($category && is_object($category) && $category->getId()) { + $layer->setCurrentCategory($category); + } + + if (!$this->getNeedBlockApplyingFilters()) { + $attributes = $layer->getFilterableAttributes(); + /** + * Apply filters + */ + foreach ($attributes as $attributeItem) { + $attributeCode = $attributeItem->getAttributeCode(); + $filterModel = $this->helper('xmlconnect')->getFilterByKey($attributeCode); + + $filterModel->setLayer($layer) + ->setAttributeModel($attributeItem); + + $filterParam = parent::REQUEST_FILTER_PARAM_REFIX . $attributeCode; + /** + * Set new request var + */ + if (isset($requestParams[$filterParam])) { + $filterModel->setRequestVar($filterParam); + } + $filterModel->apply($request, null); + $filters[] = $filterModel; + } + + /** + * Separately apply and save category filter + */ + $categoryFilter = $this->helper('xmlconnect')->getFilterByKey('category'); + $filterParam = parent::REQUEST_FILTER_PARAM_REFIX . $categoryFilter->getRequestVar(); + $categoryFilter->setLayer($layer) + ->setRequestVar($filterParam) + ->apply($this->getRequest(), null); + $filters[] = $categoryFilter; + + $this->_collectedFilters = $filters; + } + + /** + * Products + */ + $layer = $this->getLayer(); + $collection = $layer->getProductCollection(); + + /** + * Add rating and review summary, image attribute, apply sort params + */ + $this->_prepareCollection($collection); + + /** + * Apply offset and count + */ + $offset = (int)$request->getParam('offset', 0); + $count = (int)$request->getParam('count', 0); + $count = $count <= 0 ? 1 : $count; + if ($offset + $count < $collection->getSize()) { + $this->setHasProductItems(1); + } + $collection->getSelect()->limit($count, $offset); + + $collection->setFlag('require_stock_items', true); + + $this->_productCollection = $collection; + } + return $this->_productCollection; + } + + /** + * Add image attribute and apply sort fields to product collection + * + * @param Mage_Eav_Model_Entity_Collection_Abstract $collection + * @return Mage_XmlConnect_Block_Catalog_Product_List + */ + protected function _prepareCollection($collection) + { + /** + * Apply sort params + */ + $reguest = $this->getRequest(); + foreach ($reguest->getParams() as $key => $value) { + if (0 === strpos($key, parent::REQUEST_SORT_ORDER_PARAM_REFIX)) { + $key = str_replace(parent::REQUEST_SORT_ORDER_PARAM_REFIX, '', $key); + if ($value != 'desc') { + $value = 'asc'; + } + $collection->addAttributeToSort($key, $value); + } + } + $collection->addAttributeToSelect(array('image', 'name', 'description')); + + return $this; + } + + /** + * Render products list xml + * + * @return string + */ + protected function _toHtml() + { + return $this->getProductsXmlObject()->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options.php new file mode 100644 index 0000000000..18c2edf39d --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options.php @@ -0,0 +1,198 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Options extends Mage_XmlConnect_Block_Catalog +{ + + const OPTION_TYPE_SELECT = 'select'; + const OPTION_TYPE_CHECKBOX = 'checkbox'; + const OPTION_TYPE_TEXT = 'text'; + + /** + * Store supported product options xml renderers based on product types + * + * @var array + */ + protected $_renderers = array(); + + /** + * Add new product options renderer + * + * @param string $type + * @param string $renderer + * @return Mage_XmlConnect_Block_Product_Options + */ + public function addRenderer($type, $renderer) + { + if (!isset($this->_renderers[$type])) { + $this->_renderers[$type] = $renderer; + } + return $this; + } + + /** + * Create produc custom options Mage_XmlConnect_Model_Simplexml_Element object + * + * @param Mage_Catalog_Model_Product $product + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function getProductCustomOptionsXmlObject(Mage_Catalog_Model_Product $product) + { + $xmlModel = new Mage_XmlConnect_Model_Simplexml_Element(''); + $optionsNode = $xmlModel->addChild('options'); + + if (!$product->getId()) { + return $xmlModel; + } + $xmlModel->addAttribute('id', $product->getId()); + if (!$product->isSaleable() || !sizeof($product->getOptions())) { + return $xmlModel; + } + + + foreach ($product->getOptions() as $option) { + $optionNode = $optionsNode->addChild('option'); + $type = $this->_getOptionTypeForXmlByRealType($option->getType()); + $code = 'options[' . $option->getId() . ']'; + if ($type == self::OPTION_TYPE_CHECKBOX) { + $code .= '[]'; + } + $optionNode->addAttribute('code', $code); + $optionNode->addAttribute('type', $type); + $optionNode->addAttribute('label', $xmlModel->xmlentities(strip_tags($option->getTitle()))); + if ($option->getIsRequire()) { + $optionNode->addAttribute('is_required', 1); + } + + /** + * Process option price + */ + $price = Mage::helper('xmlconnect')->formatPriceForXml($option->getPrice()); + if ($price > 0.00) { + $optionNode->addAttribute('price', $price); + $formatedPrice = Mage::app()->getStore($product->getStoreId())->formatPrice($price, false); + $optionNode->addAttribute('formated_price', $formatedPrice); + } + if ($type == self::OPTION_TYPE_CHECKBOX || + $type == self::OPTION_TYPE_SELECT) { + foreach ($option->getValues() as $value) { + $valueNode = $optionNode->addChild('value'); + $valueNode->addAttribute('code', $value->getId()); + $valueNode->addAttribute('label', $xmlModel->xmlentities(strip_tags($value->getTitle()))); + + $price = Mage::helper('xmlconnect')->formatPriceForXml($value->getPrice()); + if ($price > 0.00) { + $valueNode->addAttribute('price', $price); + $formatedPrice = $this->_formatPriceString($price, $product); + $valueNode->addAttribute('formated_price', $formatedPrice); + } + } + } + } + return $xmlModel; + } + + /** + * Format price with currency code and taxes + * + * @param string|int|float $price + * @param Mage_Catalog_Model_Product $product + * @return string + */ + protected function _formatPriceString($price, $product) + { + $priceTax = Mage::helper('tax')->getPrice($product, $price); + $priceIncTax = Mage::helper('tax')->getPrice($product, $price, true); + + if (Mage::helper('tax')->displayBothPrices() && $priceTax != $priceIncTax) { + $formated = Mage::helper('core')->currency($priceTax, true, false); + $formated .= ' (+'.Mage::helper('core')->currency($priceIncTax, true, false) . ' ' . Mage::helper('tax')->__('Incl. Tax').')'; + } else { + $formated = $this->helper('core')->currency($priceTax, true, false); + } + + return $formated; + } + + /** + * Retrieve option type name by specified option real type name + * + * @param string $realType + * @return string + */ + protected function _getOptionTypeForXmlByRealType($realType) + { + switch ($realType) { + case Mage_Catalog_Model_Product_Option::OPTION_TYPE_DROP_DOWN: + case Mage_Catalog_Model_Product_Option::OPTION_TYPE_RADIO: + $type = self::OPTION_TYPE_SELECT; + break; + case Mage_Catalog_Model_Product_Option::OPTION_TYPE_MULTIPLE: + case Mage_Catalog_Model_Product_Option::OPTION_TYPE_CHECKBOX: + $type = self::OPTION_TYPE_CHECKBOX; + break; + case Mage_Catalog_Model_Product_Option::OPTION_TYPE_FIELD: + case Mage_Catalog_Model_Product_Option::OPTION_TYPE_AREA: + default: + $type = self::OPTION_TYPE_TEXT; + break; + } + return $type; + } + + /** + * Generate product options xml + * + * @return string + */ + protected function _toHtml() + { + $productId = $this->getRequest()->getParam('id', null); + $product = Mage::getModel('catalog/product')->setStoreId(Mage::app()->getStore()->getId()); + if ($productId) { + $product->load($productId); + } + + if ($product->getId()) { + $type = $product->getTypeId(); + if (isset($this->_renderers[$type])) { + $renderer = $this->getLayout()->createBlock($this->_renderers[$type]); + if ($renderer) { + return $renderer->getProductOptionsXml($product); + } + } + } + return ''; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Bundle.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Bundle.php new file mode 100644 index 0000000000..8f40ecdc50 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Bundle.php @@ -0,0 +1,122 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Options_Bundle extends Mage_XmlConnect_Block_Catalog_Product_Options +{ + /** + * Generate bundle product options xml + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getProductOptionsXml(Mage_Catalog_Model_Product $product) + { + + $xmlModel = $this->getProductCustomOptionsXmlObject($product); + $optionsXmlObj = $xmlModel->options; + $options = array(); + + if (!$product->isSaleable()) { + return $xmlModel->asNiceXml(); + } + + /** + * Bundle options + */ + $product->getTypeInstance(true)->setStoreFilter($product->getStoreId(), $product); + $optionCollection = $product->getTypeInstance(true)->getOptionsCollection($product); + $selectionCollection = $product->getTypeInstance(true)->getSelectionsCollection( + $product->getTypeInstance(true)->getOptionsIds($product), + $product + ); + $bundleOptions = $optionCollection->appendSelections($selectionCollection, false, false); + if (!sizeof($bundleOptions)) { + return $xmlModel->asNiceXml(); + } + + foreach ($bundleOptions as $_option) { + $selections = $_option->getSelections(); + if (empty($selections)) { + continue; + } + + $optionNode = $optionsXmlObj->addChild('option'); + + $type = parent::OPTION_TYPE_SELECT; + if ($_option->isMultiSelection()) { + $type = parent::OPTION_TYPE_CHECKBOX; + } + $code = 'bundle_option[' . $_option->getId() . ']'; + if ($type == parent::OPTION_TYPE_CHECKBOX) { + $code .= '[]'; + } + $optionNode->addAttribute('code', $code); + $optionNode->addAttribute('type', $type); + $optionNode->addAttribute('label', $optionsXmlObj->xmlentities(strip_tags($_option->getTitle()))); + if ($_option->getRequired()) { + $optionNode->addAttribute('is_required', 1); + } + +// $_default = $_option->getDefaultSelection(); + + foreach ($selections as $_selection) { + if (!$_selection->isSaleable()) { + continue; + } + $_qty = !($_selection->getSelectionQty() * 1) ? '1' : $_selection->getSelectionQty() * 1; + + $valueNode = $optionNode->addChild('value'); + $valueNode->addAttribute('code', $_selection->getSelectionId()); + $valueNode->addAttribute('label', $optionsXmlObj->xmlentities(strip_tags($_selection->getName()))); + if (!$_option->isMultiSelection()) { + if ($_selection->getSelectionCanChangeQty()) { + $valueNode->addAttribute('is_qty_editable', 1); + } + } + $valueNode->addAttribute('qty', $_qty); + + $price = $product->getPriceModel()->getSelectionPreFinalPrice($product, $_selection); + $price = Mage::helper('xmlconnect')->formatPriceForXml($price); + if ($price > 0.00) { + $valueNode->addAttribute('price', $price); + $valueNode->addAttribute('formated_price', $this->_formatPriceString($price, $product)); + } + +// $_selection->getIsDefault(); + } + } + + return $xmlModel->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Configurable.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Configurable.php new file mode 100644 index 0000000000..012dd6102b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Configurable.php @@ -0,0 +1,219 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Options_Configurable extends Mage_XmlConnect_Block_Catalog_Product_Options +{ + /** + * Generate bundle product options xml + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getProductOptionsXml(Mage_Catalog_Model_Product $product) + { + $xmlModel = $this->getProductCustomOptionsXmlObject($product); + $optionsXmlObj = $xmlModel->options; + $options = array(); + + if (!$product->isSaleable()) { + return $xmlModel->asNiceXml(); + } + + /** + * Configurable attributes + */ + $_attributes = $product->getTypeInstance(true)->getConfigurableAttributes($product); + if (!sizeof($_attributes)) { + return $xmlModel->asNiceXml(); + } + + $_allowProducts = array(); + $_allProducts = $product->getTypeInstance(true)->getUsedProducts(null, $product); + foreach ($_allProducts as $_product) { + if ($_product->isSaleable()) { + $_allowProducts[] = $_product; + } + } + + /** + * Allowed products options + */ + foreach ($_allowProducts as $_item) { + $_productId = $_item->getId(); + + foreach ($_attributes as $attribute) { + $productAttribute = $attribute->getProductAttribute(); + $attributeValue = $_item->getData($productAttribute->getAttributeCode()); + if (!isset($options[$productAttribute->getId()])) { + $options[$productAttribute->getId()] = array(); + } + + if (!isset($options[$productAttribute->getId()][$attributeValue])) { + $options[$productAttribute->getId()][$attributeValue] = array(); + } + $options[$productAttribute->getId()][$attributeValue][] = $_productId; + } + } + + foreach ($_attributes as $attribute) { + $productAttribute = $attribute->getProductAttribute(); + $attributeId = $productAttribute->getId(); + $info = array( + 'id' => $productAttribute->getId(), + 'label' => $attribute->getLabel(), + 'options' => array() + ); + + $prices = $attribute->getPrices(); + if (is_array($prices)) { + foreach ($prices as $value) { + if (!isset($options[$attributeId][$value['value_index']])) { + continue; + } + $price = Mage::helper('xmlconnect')->formatPriceForXml($this->_preparePrice($product, $value['pricing_value'], $value['is_percent'])); + $info['options'][] = array( + 'id' => $value['value_index'], + 'label' => $value['label'], + 'price' => $price, + 'formated_price' => $this->_formatPriceString($price, $product), + 'products' => isset($options[$attributeId][$value['value_index']]) ? $options[$attributeId][$value['value_index']] : array(), + ); + } + } + + if (sizeof($info['options']) > 0) { + $attributes[$attributeId] = $info; + } + } + + $isFirst = true; + + $_attributes = $attributes; + reset($_attributes); + foreach ($attributes as $id => $attribute) { + $optionNode = $optionsXmlObj->addChild('option'); + $optionNode->addAttribute('code', 'super_attribute[' . $id . ']'); + $optionNode->addAttribute('type', 'select'); + $optionNode->addAttribute('label', strip_tags($attribute['label'])); + $optionNode->addAttribute('is_required', 1); + if ($isFirst) { + foreach ($attribute['options'] as $option) { + $valueNode = $optionNode->addChild('value'); + $valueNode->addAttribute('code', $option['id']); + $valueNode->addAttribute('label', $optionsXmlObj->xmlentities(strip_tags($option['label']))); + if ($option['price'] > 0.00) { + $valueNode->addAttribute('price', $option['price']); + $valueNode->addAttribute('formated_price', $option['formated_price']); + } + if (sizeof($_attributes) > 1) { + $this->_prepareRecursivelyRelatedValues($valueNode, $_attributes, $option['products'], 1); + } + } + $isFirst = false; + } + } + + return $xmlModel->asNiceXml(); + } + + /** + * Add recursively relations on each option + * + * @param &Mage_XmlConnect_Model_Simplexml_Element &$valueNode value node object + * @param array $attributes all products attributes (options) + * @param array $productIds prodcuts to search in next levels attributes + * @param int $cycle + */ + protected function _prepareRecursivelyRelatedValues(&$valueNode, $attributes, $productIds, $cycle = 1) + { + $relatedNode = null; + + for ($i = 0; $i < $cycle; $i++) { + next($attributes); + } + $attribute = current($attributes); + $attrId = key($attributes); + foreach ($attribute['options'] as $option) { + /** + * Search products in option + */ + $intersect = array_intersect($productIds, $option['products']); + + if (empty($intersect)) { + continue; + } + + if ($relatedNode === null) { + $relatedNode = $valueNode->addChild('relation'); + $relatedNode->addAttribute('to', 'super_attribute[' . $attrId . ']'); + } + + $_valueNode = $relatedNode->addChild('value'); + $_valueNode->addAttribute('code', $option['id']); + $_valueNode->addAttribute('label', $_valueNode->xmlentities(strip_tags($option['label']))); + if ($option['price'] > 0.00) { + $_valueNode->addAttribute('price', $option['price']); + $_valueNode->addAttribute('formated_price', $option['formated_price']); + } + + /** + * Recursive relation adding + */ + $_attrClone = $attributes; + if (next($_attrClone) != false) { + reset($_attrClone); + $this->_prepareRecursivelyRelatedValues($_valueNode, $_attrClone, $intersect, $cycle + 1); + } + } + } + + /** + * Prepare price accordingly to percentage and store rates and round its + * + * @param Mage_Catalog_Model_Product $product + * @param float|int|string $price + * @param unknown_type $isPercent + * @return float + */ + protected function _preparePrice($product, $price, $isPercent = false) + { + if ($isPercent && !empty($price)) { + $price = $product->getFinalPrice() * $price / 100; + } + + $price = Mage::app()->getStore()->convertPrice($price); + $price = Mage::app()->getStore()->roundPrice($price); + return $price; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Grouped.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Grouped.php new file mode 100644 index 0000000000..fed7957b6d --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Grouped.php @@ -0,0 +1,92 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Options_Grouped extends Mage_XmlConnect_Block_Catalog_Product_Options +{ + /** + * Generate bundle product options xml + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getProductOptionsXml(Mage_Catalog_Model_Product $product) + { + $xmlModel = new Mage_XmlConnect_Model_Simplexml_Element(''); + $optionsNode = $xmlModel->addChild('options'); + + if (!$product->getId()) { + return $xmlModel->asNiceXml(); + } + $xmlModel->addAttribute('id', $product->getId()); + if (!$product->isSaleable()) { + return $xmlModel->asNiceXml(); + } + /** + * Grouped (associated) products + */ + $_associatedProducts = $product->getTypeInstance(true)->getAssociatedProducts($product); + if (!sizeof($_associatedProducts)) { + return $xmlModel->asNiceXml(); + } + foreach ($_associatedProducts as $_item) { + if (!$_item->isSaleable()) { + continue; + } + $optionNode = $optionsNode->addChild('option'); + + $optionNode->addAttribute('code', 'super_group[' . $_item->getId() . ']'); + $optionNode->addAttribute('type', 'product'); + $optionNode->addAttribute('label', $xmlModel->xmlentities(strip_tags($_item->getName()))); + $optionNode->addAttribute('is_qty_editable', 1); + $optionNode->addAttribute('qty', $_item->getQty()*1); + + + /** + * Process product price + */ + if ($_item->getPrice() != $_item->getFinalPrice()) { + $productPrice = $_item->getFinalPrice(); + } else { + $productPrice = $_item->getPrice(); + } + $productPrice = Mage::helper('xmlconnect')->formatPriceForXml($productPrice); + if ($productPrice > 0.00) { + $optionNode->addAttribute('price', $productPrice); + $optionNode->addAttribute('formated_price', $this->_formatPriceString($productPrice, $product)); + } + } + + return $xmlModel->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Simple.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Simple.php new file mode 100644 index 0000000000..abda11053c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Simple.php @@ -0,0 +1,47 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Options_Simple extends Mage_XmlConnect_Block_Catalog_Product_Options +{ + /** + * Generate simple product options xml + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getProductOptionsXml(Mage_Catalog_Model_Product $product) + { + return $this->getProductCustomOptionsXmlObject($product)->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Virtual.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Virtual.php new file mode 100644 index 0000000000..208ad60a71 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Options/Virtual.php @@ -0,0 +1,47 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Options_Virtual extends Mage_XmlConnect_Block_Catalog_Product_Options +{ + /** + * Generate virtual product options xml + * + * @param Mage_Catalog_Model_Product $product + * @return string + */ + public function getProductOptionsXml(Mage_Catalog_Model_Product $product) + { + return $this->getProductCustomOptionsXmlObject($product)->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price.php new file mode 100644 index 0000000000..551c3ea3fd --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price.php @@ -0,0 +1,100 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Price extends Mage_XmlConnect_Block_Catalog +{ + + /** + * Default product price renderer block factory name + * + * @var string + */ + protected $_defaultPriceRenderer = 'xmlconnect/catalog_product_price_default'; + + /** + * Store supported product price xml renderers based on product types + * + * @var array + */ + protected $_renderers = array(); + + /** + * Store already initialized renderers instances + * + * @var array + */ + protected $_renderersInstances = array(); + + /** + * Add new product price renderer + * + * @param string $type + * @param string $renderer + * @return Mage_XmlConnect_Block_Product_Options + */ + public function addRenderer($type, $renderer) + { + if (!isset($this->_renderers[$type])) { + $this->_renderers[$type] = $renderer; + } + return $this; + } + + /** + * Collect product prices to current xml object + */ + public function collectProductPrices() + { + $product = $this->getProduct(); + $xmlObject = $this->getProductXmlObj(); + + if ($product && $product->getId()) { + $type = $product->getTypeId(); + if (isset($this->_renderers[$type])) { + $blockName = $this->_renderers[$type]; + } else { + $blockName = $this->_defaultPriceRenderer; + } + + $renderer = $this->getLayout()->getBlock($blockName); + if (!$renderer) { + $renderer = $this->getLayout()->createBlock($blockName); + } + + if ($renderer) { + $renderer->collectProductPrices($product, $xmlObject); + } + } + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price/Bundle.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price/Bundle.php new file mode 100644 index 0000000000..e2c32d67bb --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price/Bundle.php @@ -0,0 +1,308 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Price_Bundle extends Mage_Bundle_Block_Catalog_Product_Price +{ + /** + * Collect product prices to specified item xml object + * + * @param Mage_Catalog_Model_Product $product + * @param Mage_XmlConnect_Model_Simplexml_Element $item + */ + public function collectProductPrices(Mage_Catalog_Model_Product $product, Mage_XmlConnect_Model_Simplexml_Element $item) + { + $this->setProduct($product) + ->setDisplayMinimalPrice(true) + ->setUseLinkForAsLowAs(false); + + $priceXmlObj = $item->addChild('price'); + + $_coreHelper = $this->helper('core'); + $_weeeHelper = $this->helper('weee'); + $_taxHelper = $this->helper('tax'); + /* @var $_coreHelper Mage_Core_Helper_Data */ + /* @var $_weeeHelper Mage_Weee_Helper_Data */ + /* @var $_taxHelper Mage_Tax_Helper_Data */ + + $_tierPrices = $this->_getTierPrices($product); + + if (count($_tierPrices) > 0) { + $tierPricesTextArray = array(); + foreach ($_tierPrices as $_price) { + $tierPricesTextArray[] = Mage::helper('catalog')->__('Buy %1$s with %2$s discount each', $_price['price_qty'], ' '.($_price['price']*1).'%'); + } + $item->addChild('price_tier', implode("\n", $tierPricesTextArray)); + } + + + list($_minimalPrice, $_maximalPrice) = $product->getPriceModel()->getPrices($product); + $_id = $product->getId(); + + $_weeeTaxAmount = 0; + + $_minimalPriceTax = $_taxHelper->getPrice($product, $_minimalPrice); + $_minimalPriceInclTax = $_taxHelper->getPrice($product, $_minimalPrice, true); + + if ($product->getPriceType() == 1) { + $_weeeTaxAmount = $_weeeHelper->getAmount($product); + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, array(0, 1, 4))) { + $_minimalPriceTax += $_weeeTaxAmount; + $_minimalPriceInclTax += $_weeeTaxAmount; + } + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 2)) { + $_minimalPriceInclTax += $_weeeTaxAmount; + } + + if ($_weeeHelper->typeOfDisplay($product, array(1,2,4))) { + $_weeeTaxAttributes = $_weeeHelper->getProductWeeeAttributesForDisplay($product); + } + } + + if ($product->getPriceView()) { + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('as_low_as_excluding_tax', $_coreHelper->currency($_minimalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + $priceXmlObj->addAttribute('as_low_as_including_tax', $_coreHelper->currency($_minimalPriceInclTax, true, false)); + } else { + $priceXmlObj->addAttribute('as_low_as', $_coreHelper->currency($_minimalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + if ($_weeeHelper->typeOfDisplay($product, 2) && $_weeeTaxAmount) { + $priceXmlObj->addAttribute('as_low_as_including_tax', $_coreHelper->currency($_minimalPriceInclTax, true, false)); + } + } + /** + * if ($product->getPriceView()) { + */ + } else { + if ($_minimalPrice <> $_maximalPrice) { + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('from_excluding_tax', $_coreHelper->currency($_minimalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('from_weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + $priceXmlObj->addAttribute('from_including_tax', $_coreHelper->currency($_minimalPriceInclTax, true, false)); + } else { + $priceXmlObj->addAttribute('from', $_coreHelper->currency($_minimalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('from_weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + if ($_weeeHelper->typeOfDisplay($product, 2) && $_weeeTaxAmount) { + $priceXmlObj->addAttribute('from_including_tax', $_coreHelper->currency($_minimalPriceInclTax, true, false)); + } + } + + $_maximalPriceTax = Mage::helper('tax')->getPrice($product, $_maximalPrice); + $_maximalPriceInclTax = Mage::helper('tax')->getPrice($product, $_maximalPrice, true); + + if ($product->getPriceType() == 1) { + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, array(0, 1, 4))) { + $_maximalPriceTax += $_weeeTaxAmount; + $_maximalPriceInclTax += $_weeeTaxAmount; + } + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 2)) { + $_maximalPriceInclTax += $_weeeTaxAmount; + } + } + + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('to_excluding_tax', $_coreHelper->currency($_maximalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('to_weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + $priceXmlObj->addAttribute('to_including_tax', $_coreHelper->currency($_maximalPriceInclTax, true, false)); + } else { + $priceXmlObj->addAttribute('to', $_coreHelper->currency($_maximalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('to_weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + if ($_weeeHelper->typeOfDisplay($product, 2) && $_weeeTaxAmount) { + $priceXmlObj->addAttribute('to_including_tax', $_coreHelper->currency($_maximalPriceInclTax, true, false)); + } + } + /** + * if ($_minimalPrice <> $_maximalPrice) { + */ + } else { + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('excluding_tax', $_coreHelper->currency($_minimalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_minimalPriceInclTax, true, false)); + } else { + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_minimalPriceTax, true, false)); + if ($_weeeTaxAmount && $product->getPriceType() == 1 && $_weeeHelper->typeOfDisplay($product, array(2, 1, 4))) { + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + if ($_weeeHelper->typeOfDisplay($product, array(2, 4))) { + $amount = $_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(); + } else { + $amount = $_weeeTaxAttribute->getAmount(); + } + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($amount, true, false)); + } + } + if ($_weeeHelper->typeOfDisplay($product, 2) && $_weeeTaxAmount) { + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_minimalPriceInclTax, true, false)); + } + } + } + } + } + + /** + * Get tier prices (formatted) + * + * @param Mage_Catalog_Model_Product $product + * @return array + */ + protected function _getTierPrices($product) + { + if (is_null($product)) { + return array(); + } + $prices = $product->getFormatedTierPrice(); + + $res = array(); + if (is_array($prices)) { + foreach ($prices as $price) { + $price['price_qty'] = $price['price_qty']*1; + $price['savePercent'] = ceil(100 - $price['price'] ); + $price['formated_price'] = Mage::app()->getStore()->formatPrice( + Mage::app()->getStore()->convertPrice( + Mage::helper('tax')->getPrice($product, $price['website_price'])), + false); + $price['formated_price_incl_tax'] = Mage::app()->getStore()->formatPrice( + Mage::app()->getStore()->convertPrice( + Mage::helper('tax')->getPrice($product, $price['website_price'], true)), + false); + $res[] = $price; + } + } + + return $res; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price/Default.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price/Default.php new file mode 100644 index 0000000000..54a500fe44 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Price/Default.php @@ -0,0 +1,495 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Price_Default extends Mage_Catalog_Block_Product_Price +{ + /** + * Collect product prices to specified item xml object + * + * @param Mage_Catalog_Model_Product $product + * @param Mage_XmlConnect_Model_Simplexml_Element $item + */ + public function collectProductPrices(Mage_Catalog_Model_Product $product, Mage_XmlConnect_Model_Simplexml_Element $item) + { + $this->setProduct($product) + ->setDisplayMinimalPrice(true) + ->setUseLinkForAsLowAs(false); + + $priceXmlObj = $item->addChild('price'); + $_tierPrices = $this->_getTierPrices($product); + if (count($_tierPrices) > 0) { + $tierPricesTextArray = array(); + $tierPricesTextArray = $this->_getTierPricesTextArray($_tierPrices, $product); + $item->addChild('price_tier', implode("\n", $tierPricesTextArray)); + } + + $_coreHelper = $this->helper('core'); + $_weeeHelper = $this->helper('weee'); + $_taxHelper = $this->helper('tax'); + + /* @var $_coreHelper Mage_Core_Helper_Data */ + /* @var $_weeeHelper Mage_Weee_Helper_Data */ + /* @var $_taxHelper Mage_Tax_Helper_Data */ + + $_id = $product->getId(); + $_weeeSeparator = ''; + $_simplePricesTax = ($_taxHelper->displayPriceIncludingTax() || $_taxHelper->displayBothPrices()); + $_minimalPriceValue = $product->getMinimalPrice(); + $_minimalPrice = $_taxHelper->getPrice($product, $_minimalPriceValue, $_simplePricesTax); + + if (!$product->isGrouped()) { + $_weeeTaxAmount = $_weeeHelper->getAmountForDisplay($product); + if ($_weeeHelper->typeOfDisplay($product, array(1,2,4))) { + $_weeeTaxAmount = $_weeeHelper->getAmount($product); + $_weeeTaxAttributes = $_weeeHelper->getProductWeeeAttributesForDisplay($product); + } + + $_price = $_taxHelper->getPrice($product, $product->getPrice()); + $_regularPrice = $_taxHelper->getPrice($product, $product->getPrice(), $_simplePricesTax); + $_finalPrice = $_taxHelper->getPrice($product, $product->getFinalPrice()); + $_finalPriceInclTax = $_taxHelper->getPrice($product, $product->getFinalPrice(), true); + $_weeeDisplayType = $_weeeHelper->getPriceDisplayType(); + if ($_finalPrice == $_price) { + if ($_taxHelper->displayBothPrices()) { + /** + * Including + */ + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 0)) { + $priceXmlObj->addAttribute('excluding_tax', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 1)) { + /** + * Including + Weee + */ + $priceXmlObj->addAttribute('excluding_tax', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount(), true, false)); + } + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 4)) { + /** + * Including + Weee + */ + $priceXmlObj->addAttribute('excluding_tax', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(), true, false)); + } + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 2)) { + /** + * Excluding + Weee + Final + */ + $priceXmlObj->addAttribute('excluding_tax', $_coreHelper->currency($_price, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount(), true, false)); + } + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + } else { + $priceXmlObj->addAttribute('excluding_tax', $_coreHelper->currency($_price, true, false)); + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_finalPriceInclTax, true, false)); + } + /** + * if ($_taxHelper->displayBothPrices()) { + */ + } else { + /** + * Including + */ + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 0)) { + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 1)) { + /** + * Including + Weee + */ + + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount(), true, false)); + } + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 4)) { + /** + * Including + Weee + */ + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(), true, false)); + } + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 2)) { + /** + * Excluding + Weee + Final + */ + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_price, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount(), true, false)); + } + $priceXmlObj->addAttribute('including_tax', $_coreHelper->currency($_price + $_weeeTaxAmount, true, false)); + } else { + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_price, true, false)); + } + } + /** + * if ($_finalPrice == $_price) { + */ + } else { + $_originalWeeeTaxAmount = $_weeeHelper->getOriginalAmount($product); + /** + * Including + */ + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 0)) { + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_regularPrice + $_originalWeeeTaxAmount, true, false)); + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('special_excluding_tax', $_coreHelper->currency($_finalPrice + $_weeeTaxAmount, true, false)); + $priceXmlObj->addAttribute('special_including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + } else { + $priceXmlObj->addAttribute('special', $_coreHelper->currency($_finalPrice + $_weeeTaxAmount, true, false)); + } + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 1)) { + /** + * Including + Weee + */ + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_regularPrice + $_originalWeeeTaxAmount, true, false)); + $priceXmlObj->addAttribute('special_excluding_tax', $_coreHelper->currency($_finalPrice + $_weeeTaxAmount, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount(), true, false)); + } + $priceXmlObj->addAttribute('special_including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 4)) { + /** + * Including + Weee + */ + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_regularPrice + $_originalWeeeTaxAmount, true, false)); + $priceXmlObj->addAttribute('special_excluding_tax', $_coreHelper->currency($_finalPrice + $_weeeTaxAmount, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + $_weeeSeparator = ' + '; + $weeeXmlObj->addAttribute('separator', $_weeeSeparator); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount() + $_weeeTaxAttribute->getTaxAmount(), true, false)); + } + $priceXmlObj->addAttribute('special_including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + } elseif ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, 2)) { + /** + * Excluding + Weee + Final + */ + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_regularPrice, true, false)); + $priceXmlObj->addAttribute('special_excluding_tax', $_coreHelper->currency($_finalPrice, true, false)); + $weeeXmlObj = $priceXmlObj->addChild('weee'); + foreach ($_weeeTaxAttributes as $_weeeTaxAttribute) { + $weeeItemXmlObj = $weeeXmlObj->addChild('item'); + $weeeItemXmlObj->addAttribute('name', $weeeItemXmlObj->xmlentities(strip_tags($_weeeTaxAttribute->getName()))); + $weeeItemXmlObj->addAttribute('amount', $_coreHelper->currency($_weeeTaxAttribute->getAmount(), true, false)); + } + $priceXmlObj->addAttribute('special_including_tax', $_coreHelper->currency($_finalPriceInclTax + $_weeeTaxAmount, true, false)); + } else { + /** + * Excluding + */ + $priceXmlObj->addAttribute('regular', $_coreHelper->currency($_regularPrice, true, false)); + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('special_excluding_tax', $_coreHelper->currency($_finalPrice, true, false)); + $priceXmlObj->addAttribute('special_including_tax', $_coreHelper->currency($_finalPriceInclTax, true, false)); + } else { + $priceXmlObj->addAttribute('special', $_coreHelper->currency($_finalPrice, true, false)); + } + } + } + + if ($this->getDisplayMinimalPrice() && $_minimalPriceValue && $_minimalPriceValue < $product->getFinalPrice()) { + $_minimalPriceDisplayValue = $_minimalPrice; + if ($_weeeTaxAmount && $_weeeHelper->typeOfDisplay($product, array(0, 1, 4))) { + $_minimalPriceDisplayValue = $_minimalPrice + $_weeeTaxAmount; + } + + if (!$this->getUseLinkForAsLowAs()) { + $priceXmlObj->addAttribute('as_low_as', $_coreHelper->currency($_minimalPriceDisplayValue, true, false)); + } + } + /** + * if (!$product->isGrouped()) { + */ + } else { + $_exclTax = $_taxHelper->getPrice($product, $_minimalPriceValue, $includingTax = null); + $_inclTax = $_taxHelper->getPrice($product, $_minimalPriceValue, $includingTax = true); + + if ($this->getDisplayMinimalPrice() && $_minimalPriceValue) { + if ($_taxHelper->displayBothPrices()) { + $priceXmlObj->addAttribute('starting_at_excluding_tax', $_coreHelper->currency($_exclTax, true, false)); + $priceXmlObj->addAttribute('starting_at_including_tax', $_coreHelper->currency($_inclTax, true, false)); + } else { + $_showPrice = $_inclTax; + if (!$_taxHelper->displayPriceIncludingTax()) { + $_showPrice = $_exclTax; + } + $priceXmlObj->addAttribute('starting_at', $_coreHelper->currency($_showPrice, true, false)); + } + } + } + } + + /** + * Get tier prices (formatted) + * + * @param Mage_Catalog_Model_Product $product + * @return array + */ + protected function _getTierPrices(Mage_Catalog_Model_Product $product) + { + if (is_null($product)) { + return array(); + } + $prices = $product->getFormatedTierPrice(); + + $res = array(); + if (is_array($prices)) { + foreach ($prices as $price) { + $price['price_qty'] = $price['price_qty']*1; + if ($product->getPrice() != $product->getFinalPrice()) { + if ($price['price']<$product->getFinalPrice()) { + $price['savePercent'] = ceil(100 - (( 100/$product->getFinalPrice() ) * $price['price'] )); + $price['formated_price'] = Mage::app()->getStore()->formatPrice(Mage::app()->getStore()->convertPrice(Mage::helper('tax')->getPrice($product, $price['website_price'])), false); + $price['formated_price_incl_tax'] = Mage::app()->getStore()->formatPrice(Mage::app()->getStore()->convertPrice(Mage::helper('tax')->getPrice($product, $price['website_price'], true)), false); + $res[] = $price; + } + } else { + if ($price['price']<$product->getPrice()) { + $price['savePercent'] = ceil(100 - (( 100/$product->getPrice() ) * $price['price'] )); + $price['formated_price'] = Mage::app()->getStore()->formatPrice(Mage::app()->getStore()->convertPrice(Mage::helper('tax')->getPrice($product, $price['website_price'])), false); + $price['formated_price_incl_tax'] = Mage::app()->getStore()->formatPrice(Mage::app()->getStore()->convertPrice(Mage::helper('tax')->getPrice($product, $price['website_price'], true)), false); + $res[] = $price; + } + } + } + } + + return $res; + } + + /** + * Get tier prices (formatted) as array of strings + * + * @param array $_tierPrices + * @param Mage_Catalog_Model_Product $_product + * + * @return array + */ + protected function _getTierPricesTextArray($_tierPrices, $_product) + { + + $pricesArray = array(); + if (Mage::helper('weee')->typeOfDisplay($_product, array(1, 2, 4))) { + $_weeeTaxAttributes = Mage::helper('weee')->getProductWeeeAttributesForDisplay($_product); + } + + if ($_product->isGrouped()) { + $_tierPrices = $this->getTierPrices($_product); + } + Mage::helper('weee')->processTierPrices($_product, $_tierPrices); + + foreach ($_tierPrices as $_price) { + $s = ''; + if ($this->helper('tax')->displayBothPrices()) { + if (Mage::helper('weee')->typeOfDisplay($_product, 0)) { + $s .= $this->__('Buy %1$s for %2$s (%3$s incl. tax) each', $_price['price_qty'], $_price['formated_price_incl_weee_only'], $_price['formated_price_incl_weee']); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 1)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); + if ($_weeeTaxAttributes) { + $s .= '('; + $s .= $this->__('%1$s incl tax.', $_price['formated_price_incl_weee']); + $separator = ' + '; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $separator; + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount()); + } + $s .= ')'; + } + $s .= $this->__('each'); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 4)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); + if ($_weeeTaxAttributes) { + $s .= '('; + $s .= $this->__('%1$s incl tax.', $_price['formated_price_incl_weee']); + $separator = ' + '; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $separator; + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount() + $_attribute->getTaxAmount()); + } + $s .= ')'; + } + $s .= $this->__('each'); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 2)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price']); + if ($_weeeTaxAttributes) { + $s .= '('; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount()); + } + $s .= $this->__('Total incl. Tax: %1$s', $_price['formated_price_incl_weee']); + $s .= ')'; + } + $s .= $this->__('each'); + } else { + $s .= $this->__('Buy %1$s for %2$s (%3$s incl. tax) each', $_price['price_qty'], $_price['formated_price'], $_price['formated_price_incl_tax']); + } + } else { + if ($this->helper('tax')->displayPriceIncludingTax()) { + if (Mage::helper('weee')->typeOfDisplay($_product, 0)) { + $s .= $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price_incl_weee']); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 1)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee']); + if ($_weeeTaxAttributes) { + $s .= '('; + $separator = ''; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $separator; + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount()); + $separator = ' + '; + } + $s .= ')'; + } + $s .= $this->__('each'); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 4)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee']); + if ($_weeeTaxAttributes) { + $s .= '('; + $separator = ''; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $separator; + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount() + $_attribute->getTaxAmount()); + $separator = ' + '; + } + $s .= ')'; + } + $s .= $this->__('each'); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 2)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_tax']); + if ($_weeeTaxAttributes) { + $s .= '('; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount()); + } + $s .= $this->__('Total incl. Tax: %1$s', $_price['formated_price_incl_weee']); + $s .= ')'; + } + $s .= $this->__('each'); + } else { + $s .= $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price_incl_tax']); + } + } else { + if (Mage::helper('weee')->typeOfDisplay($_product, 0)) { + $s .= $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price_incl_weee_only']); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 1)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); + if ($_weeeTaxAttributes) { + $s .= '('; + $separator = ''; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $separator; + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount()); + $separator = ' + '; + } + $s .= ')'; + } + $s .= $this->__('each'); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 4)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); + if ($_weeeTaxAttributes) { + $s .= '('; + $separator = ''; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $separator; + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount() + $_attribute->getTaxAmount()); + $separator = ' + '; + } + $s .= ')'; + } + $s .= $this->__('each'); + } else if (Mage::helper('weee')->typeOfDisplay($_product, 2)) { + $s .= $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price']); + if ($_weeeTaxAttributes) { + $s .= '('; + foreach ($_weeeTaxAttributes as $_attribute) { + $s .= $_attribute->getName() . ': ' . Mage::helper('core')->currency($_attribute->getAmount()); + } + $s .= $this->__('Total incl. Tax: %1$s', $_price['formated_price_incl_weee_only']); + $s .= ')'; + } + $s .= $this->__('each'); + } else { + $s .= $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price']); + } + } + } + if (!$_product->isGrouped()) { + if (($_product->getPrice() == $_product->getFinalPrice() && $_product->getPrice() > $_price['price']) + || ($_product->getPrice() != $_product->getFinalPrice() && $_product->getFinalPrice() > $_price['price'])) { + $s .= ' ' . $this->__('and') . ' ' . $this->__('save') . ' ' . $_price['savePercent'] . '%'; + } + } + $pricesArray[] = $s; + } + return $pricesArray; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Related.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Related.php new file mode 100644 index 0000000000..cf75ce4f60 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Related.php @@ -0,0 +1,97 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Related extends Mage_XmlConnect_Block_Catalog_Product_List +{ + + /** + * Retrieve related products xml object based on current product + * + * @return Mage_XmlConnect_Model_Simplexml_Element + * @see $this->getProduct() + */ + public function getRelatedProductsXmlObj() + { + $relatedXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + if ($this->getParentBlock()->getProduct()->getId() > 0) { + $collection = $this->_getProductCollection(); + if (!$collection) { + return $relatedXmlObj; + } + foreach ($collection->getItems() as $product) { + $productXmlObj = $this->productToXmlObject($product); + if ($productXmlObj) { + if ($this->getParentBlock()->getChild('product_price')) { + $this->getParentBlock()->getChild('product_price')->setProduct($product) + ->setProductXmlObj($productXmlObj) + ->collectProductPrices(); + } + $relatedXmlObj->appendChild($productXmlObj); + } + } + } + + return $relatedXmlObj; + } + + /** + * Generate related products xml + * + * @return string + */ + protected function _toHtml() + { + return $this->getRelatedProductsXmlObj()->asNiceXml(); + } + + /** + * Retrieve product collection with all prepared data + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _getProductCollection() + { + if (is_null($this->_productCollection)) { + $collection = $this->getParentBlock()->getProduct()->getRelatedProductCollection(); + Mage::getSingleton('catalog/layer')->prepareProductCollection($collection); + /** + * Add rating and review summary, image attribute, apply sort params + */ + $this->_prepareCollection($collection); + + $this->_productCollection = $collection; + } + return $this->_productCollection; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Review.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Review.php new file mode 100644 index 0000000000..c26d593ee1 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Review.php @@ -0,0 +1,89 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Review extends Mage_XmlConnect_Block_Catalog +{ + /** + * Limit to product review text length + */ + const REVIEW_DETAIL_TRUNCATE_LEN = 200; + + /** + * Retrieve review data as xml object + * + * @param Mage_Review_Model_Review $review + * @param string $itemNodeName + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function reviewToXmlObject(Mage_Review_Model_Review $review, $itemNodeName = 'item') + { + $rating = 0; + $item = new Mage_XmlConnect_Model_Simplexml_Element('<' . $itemNodeName . '>'); + if ($review->getId()) { + $item->addChild('review_id', $review->getId()); + $item->addChild('created_at', $review->getCreatedAt()); + $item->addChild('title', $item->xmlentities(strip_tags($review->getTitle()))); + $item->addChild('nickname', $item->xmlentities(strip_tags($review->getNickname()))); + $detail = $item->xmlentities($review->getDetail()); + if ($itemNodeName == 'item') { + $remainder = ''; + $detail = Mage::helper('core/string') + ->truncate($detail, self::REVIEW_DETAIL_TRUNCATE_LEN, '', $remainder, false); + } + $item->addChild('detail', $detail); + + $summary = Mage::getModel('rating/rating')->getReviewSummary($review->getId()); + if ($summary->getCount() > 0) { + $rating = round($summary->getSum() / $summary->getCount() / 10); + } + if ($rating) { + $item->addChild('rating_votes', $rating); + } + + } + return $item; + } + + /** + * Render review xml + * + * @return string + */ + protected function _toHtml() + { + $review = Mage::getModel('review/review')->load((int)$this->getRequest()->getParam('id', 0)); + return $this->reviewToXmlObject($review, 'review')->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Review/List.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Review/List.php new file mode 100644 index 0000000000..324fa83076 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Product/Review/List.php @@ -0,0 +1,113 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Product_Review_List extends Mage_XmlConnect_Block_Catalog_Product_Review +{ + /** + * Store reviews collection + * + * @var Mage_Review_Model_Mysql4_Review_Collection + */ + protected $_reviewCollection = null; + + /** + * Produce reviews list xml object + * + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function getReviewsXmlObject() + { + $reviewsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $collection = $this->_getReviewCollection(); + + if (!$collection) { + return $reviewsXmlObj; + } + foreach ($collection->getItems() as $review) { + $reviewXmlObj = $this->reviewToXmlObject($review); + if ($reviewXmlObj) { + $reviewsXmlObj->appendChild($reviewXmlObj); + } + } + + return $reviewsXmlObj; + } + + /** + * Retrieve reviews collection with all prepared data and limitations + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _getReviewCollection() + { + if (is_null($this->_reviewCollection)) { + $product = $this->getProduct(); + $request = $this->getRequest(); + if (!$product) { + return null; + } + $collection = Mage::getResourceModel('review/review_collection') + ->addEntityFilter('product', $product->getId()) + ->addStoreFilter(Mage::app()->getStore()->getId()) + ->addStatusFilter('approved'); + + /** + * Apply offset and count + */ + $offset = (int)$request->getParam('offset', 0); + $count = (int)$request->getParam('count', 0); + $count = $count <= 0 ? 1 : $count; + $collection->getSelect()->limit($count, $offset); + + $this->_reviewCollection = $collection; + } + return $this->_reviewCollection; + } + + /** + * Render reviews list xml + * + * @return string + */ + protected function _toHtml() + { + $product = Mage::getModel('catalog/product')->load((int)$this->getRequest()->getParam('id', 0)); + if ($product->getId()) { + $this->setProduct($product); + } + + return $this->getReviewsXmlObject()->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Search.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Search.php new file mode 100644 index 0000000000..89a7601c03 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Search.php @@ -0,0 +1,144 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Search extends Mage_XmlConnect_Block_Catalog +{ + /** + * Search results xml renderer + * XML also contains filters that can be apply (accorfingly already applyed filters and search query) + * and sort fields + * + * @return string + */ + protected function _toHtml() + { + $searchXmlObject = new Mage_XmlConnect_Model_Simplexml_Element(''); + $filtersXmlObject = new Mage_XmlConnect_Model_Simplexml_Element(''); + + $helper = Mage::helper('catalogsearch'); + if (method_exists($helper, 'getEngine')) { + $engine = Mage::helper('catalogsearch')->getEngine(); + $isLayeredNavigationAllowed = ($engine instanceof Varien_Object) ? $engine->isLeyeredNavigationAllowed() : true; + } else { + $isLayeredNavigationAllowed = true; + } + + $request = $this->getRequest(); + $requestParams = $request->getParams(); + $hasMoreProductItems = 0; + + /** + * Products + */ + $productListBlock = $this->getChild('product_list'); + if ($productListBlock) { + $layer = Mage::getSingleton('catalogsearch/layer'); + $productsXmlObj = $productListBlock->setLayer($layer) + ->setNeedBlockApplyingFilters(!$isLayeredNavigationAllowed) + ->getProductsXmlObject(); + $searchXmlObject->appendChild($productsXmlObj); + $hasMoreProductItems = (int)$productListBlock->getHasProductItems(); + } + + $searchXmlObject->addAttribute('has_more_items', $hasMoreProductItems); + + /** + * Filters + */ + $showFiltersAndOrders = true; + $reguest = $this->getRequest(); + foreach ($reguest->getParams() as $key => $value) { + if (0 === strpos($key, parent::REQUEST_SORT_ORDER_PARAM_REFIX) || + 0 === strpos($key, parent::REQUEST_FILTER_PARAM_REFIX)) { + $showFiltersAndOrders = false; + break; + } + } + if ($isLayeredNavigationAllowed && $productListBlock && $showFiltersAndOrders) { + $filters = $productListBlock->getCollectedFilters(); + /** + * Render filters xml + */ + foreach ($filters as $filter) { + if (!$this->_isFilterItemsHasValues($filter)) { + continue; + } + $item = $filtersXmlObject->addChild('item'); + $item->addChild('name', $searchXmlObject->xmlentities($filter->getName())); + $item->addChild('code', $filter->getRequestVar()); + $values = $item->addChild('values'); + + foreach ($filter->getItems() as $valueItem) { + $count = (int)$valueItem->getCount(); + if (!$count) { + continue; + } + $value = $values->addChild('value'); + $value->addChild('id', $valueItem->getValueString()); + $value->addChild('label', $searchXmlObject->xmlentities(strip_tags($valueItem->getLabel()))); + $value->addChild('count', $count); + } + } + $searchXmlObject->appendChild($filtersXmlObject); + } + + /** + * Sort fields + */ + if ($showFiltersAndOrders) { + $searchXmlObject->appendChild($this->getProductSortFeildsXmlObject()); + } + + return $searchXmlObject->asNiceXml(); + } + + /** + * Check if items of specified filter have values + * + * @param object $filter filter model + * @return bool + */ + protected function _isFilterItemsHasValues($filter) + { + if (!$filter->getItemsCount()) { + return false; + } + foreach ($filter->getItems() as $valueItem) { + if ((int)$valueItem->getCount()) { + return true; + } + } + return false; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Search/Suggest.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Search/Suggest.php new file mode 100644 index 0000000000..ad1d5f8689 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Catalog/Search/Suggest.php @@ -0,0 +1,73 @@ + + */ + +class Mage_XmlConnect_Block_Catalog_Search_Suggest extends Mage_CatalogSearch_Block_Autocomplete +{ + const SUGGEST_ITEM_SEPARATOR = '::sep::'; + + /** + * Search suggestions xml renderer + * + * @return string + */ + protected function _toHtml() + { + $suggestXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + if (!$this->getRequest()->getParam('q', false)) { + return $suggestXmlObj->asNiceXml(); + } + + $suggestData = $this->getSuggestData(); + if (!($count = count($suggestData))) { + return $suggestXmlObj->asNiceXml(); + } + + $items = ''; + foreach ($suggestData as $index => $item) { + $items .= $suggestXmlObj->xmlentities(strip_tags($item['title'])) + . self::SUGGEST_ITEM_SEPARATOR + . (int)$item['num_of_results'] + . self::SUGGEST_ITEM_SEPARATOR; +// $itemXmlObj = $suggestXmlObj->addChild('item'); +// $itemXmlObj->addChild('title', $suggestXmlObj->xmlentities(strip_tags($item['title']))); +// $itemXmlObj->addChild('count', (int)$item['num_of_results']); + } + + $suggestXmlObj = new Mage_XmlConnect_Model_Simplexml_Element('' . $items . ''); + + return $suggestXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Billing.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Billing.php new file mode 100644 index 0000000000..cb27eaf8b2 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Billing.php @@ -0,0 +1,63 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Address_Billing extends Mage_Checkout_Block_Onepage_Billing +{ + /** + * Render billing addresses xml + * + * @return string + */ + protected function _toHtml() + { + $billingXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + $addressId = $this->getAddress()->getId(); + $address = $this->getCustomer()->getPrimaryBillingAddress(); + if ($address) { + $addressId = $address->getId(); + } + + foreach ($this->getCustomer()->getAddresses() as $address) { + $item = $billingXmlObj->addChild('item'); + if ($addressId == $address->getId()) { + $item->addAttribute('selected', 1); + } + $this->getChild('address_list')->prepareAddressData($address, $item); + $item->addChild('address_line', $billingXmlObj->xmlentities($address->format('oneline'))); + } + + return $billingXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Form.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Form.php new file mode 100644 index 0000000000..9ce3451bfd --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Form.php @@ -0,0 +1,185 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Address_Form extends Mage_Core_Block_Template +{ + /** + * Render customer address form xml + * + * @return string + */ + protected function _toHtml() + { + + $address = $this->getAddress(); + $xmlModel = new Mage_XmlConnect_Model_Simplexml_Element(''); + $customer = Mage::getSingleton('customer/session')->getCustomer(); + $addressType = $this->getType() == 'shipping' || $this->getType() == 'billing' ? $this->getType() : 'billing'; + $isAllowedGuestCheckout= Mage::getSingleton('checkout/session')->getQuote()->isAllowedGuestCheckout(); + if ($addressType == 'shipping') { + $addressId = $customer->getDefaultShipping(); + $address = $customer->getAddressById($addressId); + } else { + $addressId = $customer->getDefaultBilling(); + $address = $customer->getAddressById($addressId); + } + + if ($addressId && $address && $address->getId()) { + + $firstname = $xmlModel->xmlentities(strip_tags($address->getFirstname())); + $lastname = $xmlModel->xmlentities(strip_tags($address->getLastname())); + $company = $xmlModel->xmlentities(strip_tags($address->getCompany())); + if ($isAllowedGuestCheckout) { + $email = $xmlModel->xmlentities(strip_tags($address->getEmail())); + } + $street1 = $xmlModel->xmlentities(strip_tags($address->getStreet(1))); + $street2 = $xmlModel->xmlentities(strip_tags($address->getStreet(2))); + $city = $xmlModel->xmlentities(strip_tags($address->getCity())); + $regionId = $xmlModel->xmlentities($address->getRegionId()); + $region = Mage::getModel('directory/region')->load($regionId)->getName(); + if (!$region) { + $region = $address->getRegion(); + } + $region = $xmlModel->xmlentities(strip_tags($region)); + $postcode = $xmlModel->xmlentities(strip_tags($address->getPostcode())); + $countryId = $xmlModel->xmlentities($address->getCountryId()); + $telephone = $xmlModel->xmlentities(strip_tags($address->getTelephone())); + $fax = $xmlModel->xmlentities(strip_tags($address->getFax())); + } else { + $firstname = $lastname = $company = $email = $street1 = $street2 = ''; + $city = $region = $postcode = $telephone = $fax = ''; + $countryId = $regionId = null; + } + + $countries = $this->_getCountryOptions(); + + $regions = array(); + $countryOptionsXml = ''; + if (is_array($countries)) { + foreach ($countries as $key => $data) { + if ($data['value']) { + $regions = $this->_getRegionOptions($data['value']); + } + $countryOptionsXml .= ' + + + ' . $xmlModel->xmlentities($data['value']) . ''; + if (is_array($regions) && !empty($regions)) { + $countryOptionsXml .= ''; + foreach ($regions as $_key => $_data) { + $countryOptionsXml .= ''; + $countryOptionsXml .= + ' + ' . $xmlModel->xmlentities($_data['value']) . ''; + $countryOptionsXml .= ''; + } + $countryOptionsXml .= ''; + } + $countryOptionsXml .= ''; + } + } + $countryOptionsXml .= ''; + + $xml = << + + + +EOT; + if ($isAllowedGuestCheckout) { + $xml .= << +EOT; + } + $xml .= << + + + + $countryOptionsXml + + + + + + + + +EOT; + return $xml; + } + + /** + * Retrieve regions by country + * + * @param string $countryId + * @return array + */ + protected function _getRegionOptions($countryId) + { + $cacheKey = 'DIRECTORY_REGION_SELECT_STORE'.Mage::app()->getStore()->getId().$countryId; + if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { + $options = unserialize($cache); + } else { + $collection = Mage::getModel('directory/region')->getResourceCollection() + ->addCountryFilter($countryId) + ->load(); + $options = $collection->toOptionArray(); + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); + } + } + return $options; + } + + /** + * Retrieve countries + * + * @return array + */ + protected function _getCountryOptions() + { + $cacheKey = 'DIRECTORY_COUNTRY_SELECT_STORE_'.Mage::app()->getStore()->getCode(); + if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { + $options = unserialize($cache); + } else { + $collection = Mage::getModel('directory/country')->getResourceCollection() + ->loadByStore(); + $options = $collection->toOptionArray(); + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); + } + } + return $options; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Shipping.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Shipping.php new file mode 100644 index 0000000000..caa6e4ad3b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Address/Shipping.php @@ -0,0 +1,63 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Address_Shipping extends Mage_Checkout_Block_Onepage_Shipping +{ + /** + * Render billing shipping xml + * + * @return string + */ + protected function _toHtml() + { + $shippingXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + $addressId = $this->getAddress()->getId(); + $address = $this->getCustomer()->getPrimaryShippingAddress(); + if ($address) { + $addressId = $address->getId(); + } + + foreach ($this->getCustomer()->getAddresses() as $address) { + $item = $shippingXmlObj->addChild('item'); + if ($addressId == $address->getId()) { + $item->addAttribute('selected', 1); + } + $this->getChild('address_list')->prepareAddressData($address, $item); + $item->addChild('address_line', $shippingXmlObj->xmlentities($address->format('oneline'))); + } + + return $shippingXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Agreements.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Agreements.php new file mode 100644 index 0000000000..7cb361f96c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Agreements.php @@ -0,0 +1,62 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Agreements extends Mage_Checkout_Block_Agreements +{ + /** + * Render agreements xml + * + * @return string + */ + protected function _toHtml() + { + $agreementsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + if ($this->getAgreements()) { + foreach ($this->getAgreements() as $agreement) { + $itemXmlObj = $agreementsXmlObj->addChild('item'); + $content = $agreementsXmlObj->xmlentities($agreement->getContent()); + if (!$agreement->getIsHtml()) { + $content = nl2br(strip_tags($content)); + } + $itemXmlObj->addChild('label', $agreementsXmlObj->xmlentities(strip_tags($agreement->getCheckboxText()))); + $itemXmlObj->addChild('content', $content); + $itemXmlObj->addChild('code', 'agreement[' . $agreement->getId() . ']'); + $itemXmlObj->addChild('agreement_id', $agreement->getId()); + } + } + + return $agreementsXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Order/Review.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Order/Review.php new file mode 100644 index 0000000000..d1fcf74dbb --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Order/Review.php @@ -0,0 +1,76 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Order_Review extends Mage_Checkout_Block_Onepage_Review +{ + /** + * Render order review xml + * + * @return string + */ + protected function _toHtml() + { + $orderXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + /** + * Order items + */ + $products = $this->getChildHtml('order_products'); + if ($products) { + $productsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($products); + $orderXmlObj->appendChild($productsXmlObj); + } + + /** + * Totals + */ + $totalsXml = $this->getChildHtml('totals'); + if ($totalsXml) { + $totalsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($totalsXml); + $orderXmlObj->appendChild($totalsXmlObj); + } + + /** + * Agreements + */ + $agreements = $this->getChildHtml('agreements'); + if ($agreements) { + $agreementsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($agreements); + $orderXmlObj->appendChild($agreementsXmlObj); + } + + return $orderXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Order/Review/Info.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Order/Review/Info.php new file mode 100644 index 0000000000..f34a2ca85c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Order/Review/Info.php @@ -0,0 +1,186 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Order_Review_Info extends Mage_Checkout_Block_Onepage_Review_Info +{ + /** + * Render order review items + * + * @return string + */ + protected function _toHtml() + { + $itemsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $quote = Mage::getSingleton('checkout/session')->getQuote(); + + /* @var $item Mage_Sales_Model_Quote_Item */ + foreach ($this->getItems() as $item) { + $type = $this->_getItemType($item); + $renderer = $this->getItemRenderer($type)->setItem($item); + + /** + * General information + */ + $itemXml = $itemsXmlObj->addChild('item'); + $itemXml->addChild('entity_id', $item->getProduct()->getId()); + $itemXml->addChild('entity_type', $type); + $itemXml->addChild('item_id', $item->getId()); + $itemXml->addChild('name', $itemsXmlObj->xmlentities(strip_tags($renderer->getProductName()))); + $itemXml->addChild('qty', $renderer->getQty()); + $icon = $renderer->getProductThumbnail()->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('product_small')); + + $iconXml = $itemXml->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + + /** + * Price + */ + $exclPrice = $inclPrice = 0.00; + if ($this->helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()) { + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $exclPrice = $item->getCalculationPrice() + $item->getWeeeTaxAppliedAmount() + $item->getWeeeTaxDisposition(); + } else { + $exclPrice = $item->getCalculationPrice(); + } + } + + if ($this->helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()) { + $_incl = $this->helper('checkout')->getPriceInclTax($item); + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $inclPrice = $_incl + $item->getWeeeTaxAppliedAmount(); + } else { + $inclPrice = $_incl - $item->getWeeeTaxDisposition(); + } + } + + $exclPrice = Mage::helper('xmlconnect')->formatPriceForXml($exclPrice); + $formatedExclPrice = $quote->getStore()->formatPrice($exclPrice, false); + + $inclPrice = Mage::helper('xmlconnect')->formatPriceForXml($inclPrice); + $formatedInclPrice = $quote->getStore()->formatPrice($inclPrice, false); + + $priceXmlObj = $itemXml->addChild('price'); + $formatedPriceXmlObj = $itemXml->addChild('formated_price'); + + if ($this->helper('tax')->displayCartBothPrices()) { + $priceXmlObj->addAttribute('excluding_tax', $exclPrice); + $priceXmlObj->addAttribute('including_tax', $inclPrice); + + $formatedPriceXmlObj->addAttribute('excluding_tax', $formatedExclPrice); + $formatedPriceXmlObj->addAttribute('including_tax', $formatedInclPrice); + } else { + if ($this->helper('tax')->displayCartPriceExclTax()) { + $priceXmlObj->addAttribute('regular', $exclPrice); + $formatedPriceXmlObj->addAttribute('regular', $formatedExclPrice); + } + if ($this->helper('tax')->displayCartPriceInclTax()) { + $priceXmlObj->addAttribute('regular', $inclPrice); + $formatedPriceXmlObj->addAttribute('regular', $formatedInclPrice); + } + } + + + /** + * Subtotal + */ + $exclPrice = $inclPrice = 0.00; + if ($this->helper('tax')->displayCartPriceExclTax() || $this->helper('tax')->displayCartBothPrices()) { + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $exclPrice = $item->getRowTotal() + $item->getWeeeTaxAppliedRowAmount() + $item->getWeeeTaxRowDisposition(); + } else { + $exclPrice = $item->getRowTotal(); + } + } + if ($this->helper('tax')->displayCartPriceInclTax() || $this->helper('tax')->displayCartBothPrices()) { + $_incl = $this->helper('checkout')->getSubtotalInclTax($item); + if (Mage::helper('weee')->typeOfDisplay($item, array(0, 1, 4), 'sales') && $item->getWeeeTaxAppliedAmount()) { + $inclPrice = $_incl + $item->getWeeeTaxAppliedRowAmount(); + } else { + $inclPrice = $_incl - $item->getWeeeTaxRowDisposition(); + } + } + + $exclPrice = Mage::helper('xmlconnect')->formatPriceForXml($exclPrice); + $formatedExclPrice = $quote->getStore()->formatPrice($exclPrice, false); + + $inclPrice = Mage::helper('xmlconnect')->formatPriceForXml($inclPrice); + $formatedInclPrice = $quote->getStore()->formatPrice($inclPrice, false); + + $subtotalPriceXmlObj = $itemXml->addChild('subtotal'); + $subtotalFormatedPriceXmlObj = $itemXml->addChild('formated_subtotal'); + + if ($this->helper('tax')->displayCartBothPrices()) { + $subtotalPriceXmlObj->addAttribute('excluding_tax', $exclPrice); + $subtotalPriceXmlObj->addAttribute('including_tax', $inclPrice); + + $subtotalFormatedPriceXmlObj->addAttribute('excluding_tax', $formatedExclPrice); + $subtotalFormatedPriceXmlObj->addAttribute('including_tax', $formatedInclPrice); + } else { + if ($this->helper('tax')->displayCartPriceExclTax()) { + $subtotalPriceXmlObj->addAttribute('regular', $exclPrice); + $subtotalFormatedPriceXmlObj->addAttribute('regular', $formatedExclPrice); + } + if ($this->helper('tax')->displayCartPriceInclTax()) { + $subtotalPriceXmlObj->addAttribute('regular', $inclPrice); + $subtotalFormatedPriceXmlObj->addAttribute('regular', $formatedInclPrice); + } + } + + /** + * Options list + */ + if ($_options = $renderer->getOptionList()) { + $itemOptionsXml = $itemXml->addChild('options'); + foreach ($_options as $_option) { + $_formatedOptionValue = $renderer->getFormatedOptionValue($_option); + $optionXml = $itemOptionsXml->addChild('option'); + $optionXml->addAttribute('label', $itemsXmlObj->xmlentities(strip_tags($_option['label']))); + $optionXml->addAttribute('text', $itemsXmlObj->xmlentities(strip_tags($_formatedOptionValue['value']))); +// if (isset($_formatedOptionValue['full_view'])) { +// $label = strip_tags($_option['label']); +// $value = strip_tags($_formatedOptionValue['full_view']); +// } + } + } + } + + return $itemsXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Authorizenet.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Authorizenet.php new file mode 100644 index 0000000000..34a940e55f --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Authorizenet.php @@ -0,0 +1,124 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_Authorizenet extends Mage_Payment_Block_Form_Ccsave +{ + /** + * Prevent any rendering + * + * @return string + */ + protected function _toHtml() + { + return ''; + } + + /** + * Retrieve payment method model + * + * @return Mage_Payment_Model_Method_Abstract + */ + public function getMethod() + { + $method = $this->getData('method'); + if (!$method) { + $method = Mage::getModel('paygate/authorizenet'); + $this->setData('method', $method); + } + + return $method; + } + + /** + * Add Authorize.net payment method form to payment XML object + * + * @param Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function addPaymentFormToXmlObj(Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj) + { + $helper = Mage::helper('xmlconnect'); + $method = $this->getMethod(); + if (!$method) { + return $paymentItemXmlObj; + } + $formXmlObj = $paymentItemXmlObj->addChild('form'); + $formXmlObj->addAttribute('name', 'payment_form_' . $method->getCode()); + $formXmlObj->addAttribute('method', 'post'); + + $ccTypes = $helper->getArrayAsXmlItemValues($this->getCcAvailableTypes(), $this->getInfoData('cc_type')); + + $ccMonths = $helper->getArrayAsXmlItemValues($this->getCcMonths(), $this->getInfoData('cc_exp_month')); + + $ccYears = $helper->getArrayAsXmlItemValues($this->getCcYears(), $this->getInfoData('cc_exp_year')); + + $verification = ''; + if ($this->hasVerification()) { + $verification = + ' + + + + '; + } + + $xml = << + + + $ccTypes + + + + + + + + + + $ccMonths + + + + + $ccYears + + + $verification + +EOT; + $fieldsetXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($xml); + $formXmlObj->appendChild($fieldsetXmlObj); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Ccsave.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Ccsave.php new file mode 100644 index 0000000000..6d724a6e10 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Ccsave.php @@ -0,0 +1,136 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_Ccsave extends Mage_Payment_Block_Form_Ccsave +{ + /** + * Prevent any rendering + * + * @return string + */ + protected function _toHtml() + { + return ''; + } + + /** + * Retrieve payment method model + * + * @return Mage_Payment_Model_Method_Abstract + */ + public function getMethod() + { + $method = $this->getData('method'); + if (!$method) { + $method = Mage::getModel('payment/method_ccsave'); + $this->setData('method', $method); + } + + return $method; + } + + /** + * Add cc save payment method form to payment XML object + * + * @param Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function addPaymentFormToXmlObj(Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj) + { + $helper = Mage::helper('xmlconnect'); + $method = $this->getMethod(); + if (!$method) { + return $paymentItemXmlObj; + } + $formXmlObj = $paymentItemXmlObj->addChild('form'); + $formXmlObj->addAttribute('name', 'payment_form_' . $method->getCode()); + $formXmlObj->addAttribute('method', 'post'); + + $owner = $this->getInfoData('cc_owner'); + + $ccTypes = $helper->getArrayAsXmlItemValues($this->getCcAvailableTypes(), $this->getInfoData('cc_type')); + + $_ccMonthArray = $this->getCcMonths(); + $ccMonths = $helper->getArrayAsXmlItemValues($_ccMonthArray, $this->getInfoData('cc_exp_month')); + + $ccYears = $helper->getArrayAsXmlItemValues($this->getCcYears(), $this->getInfoData('cc_exp_year')); + + $verification = ''; + if ($this->hasVerification()) { + $verification = + ' + + + + '; + } + + $solo = ''; + if ($this->hasSsCardType()) { + $ssCcMonths = $helper->getArrayAsXmlItemValues($_ccMonthArray, $this->getInfoData('cc_ss_start_month')); + $ssCcYears = $helper->getArrayAsXmlItemValues($this->getSsStartYears(), $this->getInfoData('cc_ss_start_year')); + $solo = $helper->getSoloXml($ssCcMonths, $ssCcYears); + } + + $xml = << + + + + $ccTypes + + $solo + + + + + + + + + $ccMonths + + + + + $ccYears + + + $verification + +EOT; + $fieldsetXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($xml); + $formXmlObj->appendChild($fieldsetXmlObj); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Checkmo.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Checkmo.php new file mode 100644 index 0000000000..afafe52751 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Checkmo.php @@ -0,0 +1,73 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_Checkmo extends Mage_Payment_Block_Form_Checkmo +{ + /** + * Prevent any rendering + * + * @return string + */ + protected function _toHtml() + { + return ''; + } + + /** + * Retrieve payment method model + * + * @return Mage_Payment_Model_Method_Abstract + */ + public function getMethod() + { + $method = $this->getData('method'); + if (!$method) { + $method = Mage::getModel('payment/method_checkmo'); + $this->setData('method', $method); + } + + return $method; + } + + /** + * Add cc save payment method form to payment XML object + * + * @param Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function addPaymentFormToXmlObj(Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj) + { + return $paymentItemXmlObj; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/List.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/List.php new file mode 100644 index 0000000000..fa8ee71d30 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/List.php @@ -0,0 +1,198 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_List extends Mage_Payment_Block_Form_Container +{ + + /** + * Prevent parent set childs + * + * @return Mage_XmlConnect_Block_Checkout_Payment_Method_List + */ + protected function _prepareLayout() + { + return $this; + } + + /** + * Retrieve quote model object + * + * @return Mage_Sales_Model_Quote + */ + public function getQuote() + { + return Mage::getSingleton('checkout/session')->getQuote(); + } + + /** + * Render payment methods xml + * + * @return string + */ + protected function _toHtml() + { + $methodsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + $methodBlocks = $this->getChild(); + $methodArray = array ( + 'payment_ccsave' => 'Mage_Payment_Model_Method_Cc', + 'payment_checkmo' => 'Mage_Payment_Model_Method_Checkmo', + 'payment_purchaseorder' => 'Mage_Payment_Model_Method_Purchaseorder'); + $usedMethods = $sortedAvailableMethodCodes = $usedCodes = array(); + $allAvailableMethods = Mage::helper('payment')->getStoreMethods(Mage::app()->getStore(), $this->getQuote()); + + foreach ($allAvailableMethods as $method) { + $sortedAvailableMethodCodes[] = $method->getCode(); + } + + /** + * Collect directly supported by xmlconnect methods + */ + foreach ($methodBlocks as $block) { + if (!$block) { + continue; + } + + $method = $block->getMethod(); + if (!$this->_canUseMethod($method) || in_array($method->getCode(), $usedCodes)) { + continue; + } + $this->_assignMethod($method); + $usedCodes[] = $method->getCode(); + $usedMethods[$method->getCode()] = array('renderer' => $block, 'method' => $method); + } + + /** + * Collect all "Credit Card" / "CheckMo" / "Purchaseorder" method compatible methods + */ + foreach ($methodArray as $methodName => $methodModelClassName) { + $methodRenderer = $this->getChild($methodName); + if (!is_null($methodRenderer)) { + foreach ($sortedAvailableMethodCodes as $methodCode) { + /** + * Skip used methods + */ + if (in_array($methodCode, $usedCodes)) { + continue; + } + try { + $method = Mage::helper('payment')->getMethodInstance($methodCode); + if (!is_subclass_of($method, $methodModelClassName)) { + continue; + } + if (!$this->_canUseMethod($method)) { + continue; + } + + $this->_assignMethod($method); + $usedCodes[] = $method->getCode(); + $usedMethods[$method->getCode()] = array('renderer' => $methodRenderer, 'method' => $method); + } catch (Exception $e) { + Mage::logException($e); + } + } + } + } + + /** + * Generate methods XML according to sort order + */ + foreach ($sortedAvailableMethodCodes as $code) { + if (!in_array($code, $usedCodes)) { + continue; + } + + $method = $usedMethods[$code]['method']; + $renderer = $usedMethods[$code]['renderer']; + /** + * Render all Credit Card method compatible methods + */ + if ($renderer instanceOf Mage_XmlConnect_Block_Checkout_Payment_Method_Ccsave) { + $renderer->setData('method', $method); + } + + $methodItemXmlObj = $methodsXmlObj->addChild('method'); + $methodItemXmlObj->addAttribute('post_name', 'payment[method]'); + $methodItemXmlObj->addAttribute('code', $method->getCode()); + $methodItemXmlObj->addAttribute('label', $methodsXmlObj->xmlentities(strip_tags($method->getTitle()))); + if ($this->getQuote()->getPayment()->getMethod() == $method->getCode()) { + $methodItemXmlObj->addAttribute('selected', 1); + } + $renderer->addPaymentFormToXmlObj($methodItemXmlObj); + } + + + return $methodsXmlObj->asNiceXml(); + } + + /** + * Check and prepare payment method model + * + * @param mixed $method + * @return bool + */ + protected function _canUseMethod($method) + { + if (!$method || !$method->canUseCheckout() || !$method->canUseForMultishipping() || !$method->isAvailable($this->getQuote())) { + return false; + } + return parent::_canUseMethod($method); + } + + /** + * Deprecated function adding Payment method to the xml + * + * @deprecated after 1.4.2.0 + * @param Mage_Core_Block_Template $block + * @param Mage_XmlConnect_Model_Simplexml_Element $methodsXmlObj + * @param array $usedCodes + * @return bool + */ + protected function _addToXml($block, $methodsXmlObj, $usedCodes) + { + return false; + } + + /** + * Deprecated function check method status + * + * @deprecated after 1.4.2.0 + * @param Mage_Payment_Model_Method_Abstract $method + * @return bool + */ + public function isAvailable($method) + { + return $method->isAvailable($this->getQuote()); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Paypal/Direct.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Paypal/Direct.php new file mode 100644 index 0000000000..e1ba260749 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Paypal/Direct.php @@ -0,0 +1,62 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_Paypal_Direct extends Mage_XmlConnect_Block_Checkout_Payment_Method_Paypal_Payflow +{ + /** + * Prevent any rendering + * + * @return string + */ + protected function _toHtml() + { + return ''; + } + + /** + * Retrieve payment method model + * + * @return Mage_Payment_Model_Method_Abstract + */ + public function getMethod() + { + $method = $this->getData('method'); + if (!$method) { + $method = Mage::getModel('paypal/direct'); + $this->setData('method', $method); + } + + return $method; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Paypal/Payflow.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Paypal/Payflow.php new file mode 100644 index 0000000000..2c1f1ffa11 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Paypal/Payflow.php @@ -0,0 +1,159 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_Paypal_Payflow extends Mage_Payment_Block_Form_Ccsave +{ + /** + * Prevent any rendering + * + * @return string + */ + protected function _toHtml() + { + return ''; + } + + /** + * Retrieve payment method model + * + * @return Mage_Payment_Model_Method_Abstract + */ + public function getMethod() + { + $method = $this->getData('method'); + if (!$method) { + $method = Mage::getModel('paypal/payflowpro'); + $this->setData('method', $method); + } + + return $method; + } + + /** + * Add Payflow Pro payment method form to payment XML object + * + * @param Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function addPaymentFormToXmlObj(Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj) + { + $method = $this->getMethod(); + if (!$method) { + return $paymentItemXmlObj; + } + $formXmlObj = $paymentItemXmlObj->addChild('form'); + $formXmlObj->addAttribute('name', 'payment_form_' . $method->getCode()); + $formXmlObj->addAttribute('method', 'post'); + + $_ccType = $this->getInfoData('cc_type'); + $ccTypes = ''; + + foreach ($this->getCcAvailableTypes() as $_typeCode => $_typeName) { + if (!$_typeCode) { + continue; + } + $ccTypes .= ' + + + ' . $_typeCode . ' + '; + } + + $ccMonthes = ''; + + $_ccExpMonth = $this->getInfoData('cc_exp_month'); + foreach ($this->getCcMonths() as $k => $v) { + if (!$k) { + continue; + } + $ccMonthes .= ' + + + ' . ($k ? $k : '') . ' + '; + } + + $ccYears = ''; + + $_ccExpYear = $this->getInfoData('cc_exp_year'); + foreach ($this->getCcYears() as $k => $v) { + if (!$k) { + continue; + } + $ccYears .= ' + + + ' . ($k ? $k : '') . ' + '; + } + + $verification = ''; + if ($this->hasVerification()) { + $verification = + ' + + + + '; + } + + $xml = << + + + $ccTypes + + + + + + + + + + $ccMonthes + + + + + $ccYears + + + $verification + +EOT; + $fieldsetXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($xml); + $formXmlObj->appendChild($fieldsetXmlObj); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Purchaseorder.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Purchaseorder.php new file mode 100644 index 0000000000..ce1546bb2b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Payment/Method/Purchaseorder.php @@ -0,0 +1,90 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Payment_Method_Purchaseorder extends Mage_Payment_Block_Form_Purchaseorder +{ + /** + * Prevent any rendering + * + * @return string + */ + protected function _toHtml() + { + return ''; + } + + /** + * Retrieve payment method model + * + * @return Mage_Payment_Model_Method_Abstract + */ + public function getMethod() + { + $method = $this->getData('method'); + if (!$method) { + $method = Mage::getModel('payment/method_purchaseorder'); + $this->setData('method', $method); + } + + return $method; + } + + /** + * Add payment method form to payment XML object + * + * @param Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function addPaymentFormToXmlObj(Mage_XmlConnect_Model_Simplexml_Element $paymentItemXmlObj) + { + $method = $this->getMethod(); + if (!$method) { + return $paymentItemXmlObj; + } + $formXmlObj = $paymentItemXmlObj->addChild('form'); + $formXmlObj->addAttribute('name', 'payment_form_' . $method->getCode()); + $formXmlObj->addAttribute('method', 'post'); + + $poNumber = $this->getInfoData('po_number'); + $xml = << + + +EOT; + $fieldsetXmlObj = new Mage_XmlConnect_Model_Simplexml_Element($xml); + $formXmlObj->appendChild($fieldsetXmlObj); + + return $paymentItemXmlObj; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Shipping/Method/Avaliable.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Shipping/Method/Avaliable.php new file mode 100644 index 0000000000..de1ac3a9fb --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Checkout/Shipping/Method/Avaliable.php @@ -0,0 +1,73 @@ + + */ +class Mage_XmlConnect_Block_Checkout_Shipping_Method_Avaliable extends Mage_Checkout_Block_Onepage_Shipping_Method_Available +{ + /** + * Render shipping methods xml + * + * @return string + */ + protected function _toHtml() + { + $methodsXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $_shippingRateGroups = $this->getShippingRates(); + if ($_shippingRateGroups) { + $store = $this->getQuote()->getStore(); + $_sole = count($_shippingRateGroups) == 1; + foreach ($_shippingRateGroups as $code => $_rates) { + $methodXmlObj = $methodsXmlObj->addChild('method'); + $methodXmlObj->addAttribute('label', $methodsXmlObj->xmlentities(strip_tags($this->getCarrierName($code)))); + $ratesXmlObj = $methodXmlObj->addChild('rates'); + + $_sole = $_sole && count($_rates) == 1; + foreach ($_rates as $_rate) { + $rateXmlObj = $ratesXmlObj->addChild('rate'); + $rateXmlObj->addAttribute('label', $methodsXmlObj->xmlentities(strip_tags($_rate->getMethodTitle()))); + $rateXmlObj->addAttribute('code', $_rate->getCode()); + if ($_rate->getErrorMessage()) { + $rateXmlObj->addChild('error_message', $methodsXmlObj->xmlentities(strip_tags($_rate->getErrorMessage()))); + } else { + $price = Mage::helper('tax')->getShippingPrice($_rate->getPrice(), false, $this->getAddress()); + $formattedPrice = $store->convertPrice($price, true, false); + $rateXmlObj->addAttribute('price', Mage::helper('xmlconnect')->formatPriceForXml($store->convertPrice($price, false, false))); + $rateXmlObj->addAttribute('formated_price', $formattedPrice); + } + } + } + } else { + Mage::throwException(Mage::helper('xmlconnect')->__('Sorry, no quotes are available for this order at this time.')); + } + return $methodsXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cms/Page.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cms/Page.php new file mode 100644 index 0000000000..eea430042d --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Cms/Page.php @@ -0,0 +1,46 @@ + + */ + +class Mage_XmlConnect_Block_Cms_Page extends Mage_Cms_Block_Page +{ + /** + * Page Id getter + * + * @return int + */ + public function getPageId() + { + return $this->getRequest()->getParam('id'); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Configuration.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Configuration.php new file mode 100644 index 0000000000..14623f747e --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Configuration.php @@ -0,0 +1,113 @@ + + */ + +class Mage_XmlConnect_Block_Configuration extends Mage_Core_Block_Template +{ + protected $_app; + + /** + * Init current application + * + * @return Mage_XmlConnect_Block_Configuration + */ + protected function _beforeToHtml() + { + $app = Mage::registry('current_app'); + if ($app) { + $this->_app = $app; + } else { + $this->_app = Mage::getModel('xmlconnect/application'); + $this->_app->loadDefaultConfiguration(); + } + return $this; + } + + /** + * Recursively build XML configuration tree + * + * @param Mage_XmlConnect_Model_Simplexml_Element $section + * @param array $subtree + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + protected function _buildRecursive($section, $subtree) + { + foreach ($subtree as $key => $value) { + if (is_array($value)) { + if ($key == 'fonts') { + $subsection = $section->addChild('fonts'); + foreach ($value as $label=>$v) { + if (empty($v['name']) || empty($v['size']) || empty($v['color'])) { + continue; + } + $font = $subsection->addChild('font'); + $font->addAttribute('label', $label); + $font->addAttribute('name', $v['name']); + $font->addAttribute('size', $v['size']); + $font->addAttribute('color', $v['color']); + } + } elseif ($key == 'pages') { + $subsection = $section->addChild('content'); + foreach ($value as $page) { + $this->_buildRecursive($subsection->addChild('page'), $page); + } + } else { + $subsection = $section->addChild($key); + $this->_buildRecursive($subsection, $value); + } + } elseif ($value instanceof Mage_XmlConnect_Model_Tabs) { + foreach ($value->getRenderTabs() as $tab) { + $subsection = $section->addChild('tab'); + $this->_buildRecursive($subsection, $tab); + } + } else { + $value = (string)$value; + if ($value != '') { + $section->addChild($key, $value); + } + } + } + } + + /** + * Render block + * + * @return string + */ + protected function _toHtml() + { + $xml = new Mage_XmlConnect_Model_Simplexml_Element(''); + $this->_buildRecursive($xml, $this->_app->getRenderConf()); + return $xml->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Address/Form.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Address/Form.php new file mode 100644 index 0000000000..c485389f65 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Address/Form.php @@ -0,0 +1,178 @@ + + */ +class Mage_XmlConnect_Block_Customer_Address_Form extends Mage_Core_Block_Template +{ + /** + * Render customer address form xml + * + * @return string + */ + protected function _toHtml() + { + $address = $this->getAddress(); + $xmlModel = new Mage_XmlConnect_Model_Simplexml_Element(''); + + /** + * Init address object and save its data to variables + */ + $addressId = (int)$this->getRequest()->getParam('id'); + if ($addressId && $address && $address->getId()) { + $defaultBillingAddressId = Mage::getSingleton('customer/session')->getCustomer()->getDefaultBilling(); + $defaultShippingAddressId = Mage::getSingleton('customer/session')->getCustomer()->getDefaultShipping(); + + $billingChecked = $addressId == $defaultBillingAddressId ? ' checked="1"' : ''; + $shippingChecked = $addressId == $defaultShippingAddressId ? ' checked="1"' : ''; + + $firstname = $xmlModel->xmlentities(strip_tags($address->getFirstname())); + $lastname = $xmlModel->xmlentities(strip_tags($address->getLastname())); + $company = $xmlModel->xmlentities(strip_tags($address->getCompany())); + $street1 = $xmlModel->xmlentities(strip_tags($address->getStreet(1))); + $street2 = $xmlModel->xmlentities(strip_tags($address->getStreet(2))); + $city = $xmlModel->xmlentities(strip_tags($address->getCity())); + $regionId = $xmlModel->xmlentities($address->getRegionId()); + $region = Mage::getModel('directory/region')->load($regionId)->getName(); + if (!$region) { + $region = $address->getRegion(); + } + $region = $xmlModel->xmlentities(strip_tags($region)); + $postcode = $xmlModel->xmlentities(strip_tags($address->getPostcode())); + $countryId = $xmlModel->xmlentities($address->getCountryId()); + $telephone = $xmlModel->xmlentities(strip_tags($address->getTelephone())); + $fax = $xmlModel->xmlentities(strip_tags($address->getFax())); + } else { + $firstname = $lastname = $company = $street1 = $street2 = $billingChecked = $shippingChecked = ''; + $city = $region = $postcode = $telephone = $fax = ''; + $countryId = $regionId = null; + } + + $countries = $this->_getCountryOptions(); + + $regions = array(); + $countryOptionsXml = ''; + if (is_array($countries)) { + foreach ($countries as $key => $data) { + if ($data['value']) { + $regions = $this->_getRegionOptions($data['value']); + } + $countryOptionsXml .= ' + + + ' . $xmlModel->xmlentities($data['value']) . ''; + if (is_array($regions) && !empty($regions)) { + $countryOptionsXml .= ''; + foreach ($regions as $_key => $_data) { + $countryOptionsXml .= ''; + $countryOptionsXml .= + ' + ' . $xmlModel->xmlentities($_data['value']) . ''; + $countryOptionsXml .= ''; + } + $countryOptionsXml .= ''; + } + $countryOptionsXml .= ''; + } + } + $countryOptionsXml .= ''; + + $xml = << +
    + + + + + +
    +
    + + + + + $countryOptionsXml + + + + + + +
    + +EOT; + return $xml; + } + + /** + * Retrieve regions by country + * + * @param string $countryId + * @return array + */ + protected function _getRegionOptions($countryId) + { + $cacheKey = 'DIRECTORY_REGION_SELECT_STORE'.Mage::app()->getStore()->getId().$countryId; + if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { + $options = unserialize($cache); + } else { + $collection = Mage::getModel('directory/region')->getResourceCollection() + ->addCountryFilter($countryId) + ->load(); + $options = $collection->toOptionArray(); + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); + } + } + return $options; + } + + /** + * Retrieve countries + * + * @return array + */ + protected function _getCountryOptions() + { + $cacheKey = 'DIRECTORY_COUNTRY_SELECT_STORE_'.Mage::app()->getStore()->getCode(); + if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { + $options = unserialize($cache); + } else { + $collection = Mage::getModel('directory/country')->getResourceCollection() + ->loadByStore(); + $options = $collection->toOptionArray(); + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); + } + } + return $options; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Address/List.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Address/List.php new file mode 100644 index 0000000000..16be94dd51 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Address/List.php @@ -0,0 +1,127 @@ + + */ +class Mage_XmlConnect_Block_Customer_Address_List extends Mage_Core_Block_Template +{ + /** + * Render customer address list xml + * + * @return string + */ + protected function _toHtml() + { + $addressXmlObj = new Mage_XmlConnect_Model_Simplexml_Element('
    '); + $customer = Mage::getSingleton('customer/session')->getCustomer(); + + $_billingAddssesId = $customer->getDefaultBilling(); + $_shippingAddssesId = $customer->getDefaultShipping(); + $billingAddress = $customer->getAddressById($_billingAddssesId); + $shippingAddress = $customer->getAddressById($_shippingAddssesId); + + if ($billingAddress && $billingAddress->getId()) { + $item = $addressXmlObj->addChild('item'); + $item->addAttribute('label', Mage::helper('xmlconnect')->__('Default Billing Address')); + $item->addAttribute('default_billing', 1); + $this->prepareAddressData($billingAddress, $item); + } + if ($shippingAddress && $shippingAddress->getId()) { + $item = $addressXmlObj->addChild('item'); + $item->addAttribute('label', Mage::helper('xmlconnect')->__('Default Shipping Address')); + $item->addAttribute('default_shipping', 1); + $this->prepareAddressData($shippingAddress, $item); + } + $_additionalAddresses = $customer->getAdditionalAddresses(); + if ($_additionalAddresses) { + foreach ($_additionalAddresses as $_address) { + $item = $addressXmlObj->addChild('item'); + $item->addAttribute('label', Mage::helper('xmlconnect')->__('Additional Address')); + $item->addAttribute('additional', 1); + $this->prepareAddressData($_address, $item); + } + } + + return $addressXmlObj->asNiceXml(); + } + + /** + * Collect address data to xml node + * Remove objects from data array and escape data values + * + * @param Mage_Customer_Model_Address $address + * @param Mage_XmlConnect_Model_Simplexml_Element $item + * @return array + */ + public function prepareAddressData(Mage_Customer_Model_Address $address, Mage_XmlConnect_Model_Simplexml_Element $item) + { + if (!$address) { + return array(); + } + + $attributes = Mage::helper('customer/address')->getAttributes(); + + $data = array( + 'entity_id' => $address->getId() + ); + + foreach ($attributes as $attribute) { + /* @var $attribute Mage_Customer_Model_Attribute */ + if (!$attribute->getIsVisible()) { + continue; + } + if ($attribute->getAttributeCode() == 'country_id') { + $data['country'] = $address->getCountryModel()->getName(); + $data['country_id'] = $address->getCountryId(); + } else if ($attribute->getAttributeCode() == 'region') { + $data['region'] = $address->getRegion(); + } else { + $dataModel = Mage_Customer_Model_Attribute_Data::factory($attribute, $address); + $value = $dataModel->outputValue(Mage_Customer_Model_Attribute_Data::OUTPUT_FORMAT_ONELINE); + if ($attribute->getFrontendInput() == 'multiline') { + $values = $dataModel->outputValue(Mage_Customer_Model_Attribute_Data::OUTPUT_FORMAT_ARRAY); + // explode lines + foreach ($values as $k => $v) { + $key = sprintf('%s%d', $attribute->getAttributeCode(), $k + 1); + $data[$key] = $v; + } + } + $data[$attribute->getAttributeCode()] = $value; + } + } + + foreach ($data as $key => $value) { + if (!empty($value)) { + $item->addChild($key, $item->xmlentities($value)); + } + } + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Form.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Form.php new file mode 100644 index 0000000000..fc95df38f7 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Form.php @@ -0,0 +1,96 @@ + + */ +class Mage_XmlConnect_Block_Customer_Form extends Mage_Core_Block_Template +{ + /** + * Render customer form xml + * + * @return string + */ + protected function _toHtml() + { + $editFlag = (int)$this->getRequest()->getParam('edit'); + $customer = $this->getCustomer(); + $xmlModel = new Mage_XmlConnect_Model_Simplexml_Element(''); + + if ($editFlag == 1 && $customer && $customer->getId()) { + $firstname = $xmlModel->xmlentities(strip_tags($customer->getFirstname())); + $lastname = $xmlModel->xmlentities(strip_tags($customer->getLastname())); + $email = $xmlModel->xmlentities(strip_tags($customer->getEmail())); + } else { + $firstname = $lastname = $email = ''; + } + + if ($editFlag) { + $passwordManageXml = ' + + +
    + + + + + password + + +
    '; + } else { + $passwordManageXml = ' + + + + password + + + '; + } + + $xml = << +
    + + + + + + + + $passwordManageXml + +EOT; + + return $xml; + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Order/List.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Order/List.php new file mode 100644 index 0000000000..cab202faab --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Customer/Order/List.php @@ -0,0 +1,75 @@ + + */ +class Mage_XmlConnect_Block_Customer_Order_List extends Mage_Core_Block_Template +{ + /** + * Linmitation for orders list + */ + const ORDERS_LIST_LIMIT = 10; + + /** + * Render customer orders list xml + * + * @return string + */ + protected function _toHtml() + { + $ordersXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + $orders = Mage::getResourceModel('sales/order_collection') + ->addFieldToSelect('*') + ->addFieldToFilter('customer_id', Mage::getSingleton('customer/session')->getCustomer()->getId()) + ->addFieldToFilter('state', array('in' => Mage::getSingleton('sales/order_config')->getVisibleOnFrontStates())) + ->setOrder('created_at', 'desc'); + + $orders->getSelect()->limit(self::ORDERS_LIST_LIMIT, 0); + $orders->load(); + + if (sizeof($orders->getItems())) { + foreach ($orders as $_order) { + $item = $ordersXmlObj->addChild('item'); + $item->addChild('entity_id', $_order->getId()); + $item->addChild('number', $_order->getRealOrderId()); + $item->addChild('date', $this->formatDate($_order->getCreatedAtStoreDate())); + if ($_order->getShippingAddress()) { + $item->addChild('ship_to', $ordersXmlObj->xmlentities(strip_tags($_order->getShippingAddress()->getName()))); + } + $item->addChild('total', $_order->getOrderCurrency()->formatPrecision($_order->getGrandTotal(), 2, array(), false, false)); + $item->addChild('status', $_order->getStatusLabel()); + } + } + + return $ordersXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Home.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Home.php new file mode 100644 index 0000000000..2368baa0a6 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Home.php @@ -0,0 +1,84 @@ + + */ + +class Mage_XmlConnect_Block_Home extends Mage_XmlConnect_Block_Catalog +{ + + /** + * Category list limitation + */ + const HOME_PAGE_CATEGORIES_COUNT = 6; + + /** + * Render home category list xml + * + * @return string + */ + protected function _toHtml() + { + $homeXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + + $categoryCollection = Mage::getResourceModel('xmlconnect/category_collection'); + $categoryCollection->setStoreId(Mage::app()->getStore()->getId()) + ->addParentIdFilter(Mage::app()->getStore()->getRootCategoryId()) + ->setOrder('position', 'ASC') + ->setLimit(0, self::HOME_PAGE_CATEGORIES_COUNT); + + if (sizeof($categoryCollection)) { + $itemsXmlObj = $homeXmlObj->addChild('categories'); + } + + foreach ($categoryCollection->getItems() as $item) { + $itemXmlObj = $itemsXmlObj->addChild('item'); + $itemXmlObj->addChild('label', $homeXmlObj->xmlentities(strip_tags($item->getName()))); + $itemXmlObj->addChild('entity_id', $item->getEntityId()); + $itemXmlObj->addChild('content_type', $item->hasChildren() ? 'categories' : 'products'); + $icon = Mage::helper('xmlconnect/catalog_category_image')->initialize($item, 'thumbnail') + ->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('category')); + + $iconXml = $itemXmlObj->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + } + + $homeXmlObj->addChild('home_banner', '/current/media/catalog/category/banner_home.png'); + + return $homeXmlObj->asNiceXml(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Block/Wishlist.php b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Wishlist.php new file mode 100644 index 0000000000..70ea51dddb --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Block/Wishlist.php @@ -0,0 +1,113 @@ + + */ + +class Mage_XmlConnect_Block_Wishlist extends Mage_Wishlist_Block_Customer_Wishlist +{ + /** + * Render customer wishlist xml + * + * @return string + */ + protected function _toHtml() + { + $wishlistXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $hasMoreItems = 0; + /** + * Apply offset and count + */ + $request= $this->getRequest(); + $offset = (int)$request->getParam('offset', 0); + $count = (int)$request->getParam('count', 0); + $count = $count <= 0 ? 1 : $count; + if ($offset + $count < $this->getWishlist()->getSize()) { + $hasMoreItems = 1; + } + $this->getWishlist()->getSelect()->limit($count, $offset); + + $wishlistXmlObj->addAttribute('items_count', $this->getWishlistItemsCount()); + $wishlistXmlObj->addAttribute('has_more_items', $hasMoreItems); + + if ($this->hasWishlistItems()) { + /** + * @var Mage_Wishlist_Model_Mysql4_Product_Collection + */ + foreach ($this->getWishlist() as $item) { + $itemXmlObj = $wishlistXmlObj->addChild('item'); + $itemXmlObj->addChild('item_id', $item->getWishlistItemId()); + $itemXmlObj->addChild('entity_id', $item->getProductId()); + $itemXmlObj->addChild('entity_type_id', $item->getTypeId()); + $itemXmlObj->addChild('name', $wishlistXmlObj->xmlentities(strip_tags($item->getName()))); + $itemXmlObj->addChild('in_stock', (int)$item->isSalable()); + /** + * If product type is grouped than it has options as its grouped items + */ + if ($item->getTypeId() == Mage_Catalog_Model_Product_Type_Grouped::TYPE_CODE) { + $item->setHasOptions(true); + } + $itemXmlObj->addChild('has_options', (int)$item->getHasOptions()); + + $icon = $this->helper('catalog/image')->init($item, 'small_image') + ->resize(Mage::helper('xmlconnect/image')->getImageSizeForContent('product_small')); + + $iconXml = $itemXmlObj->addChild('icon', $icon); + + $baseUrl = Mage::getBaseUrl('media'); + $path = str_replace($baseUrl, '', $icon); + $file = Mage::getBaseDir('media') . DS . str_replace('/', DS, $path); + + $iconXml->addAttribute('modification_time', filemtime($file)); + + + $itemXmlObj->addChild('description', $wishlistXmlObj->xmlentities(strip_tags($item->getWishlistItemDescription()))); + $itemXmlObj->addChild('added_date', $wishlistXmlObj->xmlentities($this->getFormatedDate($item->getAddedAt()))); + + if ($this->getChild('product_price')) { + $this->getChild('product_price')->setProduct($item) + ->setProductXmlObj($itemXmlObj) + ->collectProductPrices(); + } + + if (!$item->getRatingSummary()) { + Mage::getModel('review/review') + ->getEntitySummary($item, Mage::app()->getStore()->getId()); + } + + $itemXmlObj->addChild('rating_summary', round((int)$item->getRatingSummary()->getRatingSummary() / 10)); + $itemXmlObj->addChild('reviews_count', $item->getRatingSummary()->getReviewsCount()); + } + } + + return $wishlistXmlObj->asNiceXml(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Controller/Action.php b/app/code/core/Mage/XmlConnect/XmlConnect/Controller/Action.php new file mode 100644 index 0000000000..73211c4eb3 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Controller/Action.php @@ -0,0 +1,102 @@ +getResponse()->setHeader('Content-type', 'text/xml; charset=UTF-8'); + + /** + * Load application by specified code and make sure that application exists + */ + $cookieName = Mage_XmlConnect_Model_Application::APP_CODE_COOKIE_NAME; + $appCode = isset($_COOKIE[$cookieName]) ? (string) $_COOKIE[$cookieName] : ''; + $screenSizeCookieName = Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_NAME; + $screenSize = isset($_COOKIE[$screenSizeCookieName]) ? (string) $_COOKIE[$screenSizeCookieName] : ''; + if (!$appCode) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid app code.'), self::MESSAGE_STATUS_ERROR); + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + return; + } + $appModel = Mage::getModel('xmlconnect/application')->loadByCode($appCode); + $appModel->setScreenSize($screenSize); + if ($appModel && $appModel->getId()) { + Mage::app()->setCurrentStore(Mage::app()->getStore($appModel->getStoreId())->getCode()); + Mage::getSingleton('core/locale')->emulate($appModel->getStoreId()); + Mage::register('current_app', $appModel); + } else { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid app code.'), self::MESSAGE_STATUS_ERROR); + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + return; + } + } + + /** + * Validate response body + */ + public function postDispatch() + { + parent::postDispatch(); + $body = $this->getResponse()->getBody(); + if (empty($body)) { + $this->_message( + Mage::helper('xmlconnect')->__('An error occurred while processing your request.'), + self::MESSAGE_STATUS_ERROR + ); + } + } + + /** + * Generate message xml and set it to response body + * + * @param string $text + * @param string $status + * @param string $type + * @param string $action + */ + protected function _message($text, $status, $type='', $action='') + { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', $status); + $message->addChild('text', $text); + $this->getResponse()->setBody($message->asNiceXml()); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Catalog/Category/Image.php b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Catalog/Category/Image.php new file mode 100644 index 0000000000..0ff82fcd65 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Catalog/Category/Image.php @@ -0,0 +1,95 @@ + + */ +class Mage_XmlConnect_Helper_Catalog_Category_Image extends Mage_Catalog_Helper_Image +{ + + /** + * Init + * + * @param Mage_Catalog_Model_Product $product + * @param string $attributeName + * @param string $imageFile + * @return Mage_XmlConnect_Helper_Catalog_Category_Image + * + */ + public function init(Mage_Catalog_Model_Product $product, $attributeName, $imageFile = null) + { + return $this; + } + + /** + * Init image helper object + * + * @param Mage_Catalog_Model_Abstract $category + * @param string $attributeName + * @param string $imageFile + * @return Mage_XmlConnect_Helper_Catalog_Category_Image + */ + public function initialize(Mage_Catalog_Model_Abstract $category, $attributeName, $imageFile = null) + { + $this->_reset(); + $this->_setModel(Mage::getModel('xmlconnect/catalog_category_image')); + $this->_getModel()->setDestinationSubdir($attributeName); + $this->setProduct($category); + + $this->setWatermark(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_image")); + $this->setWatermarkImageOpacity(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_imageOpacity")); + $this->setWatermarkPosition(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_position")); + $this->setWatermarkSize(Mage::getStoreConfig("design/watermark/{$this->_getModel()->getDestinationSubdir()}_size")); + + if ($imageFile) { + $this->setImageFile($imageFile); + } else { + /* + * add for work original size + */ + $this->_getModel()->setBaseFile( $this->getProduct()->getData($this->_getModel()->getDestinationSubdir()) ); + } + return $this; + } + + /** + * Return placeholder image file path + * + * @return string + */ + public function getPlaceholder() + { + if (!$this->_placeholder) { + $attr = $this->_getModel()->getDestinationSubdir(); + $this->_placeholder = 'images/xmlconnect/catalog/category/placeholder/'.$attr.'.jpg'; + } + return $this->_placeholder; + } + + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Data.php b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Data.php new file mode 100644 index 0000000000..43099e1779 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Data.php @@ -0,0 +1,342 @@ +_getUrl() function to public + * + * @param string $route + * @param array $params + * @return array + */ + public function getUrl($route, $params = array()) + { + return $this->_getUrl($route, $params); + } + + + /** + * Retrieve country options array + * + * @return array + */ + public function getCountryOptionsArray() + { + Varien_Profiler::start('TEST: '.__METHOD__); + + $cacheKey = 'XMLCONNECT_COUNTRY_SELECT_STORE_'.Mage::app()->getStore()->getCode(); + if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) { + $options = unserialize($cache); + } else { + $options = Mage::getModel('directory/country') + ->getResourceCollection() + ->loadByStore() + ->toOptionArray(); + if (Mage::app()->useCache('config')) { + Mage::app()->saveCache(serialize($options), $cacheKey, array('config')); + } + } + Varien_Profiler::stop('TEST: '.__METHOD__); + return $options; + } + + /** + * Get list of predefined and supported Devices + * + * @return array + */ + static public function getSupportedDevices() + { + $devices = array ( + 'iphone' => Mage::helper('xmlconnect')->__('iPhone') + ); + + return $devices; + } + + /** + * Get list of predefined and supported Devices + * + * @return array + */ + public function getStatusOptions() + { + $options = array ( + Mage_XmlConnect_Model_Application::APP_STATUS_SUCCESS => Mage::helper('xmlconnect')->__('Submitted'), + Mage_XmlConnect_Model_Application::APP_STATUS_INACTIVE => Mage::helper('xmlconnect')->__('Not Submitted'), + ); + return $options; + } + + /** + * Retrieve supported device types as "html select options" + * + * @return array + */ + public function getDeviceTypeOptions() + { + $devices = self::getSupportedDevices(); + $options = array(); + if (count($devices) > 1) { + $options[] = array('value' => '', 'label' => Mage::helper('xmlconnect')->__('Please Select Device Type')); + } + foreach ($devices as $type => $label) { + $options[] = array('value' => $type, 'label' => $label); + } + return $options; + } + + /** + * Get default application tabs + * + * @return array + */ + public function getDefaultApplicationDesignTabs() + { + if (!isset($this->_tabs)) { + $this->_tabs = array( + array( + 'label' => Mage::helper('xmlconnect')->__('Home'), + 'image' => 'tab_home.png', + 'action' => 'Home', + ), + array( + 'label' => Mage::helper('xmlconnect')->__('Shop'), + 'image' => 'tab_shop.png', + 'action' => 'Shop', + ), + array( + 'label' => Mage::helper('xmlconnect')->__('Search'), + 'image' => 'tab_search.png', + 'action' => 'Search', + ), + array( + 'label' => Mage::helper('xmlconnect')->__('Cart'), + 'image' => 'tab_cart.png', + 'action' => 'Cart', + ), + array( + 'label' => Mage::helper('xmlconnect')->__('More'), + 'image' => 'tab_more.png', + 'action' => 'More', + ), + array( + 'label' => Mage::helper('xmlconnect')->__('Account'), + 'image' => 'tab_account.png', + 'action' => 'Account', + ), + array( + 'label' => Mage::helper('xmlconnect')->__('More Info'), + 'image' => 'tab_page.png', + 'action' => 'AboutUs', + ), + ); + } + return $this->_tabs; + } + + /** + * Return array for tabs like label -> action array + * + * @return array + */ + protected function _getTabLabelActionArray() + { + if (!isset($this->_tabLabelActionArray)) { + $this->_tabLabelActionArray = array(); + foreach ($this->getDefaultApplicationDesignTabs() as $tab) { + $this->_tabLabelActionArray[$tab['action']] = $tab['label']; + } + } + return $this->_tabLabelActionArray; + } + + /** + * Return Translated tab label for given $action + * + * @param string $action + * @return string|bool + */ + public function getTabLabel($action) + { + $action = (string) $action; + $tabs = $this->_getTabLabelActionArray(); + return (isset($tabs[$action])) ? $tabs[$action] : false; + } + + /** + * Merges $changes array to $target array recursive, overwriting existing key, and adding new one + * + * @param mixed $target + * @param mixed $changes + * @return array + */ + static public function arrayMergeRecursive($target, $changes) + { + if (!is_array($target)) { + $target = empty($target) ? array() : array($target); + } + if (!is_array($changes)) { + $changes = array($changes); + } + foreach ($changes as $key => $value) { + if (!array_key_exists($key, $target) and !is_numeric($key)) { + $target[$key] = $changes[$key]; + continue; + } + if (is_array($value) or is_array($target[$key])) { + $target[$key] = self::arrayMergeRecursive($target[$key], $changes[$key]); + } else if (is_numeric($key)) { + if (!in_array($value, $target)) { + $target[] = $value; + } + } else { + $target[$key] = $value; + } + } + + return $target; + } + + /** + * Wrap $body with HTML4 headers + * + * @param string $body + * @return string + */ + public function htmlize($body) + { + return << $v) { + if (!$k) { + continue; + } + $items[] = ' + + + ' . ($k ? $k : '') . ' + '; + } + $result = implode('', $items); + return $result; + } + + /** + * Return Solo Xml optional fieldset + * + * @param string $ssCcMonths + * @param string $ssCcYears + * @return string + */ + public function getSoloXml($ssCcMonths, $ssCcYears) + { + // issue number ==== validate-cc-ukss cvv + $solo = << + + + SS + SM + SO + + + + + + + + ; + + + $ssCcMonths + + + + + $ssCcYears + + + +EOT; + return $solo; + } + + /** + * Format price for correct view inside xml strings + * + * @param float $price + * @return string + */ + public function formatPriceForXml($price) + { + return sprintf('%01.2F', $price); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Image.php b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Image.php new file mode 100644 index 0000000000..f182f4c19f --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Image.php @@ -0,0 +1,622 @@ +getOriginalSizeUploadDir(); + + $this->_forcedConvertPng($field); + + try { + $uploader = new Varien_File_Uploader($field); + $uploader->setAllowedExtensions(array('jpg', 'jpeg', 'gif', 'png')); + $uploader->setAllowRenameFiles(true); + $uploader->save($uploadDir); + $uploadedFilename = $uploader->getUploadedFileName(); + $uploadedFilename = $this->_getResizedFilename($field, $uploadedFilename, true); + } catch (Exception $e) { + /** + * Hard coded exception catch + */ + if ($e->getMessage() == 'Disallowed file type.') { + $filename = $_FILES[$field]['name']; + Mage::throwException(Mage::helper('xmlconnect')->__('Error while uploading file "%s". Disallowed file type. Only "jpg", "jpeg", "gif", "png" are allowed.', $filename)); + } else { + Mage::logException($e); + } + } + return $uploadedFilename; + } + + /** + * Return current screen_size parameter + * + * @return string + */ + protected function _getScreenSize() + { + return Mage::registry('current_app')->getScreenSize(); + } + + /** + * Return correct system filename for current screenSize + * + * @param string $fieldPath + * @param string $fileName + * @param string $default + * @return string + */ + protected function _getResizedFilename($fieldPath, $fileName, $default = false) + { + $fileName = basename($fileName); + if ($default) { + $dir = $this->getDefaultSizeUploadDir(); + } else { + $dir = $this->getCustomSizeUploadDir($this->_getScreenSize()); + } + $customSizeFileName = $dir . DS . $fileName; + $originalSizeFileName = $this->getOriginalSizeUploadDir(). DS . $fileName; + $error = false; + /** + * Compatibility with old versions of XmlConnect + */ + if (!file_exists($originalSizeFileName)) { + $oldFileName = $this->getOldUploadDir() . DS . $fileName; + if (file_exists($oldFileName)) { + if (!(copy($oldFileName, $originalSizeFileName) && + (is_readable($customSizeFileName) || chmod($customSizeFileName, 0644)))) { + Mage::throwException(Mage::helper('xmlconnect')->__('Error while processing file "%s".', $fileName)); + } + } else { + Mage::throwException(Mage::helper('xmlconnect')->__('No such file "%s".', $fileName)); + } + } + + if ((!$error) && copy($originalSizeFileName, $customSizeFileName) && + (is_readable($customSizeFileName) || chmod($customSizeFileName, 0644))) { + $this->_handleResize($fieldPath, $customSizeFileName); + } else { + $fileName = ''; + if (isset($_FILES[$fieldPath]) && is_array($_FILES[$fieldPath]) && isset($_FILES[$fieldPath]['name'])) { + $fileName = $_FILES[$fieldPath]['name']; + } + Mage::throwException(Mage::helper('xmlconnect')->__('Error while uploading file "%s".', $fileName)); + } + return $customSizeFileName; + } + + /** + * Resize uploaded file + * + * @param string $fieldPath + * @param string $file + * @return void + */ + protected function _handleResize($fieldPath, $file) + { + $nameParts = explode('/', $fieldPath); + array_shift($nameParts); + $conf = $this->getInterfaceImageLimits(); + while (count($nameParts)) { + $next = array_shift($nameParts); + if (isset($conf[$next])) { + $conf = $conf[$next]; + } else { + /** + * No config data - nothing to resize + */ + return; + } + } + + $image = new Varien_Image($file); + $width = $image->getOriginalWidth(); + $height = $image->getOriginalHeight(); + + if (isset($conf['widthMax']) && ($conf['widthMax'] < $width)) { + $width = $conf['widthMax']; + } elseif (isset($conf['width'])) { + $width = $conf['width']; + } + + if (isset($conf['heightMax']) && ($conf['heightMax'] < $height)) { + $height = $conf['heightMax']; + } elseif (isset($conf['height'])) { + $height = $conf['height']; + } + + if (($width != $image->getOriginalWidth()) || + ($height != $image->getOriginalHeight()) ) { + $image->keepTransparency(true); + $image->keepFrame(true); + $image->keepAspectRatio(true); + $image->backgroundColor(array(255, 255, 255)); +// $image->keepAspectRatio(false); + $image->resize($width, $height); + $image->save(null, basename($file)); + } + } + + /** + * Convert uploaded file to PNG + * + * @param string $field + */ + protected function _forcedConvertPng($field) + { + $file =& $_FILES[$field]; + + $file['name'] = preg_replace('/\.(gif|jp[e]g)$/i', '.png', $file['name']); + + list($x, $x, $fileType) = getimagesize($file['tmp_name']); + if ($fileType != IMAGETYPE_PNG ) { + switch( $fileType ) { + case IMAGETYPE_GIF: + $img = imagecreatefromgif($file['tmp_name']); + break; + case IMAGETYPE_JPEG: + $img = imagecreatefromjpeg($file['tmp_name']); + break; + default: + return; + } + imagealphablending($img, false); + imagesavealpha($img, true); + imagepng($img, $file['tmp_name']); + imagedestroy($img); + } + } + + /** + * Retrieve xmlconnect images skin url + * + * @param string $name + * @return string + */ + public function getSkinImagesUrl($name = null) + { + return Mage::getDesign()->getSkinUrl('images/xmlconnect/' . $name); + } + + /** + * Return CustomSizeDirPrefix + * + * @return string + */ + public function getCustomSizeDirPrefix() + { + return $this->_getScreenSize() . DS . 'custom'; + } + + /** + * Return FileDefaultSizeSuffixAsUrl + * + * @param string $fileName + * @return string + */ + public function getFileDefaultSizeSuffixAsUrl($fileName) + { + return 'custom'.'/'.$this->_getScreenSize().'/'.basename($fileName); + } + + /** + * Return getFileCustomDirSuffixAsUrl + * + * @param string $confPath + * @param string $fileName + * @return string + */ + public function getFileCustomDirSuffixAsUrl($confPath, $fileName) + { + return 'custom'.'/'.$this->_getScreenSize().'/'.basename($this->_getResizedFilename($confPath, $fileName)); + } + + /** + * Return correct size for given $imageName and device screen_size + * + * @param string $imageName + * @return int + */ + public function getImageSizeForContent($imageName) + { + $size = 0; + if (!isset($this->_content)) { + $app = Mage::registry('current_app'); + if (!$app) { + return 0; + } else { + $imageLimits = $this->getImageLimits($this->_getScreenSize()); + if (($imageLimits['content']) && is_array($imageLimits['content'])) { + $this->_content = $imageLimits['content']; + } else { + $this->_content = array(); + } + } + } + $size = isset($this->_content[$imageName]) ? (int) $this->_content[$imageName] : 0; + return $size; + } + + /** + * Return setting for interface images (image size limits) + * + * @return array + */ + public function getInterfaceImageLimits() + { + if (!isset($this->_interface)) { + $imageLimits = $this->getImageLimits($this->_getScreenSize()); + $this->_interface = $imageLimits['interface']; + } + return $this->_interface; + } + + /** + * Return correct size for given $imageName and device screen_size + * + * @param string $imagePath + * @return int + */ + public function getImageSizeForInterface($imagePath) + { + $size = 0; + if (!isset($this->_interfacePath[$imagePath])) { + $app = Mage::registry('current_app'); + if (!$app) { + return 0; + } else { + $imageLimits = $this->getImageLimits($this->_getScreenSize()); + $size = $this->findPath($imageLimits, $imagePath); + $this->_interfacePath[$imagePath] = $size; + } + } + $size = isset($this->_interfacePath[$imagePath]) ? (int) $this->_interfacePath[$imagePath] : 0; + return $size; + } + + + /** + * Ensure correct $screenSize value + * + * @param string $screenSize + * @return string + */ + public function filterScreenSize($screenSize) + { + + $screenSize = preg_replace('/[^0-9A-z_]/', '', $screenSize); + if (isset($this->_imageLimits[$screenSize])) { + return $screenSize; + } + $screenSizeExplodeArray = explode(self::XMLCONNECT_GLUE, $screenSize); + $version = ''; + switch (count($screenSizeExplodeArray)) { + case 2: + $version = $screenSizeExplodeArray[1]; + case 1: + $resolution = $screenSizeExplodeArray[0]; + break; + default: + $resolution = Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_DEFAULT; + break; + } + + $sourcePath = empty($version) ? Mage_XmlConnect_Model_Application::APP_SCREEN_SOURCE_DEFAULT : $version; + $xmlPath = 'screen_size/'.self::XMLCONNECT_GLUE.$resolution.'/'.$sourcePath.'/source'; + + + $source = Mage::getStoreConfig($xmlPath); + if (!empty($source)) { + $screenSize = $resolution . (empty($version) ? '' : self::XMLCONNECT_GLUE.$version); + } else { + $screenSize = Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_DEFAULT; + } + return $screenSize; + } + + /** + * Return correct size array for given device screen_size(320x480/640x960_a) + * + * @param string $screenSize + * @return array + */ + public function getImageLimits($screenSize = Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_DEFAULT) + { + $defaultScreenSize = Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_DEFAULT; + $defaultScreenSource = Mage_XmlConnect_Model_Application::APP_SCREEN_SOURCE_DEFAULT; + + $screenSize = preg_replace('/[^0-9A-z_]/', '', $screenSize); + if (isset($this->_imageLimits[$screenSize])) { + return $this->_imageLimits[$screenSize]; + } + $screenSizeExplodeArray = explode(self::XMLCONNECT_GLUE, $screenSize); + $version = ''; + switch (count($screenSizeExplodeArray)) { + case 2: + $version = $screenSizeExplodeArray[1]; + case 1: + $resolution = $screenSizeExplodeArray[0]; + break; + default: + $resolution = $defaultScreenSize; + break; + } + + $sourcePath = empty($version) ? $defaultScreenSource : $version; + $xmlPath = 'screen_size/'.self::XMLCONNECT_GLUE.$resolution.'/'.$sourcePath; + + $root = Mage::getStoreConfig($xmlPath); + $updates = array(); + if (!empty($root)) { + $screenSize = $resolution . (empty($version) ? '' : self::XMLCONNECT_GLUE.$version); + $source = !empty($root['source']) ? $root['source'] : $defaultScreenSource; + $updates = isset($root['updates']) && is_array($root['updates']) ? $root['updates'] : array(); + } else { + $screenSize = $defaultScreenSize; + $source = $defaultScreenSource; + } + $imageLimits = Mage::getStoreConfig('screen_size/'.$source); + if (!is_array($imageLimits)) { + $imageLimits = Mage::getStoreConfig('screen_size/default'); + } + + foreach ($updates as $update) { + $path = $update['path']; + $function = $update['function']; + switch ($function) { + case 'zoom': + $data = $update['data']; + $target =& $this->findPath($imageLimits, $path); + if (is_array($target)) { + array_walk_recursive($target, array($this, '_zoom'), $data); + } else { + $this->_zoom($target, null, $data); + } + break; + case 'update': + $data = $update['data']; + $target =& $this->findPath($imageLimits, $path); + $target = $data; + break; + case 'insert': + $data = $update['data']; + $target =& $this->findPath($imageLimits, $path); + if (($target !== null) && (is_array($target)) && (is_array($data))) { + foreach ($data as $key => $val) { + $target[$key] = $val; + } + } + break; + case 'delete': + $data = $update['data']; + $target =& $this->findPath($imageLimits, $path); + if (isset($target[$data])) { + unset($target[$data]); + } + break; + default: + break; + } + + } + if (!is_array($imageLimits)) { + $imageLimits = array(); + } + + $this->_imageLimits[$screenSize] = $imageLimits; + return $imageLimits; + } + + /** + * Return reference to the $path in $array + * + * @param array $array + * @param string $path + * @return &mixed //(reference) + */ + public function &findPath(&$array, $path) + { + $target =& $array; + if ($path !== '/') { + $pathArray = explode('/', $path); + foreach ($pathArray as $node) { + if (is_array($target) && isset($target[$node])) { + $target =& $target[$node]; + } else { + $targetNull = null; + return $targetNull; + } + } + } + return $target; + } + + /** + * Multiply given $item by $value if non array + * + * @param mixed $item (argument to change) + * @param mixed $key (not used) + * @param string $value (contains float) + * @return void + */ + protected function _zoom(&$item, $key, $value) + { + if (is_string($item)) { + $item = (int) round($item*$value); + } + } + + /** + * Ensure $dir exists (if not then create one) + * + * @param string $dir + * @throw Mage_Core_Exception + */ + protected function _verifyDirExist($dir) + { + $io = new Varien_Io_File(); + $io->checkAndCreateFolder($dir); + } + + /** + * Return customSizeUploadDir path + * + * @param string $screenSize + * @return string + */ + public function getCustomSizeUploadDir($screenSize) + { + $screenSize = $this->filterScreenSize($screenSize); + $customDirRoot = Mage::getBaseDir('media') . DS . 'xmlconnect' . DS . 'custom'; + $this->_verifyDirExist($customDirRoot); + $customDir = $customDirRoot . DS .$screenSize; + $this->_verifyDirExist($customDir); + return $customDir; + } + + /** + * Return originalSizeUploadDir path + * + * @return string + */ + public function getOriginalSizeUploadDir() + { + $dir = Mage::getBaseDir('media') . DS . 'xmlconnect' . DS . 'original'; + $this->_verifyDirExist($dir); + return $dir; + } + + /** + * Return oldUpload dir path (media/xmlconnect) + * + * @return string + */ + public function getOldUploadDir() + { + $dir = Mage::getBaseDir('media') . DS . 'xmlconnect'; + $this->_verifyDirExist($dir); + return $dir; + } + + /** + * Return default size upload dir path + * + * @return string + */ + public function getDefaultSizeUploadDir() + { + return $this->getCustomSizeUploadDir(Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_DEFAULT); + } + + /** + * Return array for interface images paths in the config + * + * @return array + */ + public function getInterfaceImagesPathsConf() + { + if (!isset($this->_confPaths)) { + $paths = $this->getInterfaceImagesPaths(); + $this->_confPaths = array(); + $len = strlen('conf/native/'); + foreach ($paths as $path => $defaultFileName) { + $this->_confPaths[$path] = substr($path, $len); + } + } + return $this->_confPaths; + } + + /** + * Return 1) default interface image path for specified $imagePath + * 2) array of image paths + * + * @param string $imagePath + * @return array|string + */ + public function getInterfaceImagesPaths($imagePath = null) + { + $paths = array ( + 'conf/native/navigationBar/icon' => 'smallIcon_1_6.png', + 'conf/native/body/bannerImage' => 'banner_1_2.png', + 'conf/native/body/backgroundImage' => 'accordion_open.png', + ); + if ($imagePath == null) { + return $paths; + } else if (isset($paths[$imagePath])) { + return $paths[$imagePath]; + } else { + return null; + } + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Iphone.php b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Iphone.php new file mode 100644 index 0000000000..1cf20fb9e5 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Iphone.php @@ -0,0 +1,341 @@ + array( + 'body' => array( + 'backgroundColor' => '#ABABAB', + 'scrollBackgroundColor' => '#EDEDED', + ), + 'itemActions' => array( + 'relatedProductBackgroundColor' => '#404040', + ), + 'fonts' => array( + 'Title1' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '14', + 'color' => '#FEFEFE', + ), + 'Title2' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '12', + 'color' => '#222222', + ), + 'Title3' => array( + 'name' => 'HelveticaNeue', + 'size' => '13', + 'color' => '#000000', + ), + 'Title4' => array( + 'name' => 'HelveticaNeue', + 'size' => '12', + 'color' => '#FFFFFF', + ), + 'Title5' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '13', + 'color' => '#dc5f02', + ), + 'Title6' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '16', + 'color' => '#222222', + ), + 'Title7' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '13', + 'color' => '#000000', + ), + 'Title8' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '11', + 'color' => '#FFFFFF', + ), + 'Title9' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '12', + 'color' => '#FFFFFF', + ), + 'Text1' => array( + 'name' => 'HelveticaNeue-Bold', + 'size' => '12', + 'color' => '#777777', + ), + 'Text2' => array( + 'name' => 'HelveticaNeue', + 'size' => '10', + 'color' => '#555555', + ), + ), + ), + ); + } + + /** + * List of allowed fonts for iPhone application + * + * @return array + */ + public function getFontList() + { + return array( + array( + 'value' => 'HiraKakuProN-W3', + 'label' => 'HiraKakuProN-W3', + ), + array( + 'value' => 'Courier', + 'label' => 'Courier', + ), + array( + 'value' => 'Courier-BoldOblique', + 'label' => 'Courier-BoldOblique', + ), + array( + 'value' => 'Courier-Oblique', + 'label' => 'Courier-Oblique', + ), + array( + 'value' => 'Courier-Bold', + 'label' => 'Courier-Bold', + ), + array( + 'value' => 'ArialMT', + 'label' => 'ArialMT', + ), + array( + 'value' => 'Arial-BoldMT', + 'label' => 'Arial-BoldMT', + ), + array( + 'value' => 'Arial-BoldItalicMT', + 'label' => 'Arial-BoldItalicMT', + ), + array( + 'value' => 'Arial-ItalicMT', + 'label' => 'Arial-ItalicMT', + ), + array( + 'value' => 'STHeitiTC-Light', + 'label' => 'STHeitiTC-Light', + ), + array( + 'value' => 'STHeitiTC-Medium', + 'label' => 'STHeitiTC-Medium', + ), + array( + 'value' => 'AppleGothic', + 'label' => 'AppleGothic', + ), + array( + 'value' => 'CourierNewPS-BoldMT', + 'label' => 'CourierNewPS-BoldMT', + ), + array( + 'value' => 'CourierNewPS-ItalicMT', + 'label' => 'CourierNewPS-ItalicMT', + ), + array( + 'value' => 'CourierNewPS-BoldItalicMT', + 'label' => 'CourierNewPS-BoldItalicMT', + ), + array( + 'value' => 'CourierNewPSMT', + 'label' => 'CourierNewPSMT', + ), + array( + 'value' => 'Zapfino', + 'label' => 'Zapfino', + ), + array( + 'value' => 'HiraKakuProN-W6', + 'label' => 'HiraKakuProN-W6', + ), + array( + 'value' => 'ArialUnicodeMS', + 'label' => 'ArialUnicodeMS', + ), + array( + 'value' => 'STHeitiSC-Medium', + 'label' => 'STHeitiSC-Medium', + ), + array( + 'value' => 'STHeitiSC-Light', + 'label' => 'STHeitiSC-Light', + ), + array( + 'value' => 'AmericanTypewriter', + 'label' => 'AmericanTypewriter', + ), + array( + 'value' => 'AmericanTypewriter-Bold', + 'label' => 'AmericanTypewriter-Bold', + ), + array( + 'value' => 'Helvetica-Oblique', + 'label' => 'Helvetica-Oblique', + ), + array( + 'value' => 'Helvetica-BoldOblique', + 'label' => 'Helvetica-BoldOblique', + ), + array( + 'value' => 'Helvetica', + 'label' => 'Helvetica', + ), + array( + 'value' => 'Helvetica-Bold', + 'label' => 'Helvetica-Bold', + ), + array( + 'value' => 'MarkerFelt-Thin', + 'label' => 'MarkerFelt-Thin', + ), + array( + 'value' => 'HelveticaNeue', + 'label' => 'HelveticaNeue', + ), + array( + 'value' => 'HelveticaNeue-Bold', + 'label' => 'HelveticaNeue-Bold', + ), + array( + 'value' => 'DBLCDTempBlack', + 'label' => 'DBLCDTempBlack', + ), + array( + 'value' => 'Verdana-Bold', + 'label' => 'Verdana-Bold', + ), + array( + 'value' => 'Verdana-BoldItalic', + 'label' => 'Verdana-BoldItalic', + ), + array( + 'value' => 'Verdana', + 'label' => 'Verdana', + ), + array( + 'value' => 'Verdana-Italic', + 'label' => 'Verdana-Italic', + ), + array( + 'value' => 'TimesNewRomanPSMT', + 'label' => 'TimesNewRomanPSMT', + ), + array( + 'value' => 'TimesNewRomanPS-BoldMT', + 'label' => 'TimesNewRomanPS-BoldMT', + ), + array( + 'value' => 'TimesNewRomanPS-BoldItalicMT', + 'label' => 'TimesNewRomanPS-BoldItalicMT', + ), + array( + 'value' => 'TimesNewRomanPS-ItalicMT', + 'label' => 'TimesNewRomanPS-ItalicMT', + ), + array( + 'value' => 'Georgia-Bold', + 'label' => 'Georgia-Bold', + ), + array( + 'value' => 'Georgia', + 'label' => 'Georgia', + ), + array( + 'value' => 'Georgia-BoldItalic', + 'label' => 'Georgia-BoldItalic', + ), + array( + 'value' => 'Georgia-Italic', + 'label' => 'Georgia-Italic', + ), + array( + 'value' => 'STHeitiJ-Medium', + 'label' => 'STHeitiJ-Medium', + ), + array( + 'value' => 'STHeitiJ-Light', + 'label' => 'STHeitiJ-Light', + ), + array( + 'value' => 'ArialRoundedMTBold', + 'label' => 'ArialRoundedMTBold', + ), + array( + 'value' => 'TrebuchetMS-Italic', + 'label' => 'TrebuchetMS-Italic', + ), + array( + 'value' => 'TrebuchetMS', + 'label' => 'TrebuchetMS', + ), + array( + 'value' => 'Trebuchet-BoldItalic', + 'label' => 'Trebuchet-BoldItalic', + ), + array( + 'value' => 'TrebuchetMS-Bold', + 'label' => 'TrebuchetMS-Bold', + ), + array( + 'value' => 'STHeitiK-Medium', + 'label' => 'STHeitiK-Medium', + ), + array( + 'value' => 'STHeitiK-Light', + 'label' => 'STHeitiK-Light', + ), + ); + } + + /** + * List of allowed font sizes for iPhone application + * + * @return array + */ + public function getFontSizes() + { + $result = array( ); + for ($i = 6; $i < 32; $i++) { + $result[] = array( + 'value' => $i, + 'label' => $i . ' pt', + ); + } + return $result; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Payment.php b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Payment.php new file mode 100644 index 0000000000..ed6262d333 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Payment.php @@ -0,0 +1,42 @@ + methodCode array + * + * @deprecated after 1.4.2.0 + * @return array + */ + public function getPaymentMethodCodeList() + { + return array(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Theme.php b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Theme.php new file mode 100644 index 0000000000..2b6aad8988 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Helper/Theme.php @@ -0,0 +1,174 @@ + 'conf[native][navigationBar][tintColor]', + 'conf_native_body_primaryColor' => 'conf[native][body][primaryColor]', + 'conf_native_body_secondaryColor' => 'conf[native][body][secondaryColor]', + 'conf_native_categoryItem_backgroundColor' => 'conf[native][categoryItem][backgroundColor]', + 'conf_native_categoryItem_tintColor' => 'conf[native][categoryItem][tintColor]', + + 'conf_extra_fontColors_header' => 'conf[extra][fontColors][header]', + 'conf_extra_fontColors_primary' => 'conf[extra][fontColors][primary]', + 'conf_extra_fontColors_secondary' => 'conf[extra][fontColors][secondary]', + 'conf_extra_fontColors_price' => 'conf[extra][fontColors][price]', + + 'conf_native_body_backgroundColor' => 'conf[native][body][backgroundColor]', + 'conf_native_body_scrollBackgroundColor' => 'conf[native][body][scrollBackgroundColor]', + 'conf_native_itemActions_relatedProductBackgroundColor' => 'conf[native][itemActions][relatedProductBackgroundColor]' + ); + return $themesArray; + } + + /** + * Returns JSON ready Themes array + * + * @param bool $flushCache - load defaults + * @return array + */ + public function getAllThemesArray($flushCache = false) + { + $result = array(); + $themes = $this->getAllThemes($flushCache); + foreach ($themes as $theme) { + $result[$theme->getName()] = $theme->getFormData(); + } + return $result; + } + + /** + * Reads directory media/xmlconnect/themes/* + * + * @param bool $flushCache - Reads default color Themes + * @return array - (of Mage_XmlConnect_Model_Theme) + */ + public function getAllThemes($flushCache = false) + { + if (!$this->_themeArray || $flushCache) { + $saveLibxmlErrors = libxml_use_internal_errors(TRUE); + $this->_themeArray = array(); + $themeDir = Mage::getBaseDir('media') . DS . 'xmlconnect' . DS . 'themes'; + $io = new Varien_Io_File(); + $io->open(array('path' => $themeDir)); + + $fileList = $io->ls(Varien_Io_File::GREP_FILES); + foreach ($fileList as $file) { + $src = $themeDir . DS . $file['text']; + if (is_readable($src)) { + try { + $theme = Mage::getModel('xmlconnect/theme', $src); + $this->_themeArray[$theme->getName()] = $theme; + } catch (Exception $e) { + Mage::logException($e); + } + } + } + libxml_use_internal_errors($saveLibxmlErrors); + } + return $this->_themeArray; + } + + /** + * Reset all theme color changes + * Copy media/xmlconnect/themes/default/* to media/xmlconnect/themes/* + * + * @return void + */ + public function resetAllThemes() + { + $themeDir = Mage::getBaseDir('media') . DS . 'xmlconnect' . DS . 'themes'; + $defaultThemeDir = Mage::getBaseDir('media') . DS . 'xmlconnect' . DS . 'themes' . DS . 'default'; + + $io = new Varien_Io_File(); + $io->open(array('path'=>$defaultThemeDir)); + $fileList = $io->ls(Varien_Io_File::GREP_FILES); + foreach ($fileList as $file) { + $f = $file['text']; + $src = $defaultThemeDir . DS . $f; + $dst = $themeDir . DS .$f; + if ($io->isWriteable($dst)) { + try { + if (!$io->cp($src, $dst)) { + Mage::throwException(Mage::helper('xmlconnect')->__('Can\t copy file "%s" to "%s".', $src, $dst)); + } else { + $io->chmod($dst, 0755); + } + } catch (Exception $e) { + Mage::logException($e); + } + } + } + } + + /** + * Get theme object by name + * + * @param string $name + * @return Mage_XmlConnect_Model_Theme|null + */ + public function getThemeByName($name) + { + $themes = $this->getAllThemes(); + $theme = isset($themes[$name]) ? $themes[$name] : null; + return $theme; + } + + /** + * Return predefined custom theme name + * + * @return string + */ + public function getCustomThemeName() + { + return 'custom'; + } + + /** + * Return predefined default theme name + * + * @return string + */ + public function getDefaultThemeName() + { + return 'default'; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Adminhtml/System/Config/Backend/Baseurl.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Adminhtml/System/Config/Backend/Baseurl.php new file mode 100644 index 0000000000..cbebbf51a7 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Adminhtml/System/Config/Backend/Baseurl.php @@ -0,0 +1,43 @@ +isValueChanged()) { + Mage::getModel('xmlconnect/application')->updateAllAppsUpdatedAtParameter(); + } + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Adminhtml/System/Config/Backend/Currency/Default.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Adminhtml/System/Config/Backend/Currency/Default.php new file mode 100644 index 0000000000..36749aa27b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Adminhtml/System/Config/Backend/Currency/Default.php @@ -0,0 +1,43 @@ +isValueChanged()) { + Mage::getModel('xmlconnect/application')->updateAllAppsUpdatedAtParameter(); + } + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Application.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Application.php new file mode 100644 index 0000000000..20d60294a8 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Application.php @@ -0,0 +1,728 @@ + + */ +class Mage_XmlConnect_Model_Application extends Mage_Core_Model_Abstract +{ + + /** + * Application code cookie name + */ + const APP_CODE_COOKIE_NAME = 'app_code'; + + /** + * Device screen size name + */ + const APP_SCREEN_SIZE_NAME = 'screen_size'; + + + /** + * Device screen size name + */ + const APP_SCREEN_SIZE_DEFAULT = '320x480'; + + /** + * Device screen size source name + */ + const APP_SCREEN_SOURCE_DEFAULT = 'default'; + + /** + * Application status "submitted" value + * + * @var int + */ + const APP_STATUS_SUCCESS = 1; + + /** + * Application status "not submitted" value + * + * @var int + */ + const APP_STATUS_INACTIVE = 0; + + /** + * Application prefix length of cutted part of deviceType and storeCode + * + * @var int + */ + const APP_PREFIX_CUT_LENGTH = 3; + + /** + * Images in "Params" history table + * + * @var array + */ + protected $_imageIds = array('icon', 'loader_image', 'logo', 'big_logo'); + + + /** + * Initialize application + */ + protected function _construct() + { + $this->_init('xmlconnect/application'); + } + + /** + * Checks is it app is submitted + * (edit is premitted only before submission) + * + * @return bool + */ + public function getIsSubmitted() + { + return $this->getStatus() == Mage_XmlConnect_Model_Application::APP_STATUS_SUCCESS; + } + + /** + * Load data (flat array) for Varien_Data_Form + * + * @return array + */ + public function getFormData() + { + $data = $this->getData(); + return $this->_flatArray($data); + } + + /** + * Load data (flat array) for Varien_Data_Form + * + * @param array $subtree + * @param string $prefix + * @return array + */ + protected function _flatArray($subtree, $prefix=null) + { + $result = array(); + foreach ($subtree as $key => $value) { + if (is_null($prefix)) { + $name = $key; + } else { + $name = $prefix . '[' . $key . ']'; + } + + if (is_array($value)) { + $result = array_merge($result, $this->_flatArray($value, $name)); + } else { + $result[$name] = $value; + } + } + return $result; + } + + /** + * Like array_merge_recursive(), but string values is replaced + * + * @param array $a + * @param array $b + * @return array + */ + protected function _configMerge (array $a, array $b) + { + $result = array(); + $keys = array_unique(array_merge(array_keys($a), array_keys($b))); + foreach ($keys as $key) { + if (!isset($a[$key])) { + $result[$key] = $b[$key]; + } elseif (!isset($b[$key])) { + $result[$key] = $a[$key]; + } elseif (is_scalar($a[$key]) || is_scalar($b[$key])) { + $result[$key] = $b[$key]; + } else { + $result[$key] = $this->_configMerge($a[$key], $b[$key]); + } + } + return $result; + } + + /** + * Set default configuration data + */ + public function loadDefaultConfiguration() + { + $this->setType('iphone'); + $this->setCode($this->getCodePrefix()); + $this->setConf(Mage::helper('xmlconnect/iphone')->getDefaultConfiguration()); + } + + /** + * Return first part for application code field + * + * @return string + */ + public function getCodePrefix() + { + return substr(Mage::app()->getStore($this->getStoreId())->getCode(), 0, self::APP_PREFIX_CUT_LENGTH) + . substr($this->getType(), 0, self::APP_PREFIX_CUT_LENGTH); + } + + /** + * Checks if application code field has autoincrement + * + * @return bool + */ + public function isCodePrefixed() + { + $suffix = substr($this->getCode(), self::APP_PREFIX_CUT_LENGTH * 2); + return !empty($suffix); + } + + /** + * Load application configuration + * + * @return array + */ + public function prepareConfiguration() + { + return $this->getData('conf'); + } + + /** + * Get config formatted for rendering + * + * @return array + */ + public function getRenderConf() + { + $result = Mage::helper('xmlconnect/iphone')->getDefaultConfiguration(); + $result = $result['native']; + $extra = array(); + if (isset($this->_data['conf'])) { + if (isset($this->_data['conf']['native'])) { + $result = $this->_configMerge($result, $this->_data['conf']['native']); + } + if (isset($this->_data['conf']['extra'])) { + $extra = $this->_data['conf']['extra']; + if (isset($extra['tabs'])) { + $tabs = Mage::getModel('xmlconnect/tabs', $extra['tabs']); + $result['tabBar']['tabs'] = $tabs; + } + if (isset($extra['fontColors'])) { + if (!empty($extra['fontColors']['header'])) { + $result['fonts']['Title1']['color'] = $extra['fontColors']['header']; + } + if (!empty($extra['fontColors']['primary'])) { + $result['fonts']['Title2']['color'] = $extra['fontColors']['primary']; + $result['fonts']['Title3']['color'] = $extra['fontColors']['primary']; + $result['fonts']['Text1']['color'] = $extra['fontColors']['primary']; + $result['fonts']['Text2']['color'] = $extra['fontColors']['primary']; + $result['fonts']['Title7']['color'] = $extra['fontColors']['primary']; + } + if (!empty($extra['fontColors']['secondary'])) { + $result['fonts']['Title4']['color'] = $extra['fontColors']['secondary']; + $result['fonts']['Title6']['color'] = $extra['fontColors']['secondary']; + $result['fonts']['Title8']['color'] = $extra['fontColors']['secondary']; + $result['fonts']['Title9']['color'] = $extra['fontColors']['secondary']; + } + if (!empty($extra['fontColors']['price'])) { + $result['fonts']['Title5']['color'] = $extra['fontColors']['price']; + } + } + } + } + $helperImage = Mage::helper('xmlconnect/image'); + $screenSize = $this->getScreenSize(); + $paths = $helperImage->getInterfaceImagesPathsConf(); + foreach ($paths as $confPath => $dataPath) { + $imageNodeValue =& $helperImage->findPath($result, $dataPath); + if ($imageNodeValue) { + /** + * Creating file ending (some_inner/some_dir/filename.png) For url + */ + $imageNodeValue = $helperImage->getFileCustomDirSuffixAsUrl($confPath, $imageNodeValue); + } + } + $result = $this->_absPath($result); + /** + * General configuration + */ + $result['general']['updateTimeUTC'] = strtotime($this->getUpdatedAt()); + $result['general']['browsingMode'] = $this->getBrowsingMode(); + $result['general']['currencyCode'] = Mage::app()->getStore($this->getStoreId())->getDefaultCurrencyCode(); + $result['general']['secureBaseUrl'] = Mage::getStoreConfig('web/secure/base_url', $this->getStoreId()); + $maxRecipients = 0; + $allowGuest = 0; + if (Mage::getStoreConfig('sendfriend/email/enabled')) { + $maxRecipients = Mage::getStoreConfig('sendfriend/email/max_recipients'); + $allowGuest = Mage::getStoreConfig('sendfriend/email/allow_guest'); + } + $result['general']['emailToFriendMaxRecepients'] = $maxRecipients; + $result['general']['emailAllowGuest'] = $allowGuest; + $result['general']['primaryStoreLang'] = Mage::app() + ->getStore($this->getStoreId())->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE); + $result['general']['magentoVersion'] = Mage::getVersion(); + $result['general']['copyright'] = Mage::getStoreConfig('design/footer/copyright', $this->getStoreId()); + + $result['general']['isAllowedGuestCheckout'] = Mage::getSingleton('checkout/session') + ->getQuote()->isAllowedGuestCheckout(); + + /** + * PayPal configuration + */ + $result['paypal']['businessAccount'] = Mage::getModel('paypal/config')->businessAccount; + $result['paypal']['merchantLabel'] = $this->getData('conf/special/merchantLabel'); + + $isActive = 0; + if (isset($result['paypal']) && isset($result['paypal']['isActive'])) { + $isActive = (int)($result['paypal']['isActive'] && Mage::getModel('xmlconnect/payment_method_paypal_mep')->isAvailable(null, $this->getStoreId())); + } + $result['paypal']['isActive'] = $isActive; + + return $result; + } + + /** + * Return current screen_size parameter + * + * @return string + */ + public function getScreenSize() + { + if (!isset($this->_data['screen_size'])) { + $this->_data['screen_size'] = self::APP_SCREEN_SIZE_DEFAULT; + } + return $this->_data['screen_size']; + } + + /** + * Setter + * for current screen_size parameter + * + * @param string $screenSize + * @return this + */ + public function setScreenSize($screenSize) + { + $this->_data['screen_size'] = Mage::helper('xmlconnect/image')->filterScreenSize((string) $screenSize); + return $this; + } + + /** + * Return Enabled Tabs array from actual config + * + * @return array: + */ + public function getEnabledTabsArray() + { + if ($this->getData('conf/extra/tabs')) { + return Mage::getModel('xmlconnect/tabs', $this->getData('conf/extra/tabs'))->getRenderTabs(); + } + return array(); + } + + /** + * Change URLs to absolute + * + * @param array $subtree + * @return array + */ + protected function _absPath($subtree) + { + foreach ($subtree as $key => $value) { + if (!empty($value)) { + if (is_array($value)) { + $subtree[$key] = $this->_absPath($value); + } elseif ((substr($key, -4) == 'icon') || + (substr($key, -4) == 'Icon') || + (substr($key, -5) == 'Image')) { + $subtree[$key] = Mage::getBaseUrl('media') . 'xmlconnect/' . $value; + } + } + } + return $subtree; + } + + /** + * Return content pages + * + * @return array + */ + public function getPages() + { + if (isset($this->_data['conf']['native']['pages'])) { + return $this->_data['conf']['native']['pages']; + } + return array(); + } + + /** + * Processing object before save data + * + * @return Mage_XmlConnect_Model_Application + */ + protected function _beforeSave() + { + $conf = serialize($this->prepareConfiguration()); + $this->setConfiguration($conf); + $this->setUpdatedAt(date('Y-m-d H:i:s', time())); + return $this; + } + + /** + * Load configuration data (from serialized blob) + * + * @return Mage_XmlConnect_Model_Application + */ + public function loadConfiguration() + { + $configuration = $this->getConfiguration(); + if (!empty($configuration)) { + $configuration = unserialize($configuration); + $this->setData('conf', $configuration); + } + return $this; + } + + /** + * Load application by code + * + * @param string $code + * @return Mage_XmlConnect_Model_Application + */ + public function loadByCode($code) + { + $this->_getResource()->load($this, $code, 'code'); + return $this; + } + + /** + * Loads submit tab data from xmlconnect/history table + * + * @return bool + */ + public function loadSubmit() + { + $isResubmitAction = false; + if ($this->getId()) { + $params = $this->getLastParams(); + if (!empty($params)) { + // Using Pointer ! + $conf = &$this->_data['conf']; + if (!isset($conf['submit_text']) || !is_array($conf['submit_text'])) { + $conf['submit_text'] = array(); + } + if (!isset($conf['submit_restore']) || !is_array($conf['submit_restore'])) { + $conf['submit_restore'] = array(); + } + foreach ($params as $id => $value) { + if (!in_array($id, $this->_imageIds)) { + $conf['submit_text'][$id] = $value; + } else { + $conf['submit_restore'][$id] = $value; + } + $isResubmitAction = true; + } + } + } + $this->setIsResubmitAction($isResubmitAction); + return $isResubmitAction; + } + + /** + * Returns ( image[ ID ] => "SRC" ) array + * + * @return array + */ + public function getImages() + { + $images = array(); + $params = $this->getLastParams(); + + foreach ($this->_imageIds as $id) { + $path = $this->getData('conf/submit/'.$id); + $basename = null; + if (!empty($path)) { + /** + * Fetching data from session restored array + */ + $basename = basename($path); + } else if (isset($params[$id])) { + /** + * Fetching data from submission history table record + * + * converting : "@\var\somedir\media\xmlconnect\form_icon_6.png" to "\var\somedir\media\xmlconnect\forn_icon_6.png" + */ +// $path = substr($params[$id], 1); + $basename = basename($params[$id]); + } + if (!empty($basename)) { + $images['conf/submit/'.$id] = Mage::getBaseUrl('media').'xmlconnect/' + . Mage::helper('xmlconnect/image')->getFileDefaultSizeSuffixAsUrl($basename); + } + } + return $images; + } + + /** + * Return last submitted data from history table + * + * @return array + */ + public function getLastParams() + { + if (!isset($this->_lastParams)) { + $this->_lastParams = Mage::getModel('xmlconnect/history')->getLastParams($this->getId()); + } + return $this->_lastParams; + } + + /** + * Validate application data + * + * @return array|bool + */ + public function validate() + { + $errors = array(); + + $validateConf = $this->_validateConf(); + if ($validateConf !== true) { + $errors = $validateConf; + } + if (!Zend_Validate::is($this->getName(), 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please enter "App Title".'); + } + + if (empty($errors)) { + return true; + } + return $errors; + } + + /** + * Validate submit application data + * + * @param array $params + * @return array|bool + */ + public function validateSubmit($params) + { + $errors = array(); + $validateConf = $this->_validateConf(); + if ($validateConf !== true) { + $errors = $validateConf; + } + if (!Zend_Validate::is(isset($params['title']) ? $params['title'] : null, 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please enter the Title.'); + } + + if (!Zend_Validate::is(isset($params['copyright']) ? $params['copyright'] : null, 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please enter the Copyright.'); + } + + if (empty($params['price_free'])) { + if (!Zend_Validate::is(isset($params['price']) ? $params['price'] : null, 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please enter the Price.'); + } + } + + if (!Zend_Validate::is(isset($params['country']) ? $params['country'] : null, 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please select at least one country.'); + } + + if ($this->getIsResubmitAction()) { + if (!Zend_Validate::is( + isset($params['resubmission_activation_key']) ? $params['resubmission_activation_key'] : null, + 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please enter the Resubmission Key.'); + } + } else { + if (!Zend_Validate::is(isset($params['key']) ? $params['key'] : null, 'NotEmpty')) { + $errors[] = Mage::helper('xmlconnect')->__('Please enter the Activation Key.'); + } + } + + if (empty($errors)) { + return true; + } + return $errors; + } + + /** + * Check config for valid values + * + * @return bool|array + */ + protected function _validateConf() + { + $errors = array(); + $conf = $this->getConf(); + $native = isset($conf['native']) && is_array($conf['native']) ? $conf['native'] : false; + + if ( ($native === false) + || (!isset($native['navigationBar']) || !is_array($native['navigationBar']) + || !isset($native['navigationBar']['icon']) + || !Zend_Validate::is($native['navigationBar']['icon'], 'NotEmpty'))) { + $errors[] = Mage::helper('xmlconnect')->__('Please upload an image for "Logo in Header" field from Design Tab.'); + } + + if ( ($native === false) + || (!isset($native['body']) || !is_array($native['body']) + || !isset($native['body']['bannerImage']) + || !Zend_Validate::is($native['body']['bannerImage'], 'NotEmpty'))) { + $errors[] = Mage::helper('xmlconnect')->__('Please upload an image for "Banner on Home Screen" field from Design Tab.'); + } + + if (($native === false) + || (!isset($native['body']) || !is_array($native['body']) + || !isset($native['body']['backgroundImage']) + || !Zend_Validate::is($native['body']['backgroundImage'], 'NotEmpty'))) { + $errors[] = Mage::helper('xmlconnect')->__('Please upload an image for "App Background" field from Design Tab.'); + } + + if (empty($errors)) { + return true; + } + return $errors; + } + + /** + * Imports post/get data into the model + * + * @param array $data - $_REQUEST[] + * @return array + */ + public function prepareSubmitParams($data) + { + + $params = array(); + if (isset($data['conf']) && is_array($data['conf'])) { + + if (isset($data['conf']['submit_text']) && is_array($data['conf']['submit_text'])) { + $params = $data['conf']['submit_text']; + } + + $params['name'] = $this->getName(); + $params['code'] = $this->getCode(); + $params['type'] = $this->getType(); + $params['url'] = Mage::getBaseUrl() . 'xmlconnect/configuration/index/app_code/' . $this->getCode(); + $params['magentoversion'] = Mage::getVersion(); + + if (isset($params['country']) && is_array($params['country'])) { + $params['country'] = implode(',', $params['country']); + } + if ($this->getIsResubmitAction()) { + if (isset($params['resubmission_activation_key'])) { + $params['resubmission_activation_key'] = trim($params['resubmission_activation_key']); + $params['key'] = $params['resubmission_activation_key']; + } else { + $params['key'] = ''; + } + } else { + $params['key'] = isset($params['key']) ? trim($params['key']) : ''; + } + // processing files : + $submit = array(); + if (isset($this->_data['conf']['submit']) && is_array($this->_data['conf']['submit'])) { + $submit = $this->_data['conf']['submit']; + } + + $submitRestore = array(); + if (isset($this->_data['conf']['submit_restore']) && is_array($this->_data['conf']['submit_restore'])) { + $submitRestore = $this->_data['conf']['submit_restore']; + } + + foreach ($this->_imageIds as $id) { + if (isset($submit[$id])) { + $params[$id] = '@' . $submit[$id]; + } else if (isset($submitRestore[$id])) { + $params[$id] = $submitRestore[$id]; + } + } + } + $this->setSubmitParams($params); + return $params; + } + + /** + * Retrieve Store Id + * + * @return int + */ + public function getStoreId() + { + if ($this->hasData('store_id')) { + return $this->getData('store_id'); + } + return Mage::app()->getStore()->getId(); + } + + /** + * Getter, returns activation key for current application + * + * @return string|null + */ + public function getActivationKey() + { + $key = null; + if (isset($this->_data['conf']) && is_array($this->_data['conf']) && + isset($this->_data['conf']['submit_text']) && is_array($this->_data['conf']['submit_text']) && + isset($this->_data['conf']['submit_text']['key'])) { + + $key = $this->_data['conf']['submit_text']['key']; + } + return $key; + } + + /** + * Perform update for all applications "updated at" parameter with current date + * + * @return Mage_XmlConnect_Model_Application + */ + public function updateAllAppsUpdatedAtParameter() + { + $this->_getResource()->updateAllAppsUpdatedAtParameter(); + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Catalog/Category/Image.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Catalog/Category/Image.php new file mode 100644 index 0000000000..7753cae571 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Catalog/Category/Image.php @@ -0,0 +1,191 @@ + + */ +class Mage_XmlConnect_Model_Catalog_Category_Image extends Mage_Catalog_Model_Product_Image +{ + + /** + * Set filenames for base file and new file + * + * @param string $file + * @return Mage_Catalog_Model_Product_Image + */ + public function setBaseFile($file) + { + $this->_isBaseFilePlaceholder = false; + + if (($file) && (0 !== strpos($file, '/', 0))) { + $file = '/' . $file; + } + $baseDir = Mage::getSingleton('xmlconnect/catalog_category_media_config')->getBaseMediaPath(); + + if ('/no_selection' == $file) { + $file = null; + } + if ($file) { + if ((!file_exists($baseDir . $file)) || !$this->_checkMemory($baseDir . $file)) { + $file = null; + } + } + if (!$file) { + // check if placeholder defined in config + $isConfigPlaceholder = Mage::getStoreConfig("catalog/placeholder/{$this->getDestinationSubdir()}_placeholder"); + $configPlaceholder = '/placeholder/' . $isConfigPlaceholder; + if ($isConfigPlaceholder && file_exists($baseDir . $configPlaceholder)) { + $file = $configPlaceholder; + } else { + // replace file with skin or default skin placeholder + $skinBaseDir = Mage::getDesign()->getSkinBaseDir(); + $skinPlaceholder = "/images/xmlconnect/catalog/category/placeholder/{$this->getDestinationSubdir()}.jpg"; + $file = $skinPlaceholder; + if (file_exists($skinBaseDir . $file)) { + $baseDir = $skinBaseDir; + } else { + $baseDir = Mage::getDesign()->getSkinBaseDir(array('_theme' => 'default')); + if (!file_exists($baseDir . $file)) { + $baseDir = Mage::getDesign()->getSkinBaseDir(array('_theme' => 'default', '_package' => 'base')); + } + } + } + $this->_isBaseFilePlaceholder = true; + } + + $baseFile = $baseDir . $file; + + if ((!$file) || (!file_exists($baseFile))) { + throw new Exception(Mage::helper('xmlconnect')->__('Image file was not found.')); + } + + $this->_baseFile = $baseFile; + + // build new filename (most important params) + $path = array( + Mage::getSingleton('xmlconnect/catalog_category_media_config')->getBaseMediaPath(), + 'cache', + Mage::app()->getStore()->getId(), + $path[] = $this->getDestinationSubdir() + ); + if ((!empty($this->_width)) || (!empty($this->_height))) + $path[] = "{$this->_width}x{$this->_height}"; + + // add misk params as a hash + $miscParams = array( + ($this->_keepAspectRatio ? '' : 'non') . 'proportional', + ($this->_keepFrame ? '' : 'no') . 'frame', + ($this->_keepTransparency ? '' : 'no') . 'transparency', + ($this->_constrainOnly ? 'do' : 'not') . 'constrainonly', + $this->_rgbToString($this->_backgroundColor), + 'angle' . $this->_angle, + 'quality' . $this->_quality + ); + + // if has watermark add watermark params to hash + if ($this->getWatermarkFile()) { + $miscParams[] = $this->getWatermarkFile(); + $miscParams[] = $this->getWatermarkImageOpacity(); + $miscParams[] = $this->getWatermarkPosition(); + $miscParams[] = $this->getWatermarkWidth(); + $miscParams[] = $this->getWatermarkHeigth(); + } + + $path[] = md5(implode('_', $miscParams)); + + // append prepared filename + $this->_newFile = implode('/', $path) . $file; // the $file contains heading slash + + return $this; + } + + /** + * Get relative watermark file path + * or false if file not found + * + * @return string | bool + */ + protected function _getWatermarkFilePath() + { + $filePath = false; + + if (!$file = $this->getWatermarkFile()) { + return $filePath; + } + + $baseDir = Mage::getSingleton('xmlconnect/catalog_category_media_config')->getBaseMediaPath(); + + if ( file_exists($baseDir . '/watermark/stores/' . Mage::app()->getStore()->getId() . $file) ) { + $filePath = $baseDir . '/watermark/stores/' . Mage::app()->getStore()->getId() . $file; + } elseif ( file_exists($baseDir . '/watermark/websites/' . Mage::app()->getWebsite()->getId() . $file) ) { + $filePath = $baseDir . '/watermark/websites/' . Mage::app()->getWebsite()->getId() . $file; + } elseif ( file_exists($baseDir . '/watermark/default/' . $file) ) { + $filePath = $baseDir . '/watermark/default/' . $file; + } elseif ( file_exists($baseDir . '/watermark/' . $file) ) { + $filePath = $baseDir . '/watermark/' . $file; + } else { + $baseDir = Mage::getDesign()->getSkinBaseDir(); + if ( file_exists($baseDir . $file) ) { + $filePath = $baseDir . $file; + } + } + + return $filePath; + } + + /** + * Clear catalog cache + */ + public function clearCache() + { + $directory = Mage::getBaseDir('media') . DS.'catalog'.DS.'category'.DS.'cache'.DS; + $io = new Varien_Io_File(); + $io->rmdir($directory, true); + } + + /** + * Convert array of 3 items (decimal r, g, b) to string of their hex values + * + * @param array $rgbArray + * @return string + */ + protected function _rgbToString($rgbArray) + { + $result = array(); + foreach ($rgbArray as $value) { + if (null === $value) { + $result[] = 'null'; + } else { + $result[] = sprintf('%02s', dechex($value)); + } + } + return implode($result); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Catalog/Category/Media/Config.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Catalog/Category/Media/Config.php new file mode 100644 index 0000000000..cce12e6e7f --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Catalog/Category/Media/Config.php @@ -0,0 +1,77 @@ + + */ +class Mage_XmlConnect_Model_Catalog_Category_Media_Config extends Mage_Catalog_Model_Product_Media_Config +{ + /** + * Getter , return Catalog baseMediaPath + * + * @return string + */ + public function getBaseMediaPath() + { + return Mage::getBaseDir('media') . DS . 'catalog' . DS . 'category'; + } + + /** + * Getter, return catalog baseMediaUrl + * + * @return string + */ + public function getBaseMediaUrl() + { + return Mage::getBaseUrl('media') . 'catalog/category'; + } + + /** + * Getter, return catalog baseMedia temporary dir path + * + * @return string + */ + public function getBaseTmpMediaPath() + { + return Mage::getBaseDir('media') . DS . 'tmp' . DS . 'catalog' . DS . 'category'; + } + + /** + * Getter, return catalog baseMedia temporary dir URL + * + * @return string + */ + public function getBaseTmpMediaUrl() + { + return Mage::getBaseUrl('media') . 'tmp/catalog/category'; + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/History.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/History.php new file mode 100644 index 0000000000..19e10dd801 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/History.php @@ -0,0 +1,64 @@ +_init('xmlconnect/history'); + } + + /** + * Get array of existing images + * + * @param int $id application instance Id + * @return array + */ + public function getLastParams($id) + { + return $this->_getResource()->getLastParams($id); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Application.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Application.php new file mode 100644 index 0000000000..a42814d38a --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Application.php @@ -0,0 +1,113 @@ +_init('xmlconnect/application', 'application_id'); + } + + /** + * Update Application Status field, insert data to history table + * + * @param int $applicationId + * @param string $status + * @return Mage_XmlConnect_Model_Mysql4_Application + */ + public function updateApplicationStatus($applicationId, $status) + { + $this->_getWriteAdapter()->update( + $this->getMainTable(), + array('status' => $status), + $this->_getWriteAdapter()->quoteInto($this->getIdFieldName() . '=?', $applicationId) + ); + return $this; + } + + /** + * Processing object before save data + * Update app_code as Store + Device + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Abstract + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getId()) { + $object->setCode($object->getCodePrefix()); + } + return parent::_beforeSave($object); + } + + /** + * Processing object after save data + * Update app_code as Store + Device + 123 (increment). + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Abstract + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $appCode = $object->getCode(); + $isCodePrefixed = $object->isCodePrefixed(); + if (!$isCodePrefixed) { + $this->_getWriteAdapter()->update( + $this->getMainTable(), + array('code' => $appCode . $object->getId()), + $this->_getWriteAdapter()->quoteInto($this->getIdFieldName() . '=?', $object->getId()) + ); + } + return parent::_afterSave($object); + } + + /** + * Collect existing stores and type unique pairs + * + * @return array + */ + public function getExistingStoreDeviceType() + { + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), array('store_id', 'type')) + ->group(array('store_id', 'type')) + ->order(array('store_id', 'type')); + return $this->_getReadAdapter()->fetchAll($select, array('store_id', 'type')); + } + + /** + * Update all applications "updated at" parameter with current date + * + * @return Mage_XmlConnect_Model_Mysql4_Application + */ + public function updateAllAppsUpdatedAtParameter() + { + $select = $this->_getWriteAdapter()->update($this->getMainTable(), array('updated_at' => date('Y-m-d H:i:s'))); + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Application/Collection.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Application/Collection.php new file mode 100644 index 0000000000..9e94ea516e --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Application/Collection.php @@ -0,0 +1,35 @@ +_init('xmlconnect/application'); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Category/Collection.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Category/Collection.php new file mode 100644 index 0000000000..62e7cfaa28 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Category/Collection.php @@ -0,0 +1,96 @@ + + */ +class Mage_XmlConnect_Model_Mysql4_Category_Collection + extends Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection +{ + /** + * Level of parent categories + */ + const PARENT_CATEGORIES_LEVEL = 2; + + /** + * Before collection load + * + * @return Mage_XmlConnect_Model_Mysql4_Category_Collection + */ + protected function _beforeLoad() + { + $this->addNameToResult(); + $this->addAttributeToSelect('thumbnail'); + $this->addIsActiveFilter(); + return parent::_beforeLoad(); + } + + /** + * Adding filter level + * + * @param string $level + * @return Mage_XmlConnect_Model_Mysql4_Category_Collection + */ + public function addLevelExactFilter($level) + { + $this->getSelect()->where('e.level = ?', $level); + return $this; + } + + /** + * Set limit collection + * + * @param int $offset + * @param int $count + * @return Mage_XmlConnect_Model_Mysql4_Category_Collection + */ + public function setLimit($offset, $count) + { + $this->getSelect()->limit($count, $offset); + return $this; + } + + /** + * Adding parentCategory filter + * + * @param int $parentId + * @return Mage_XmlConnect_Model_Mysql4_Category_Collection + */ + public function addParentIdFilter($parentId) + { + if (!is_null($parentId)) { + $this->getSelect()->where('e.parent_id = ?', (int)$parentId); + } else { + $this->addLevelExactFilter(self::PARENT_CATEGORIES_LEVEL); + } + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Cms/Page/Collection.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Cms/Page/Collection.php new file mode 100644 index 0000000000..947546aaa0 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Cms/Page/Collection.php @@ -0,0 +1,63 @@ + + */ + +class Mage_XmlConnect_Model_Mysql4_Cms_Page_Collection extends Mage_Cms_Model_Mysql4_Page_Collection +{ + /** + * Returns pairs identifier - title for unique identifiers + * and pairs identifier|page_id - title for non-unique after first + * + * @return array + */ + public function toOptionIdArray() + { + $res = array(); + $existingIdentifiers = array(); + foreach ($this as $item) { + $identifier = $item->getData('identifier'); + + $data['value'] = $identifier; + $data['label'] = $item->getData('title'); + if (in_array($identifier, $existingIdentifiers)) { + $data['value'] .= '|' . $item->getData('page_id'); + } else { + $existingIdentifiers[] = $identifier; + } + + $res[] = $data; + } + + return $res; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Filter/Collection.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Filter/Collection.php new file mode 100644 index 0000000000..eec097716c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Filter/Collection.php @@ -0,0 +1,102 @@ + + */ +class Mage_XmlConnect_Model_Mysql4_Filter_Collection extends Varien_Data_Collection +{ + /** + * Set CategoryId filter + * + * @param int $categoryId + * @return Mage_XmlConnect_Model_Mysql4_Filter_Collection + */ + public function setCategoryId($categoryId) + { + if ((int)$categoryId > 0) { + $this->addFilter('category_id', $categoryId); + } + return $this; + } + + /** + * Load data + * + * @param bool $printQuery + * @param bool $logQuery + * @return Mage_XmlConnect_Model_Mysql4_Filter_Collection + */ + public function load($printQuery = false, $logQuery = false) + { + if (empty($this->_items)) { + $layer = Mage::getSingleton('catalog/layer'); + foreach ($this->_filters as $filter) { + if ('category_id' == $filter['field']) { + $layer->setCurrentCategory((int)$filter['value']); + } + } + if ($layer->getCurrentCategory()->getIsAnchor()) { + foreach ($layer->getFilterableAttributes() as $attributeItem) { + $filterModelName = 'catalog/layer_filter_attribute'; + switch ($attributeItem->getAttributeCode()) { + case 'price': + $filterModelName = 'catalog/layer_filter_price'; + break; + case 'decimal': + $filterModelName = 'catalog/layer_filter_decimal'; + break; + default: + $filterModelName = 'catalog/layer_filter_attribute'; + break; + } + + $filterModel = Mage::getModel($filterModelName); + $filterModel->setLayer($layer)->setAttributeModel($attributeItem); + $filterValues = new Varien_Data_Collection; + foreach ($filterModel->getItems() as $valueItem) { + $valueObject = new Varien_Object(); + $valueObject->setLabel($valueItem->getLabel()); + $valueObject->setValueString($valueItem->getValueString()); + $valueObject->setProductsCount($valueItem->getCount()); + $filterValues->addItem($valueObject); + } + $item = new Varien_Object; + $item->setCode($attributeItem->getAttributeCode()); + $item->setName($filterModel->getName()); + $item->setValues($filterValues); + $this->addItem($item); + } + } + } + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/History.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/History.php new file mode 100644 index 0000000000..ec2a49a662 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/History.php @@ -0,0 +1,85 @@ +_init('xmlconnect/history', 'history_id'); + + } + + /** + * Serialization for 'params' variable + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Abstract + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + $object->setParams(serialize($object->getParams())); + return parent::_beforeSave($object); + } + + /** + * Deserialization for 'params' variable + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Abstract + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + $object->setParams(unserialize($object->getParams())); + return parent::_afterLoad($object); + } + + /** + * Returns array of existing images + * + * @param int $id - application instance Id + * + * @return array + */ + public function getLastParams($id) + { + $paramArray = array(); + $idFieldName = Mage::getModel('xmlconnect/application')->getIdFieldName(); + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), 'params') + ->where($idFieldName . '=?', $id) + ->order(array('created_at DESC')); + + $params = $this->_getReadAdapter()->fetchOne($select); + + if (isset($params)) { + $paramArray = unserialize($params); + } + return $paramArray; + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/History/Collection.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/History/Collection.php new file mode 100644 index 0000000000..4c727d9e75 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/History/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_XmlConnect_Model_Mysql4_History_Collection + extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + /** + * Internal constructor + */ + protected function _construct() + { + $this->_init('xmlconnect/history'); + } + + /** + * Filter collection by store + * + * @param int $storeId + * @return Mage_Core_Model_Mysql4_Collection_Abstract + */ + public function addStoreFilter($storeId) + { + $this->addFieldToFilter('store_id', $storeId); + return $this; + } + + /** + * Filter collection by application_id + * + * @param int $applicationId + * @return Mage_Core_Model_Mysql4_Collection_Abstract + */ + public function addApplicationFilter($applicationId) + { + $this->addFieldToFilter('application_id', $applicationId); + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Setup.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Setup.php new file mode 100644 index 0000000000..c8c61b0cbb --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Mysql4/Setup.php @@ -0,0 +1,29 @@ + + */ +class Mage_XmlConnect_Model_Observer +{ + /** + * List of config field names which changing affects mobile applications behaviour + * + * @var array + */ + protected $_appDependOnConfigFieldPathes = array( + 'paypal/general/business_account', + 'sendfriend/email/max_recipients', + 'sendfriend/email/allow_guest', + 'general/locale/code', + 'currency/options/default' + ); + + /** + * Update all applications "updated at" parameter with current date on save some configurations + * + * @param Varien_Event_Observer $observer + */ + public function changeUpdatedAtParamOnConfigSave($observer) + { + $configData = $observer->getEvent()->getConfigData(); + if ($configData && (int)$configData->isValueChanged() && in_array($configData->getPath(), $this->_appDependOnConfigFieldPathes)) { + Mage::getModel('xmlconnect/application')->updateAllAppsUpdatedAtParameter(); + } + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Payment/Method/Paypal/Mep.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Payment/Method/Paypal/Mep.php new file mode 100644 index 0000000000..384c169a9b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Payment/Method/Paypal/Mep.php @@ -0,0 +1,85 @@ + + */ +class Mage_XmlConnect_Model_Payment_Method_Paypal_Mep extends Mage_Paypal_Model_Express +{ + /** + * Store MEP payment method code + */ + const MEP_METHOD_CODE = 'paypal_mep'; + + protected $_code = self::MEP_METHOD_CODE; + + protected $_canUseInternal = false; + protected $_canUseForMultishipping = false; + protected $_isInitializeNeeded = false; + protected $_canUseCheckout = false; + protected $_canManageRecurringProfiles = false; + + /** + * Get config peyment action url + * Used to universalize payment actions when processing payment place + * + * @return string + */ + public function getConfigPaymentAction() + { + return Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE; + } + + /** + * Check whether payment method can be used + * + * @param Mage_Sales_Model_Quote $quote + * @return bool + */ + public function isAvailable($quote = null) + { + return Mage::getModel('paypal/config') + ->setStoreId(Mage::app()->getStore()->getId()) + ->isMethodAvailable(Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS); + } + + /** + * Capture payment + * + * @param Varien_Object $payment + * @param float $amount + * @return Mage_Payment_Model_Abstract + */ + public function capture(Varien_Object $payment, $amount) + { + $transactionId = $payment->getAdditionalInformation(Mage_XmlConnect_Model_Paypal_Mep_Checkout::PAYMENT_INFO_TRANSACTION_ID); + $payment->setTransactionId($transactionId); + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Paypal/Mep/Checkout.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Paypal/Mep/Checkout.php new file mode 100644 index 0000000000..aeef37f69b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Paypal/Mep/Checkout.php @@ -0,0 +1,305 @@ +_checkoutSession = Mage::getSingleton('checkout/session'); + if (isset($params['quote']) && $params['quote'] instanceof Mage_Sales_Model_Quote) { + $this->_quote = $params['quote']; + } else { + Mage::throwException(Mage::helper('xmlconnect')->__('Quote instance is required.')); + } + } + + /** + * Prepare quote, reserve order ID for specified quote + * + * @return string + */ + public function initCheckout() + { + $this->_quote->reserveOrderId()->save(); + + /** + * Reset multishipping flag before any manipulations with quote address + * addAddress method for quote object related on this flag + */ + if ($this->_quote->getIsMultiShipping()) { + $this->_quote->setIsMultiShipping(false); + $this->_quote->save(); + } + + /* + * want to load the correct customer information by assigning to address + * instead of just loading from sales/quote_address + */ + $customer = Mage::getSingleton('customer/session')->getCustomer(); + if ($customer) { + $this->_quote->assignCustomer($customer); + } + if (!Mage::getSingleton('customer/session')->isLoggedIn() + && Mage::getSingleton('checkout/session')->getQuote()->isAllowedGuestCheckout()) { + $this->_prepareGuestQuote(); + } + return $this->_quote->getReservedOrderId(); + } + + /** + * Save shipping and billing address information to quote + * + * @param array $data + * @return array + */ + public function saveShipping($data) + { + if (empty($data)) { + return array('error' => 1, 'message' => Mage::helper('xmlconnect')->__('Invalid data.')); + } + + $address = $this->_quote->getBillingAddress(); + /** + * Start hard code data + * + * @todo remove this hard code + */ + $data['country_id'] = 'US'; + if (Mage::getSingleton('customer/session')->isLoggedIn()) { + $customer = Mage::getSingleton('customer/session')->getCustomer(); + $data['firstname'] = $customer->getFirstname(); + $data['lastname'] = $customer->getLastname(); + + } else { + $data['firstname'] = Mage::helper('xmlconnect')->__('Guest'); + $data['lastname'] = Mage::helper('xmlconnect')->__('Guest'); + } + /** + * End hard code + */ + + $address->addData($data); + + $this->_ignoreAddressValidation(); + + $address->implodeStreetAddress(); + + if (!$this->_quote->isVirtual()) { + $billing = clone $address; + $billing->unsAddressId()->unsAddressType(); + $shipping = $this->_quote->getShippingAddress(); + $shippingMethod = $shipping->getShippingMethod(); + $shipping->addData($billing->getData()) + ->setSameAsBilling(1) + ->setShippingMethod($shippingMethod) + ->setCollectShippingRates(true); + } + + $this->_quote->collectTotals()->save(); + return array(); + } + + /** + * Specify quote shipping method + * + * @param string $shippingMethod + * @return array + */ + public function saveShippingMethod($shippingMethod) + { + if (empty($shippingMethod)) { + return array('error' => 1, 'message' => Mage::helper('xmlconnect')->__('Invalid shipping method.')); + } + $rate = $this->_quote->getShippingAddress()->getShippingRateByCode($shippingMethod); + if (!$rate) { + return array('error' => 1, 'message' => Mage::helper('xmlconnect')->__('Invalid shipping method.')); + } + if (!$this->_quote->getIsVirtual() && $shippingAddress = $this->_quote->getShippingAddress()) { + if ($shippingMethod != $shippingAddress->getShippingMethod()) { + $this->_ignoreAddressValidation(); + $this->_quote->getShippingAddress() + ->setShippingMethod($shippingMethod); + $this->_quote->collectTotals() + ->save(); + } + } + + return array(); + } + + /** + * Specify quote payment method + * + * @param array $data + * @return array + */ + public function savePayment($data) + { + if ($this->_quote->isVirtual()) { + $this->_quote->getBillingAddress()->setPaymentMethod($this->_methodType); + } else { + $this->_quote->getShippingAddress()->setPaymentMethod($this->_methodType); + } + + $payment = $this->_quote->getPayment(); + $data['method'] = $this->_methodType; + $payment->importData($data); + + $email = isset($data['payer']) ? $data['payer'] : null; + $payment->setAdditionalInformation(self::PAYMENT_INFO_PAYER_EMAIL, $email); + $payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSACTION_ID, isset($data['transaction_id']) ? $data['transaction_id'] : null); + $this->_quote->setCustomerEmail($email); + + $this->_quote->collectTotals()->save(); + + return array(); + } + + /** + * Place the order when customer returned from paypal + * Until this moment all quote data must be valid + * + * @return array + */ + public function saveOrder() + { + $this->_ignoreAddressValidation(); + + $order = Mage::getModel('sales/service_quote', $this->_quote)->submit(); + $this->_quote->save(); + + /** + * Prepare session to success or cancellation page + */ + $quoteId = $this->_quote->getId(); + $this->_getCheckoutSession() + ->setLastQuoteId($quoteId) + ->setLastSuccessQuoteId($quoteId) + ->setLastOrderId($order->getId()) + ->setLastRealOrderId($order->getIncrementId()); + + if ($order->getState() == Mage_Sales_Model_Order::STATE_PROCESSING) { + try { + $order->sendNewOrderEmail(); + } catch (Exception $e) { + Mage::logException($e); + } + } + + return array(); + } + + /** + * Get last order increment id by order id + * + * @return string + */ + public function getLastOrderId() + { + $lastId = $this->_getCheckoutSession()->getLastOrderId(); + $orderId = false; + if ($lastId) { + $order = Mage::getModel('sales/order'); + $order->load($lastId); + $orderId = $order->getIncrementId(); + } + return $orderId; + } + + /** + * Make sure addresses will be saved without validation errors + */ + protected function _ignoreAddressValidation() + { + $this->_quote->getBillingAddress()->setShouldIgnoreValidation(true); + if (!$this->_quote->getIsVirtual()) { + $this->_quote->getShippingAddress()->setShouldIgnoreValidation(true); + } + } + + /** + * Get frontend checkout session object + * + * @return Mage_Checkout_Model_Session + */ + protected function _getCheckoutSession() + { + return $this->_checkoutSession; + } + + /** + * Prepare quote for guest checkout order submit + * + * @return Mage_XmlConnect_Model_Paypal_Mep_Checkout + */ + protected function _prepareGuestQuote() + { + $quote = $this->_quote; + $quote->setCustomerId(null) + ->setCustomerIsGuest(true) + ->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID); + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Simplexml/Element.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Simplexml/Element.php new file mode 100644 index 0000000000..3898687512 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Simplexml/Element.php @@ -0,0 +1,86 @@ + + */ +class Mage_XmlConnect_Model_Simplexml_Element extends Varien_Simplexml_Element +{ + /** + * Appends $source to current node + * + * @param Mage_XmlConnect_Model_Simplexml_Element $source + * @return Mage_XmlConnect_Model_Simplexml_Element + */ + public function appendChild($source) + { + if (sizeof($source->children())) { + /** + * @see http://bugs.php.net/bug.php?id=41867 , fixed in 5.2.4 + */ + if (version_compare(phpversion(), '5.2.4', '<')===true) { + $name = $source->children()->getName(); + } else { + $name = $source->getName(); + } + $child = $this->addChild($name); + } else { + $child = $this->addChild($source->getName(), $this->xmlentities($source)); + } + $child->setParent($this); + + $attributes = $source->attributes(); + foreach ($attributes as $key=>$value) { + $child->addAttribute($key, $this->xmlAttribute($value)); + } + + foreach ($source->children() as $sourceChild) { + $child->appendChild($sourceChild); + } + return $this; + } + + /** + * Converts meaningful xml character (") to xml attribute specification + * + * @param string $value + * @return string|this + */ + public function xmlAttribute($value = null) + { + if (is_null($value)) { + $value = $this; + } + $value = (string)$value; + + $value = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $value); + + return $value; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Tabs.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Tabs.php new file mode 100644 index 0000000000..67d222ba77 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Tabs.php @@ -0,0 +1,114 @@ +_enabledTabs = Mage::helper('xmlconnect')->getDefaultApplicationDesignTabs(); + if (is_string($data)) { + $data = json_decode($data); + if (is_object($data)) { + $this->_enabledTabs = $data->enabledTabs; + $this->_disabledTabs = $data->disabledTabs; + } + } + $this->_translateLabel($this->_enabledTabs); + $this->_translateLabel($this->_disabledTabs); + } + + /** + * Translate Label fields + * + * @param &array $tabItems + * @return this + */ + protected function _translateLabel(&$tabItems) + { + if (is_array($tabItems)) { + foreach ($tabItems as $id => $tab) { + if (isset($tab->label)) { + $temp = $tabItems[$id]; + $temp->label = Mage::helper('xmlconnect')->getTabLabel($tab->action); + } + } + } + return $this; + } + + /** + * Getter for enabled tabs + * + * @return array + */ + public function getEnabledTabs() + { + return $this->_enabledTabs; + } + + /** + * Getter for disabled tabs + * + * @return array + */ + public function getDisabledTabs() + { + return $this->_disabledTabs; + } + + /** + * Collect tabs with images + * + * @return array + */ + public function getRenderTabs() + { + $result = array(); + foreach ($this->_enabledTabs as $tab) { + $tab->image = Mage::getDesign()->getSkinUrl('images/xmlconnect/' . $tab->image); + $result[] = $tab; + } + return $result; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/Model/Theme.php b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Theme.php new file mode 100644 index 0000000000..97d8970273 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/Model/Theme.php @@ -0,0 +1,204 @@ +_file = $file; + if (!file_exists($file)) { + Mage::throwException(Mage::helper('xmlconnect')->__('File doesn\'t exist "%s".', $file)); + } + if (!is_readable($file)) { + Mage::throwException(Mage::helper('xmlconnect')->__('Can\'t read file "%s".', $file)); + } + $text = file_get_contents($file); + try { + $this->_xml = simplexml_load_string($text); + } catch (Exception $e) { + Mage::throwException(Mage::helper('xmlconnect')->__('Can\'t load XML.')); + } + if (empty($this->_xml)) { + Mage::throwException(Mage::helper('xmlconnect')->__('Invalid XML.')); + } + $this->_conf = $this->_xmlToArray($this->_xml->configuration); + $this->_conf = $this->_conf['configuration']; + if (!is_array($this->_conf)) { + Mage::throwException(Mage::helper('xmlconnect')->__('Wrong theme format.')); + } + } + + /** + * Get theme xml as array + * + * @param array $xml + * @return array + */ + protected function _xmlToArray($xml) + { + $result = array(); + foreach ($xml as $key => $value) { + if (count($value)) { + $result[$key] = $this->_xmlToArray($value); + } else { + $result[$key] = (string) $value; + } + } + return $result; + } + + /** + * Getter for theme name + * + * @return string + */ + public function getName() + { + return (string) $this->_xml->manifest->name; + } + + /** + * Getter for theme Label + * + * @return string + */ + public function getLabel() + { + return (string) $this->_xml->manifest->label; + } + + /** + * Load data (flat array) for Varien_Data_Form + * + * @return array + */ + public function getFormData() + { + return $this->_flatArray($this->_conf, 'conf'); + } + + /** + * Load data (flat array) for Varien_Data_Form + * + * @param array $subtree + * @param string $prefix + * @return array + */ + protected function _flatArray($subtree, $prefix=null) + { + $result = array(); + foreach ($subtree as $key => $value) { + if (is_null($prefix)) { + $name = $key; + } else { + $name = $prefix . '[' . $key . ']'; + } + + if (is_array($value)) { + $result = array_merge($result, $this->_flatArray($value, $name)); + } else { + $result[$name] = $value; + } + } + return $result; + } + + /** + * Validate input Array, recursive + * + * @param array $data + * @param array $xml + * @return array + */ + protected function _validateFormInput($data, $xml=NULL) + { + $root = false; + $result = array(); + if (is_null($xml)) { + $root = true; + $data = array('configuration' => $data); + $xml = $this->_xml->configuration; + } + foreach ($xml as $key => $value) { + if (isset($data[$key])) { + if (is_array($data[$key])) { + $result[$key] = $this->_validateFormInput($data[$key], $value); + } else { + $result[$key] = $data[$key]; + } + } + } + if ($root) { + $result = $result['configuration']; + } + return $result; + } + + /** + * Build XML object recursively from $data array + * + * @param SimpleXMLElement $parent + * @param array $data + * @return void + */ + protected function _buildRecursive($parent, $data) + { + foreach ($data as $key=>$value) { + if (is_array($value)) { + $this->_buildRecursive($parent->addChild($key), $value); + } else { + $parent->addChild($key, $value); + } + } + } + + /** + * Import data into theme form $data array, and save XML to file + * + * @param array $data + * @return void + */ + public function importAndSaveData($data) + { + $xml = new SimpleXMLElement(''.$this->_xml->manifest->asXML().''); + $this->_buildRecursive($xml->addChild('configuration'), $this->_validateFormInput($data)); + clearstatcache(); + if (is_writeable($this->_file)) { + file_put_contents($this->_file, $xml->asXML()); + } else { + Mage::throwException(Mage::helper('xmlconnect')->__('Can\'t write to file "%s".', $this->_file)); + } + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/Adminhtml/MobileController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/Adminhtml/MobileController.php new file mode 100644 index 0000000000..36aee8290c --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/Adminhtml/MobileController.php @@ -0,0 +1,732 @@ +getRequest()->getParam($paramName); + $app = Mage::getModel('xmlconnect/application'); + if ($id) { + $app->load($id); + if ($app->getId()) { + $app->loadConfiguration(); + } + } else { + $app->loadDefaultConfiguration(); + } + Mage::register('current_app', $app); + return $app; + } + + /** + * Restore data from session $_POST and $_FILES (processed) + * + * @param array $data + * @return array|null + */ + protected function _restoreSessionFilesFormData($data) + { + $filesData = Mage::getSingleton('adminhtml/session')->getUploadedFilesFormData(true); + if (!empty($filesData) && is_array($filesData)) { + if (!is_array($data)) { + $data = array(); + } + foreach ($filesData as $filePath => $fileName) { + $target =& $data; + $this->_injectFieldToArray($target, $filePath, $fileName); + } + } + return $data; + } + + + /** + * Set value into multidimensional array 'conf/native/navigationBar/icon' + * + * @param &array $target // pointer to target array + * @param string $fieldPath //'conf/native/navigationBar/icon' + * @param mixed $fieldValue // 'Some Value' || 12345 || array(1=>3, 'aa'=>43) + * @param string $delimiter // path delimiter + * @return null + */ + protected function _injectFieldToArray(&$target, $fieldPath, $fieldValue, $delimiter = '/') + { + $nameParts = explode($delimiter, $fieldPath); + foreach ($nameParts as $next) { + if (!isset($target[$next])) { + $target[$next] = array(); + } + $target =& $target[$next]; + } + $target = $fieldValue; + return null; + } + + /** + * Mobile applications management + * + * @return void + */ + public function indexAction() + { + $this->loadLayout(); + $this->_setActiveMenu('xmlconnect/mobile'); + $this->renderLayout(); + } + + /** + * Create new app + */ + public function newAction() + { + $this->_forward('edit'); + } + + /** + * Submission Action, loads application data + */ + public function submissionAction() + { + try { + $app = $this->_initApp(); + if (!$app->getId()) { + $this->_getSession()->addError(Mage::helper('xmlconnect')->__('App does not exist.')); + $this->_redirect('*/*/'); + return; + } + $app->loadSubmit(); + if ((bool) Mage::getSingleton('adminhtml/session')->getLoadSessionFlag(true)) { + $data = $this->_restoreSessionFilesFormData(Mage::getSingleton('adminhtml/session')->getFormSubmissionData(true)); + if (!empty($data)) { + $app->setData(Mage::helper('xmlconnect')->arrayMergeRecursive($app->getData(), $data)); + } + } + + $this->loadLayout(); + $this->_setActiveMenu('xmlconnect/mobile'); + $this->renderLayout(); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + if (isset($app)) { + $this->_redirect('*/*/edit', array('application_id' => $app->getId())); + } else { + $this->_redirect('*/*/'); + } + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('xmlconnect')->__('Can\'t open submission form.')); + if (isset($app)) { + $this->_redirect('*/*/edit', array('application_id' => $app->getId())); + } else { + $this->_redirect('*/*/'); + } + } + } + + /** + * Edit app form + * + * @return void + */ + public function editAction() + { + $redirectBack = false; + try { + $id = (int) $this->getRequest()->getParam('application_id'); + $app = $this->_initApp(); + + if (!$app->getId() && $id) { + $this->_getSession()->addError(Mage::helper('xmlconnect')->__('App does not exist.')); + $this->_redirect('*/*/'); + return; + } + $app->loadSubmit(); + if ((bool) Mage::getSingleton('adminhtml/session')->getLoadSessionFlag(true)) { + $newAppData = $this->_restoreSessionFilesFormData(Mage::getSingleton('adminhtml/session')->getFormData(true)); + if (!empty($newAppData)) { + $app->setData(Mage::helper('xmlconnect')->arrayMergeRecursive($app->getData(), $newAppData)); + } + } + $this->loadLayout(); + $this->_setActiveMenu('xmlconnect/mobile'); + $this->renderLayout(); + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + $redirectBack = true; + } catch (Exception $e) { + $this->_getSession()->addError(Mage::helper('xmlconnect')->__('Unable to load application form.')); + $redirectBack = true; + Mage::logException($e); + } + if ($redirectBack) { + $this->_redirect('*/*/'); + return; + } + } + + /** + * Submit POST application action + */ + public function submissionPostAction() + { + $data = $this->getRequest()->getPost(); + try { + $isError = false; + if (!empty($data)) { + Mage::getSingleton('adminhtml/session')->setFormSubmissionData($this->_filterFormDataForSession($data)); + } + /** @var $app Mage_XmlConnect_Model_Application */ + $app = $this->_initApp('key'); + $app->loadSubmit(); + $newAppData = $this->_processUploadedFiles($app->getData(), true); + if (!empty($newAppData)) { + $app->setData(Mage::helper('xmlconnect')->arrayMergeRecursive($app->getData(), $newAppData)); + } + $params = $app->prepareSubmitParams($data); + $errors = $app->validateSubmit($params); + if ($errors !== true) { + foreach ($errors as $err) { + $this->_getSession()->addError($err); + } + $isError = true; + } + if (!$isError) { + $this->_processPostRequest(); + $history = Mage::getModel('xmlconnect/history'); + $history->setData(array( + 'params' => $params, + 'application_id' => $app->getId(), + 'created_at' => Mage::getModel('core/date')->date(), + 'store_id' => $app->getStoreId(), + 'title' => isset($params['title']) ? $params['title'] : '', + 'code' => $app->getCode(), + 'activation_key' => isset($params['resubmission_activation_key']) ? + $params['resubmission_activation_key'] : $params['key'], + )); + $history->save(); + $app->getResource()->updateApplicationStatus($app->getId(), + Mage_XmlConnect_Model_Application::APP_STATUS_SUCCESS); + $this->_getSession()->addSuccess(Mage::helper('xmlconnect')->__('App has been submitted.')); + $this->_clearSessionData(); + $this->_redirect('*/*/edit', array('application_id' => $app->getId())); + } else { + Mage::getSingleton('adminhtml/session')->setLoadSessionFlag(true); + $this->_redirect('*/*/submission', array('application_id' => $app->getId())); + } + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addError($e->getMessage()); + if (isset($app)) { + Mage::getSingleton('adminhtml/session')->setLoadSessionFlag(true); + $this->_redirect('*/*/submission', array('application_id' => $app->getId())); + } else { + $this->_redirect('*/*/'); + } + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('xmlconnect')->__('Can\'t submit application.')); + Mage::logException($e); + if (isset($app)) { + Mage::getSingleton('adminhtml/session')->setLoadSessionFlag(true); + $this->_redirect('*/*/submission', array('application_id' => $app->getId())); + } else { + $this->_redirect('*/*/'); + } + } + } + + /** + * Format post/get data for session storage + * + * @param array $data - $_REQUEST[] + * @return array + */ + protected function _filterFormDataForSession($data) + { + $params = null; + if (isset($data['conf']) && is_array($data['conf'])) { + if (isset($data['conf']['submit_text']) && is_array($data['conf']['submit_text'])) { + $params = &$data['conf']['submit_text']; + } + } + if (isset($params['country']) && is_array($params['country'])) { + $data['conf']['submit_text']['country'] = implode(',', $params['country']); + } + return $data; + } + + /** + * Clear session data + * Used after successful save/submit action + * + * @return this + */ + protected function _clearSessionData() + { + Mage::getSingleton('adminhtml/session')->unsFormData(); + Mage::getSingleton('adminhtml/session')->unsFormSubmissionData(); + Mage::getSingleton('adminhtml/session')->unsUploadedFilesFormData(); + return $this; + } + + /** + * Send HTTP POST request to magentocommerce.com + * + * @return void + */ + protected function _processPostRequest() + { + try { + $app = Mage::registry('current_app'); + $params = $app->getSubmitParams(); + + $appConnectorUrl = Mage::getStoreConfig('xmlconnect/mobile_application/magentocommerce_url'); + $ch = curl_init($appConnectorUrl . $params['key']); + + // set URL and other appropriate options + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_TIMEOUT, 60); + + // Execute the request. + $result = curl_exec($ch); + $succeeded = curl_errno($ch) == 0 ? true : false; + + // close cURL resource, and free up system resources + curl_close($ch); + + // Assert that we received an expected message in reponse. + $resultArray = json_decode($result, true); + + $app->setResult($result); + $success = (isset($resultArray['success'])) && ($resultArray['success'] === true); + + $app->setSuccess($success); + if (!$app->getSuccess()) { + $message = ''; + $message = isset($resultArray['message']) ? $resultArray['message']: ''; + if (is_array($message)) { + $message = implode(' ,', $message); + } + Mage::throwException(Mage::helper('xmlconnect')->__('Submit App failure. %s', $message)); + } + } catch (Exception $e) { + throw $e; + } + } + + /** + * Save action + */ + public function saveAction() + { + $data = $this->getRequest()->getPost(); + $redirectBack = $this->getRequest()->getParam('back', false); + $redirectSubmit = $this->getRequest()->getParam('submitapp', false); + $app = false; + $isError = false; + if ($data) { + Mage::getSingleton('adminhtml/session')->setFormData($data); + try { + $id = $this->getRequest()->getParam('application_id'); + $app = $this->_initApp(); + if (!$app->getId() && $id) { + $this->_getSession()->addError(Mage::helper('xmlconnect')->__('App does not exist.')); + $this->_redirect('*/*/'); + return; + } + $app->addData($this->_preparePostData($data)); + $app->addData($this->_processUploadedFiles($app->getData())); + $errors = $app->validate(); + if ($errors !== true) { + foreach ($errors as $err) { + $this->_getSession()->addError($err); + } + $isError = true; + } + if (!$isError) { + $this->_saveThemeAction($data, 'current_theme'); + $app->save(); + $this->_getSession()->addSuccess(Mage::helper('xmlconnect')->__('App has been saved.')); + $this->_clearSessionData(); + } + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addException($e, $e->getMessage()); + $isError = true; + $redirectBack = true; + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('xmlconnect')->__('Unable to save app.')); + $isError = true; + $redirectBack = true; + Mage::logException($e); + } + } + if (!$isError && $app->getId() && $redirectSubmit) { + $this->_redirect('*/*/submission', array('application_id' => $app->getId())); + } else if ($isError || ($app->getId() && $redirectBack)) { + if ($isError) { + Mage::getSingleton('adminhtml/session')->setLoadSessionFlag(true); + } + $this->_redirect('*/*/edit', array('application_id' => $app->getId())); + } else { + $this->_redirect('*/*/'); + } + } + + /** + * Save changes to theme + * + * @param array $data + * @param string $paramId + */ + protected function _saveThemeAction($data, $paramId = 'saveTheme') + { + try { + $themeName = $this->getRequest()->getParam($paramId, false); + if ($themeName) { + if ($themeName == Mage::helper('xmlconnect/theme')->getCustomThemeName()) { + $theme = Mage::helper('xmlconnect/theme')->getThemeByName($themeName); + if ($theme instanceof Mage_XmlConnect_Model_Theme) { + if ($paramId == 'saveTheme') { + $convertedConf = $this->_convertPost($data); + } else { + if (isset($data['conf'])) { + $convertedConf = $data['conf']; + } else { + $response = array('error' => true, 'message' => Mage::helper('xmlconnect')->__('Cannot save theme "%s". Incorrect data received', $themeName)); + } + } + $theme->importAndSaveData($convertedConf); + $response = Mage::helper('xmlconnect/theme')->getAllThemesArray(true); + } else { + $response = array('error' => true, 'message' => Mage::helper('xmlconnect')->__('Cannot load theme "%s".', $themeName)); + } + } else { + $response = Mage::helper('xmlconnect/theme')->getAllThemesArray(true); + } + } else { + $response = array('error' => true, 'message' => Mage::helper('xmlconnect')->__('Theme name is not set.')); + } + } catch (Mage_Core_Exception $e) { + $response = array( + 'error' => true, + 'message' => $e->getMessage(), + ); + } catch (Exception $e) { + $response = array( + 'error' => true, + 'message' => Mage::helper('xmlconnect')->__('Can\'t save theme.') + ); + } + if (is_array($response)) { + $response = Mage::helper('core')->jsonEncode($response); + $this->getResponse()->setBody($response); + } + } + + /** + * Converts native Ajax data from flat to real array + * Convert array key->value pairs inside array like: + * "conf_native_bar_tintcolor" => $val to $conf['native']['bar']['tintcolor'] => $val + * + * @param array $data $_POST + * @return array + */ + protected function _convertPost($data) + { + $conf = array(); + foreach ($data as $key => $val) { + $parts = explode('_', $key); + // "4" - is number of expected params conf_native_bar_tintcolor in correct data + if (is_array($parts) && (count($parts) == 4)) { + @list($key0, $key1, $key2, $key3) = $parts; + if (!isset($conf[$key1])) { + $conf[$key1] = array(); + } + if (!isset($conf[$key1][$key2])) { + $conf[$key1][$key2] = array(); + } + $conf[$key1][$key2][$key3] = $val; + } + } + return $conf; + } + + /** + * Save Theme action + */ + public function saveThemeAction() + { + $data = $this->getRequest()->getPost(); + $response = false; + $this->_saveThemeAction($data); + } + + /** + * Save Theme action + */ + public function resetThemeAction() + { + $response = false; + try { + Mage::helper('xmlconnect/theme')->resetAllThemes(); + $response = Mage::helper('xmlconnect/theme')->getAllThemesArray(true); + } catch (Mage_Core_Exception $e) { + $response = array( + 'error' => true, + 'message' => $e->getMessage(), + ); + } catch (Exception $e) { + $response = array( + 'error' => true, + 'message' => Mage::helper('xmlconnect')->__('Can\'t reset theme.') + ); + } + if (is_array($response)) { + $response = Mage::helper('core')->jsonEncode($response); + $this->getResponse()->setBody($response); + } + } + + /** + * Preview Home action handler + */ + public function previewHomeAction() + { + $this->_previewAction('preview_home_content'); + } + + /** + * Preview Catalog action handler + */ + public function previewCatalogAction() + { + $this->_previewAction('preview_catalog_content'); + } + + /** + * Preview action implementation + * + * @param string $block + */ + protected function _previewAction($block) + { + $redirectBack = false; + $app = false; + try { + $app = $this->_initApp(); + if (!$this->getRequest()->getParam('submission_action')) { + $app->addData($this->_preparePostData($this->getRequest()->getPost())); + } + $app->addData($this->_processUploadedFiles($app->getData())); + + $this->loadLayout(FALSE); + $preview = $this->getLayout()->getBlock($block); + $preview->setConf($app->getRenderConf()); + $this->renderLayout(); + return; + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addException($e, $e->getMessage()); + $redirectBack = true; + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('xmlconnect')->__('Unable to process preview.')); + $redirectBack = true; + } + if (isset($app) && $redirectBack) { + $this->_redirect('*/*/edit', array('application_id' => $app->getId())); + } else { + $this->_redirect('*/*/'); + } + } + + /** + * Delete action + */ + public function deleteAction() + { + try { + $app = $this->_initApp(); + if (!$app->getIsSubmitted()) { + $app->delete(); + $this->_getSession()->addSuccess(Mage::helper('xmlconnect')->__('App has been deleted.')); + } else { + Mage::throwException(Mage::helper('xmlconnect')->__('It\'s not allowed to delete submitted application.')); + } + } catch (Mage_Core_Exception $e) { + $this->_getSession()->addException($e, $e->getMessage()); + } catch (Exception $e) { + $this->_getSession()->addException($e, Mage::helper('xmlconnect')->__('Unable to find an app to delete.')); + } + $this->_redirect('*/*/'); + } + + /** + * Check the permission to run it + * + * @return boolean + */ + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('xmlconnect'); + } + + /** + * List application submit history + */ + public function historyAction() + { + $this->loadLayout(); + $this->_setActiveMenu('xmlconnect/history'); + $this->renderLayout(); + } + + /** + * Render apps grid + */ + public function gridAction() + { + $this->loadLayout(false); + $this->_setActiveMenu('xmlconnect/mobile'); + $this->renderLayout(); + } + + /** + * Process all uploaded files + * setup filenames to the configuration return array + * + * @param array $data + * @param bool $restore + * @return array + */ + protected function _processUploadedFiles($data, $restore = false) + { + if ($restore === true) { + $this->_uploadedFiles = Mage::getSingleton('adminhtml/session')->getUploadedFilesFormDataSubmit(); + } + if (!isset($this->_uploadedFiles) || !is_array($this->_uploadedFiles)) { + $this->_uploadedFiles = array(); + } + + if (!empty($_FILES)) { + foreach ($_FILES as $field => $file) { + if (!empty($file['name']) && is_scalar($file['name'])) { + $uploadedFileName = Mage::helper('xmlconnect/image')->handleUpload($field, $data); + if (!empty($uploadedFileName)) { + $this->_uploadedFiles[$field] = $uploadedFileName; + } + } + } + } + foreach ($this->_uploadedFiles as $fieldPath => $fileName) { + $this->_injectFieldToArray($data, $fieldPath, $fileName); + } + Mage::getSingleton('adminhtml/session')->setUploadedFilesFormData($this->_uploadedFiles); + if ($restore === true) { + Mage::getSingleton('adminhtml/session')->setUploadedFilesFormDataSubmit($this->_uploadedFiles); + } + return $data; + } + + + + + + + /** + * Prepare post data + * + * Retains previous data in the object. + * + * @param array $arr + * @return array + */ + public function _preparePostData(array $arr) + { + unset($arr['code']); + if (isset($arr['conf']['new_pages']) && isset($arr['conf']['new_pages']['ids']) + && isset($arr['conf']['new_pages']['labels'])) { + + $newPages = array(); + foreach ($arr['conf']['new_pages']['ids'] as $key=>$value) { + $newPages[$key]['id'] = trim($value); + } + foreach ($arr['conf']['new_pages']['labels'] as $key=>$value) { + $newPages[$key]['label'] = trim($value); + } + if (!isset($arr['conf']['native']['pages'])) { + $arr['conf']['native']['pages'] = array(); + } + foreach ($newPages as $key => $page) { + if (empty($page['id']) || empty($page['label'])) { + unset($newPages[$key]); + } + } + if (!empty($newPages)) { + $arr['conf']['native']['pages'] = array_merge($arr['conf']['native']['pages'], $newPages); + } + unset($arr['conf']['new_pages']); + } + /** + * Restoring current_theme over selected but not applied theme + */ + if (isset($arr['current_theme'])) { + $arr['conf']['extra']['theme'] = $arr['current_theme']; + } + if (!isset($arr['conf']['defaultCheckout'])) { + $arr['conf']['defaultCheckout'] = array(); + } + if (!isset($arr['conf']['defaultCheckout']['isActive'])) { + $arr['conf']['defaultCheckout']['isActive'] = 0; + } + + if (!isset($arr['conf']['paypal'])) { + $arr['conf']['paypal'] = array(); + } + if (!isset($arr['conf']['paypal']['isActive'])) { + $arr['conf']['paypal']['isActive'] = 0; + } + return $arr; + } + + /** + * Submission history grid action on submission history tab + */ + public function submissionHistoryGridAction() + { + $this->_initApp(); + $this->loadLayout(); + $this->renderLayout(); + } +} + + diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CartController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CartController.php new file mode 100644 index 0000000000..9974097be3 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CartController.php @@ -0,0 +1,319 @@ + + */ + +class Mage_XmlConnect_CartController extends Mage_XmlConnect_Controller_Action +{ + /** + * Shopping cart display action + */ + public function indexAction() + { + $messages = array(); + $cart = $this->_getCart(); + if ($cart->getQuote()->getItemsCount()) { + $cart->init(); + $cart->save(); + + if (!$this->_getQuote()->validateMinimumAmount()) { + $warning = Mage::getStoreConfig('sales/minimum_order/description'); + $messages[parent::MESSAGE_STATUS_WARNING][] = $warning; + } + } + + foreach ($cart->getQuote()->getMessages() as $message) { + if ($message) { + $messages[$message->getType()][] = $message->getText(); + } + } + + /** + * if customer enteres shopping cart we should mark quote + * as modified bc he can has checkout page in another window. + */ + $this->_getSession()->setCartWasUpdated(true); + + $this->loadLayout(false)->getLayout()->getBlock('xmlconnect.cart')->setMessages($messages); + $this->renderLayout(); + } + + /** + * Update shoping cart data action + */ + public function updateAction() + { + try { + $cartData = $this->getRequest()->getParam('cart'); + if (is_array($cartData)) { + $filter = new Zend_Filter_LocalizedToNormalized( + array('locale' => Mage::app()->getLocale()->getLocaleCode()) + ); + foreach ($cartData as $index => $data) { + if (isset($data['qty'])) { + $cartData[$index]['qty'] = $filter->filter($data['qty']); + } + } + $cart = $this->_getCart(); + if (! $cart->getCustomerSession()->getCustomer()->getId() && $cart->getQuote()->getCustomerId()) { + $cart->getQuote()->setCustomerId(null); + } + $cart->updateItems($cartData) + ->save(); + } + $this->_getSession()->setCartWasUpdated(true); + $this->_message(Mage::helper('xmlconnect')->__('Cart has been updated.'), parent::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t update cart.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Get request for product add to cart procedure + * + * @param mixed $requestInfo + * @return Varien_Object + */ + protected function _getProductRequest($requestInfo) + { + if ($requestInfo instanceof Varien_Object) { + $request = $requestInfo; + } elseif (is_numeric($requestInfo)) { + $request = new Varien_Object(); + $request->setQty($requestInfo); + } else { + $request = new Varien_Object($requestInfo); + } + + if (!$request->hasQty()) { + $request->setQty(1); + } + return $request; + } + + /** + * Add product to shopping cart action + * + * @return void + */ + public function addAction() + { + $cart = $this->_getCart(); + $params = $this->getRequest()->getParams(); + try { + if (isset($params['qty'])) { + $filter = new Zend_Filter_LocalizedToNormalized( + array('locale' => Mage::app()->getLocale()->getLocaleCode()) + ); + $params['qty'] = $filter->filter($params['qty']); + } + + $product = null; + $productId = (int) $this->getRequest()->getParam('product'); + if ($productId) { + $_product = Mage::getModel('catalog/product') + ->setStoreId(Mage::app()->getStore()->getId()) + ->load($productId); + if ($_product->getId()) { + $product = $_product; + } + } + $related = $this->getRequest()->getParam('related_product'); + + /** + * Check product availability + */ + if (!$product) { + $this->_message(Mage::helper('xmlconnect')->__('Product is unavailable.'), parent::MESSAGE_STATUS_ERROR); + return; + } + + if ($product->isConfigurable()) { + + $request = $this->_getProductRequest($params); + $cartCandidates = $product->getTypeInstance(true)->prepareForCart($request, $product); + /** + * Hardcoded Configurable product default + */ + $minSaleQty = ((isset($params['qty']) ? $params['qty'] : 0) > 1) ? $params['qty'] : 1; + if (is_array($cartCandidates)) { + foreach ($cartCandidates as $candidate) { + $current = $candidate->getStockItem()->getMinSaleQty(); + if ($minSaleQty < $current) { + $minSaleQty = $current; + } + } + } + if ($minSaleQty) { + $params['qty'] = $minSaleQty; + } + } + + $cart->addProduct($product, $params); + if (!empty($related)) { + $cart->addProductsByIds(explode(',', $related)); + } + + $cart->save(); + + $this->_getSession()->setCartWasUpdated(true); + + /** + * @todo remove wishlist observer processAddToCart + */ + Mage::dispatchEvent('checkout_cart_add_product_complete', + array('product' => $product, 'request' => $this->getRequest(), 'response' => $this->getResponse()) + ); + + if (!$this->_getSession()->getNoCartRedirect(true)) { + $message = Mage::helper('xmlconnect')->__('%s has been added to your cart.', Mage::helper('core')->htmlEscape($product->getName())); + if ($cart->getQuote()->getHasError()) { + $message .= Mage::helper('xmlconnect')->__(' But cart has some errors.'); + } + $this->_message($message, parent::MESSAGE_STATUS_SUCCESS); + } + } catch (Mage_Core_Exception $e) { + if ($this->_getSession()->getUseNotice(true)) { + $this->_message($e->getMessage(), parent::MESSAGE_STATUS_ERROR); + } else { + $this->_message(implode("\n", array_unique(explode("\n", $e->getMessage()))), parent::MESSAGE_STATUS_ERROR); + } + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t add item to shopping cart.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Delete shoping cart item action + */ + public function deleteAction() + { + $id = (int) $this->getRequest()->getParam('item_id'); + if ($id) { + try { + $this->_getCart()->removeItem($id)->save(); + $this->_message(Mage::helper('xmlconnect')->__('Item has been deleted from cart.'), parent::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), parent::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t remove the item.'), self::MESSAGE_STATUS_ERROR); + } + } + } + + /** + * Initialize coupon + */ + public function couponAction() + { + /** + * No reason continue with empty shopping cart + */ + if (!$this->_getQuote()->getItemsCount()) { + $this->_message(Mage::helper('xmlconnect')->__('Shopping cart is empty.'), self::MESSAGE_STATUS_ERROR); + return; + } + + $couponCode = (string) $this->getRequest()->getParam('coupon_code'); + if ($this->getRequest()->getParam('remove') == 1) { + $couponCode = ''; + } + $oldCouponCode = $this->_getQuote()->getCouponCode(); + + if (!strlen($couponCode) && !strlen($oldCouponCode)) { + $this->_message(Mage::helper('xmlconnect')->__('Coupon code is empty.'), self::MESSAGE_STATUS_ERROR); + return; + } + + try { + $this->_getQuote()->getShippingAddress()->setCollectShippingRates(true); + $this->_getQuote()->setCouponCode(strlen($couponCode) ? $couponCode : '') + ->collectTotals() + ->save(); + + if ($couponCode) { + if ($couponCode == $this->_getQuote()->getCouponCode()) { + $this->_message(Mage::helper('xmlconnect')->__('Coupon code %s was applied.', strip_tags($couponCode)), parent::MESSAGE_STATUS_SUCCESS); + } else { + $this->_message(Mage::helper('xmlconnect')->__('Coupon code %s is not valid.', strip_tags($couponCode)), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('Coupon code was canceled.'), parent::MESSAGE_STATUS_SUCCESS); + } + + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t apply the coupon code.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Get shopping cart summary and flag is_virtual + */ + public function infoAction() + { + $this->_getQuote()->collectTotals()->save(); + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Retrieve shopping cart model object + * + * @return Mage_Checkout_Model_Cart + */ + protected function _getCart() + { + return Mage::getSingleton('checkout/cart'); + } + + /** + * Get checkout session model instance + * + * @return Mage_Checkout_Model_Session + */ + protected function _getSession() + { + return Mage::getSingleton('checkout/session'); + } + + /** + * Get current active quote instance + * + * @return Mage_Sales_Model_Quote + */ + protected function _getQuote() + { + return $this->_getCart()->getQuote(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CatalogController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CatalogController.php new file mode 100644 index 0000000000..8568c78e33 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CatalogController.php @@ -0,0 +1,276 @@ + + */ + +class Mage_XmlConnect_CatalogController extends Mage_XmlConnect_Controller_Action +{ + /** + * Category list + */ + public function categoryAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Filter product list + */ + public function filtersAction() + { + try{ + $this->loadLayout(false); + $this->renderLayout(); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + Mage::logException($e); + $this->_message(Mage::helper('xmlconnect')->__('An error occurred while loading category filters.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Product information + */ + public function productAction() + { + try { + $this->loadLayout(false); + $this->renderLayout(); + return; + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to load product info.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Product options list + */ + public function productOptionsAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + + /** + * Product gallery images list + */ + public function productGalleryAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Product reviews list + */ + public function productReviewsAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Add new review + */ + public function productReviewAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Perform search products + */ + public function searchAction() + { + $_helper = Mage::helper('catalogsearch'); + $queryParam = str_replace('%20', ' ', $this->getRequest()->getParam('query')); + $this->getRequest()->setParam($_helper->getQueryParamName(), $queryParam); + $query = $_helper->getQuery(); + /* @var $query Mage_CatalogSearch_Model_Query */ + + $query->setStoreId(Mage::app()->getStore()->getId()); + // + // return Mage::helper('catalogsearch')->__('Minimum Search query length is %s', $this->_getQuery()->getMinQueryLength()); + // + if ($query->getQueryText()) { + if ($_helper->isMinQueryLength()) { + $query->setId(0) + ->setIsActive(1) + ->setIsProcessed(1); + } else { + if ($query->getId()) { + $query->setPopularity($query->getPopularity()+1); + } else { + $query->setPopularity(1); + } + + if ($query->getRedirect()) { + $query->save(); + $this->getResponse()->setRedirect($query->getRedirect()); + return; + } else { + $query->prepare(); + } + } + + $_helper->checkNotes(); + + if (!$_helper->isMinQueryLength()) { + $query->save(); + } + } + + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Retrieve suggestions based on search query + */ + public function searchSuggestAction() + { + $this->getRequest()->setParam('q', $this->getRequest()->getParam('query')); + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Send product link to friend action + * + * @return this + */ + public function sendEmailAction() + { + /* @var $helper Mage_Sendfriend_Helper_Data */ + $helper = Mage::helper('sendfriend'); + /* @var $session Mage_Customer_Model_Session */ + $session = Mage::getSingleton('customer/session'); + + if (!$helper->isEnabled()) { + $this->_message(Mage::helper('xmlconnect')->__('Tell a Friend is disabled.'), self::MESSAGE_STATUS_ERROR); + return $this; + } + + if (!$helper->isAllowForGuest() && !$session->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return $this; + } + + /** + * Initialize product + */ + $productId = (int)$this->getRequest()->getParam('product_id'); + if (!$productId) { + $this->_message(Mage::helper('xmlconnect')->__('No product selected.'), self::MESSAGE_STATUS_ERROR); + return $this; + } + $product = Mage::getModel('catalog/product') + ->load($productId); + if (!$product->getId() || !$product->isVisibleInCatalog()) { + $this->_message(Mage::helper('xmlconnect')->__('Selected product is unavailable.'), self::MESSAGE_STATUS_ERROR); + return $this; + } + + Mage::register('product', $product); + + /** + * Initialize send friend model + */ + $model = Mage::getModel('sendfriend/sendfriend'); + $model->setRemoteAddr(Mage::helper('core/http')->getRemoteAddr(true)); + $model->setCookie(Mage::app()->getCookie()); + $model->setWebsiteId(Mage::app()->getStore()->getWebsiteId()); + + Mage::register('send_to_friend_model', $model); + + if ($model->getMaxSendsToFriend()) { +// $this->_message(Mage::helper('xmlconnect')->__('Messages cannot be sent more than %d times in an hour.', $model->getMaxSendsToFriend()), self::MESSAGE_STATUS_WARNING); +// return $this; + } + + $data = $this->getRequest()->getPost(); + + if (!$data) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return $this; + } + + $sender = (array)$this->getRequest()->getPost('sender'); + if ($session->isLoggedIn()) { + $sender['email'] = $session->getCustomer()->getEmail(); + $sender['name'] = $session->getCustomer()->getFirstName() . ' ' . $session->getCustomer()->getLastName(); + } + + /** + * Initialize category and set it to product + */ + $categoryId = $this->getRequest()->getParam('category_id', null); + if ($categoryId) { + $category = Mage::getModel('catalog/category') + ->load($categoryId); + $product->setCategory($category); + Mage::register('current_category', $category); + } + + $model->setSender($sender); + $model->setRecipients($this->getRequest()->getPost('recipients')); + $model->setProduct($product); + + try { + $validate = $model->validate(); + if ($validate === true) { + $model->send(); + $this->_message(Mage::helper('xmlconnect')->__('Tell a Friend link has been sent.'), self::MESSAGE_STATUS_SUCCESS); + return; + } else { + if (is_array($validate)) { + $this->_message(implode(' ', $validate), self::MESSAGE_STATUS_ERROR); + return; + } else { + $this->_message(Mage::helper('xmlconnect')->__('There were some problems with the data.'), self::MESSAGE_STATUS_ERROR); + return; + } + } + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Some emails were not sent.'), self::MESSAGE_STATUS_ERROR); + } + + return $this; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CheckoutController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CheckoutController.php new file mode 100644 index 0000000000..8ed89734e4 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CheckoutController.php @@ -0,0 +1,336 @@ + + */ + +class Mage_XmlConnect_CheckoutController extends Mage_XmlConnect_Controller_Action +{ + + /** + * Make sure customer is logged in + * + * @return void + */ + public function preDispatch() + { + parent::preDispatch(); + if (!Mage::getSingleton('customer/session')->isLoggedIn() + && !Mage::getSingleton('checkout/session')->getQuote()->isAllowedGuestCheckout()) { + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + } + + /** + * Get one page checkout model + * + * @return Mage_Checkout_Model_Type_Onepage + */ + public function getOnepage() + { + return Mage::getSingleton('checkout/type_onepage'); + } + + /** + * Onepage Checkout page + */ + public function indexAction() + { + if (!Mage::helper('checkout')->canOnepageCheckout()) { + $this->_message(Mage::helper('xmlconnect')->__('Onepage checkout is disabled.'), self::MESSAGE_STATUS_ERROR); + return; + } + $quote = $this->getOnepage()->getQuote(); + if ($quote->getHasError()) { + $this->_message(Mage::helper('xmlconnect')->__('Cart has some errors.'), self::MESSAGE_STATUS_ERROR); + return; + } else if (!$quote->hasItems()) { + $this->_message(Mage::helper('xmlconnect')->__('Cart is empty.'), self::MESSAGE_STATUS_ERROR); + return; + } else if (!$quote->validateMinimumAmount()) { + $error = Mage::getStoreConfig('sales/minimum_order/error_message'); + $this->_message($error, self::MESSAGE_STATUS_ERROR); + return; + } + Mage::getSingleton('checkout/session')->setCartWasUpdated(false); + $this->getOnepage()->initCheckout(); + + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Display customer new billing addrress form + */ + public function newBillingAddressFormAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Display customer new shipping addrress form + */ + public function newShippingAddressFormAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Billing addresses list action + */ + public function billingAddressAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Save billing address to current quote using onepage model + */ + public function saveBillingAddressAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + + $data = $this->getRequest()->getPost('billing', array()); + $customerAddressId = $this->getRequest()->getPost('billing_address_id', false); + if (isset($data['email'])) { + $data['email'] = trim($data['email']); + } + $result = $this->getOnepage()->saveBilling($data, $customerAddressId); + if (!isset($result['error'])) { + $this->_message(Mage::helper('xmlconnect')->__('Billing address has been set.'), self::MESSAGE_STATUS_SUCCESS); + } else { + if (!is_array($result['message'])) { + $result['message'] = array($result['message']); + } + $this->_message(implode('. ', $result['message']), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Shipping addresses list action + */ + public function shippingAddressAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Save shipping address to current quote using onepage model + */ + public function saveShippingAddressAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + + $data = $this->getRequest()->getPost('shipping', array()); + $customerAddressId = $this->getRequest()->getPost('shipping_address_id', false); + $result = $this->getOnepage()->saveShipping($data, $customerAddressId); + if (!isset($result['error'])) { + $this->_message(Mage::helper('xmlconnect')->__('Shipping address has been set.'), self::MESSAGE_STATUS_SUCCESS); + } else { + if (!is_array($result['message'])) { + $result['message'] = array($result['message']); + } + $this->_message(implode('. ', $result['message']), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Get shipping methods for current quote + */ + public function shippingMethodsAction() + { + try { + $result = array('error' => Mage::helper('xmlconnect')->__('Error.')); + $this->getOnepage()->getQuote()->getShippingAddress()->setCollectShippingRates(true); + $this->getOnepage()->getQuote()->collectTotals()->save(); + $this->loadLayout(false); + $this->renderLayout(); + return; + } catch (Mage_Core_Exception $e) { + $result['error'] = $e->getMessage(); + } + $this->_message($result['error'], self::MESSAGE_STATUS_ERROR); + } + + /** + * Shipping method save action + */ + public function saveShippingMethodAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + + $data = $this->getRequest()->getPost('shipping_method', ''); + $result = $this->getOnepage()->saveShippingMethod($data); + if (!isset($result['error'])) { + $this->_message(Mage::helper('xmlconnect')->__('Shipping method has been set.'), self::MESSAGE_STATUS_SUCCESS); + } else { + if (!is_array($result['message'])) { + $result['message'] = array($result['message']); + } + Mage::dispatchEvent('checkout_controller_onepage_save_shipping_method', array('request'=>$this->getRequest(), 'quote'=>$this->getOnepage()->getQuote())); + $this->_message(implode('. ', $result['message']), self::MESSAGE_STATUS_ERROR); + } + } + + + /** + * Save checkout method + */ + public function saveMethodAction() + { + if ($this->getRequest()->isPost()) { + $method = (string) $this->getRequest()->getPost('method'); + $result = $this->getOnepage()->saveCheckoutMethod($method); + if (!isset($result['error'])) { + $this->_message(Mage::helper('xmlconnect')->__('Payment Method has been set.'), self::MESSAGE_STATUS_SUCCESS); + } else { + if (!is_array($result['message'])) { + $result['message'] = array($result['message']); + } + $this->_message(implode('. ', $result['message']), self::MESSAGE_STATUS_ERROR); + } + } + } + + /** + * Get payment methods action + */ + public function paymentMethodsAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Save payment action + */ + public function savePaymentAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + try { + // set payment to quote + $result = array(); + $data = $this->getRequest()->getPost('payment', array()); + $result = $this->getOnepage()->savePayment($data); + $this->_message(Mage::helper('xmlconnect')->__('Payment method was successfully set.'), self::MESSAGE_STATUS_SUCCESS); + return; + } catch (Mage_Payment_Exception $e) { + $result['error'] = $e->getMessage(); + } catch (Mage_Core_Exception $e) { + $result['error'] = $e->getMessage(); + } catch (Exception $e) { + Mage::logException($e); + $result['error'] = Mage::helper('xmlconnect')->__('Unable to set payment method.'); + } + $this->_message($result['error'], self::MESSAGE_STATUS_ERROR); + } + + /** + * Order summary info action + */ + public function orderReviewAction() + { + $this->getOnepage()->getQuote()->collectTotals()->save(); + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Create order action + */ + public function saveOrderAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + + try { + if ($requiredAgreements = Mage::helper('checkout')->getRequiredAgreementIds()) { + $postedAgreements = array_keys($this->getRequest()->getPost('agreement', array())); + if ($diff = array_diff($requiredAgreements, $postedAgreements)) { + $error = Mage::helper('xmlconnect')->__('Please agree to all the terms and conditions before placing the order.'); + $this->_message($error, self::MESSAGE_STATUS_ERROR); + return; + } + } + if ($data = $this->getRequest()->getPost('payment', false)) { + $this->getOnepage()->getQuote()->getPayment()->importData($data); + } + $this->getOnepage()->saveOrder(); + + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_SUCCESS); + + $orderId = $this->getOnepage()->getLastOrderId(); + + $text = Mage::helper('xmlconnect')->__('Thank you for your purchase! '); + $text .= Mage::helper('xmlconnect')->__('Your order # is: %s. ', $orderId); + $text .= Mage::helper('xmlconnect')->__('You will receive an order confirmation email with details of your order and a link to track its progress.'); + $message->addChild('text', $text); + + $message->addChild('order_id', $orderId); + + $this->getOnepage()->getQuote()->save(); + $this->getOnepage()->getCheckout()->clear(); + + $this->getResponse()->setBody($message->asNiceXml()); + return; + } catch (Mage_Core_Exception $e) { + Mage::logException($e); + Mage::helper('checkout')->sendPaymentFailedEmail($this->getOnepage()->getQuote(), $e->getMessage()); + $error = $e->getMessage(); + } catch (Exception $e) { + Mage::logException($e); + Mage::helper('checkout')->sendPaymentFailedEmail($this->getOnepage()->getQuote(), $e->getMessage()); + $error = Mage::helper('xmlconnect')->__('An error occurred while processing your order. Please contact us or try again later.'); + } + $this->getOnepage()->getQuote()->save(); + + $this->_message($error, self::MESSAGE_STATUS_ERROR); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CmsController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CmsController.php new file mode 100644 index 0000000000..99140755a2 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CmsController.php @@ -0,0 +1,54 @@ + + */ + +class Mage_XmlConnect_CmsController extends Mage_XmlConnect_Controller_Action +{ + + /** + * Declare content type header + */ + public function preDispatch() + { + parent::preDispatch(); + $this->getResponse()->setHeader('Content-type', 'text/html; charset=UTF-8'); + } + + /** + * Category list + * + */ + public function pageAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/ConfigurationController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/ConfigurationController.php new file mode 100644 index 0000000000..2c1d58d593 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/ConfigurationController.php @@ -0,0 +1,132 @@ + + */ + +class Mage_XmlConnect_ConfigurationController extends Mage_Core_Controller_Front_Action +{ + /** + * Declare content type header + */ + public function preDispatch() + { + parent::preDispatch(); + $this->getResponse()->setHeader('Content-type', 'text/xml; charset=UTF-8'); + } + + /** + * Initialize application + * + * @return Mage_XmlConnect_Model_Application + */ + protected function _initApp() + { + + $cookieName = Mage_XmlConnect_Model_Application::APP_CODE_COOKIE_NAME; + $screenSizeCookieName = Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_NAME; + $code = $this->getRequest()->getParam($cookieName); + $screenSize = (string) $this->getRequest()->getParam($screenSizeCookieName); + $app = Mage::getModel('xmlconnect/application'); + if ($app) { + $app->loadByCode($code); + Mage::app()->setCurrentStore(Mage::app()->getStore($app->getStoreId())->getCode()); + Mage::getSingleton('core/locale')->emulate($app->getStoreId()); + $app->setScreenSize($screenSize); + if (!$app->getId()) { + Mage::throwException(Mage::helper('xmlconnect')->__('App with specified code does not exist.')); + } + $app->loadConfiguration(); + } else { + Mage::throwException(Mage::helper('xmlconnect')->__('App code required.')); + } + Mage::register('current_app', $app); + return $app; + } + + /** + * Default action + */ + public function indexAction() + { + try { + + $app = $this->_initApp(); + + $cookieToSetArray = array ( + array( + 'cookieName' => Mage_XmlConnect_Model_Application::APP_CODE_COOKIE_NAME, + 'paramName' => 'app_code', + 'value' => $app->getCode()), + array( + 'cookieName' => Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_NAME, + 'paramName' => Mage_XmlConnect_Model_Application::APP_SCREEN_SIZE_NAME, + 'value' => $app->getScreenSize()) + ); + foreach ($cookieToSetArray as $item) { + if (!isset($_COOKIE[$item['cookieName']]) || + (isset($_COOKIE[$item['cookieName']]) && + ($_COOKIE[$item['cookieName']] != $this->getRequest()->getParam($item['paramName']))) + ) { + /** + * @todo add management of cookie expire to application admin panel + */ + $cookieExpireOffset = 3600 * 24 * 30; + Mage::getSingleton('core/cookie')->set($item['cookieName'], $item['value'], $cookieExpireOffset, '/', null, null, true); + } + } + + if ($this->getRequest()->getParam('updated_at')) { + $updatedAt = strtotime($app->getUpdatedAt()); + $loadedAt = (int) $this->getRequest()->getParam('updated_at'); + if ($loadedAt >= $updatedAt) { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', Mage_XmlConnect_Controller_Action::MESSAGE_STATUS_SUCCESS); + $message->addChild('no_changes', '1'); + $this->getResponse()->setBody($message->asNiceXml()); + return; + } + } + $this->loadLayout(false); + $this->renderLayout(); + } catch (Mage_Core_Exception $e) { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', Mage_XmlConnect_Controller_Action::MESSAGE_STATUS_ERROR); + $message->addChild('text', $e->getMessage()); + $this->getResponse()->setBody($message->asNiceXml()); + } catch (Exception $e) { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', Mage_XmlConnect_Controller_Action::MESSAGE_STATUS_ERROR); + $message->addChild('text', Mage::helper('xmlconnect')->__('Can\'t show configuration.')); + Mage::logException($e); + $this->getResponse()->setBody($message->asNiceXml()); + } + + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CustomerController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CustomerController.php new file mode 100644 index 0000000000..27f0e0feb2 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/CustomerController.php @@ -0,0 +1,545 @@ + + */ + +class Mage_XmlConnect_CustomerController extends Mage_XmlConnect_Controller_Action +{ + /** + * Customer authentification action + * + * @return void + */ + public function loginAction() + { + $session = $this->_getSession(); + $request = $this->getRequest(); + if ($session->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('You are already logged in.'), self::MESSAGE_STATUS_ERROR); + return; + } + + if ($request->isPost()) { + $user = $request->getParam('username'); + $pass = $request->getParam('password'); + try { + if ($session->login($user, $pass)) { + if ($session->getCustomer()->getIsJustConfirmed()) { + $session->getCustomer()->sendNewAccountEmail('confirmed'); + } + $this->_message(Mage::helper('xmlconnect')->__('Authentication complete.'), self::MESSAGE_STATUS_SUCCESS); + } else { + $this->_message(Mage::helper('xmlconnect')->__('Invalid login or password.'), self::MESSAGE_STATUS_ERROR); + } + } catch (Mage_Core_Exception $e) { + switch ($e->getCode()) { + case Mage_Customer_Model_Customer::EXCEPTION_EMAIL_NOT_CONFIRMED: + // TODO: resend configmation email message with action + break; + case Mage_Customer_Model_Customer::EXCEPTION_INVALID_EMAIL_OR_PASSWORD: + $message = $e->getMessage(); + break; + default: + $message = $e->getMessage(); + } + $this->_message($message, self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Customer authentication problem.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('Login and password are required.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Customer logout + * + */ + public function logoutAction() + { + try { + $this->_getSession()->logout(); + $this->_message(Mage::helper('xmlconnect')->__('Logout complete.'), self::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Customer logout problem.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Customer registration/edit account form + * + * @return void + */ + public function formAction() + { + $customer = null; + $editFlag = (int)$this->getRequest()->getParam('edit'); + if ($editFlag == 1) { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + $customer = $this->_getSession()->getCustomer(); + } + + $this->loadLayout(false)->getLayout()->getBlock('xmlconnect.customer.form')->setCustomer($customer); + $this->renderLayout(); + } + + /** + * Change customer data action + * + * @return void + */ + public function editAction() + { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + if ($this->getRequest()->isPost()) { + $customer = $this->_getSession()->getCustomer(); + + /* @var $customerForm Mage_Customer_Model_Form */ + $customerForm = Mage::getModel('customer/form'); + $customerForm->setFormCode('customer_account_edit') + ->setEntity($customer); + + $customerData = $customerForm->extractData($this->getRequest()); + + $errors = array(); + $customerErrors = $customerForm->validateData($customerData); + if ($customerErrors !== true) { + $errors = array_merge($customerErrors, $errors); + } else { + $customerForm->compactData($customerData); + $customerErrors = $customer->validate(); + if (is_array($customerErrors)) { + $errors = array_merge($customerErrors, $errors); + } + } + + if ($this->getRequest()->getParam('change_password')) { + $currPass = $this->getRequest()->getPost('current_password'); + $newPass = $this->getRequest()->getPost('password'); + $confPass = $this->getRequest()->getPost('confirmation'); + + if (empty($currPass) || empty($newPass) || empty($confPass)) { + $errors[] = Mage::helper('xmlconnect')->__('Password fields cannot be empty.'); + } + + if ($newPass != $confPass) { + $errors[] = Mage::helper('xmlconnect')->__('Please make sure your passwords match.'); + } + + $oldPass = $this->_getSession()->getCustomer()->getPasswordHash(); + if (strpos($oldPass, ':')) { + list($_salt, $salt) = explode(':', $oldPass); + } else { + $salt = false; + } + + if ($customer->hashPassword($currPass, $salt) == $oldPass) { + $customer->setPassword($newPass); + } else { + $errors[] = Mage::helper('xmlconnect')->__('Invalid current password.'); + } + } + + if (!empty($errors)) { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_ERROR); + $message->addChild('text', implode(' ', $errors)); + $this->getResponse()->setBody($message->asNiceXml()); + return; + } + + try { + $customer->save(); + $this->_getSession()->setCustomer($customer); + $this->_message(Mage::helper('xmlconnect')->__('Account information has been saved.'), self::MESSAGE_STATUS_SUCCESS); + return; + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t save the customer.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('POST data is not valid.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Save customer account + * + * @return void + */ + public function saveAction() + { + $session = $this->_getSession(); + $request = $this->getRequest(); + if ($session->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('You are already logged in.'), self::MESSAGE_STATUS_ERROR); + return; + } + + $session->setEscapeMessages(true); // prevent XSS injection in user input + if ($request->isPost()) { + $errors = array(); + + /* @var $customer Mage_Customer_Model_Customer */ + $customer = Mage::registry('current_customer'); + if (is_null($customer)) { + $customer = Mage::getModel('customer/customer'); + } + + /* @var $customerForm Mage_Customer_Model_Form */ + $customerForm = Mage::getModel('customer/form'); + $customerForm->setFormCode('customer_account_create') + ->setEntity($customer); + + $customerData = $customerForm->extractData($this->getRequest()); + + if ($this->getRequest()->getParam('is_subscribed', false)) { + $customer->setIsSubscribed(1); + } + + /** + * Initialize customer group id + */ + $customer->getGroupId(); + + try { + $customerErrors = $customerForm->validateData($customerData); + if ($customerErrors !== true) { + $errors = array_merge($customerErrors, $errors); + } else { + $customerForm->compactData($customerData); + $customer->setPassword($this->getRequest()->getPost('password')); + $customer->setConfirmation($this->getRequest()->getPost('confirmation')); + $customerErrors = $customer->validate(); + if (is_array($customerErrors)) { + $errors = array_merge($customerErrors, $errors); + } + } + + $validationResult = count($errors) == 0; + if (true === $validationResult) { + $customer->save(); + + if ($customer->isConfirmationRequired()) { + $customer->sendNewAccountEmail('confirmation', $session->getBeforeAuthUrl()); + $message = Mage::helper('xmlconnect')->__('Account confirmation is required. Please check your email for the confirmation link.'); + $messageXmlObj = new Mage_XmlConnect_Model_Simplexml_Element(''); + $messageXmlObj->addChild('status', self::MESSAGE_STATUS_SUCCESS); + $messageXmlObj->addChild('text', $message); + $messageXmlObj->addChild('confirmation', 1); + $this->getResponse()->setBody($messageXmlObj->asNiceXml()); + return; + } else { + $session->setCustomerAsLoggedIn($customer); + $customer->sendNewAccountEmail('registered'); + $this->_message(Mage::helper('xmlconnect')->__('Thank you for registering!'), self::MESSAGE_STATUS_SUCCESS); + return; + } + } else { + if (is_array($errors)) { + $message = implode("\n", $errors); + } else { + $message = Mage::helper('xmlconnect')->__('Invalid customer data.'); + } + $this->_message($message, self::MESSAGE_STATUS_ERROR); + return ; + } + } catch (Mage_Core_Exception $e) { + if ($e->getCode() === Mage_Customer_Model_Customer::EXCEPTION_EMAIL_EXISTS) { + $message = Mage::helper('xmlconnect')->__('An account with this email address already exists.'); + $session->setEscapeMessages(false); + } else { + $message = $e->getMessage(); + } + $this->_message($message, self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t save the customer.'), self::MESSAGE_STATUS_ERROR); + } + } + } + + /** + * Send new password to customer by specified email + * + * @return void + */ + public function forgotPasswordAction() + { + $email = $this->getRequest()->getPost('email'); + if ($email) { + if (!Zend_Validate::is($email, 'EmailAddress')) { + $this->_message(Mage::helper('xmlconnect')->__('Invalid email address.'), self::MESSAGE_STATUS_ERROR); + return; + } + $customer = Mage::getModel('customer/customer') + ->setWebsiteId(Mage::app()->getStore()->getWebsiteId()) + ->loadByEmail($email); + + if ($customer->getId()) { + try { + $newPassword = $customer->generatePassword(); + $customer->changePassword($newPassword, false); + $customer->sendPasswordReminderEmail(); + $this->_message(Mage::helper('xmlconnect')->__('A new password has been sent.'), self::MESSAGE_STATUS_SUCCESS); + + return; + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Problem changing or sending password.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('This email address was not found in our records.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('Customer email not specified.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Customer addresses list + * + * @return void + */ + public function addressAction() + { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + + if (count($this->_getSession()->getCustomer()->getAddresses())) { + $this->loadLayout(false); + $this->renderLayout(); + } else { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_ERROR); + $message->addChild('is_empty_address_book', 1); + $this->getResponse()->setBody($message->asNiceXml()); + } + } + + /** + * Customer add/edit address form + * + * @return void + */ + public function addressFormAction() + { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + + $address = Mage::getModel('customer/address'); + + /** + * Init address object + */ + $addressId = (int)$this->getRequest()->getParam('id'); + if ($addressId) { + $address->load($addressId); + if ($address->getCustomerId() != $this->_getSession()->getCustomerId()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified address does not exist.'), self::MESSAGE_STATUS_ERROR); + return ; + } + } + + $this->loadLayout(false)->getLayout()->getBlock('xmlconnect.customer.address.form')->setAddress($address); + $this->renderLayout(); + } + + /** + * Remove customer address + * + * @return void + */ + public function deleteAddressAction() + { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + + $addressId = $this->getRequest()->getParam('id', false); + + if ($addressId) { + $address = Mage::getModel('customer/address')->load($addressId); + + // Validate address_id <=> customer_id + if ($address->getCustomerId() != $this->_getSession()->getCustomerId()) { + $this->_message(Mage::helper('xmlconnect')->__('Address does not belong to this customer.'), self::MESSAGE_STATUS_ERROR); + return; + } + + try { + $address->delete(); + $this->_message(Mage::helper('xmlconnect')->__('Address has been deleted.'), self::MESSAGE_STATUS_SUCCESS); + } catch (Exception $e) { + Mage::logException($e); + $this->_message(Mage::helper('xmlconnect')->__('An error occurred while deleting the address.'), self::MESSAGE_STATUS_ERROR); + } + } + } + + /** + * Add/Save customer address + * + * @return void + */ + public function saveAddressAction() + { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + + // Save data + if ($this->getRequest()->isPost()) { + $customer = $this->_getSession()->getCustomer(); + /* @var $address Mage_Customer_Model_Address */ + $address = Mage::getModel('customer/address'); + $addressId = $this->getRequest()->getParam('id'); + if ($addressId) { + $existsAddress = $customer->getAddressById($addressId); + if ($existsAddress->getId() && $existsAddress->getCustomerId() == $customer->getId()) { + $address->setId($existsAddress->getId()); + } + } + + $errors = array(); + + /* @var $addressForm Mage_Customer_Model_Form */ + $addressForm = Mage::getModel('customer/form'); + $addressForm->setFormCode('customer_address_edit') + ->setEntity($address); + $addressData = $addressForm->extractData($this->getRequest()); + $addressErrors = $addressForm->validateData($addressData); + if ($addressErrors !== true) { + $errors = $addressErrors; + } + + try { + $addressForm->compactData($addressData); + $address->setCustomerId($customer->getId()) + ->setIsDefaultBilling($this->getRequest()->getParam('default_billing', false)) + ->setIsDefaultShipping($this->getRequest()->getParam('default_shipping', false)); + + $addressErrors = $address->validate(); + if ($addressErrors !== true) { + $errors = array_merge($errors, $addressErrors); + } + + $addressValidation = count($errors) == 0; + + if (true === $addressValidation) { + $address->save(); + + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_SUCCESS); + $message->addChild('text', Mage::helper('xmlconnect')->__('Address has been saved.')); + $message->addChild('address_id', $address->getId()); + $this->getResponse()->setBody($message->asNiceXml()); + return; + } else { + if (is_array($addressValidation)) { + $this->_message(implode('. ', $addressValidation), self::MESSAGE_STATUS_ERROR); + } else { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t save address.'), self::MESSAGE_STATUS_ERROR); + } + } + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + Mage::logException($e); + $this->_message(Mage::helper('xmlconnect')->__('Can\'t save address.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('Address data not specified.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Customer orders list + * + * @return void + */ + public function orderListAction() + { + if (!$this->_getSession()->isLoggedIn()) { + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Check if customer is loggined + */ + public function isLogginedAction() + { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('is_loggined', (int)$this->_getSession()->isLoggedIn()); + $this->getResponse()->setBody($message->asNiceXml()); + } + + /** + * Filtering posted data. Converting localized data if needed + * + * @param array $data + * @return array + */ + protected function _filterPostData($data) + { + $data = $this->_filterDates($data, array('dob')); + return $data; + } + + /** + * Get customer session model + * + * @return Mage_Customer_Model_Session + */ + protected function _getSession() + { + return Mage::getSingleton('customer/session'); + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/IndexController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/IndexController.php new file mode 100644 index 0000000000..784929b953 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/IndexController.php @@ -0,0 +1,46 @@ + + */ + +class Mage_XmlConnect_IndexController extends Mage_XmlConnect_Controller_Action +{ + + /** + * Default action + * + */ + public function indexAction() + { + $this->loadLayout(false); + $this->renderLayout(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/Paypal/MepController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/Paypal/MepController.php new file mode 100644 index 0000000000..8ed7196993 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/Paypal/MepController.php @@ -0,0 +1,279 @@ + + */ + +class Mage_XmlConnect_Paypal_MepController extends Mage_XmlConnect_Controller_Action +{ + /** + * Store MEP checkout model instance + * + * @var Mage_XmlConnect_Model_Paypal_Mep_Checkout + */ + protected $_checkout = null; + + /** + * Store Quote mdoel instance + * + * @var Mage_Sales_Model_Quote + */ + protected $_quote = false; + + /** + * Make sure customer is logged in + * + * @return void + */ + public function preDispatch() + { + parent::preDispatch(); + if (!Mage::getSingleton('customer/session')->isLoggedIn() + && !Mage::getSingleton('checkout/session')->getQuote()->isAllowedGuestCheckout()) { + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + } + + /** + * Start MEP Checkout + */ + public function indexAction() + { + try { + $this->_initCheckout(); + $reservedOrderId = $this->_checkout->initCheckout(); + $this->_message(Mage::helper('xmlconnect')->__('Checkout has been initialized.'), self::MESSAGE_STATUS_SUCCESS); + return; + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to start MEP Checkout.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Save shipping address to current quote using onepage model + */ + public function saveShippingAddressAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + try { + $this->_initCheckout(); + $data = $this->getRequest()->getPost('shipping', array()); + $result = $this->_checkout->saveShipping($data); + if (!isset($result['error'])) { + $this->_message(Mage::helper('xmlconnect')->__('Shipping address has been set.'), self::MESSAGE_STATUS_SUCCESS); + } else { + if (!is_array($result['message'])) { + $result['message'] = array($result['message']); + } + $this->_message(implode('. ', $result['message']), self::MESSAGE_STATUS_ERROR); + } + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to save shipping address.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Get shipping methods for current quote + */ + public function shippingMethodsAction() + { + try { + $this->_initCheckout(); + $this->loadLayout(false); + $this->renderLayout(); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to get shipping methods list.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Shipping method save action + */ + public function saveShippingMethodAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + try { + $this->_initCheckout(); + $data = $this->getRequest()->getPost('shipping_method', ''); + $result = $this->_checkout->saveShippingMethod($data); + if (!isset($result['error'])) { + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_SUCCESS); + $message->addChild('text', Mage::helper('xmlconnect')->__('Shipping method has been set.')); + if ($this->_getQuote()->isVirtual()) { + $quoteAddress = $this->_getQuote()->getBillingAddress(); + } else { + $quoteAddress = $this->_getQuote()->getShippingAddress(); + } + $taxAmount = Mage::helper('core')->currency($quoteAddress->getBaseTaxAmount(), false, false); + $message->addChild('tax_amount', Mage::helper('xmlconnect')->formatPriceForXml($taxAmount)); + $this->getResponse()->setBody($message->asNiceXml()); + } else { + if (!is_array($result['message'])) { + $result['message'] = array($result['message']); + } + $this->_message(implode('. ', $result['message']), self::MESSAGE_STATUS_ERROR); + } + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to save shipping method.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Shopping cart totals + */ + public function cartTotalsAction() + { + try { + $this->_initCheckout(); + $this->loadLayout(false); + $this->renderLayout(); + return; + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to collect cart totals.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Submit the order + */ + public function saveOrderAction() + { + if (!$this->getRequest()->isPost()) { + $this->_message(Mage::helper('xmlconnect')->__('Specified invalid data.'), self::MESSAGE_STATUS_ERROR); + return; + } + try { + /** + * Init checkout + */ + $this->_initCheckout(); + + /** + * Set payment data + */ + $data = $this->getRequest()->getPost('payment', array()); + $this->_checkout->savePayment($data); + + /** + * Place order + */ + $this->_checkout->saveOrder(); + + /** + * Format success report + */ + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_SUCCESS); + + $orderId = $this->_checkout->getLastOrderId(); + + $text = Mage::helper('xmlconnect')->__('Thank you for your purchase! '); + $text .= Mage::helper('xmlconnect')->__('Your order # is: %s. ', $orderId); + $text .= Mage::helper('xmlconnect')->__('You will receive an order confirmation email with details of your order and a link to track its progress.'); + $message->addChild('text', $text); + + $message->addChild('order_id', $orderId); + $this->getResponse()->setBody($message->asNiceXml()); + return; + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Unable to place the order.'), self::MESSAGE_STATUS_ERROR); + Mage::logException($e); + } + } + + /** + * Instantiate quote and checkout + * + * @throws Mage_Core_Exception + */ + protected function _initCheckout() + { + + $quote = $this->_getQuote(); + if (!$quote->hasItems() || $quote->getHasError()) { + Mage::throwException(Mage::helper('xmlconnect')->__('Unable to initialize MEP Checkout.')); + } + if (!$quote->validateMinimumAmount()) { + $error = Mage::getStoreConfig('sales/minimum_order/error_message'); + Mage::throwException($error); + } + $this->_getCheckoutSession()->setCartWasUpdated(false); + + $this->_checkout = Mage::getSingleton('xmlconnect/paypal_mep_checkout', array('quote' => $quote)); + } + + /** + * Return checkout session object + * + * @return Mage_Checkout_Model_Session + */ + protected function _getCheckoutSession() + { + return Mage::getSingleton('checkout/session'); + } + + /** + * Return checkout quote object + * + * @return Mage_Sale_Model_Quote + */ + protected function _getQuote() + { + if (!$this->_quote) { + $this->_quote = $this->_getCheckoutSession()->getQuote(); + } + return $this->_quote; + } +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/controllers/WishlistController.php b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/WishlistController.php new file mode 100644 index 0000000000..1986c84aba --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/controllers/WishlistController.php @@ -0,0 +1,294 @@ + + */ + +class Mage_XmlConnect_WishlistController extends Mage_XmlConnect_Controller_Action +{ + + /** + * Check if customer is logged in + * + * @return void + */ + public function preDispatch() + { + parent::preDispatch(); + if (!$this->_getCustomerSession()->isLoggedIn()) { + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + $this->_message(Mage::helper('xmlconnect')->__('Customer not logged in.'), self::MESSAGE_STATUS_ERROR); + return ; + } + } + + /** + * Get customer session model + * + * @return Mage_Customer_Model_Session + */ + protected function _getCustomerSession() + { + return Mage::getSingleton('customer/session'); + } + + /** + * Retrieve wishlist object + * + * @return Mage_Wishlist_Model_Wishlist|false + */ + protected function _getWishlist() + { + try { + $wishlist = Mage::getModel('wishlist/wishlist') + ->loadByCustomer($this->_getCustomerSession()->getCustomer(), true); + Mage::register('wishlist', $wishlist); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + return false; + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t create wishlist.'), self::MESSAGE_STATUS_ERROR); + return false; + } + return $wishlist; + } + + /** + * Display customer wishlist + */ + public function indexAction() + { + $this->_getWishlist(); + $this->loadLayout(false); + $this->renderLayout(); + } + + /** + * Adding new item + */ + public function addAction() + { + $session = $this->_getCustomerSession(); + $wishlist = $this->_getWishlist(); + if (!$wishlist) { + return; + } + + $request = $this->getRequest(); + $productId = (int)$request->getParam('product'); + if (!$productId) { + $this->_message(Mage::helper('xmlconnect')->__('Product was not specified.'), self::MESSAGE_STATUS_ERROR); + return; + } + + $product = Mage::getModel('catalog/product')->load($productId); + if (!$product->getId() || !$product->isVisibleInCatalog()) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t specify product.'), self::MESSAGE_STATUS_ERROR); + return; + } + + try { + $item = $wishlist->addNewItem($product->getId()); + if (strlen(trim((string)$request->getParam('description')))) { + $item->setDescription($request->getParam('description')) + ->save(); + } + $wishlist->save(); + + Mage::dispatchEvent('wishlist_add_product', array('wishlist'=>$wishlist, 'product'=>$product)); + + Mage::helper('wishlist')->calculate(); + + $message = Mage::helper('xmlconnect')->__('%1$s has been added to your wishlist.', $product->getName()); + $this->_message($message, self::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('An error occurred while adding item to wishlist.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Remove item + */ + public function removeAction() + { + $wishlist = $this->_getWishlist(); + $id = (int) $this->getRequest()->getParam('item'); + $item = Mage::getModel('wishlist/item')->load($id); + + if ($item->getWishlistId() == $wishlist->getId()) { + try { + $item->delete(); + $wishlist->save(); + $this->_message(Mage::helper('xmlconnect')->__('Item has been removed from wishlist.'), self::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch(Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('An error occurred while removing item from wishlist.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('Specified item does not exist in wishlist.'), self::MESSAGE_STATUS_ERROR); + } + + Mage::helper('wishlist')->calculate(); + } + + /** + * Clear wishlist action + */ + public function clearAction() + { + $wishlist = $this->_getWishlist(); + $items = $wishlist->getItemCollection(); + + try { + foreach ($items as $item) { + $item->delete(); + } + $wishlist->save(); + $this->_message(Mage::helper('xmlconnect')->__('Wishlist has been cleared.'), self::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } catch(Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('An error occurred while removing items from wishlist.'), self::MESSAGE_STATUS_ERROR); + } + + Mage::helper('wishlist')->calculate(); + } + + /** + * Update wishlist item comments + */ + public function updateAction() + { + $post = $this->getRequest()->getPost(); + if ($post && isset($post['description']) && is_array($post['description'])) { + $wishlist = $this->_getWishlist(); + if (!$wishlist) { + return; + } + $updatedItems = 0; + $problemsFlag = false; + + foreach ($post['description'] as $itemId => $description) { + $item = Mage::getModel('wishlist/item')->load($itemId); + $description = (string) $description; + if ($item->getWishlistId() != $wishlist->getId()) { + continue; + } + try { + $item->setDescription($description) + ->save(); + $updatedItems++; + } catch (Exception $e) { + $problemsFlag = true; + } + } + + // save wishlist model for setting date of last update + if ($updatedItems) { + try { + $wishlist->save(); + if ($problemsFlag) { + $message = Mage::helper('xmlconnect')->__('Wishlist has been updated. But there are accrued some errors while updating some items.'); + } else { + $message = Mage::helper('xmlconnect')->__('Wishlist has been updated.'); + } + $this->_message($message, self::MESSAGE_STATUS_SUCCESS); + } + catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Items were updated. But can\'t update wishlist.'), self::MESSAGE_STATUS_SUCCESS); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('No items were updated.'), self::MESSAGE_STATUS_ERROR); + } + } else { + $this->_message(Mage::helper('xmlconnect')->__('No items were specifed to update.'), self::MESSAGE_STATUS_ERROR); + } + } + + /** + * Add wishlist item to shopping cart and remove from wishlist + * + * If Product has required options - item removed from wishlist and redirect + * to product view page with message about needed defined required options + * + */ + public function cartAction() + { + $wishlist = $this->_getWishlist(); + if (!$wishlist) { + return; + } + $itemId = (int)$this->getRequest()->getParam('item'); + + /* @var $item Mage_Wishlist_Model_Item */ + $item = Mage::getModel('wishlist/item')->load($itemId); + + if (!$item->getId() || $item->getWishlistId() != $wishlist->getId()) { + $this->_message(Mage::helper('xmlconnect')->__('Invalid item or wishlist.'), self::MESSAGE_STATUS_ERROR); + return; + } + + /* @var $session Mage_Wishlist_Model_Session */ + $session = Mage::getSingleton('wishlist/session'); + $cart = Mage::getSingleton('checkout/cart'); + + try { + $item->addToCart($cart, true); + $cart->save()->getQuote()->collectTotals(); + $wishlist->save(); + + Mage::helper('wishlist')->calculate(); + + $this->_message(Mage::helper('xmlconnect')->__('Item has been added to cart.'), self::MESSAGE_STATUS_SUCCESS); + } catch (Mage_Core_Exception $e) { + if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_NOT_SALABLE) { + $this->_message(Mage::helper('xmlconnect')->__('Product(s) currently out of stock.'), self::MESSAGE_STATUS_ERROR); + } else if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_HAS_REQUIRED_OPTIONS || + $e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_IS_GROUPED_PRODUCT) { + $item->delete(); + + $message = new Mage_XmlConnect_Model_Simplexml_Element(''); + $message->addChild('status', self::MESSAGE_STATUS_SUCCESS); + $message->addChild('has_required_options', 1); + $message->addChild('product_id', $item->getProductId()); + $this->getResponse()->setBody($message->asNiceXml()); + } else { + $this->_message($e->getMessage(), self::MESSAGE_STATUS_ERROR); + } + } catch (Exception $e) { + $this->_message(Mage::helper('xmlconnect')->__('Can\'t add item to shopping cart.'), self::MESSAGE_STATUS_ERROR); + } + + Mage::helper('wishlist')->calculate(); + } + +} diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/adminhtml.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/adminhtml.xml new file mode 100644 index 0000000000..ce9192bc6b --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/adminhtml.xml @@ -0,0 +1,69 @@ + + + + + + Mobile + 35 + + + Manage Apps + 10 + adminhtml/mobile + + + Submission History + 20 + adminhtml/mobile/history + + + + + + + + + + Mobile + 100 + + + Manage Apps + 10 + + + Submission History + 20 + + + + + + + + diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/config.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/config.xml new file mode 100644 index 0000000000..28a67a2846 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/config.xml @@ -0,0 +1,299 @@ + + + + + + 1.4.0.8 + + + + + + Mage_XmlConnect_Model + xmlconnect_mysql4 + + + Mage_XmlConnect_Model_Mysql4 + + xmlconnect_application
    + xmlconnect_history
    + xmlconnect_application
    +
    +
    +
    + + + Mage_XmlConnect_Block + + + + + Mage_XmlConnect_Helper + + + + + + Mage_XmlConnect + Mage_XmlConnect_Model_Mysql4_Setup + + + + + + + + +
    + + + + + + Mage_XmlConnect_Adminhtml + + + + + + + + + + xmlconnect.xml + + + + + + + + Mage_XmlConnect.csv + + + + + + + + + xmlconnect/observer + changeUpdatedAtParamOnConfigSave + + + + + + + + + standard + + Mage_XmlConnect + xmlconnect + + + + + + + xmlconnect.xml + + + + + + + + Mage_XmlConnect.csv + + + + + + + + + 1 + xmlconnect/payment_method_paypal_mep + PayPal Mobile Payments Library + 0 + paypal + + + + + + + 70 + 130 + 80 + 280 + 40 + + + + + 150 35 + + + 320 40 + + + 35 + 35 + 35 + 35 + 35 + + + 320 230 + 90 120 + 320 367 + 320 90 + 30 90 + 136 28 + 107 37 + 320 37 + + + 320 20 + + + 320 90 + 40 40 + 40 40 + 40 40 + 40 40 + 40 40 + 20 20 + 20 20 + 20 20 + 20 20 + + + + 512 512 + 320 460 + 57 57 + 100 100 + + + + <_320x480> + + default + + + default + + + interface/native/tabBar + zoom + 1.02857 + + + + + default + + + interface/native/tabBar + + zoom + 1.31428 + + + + + default + + + interface/native/tabBar + + zoom + 2.05714 + + + + + + <_640x960> + + default + + + / + zoom + 2.0 + + + + + + + + www.magentocommerce.com/mobile/activate/ + http://www.magentocommerce.com/product/mobile + http://www.magentocommerce.com/product/mobile#resubmission + + + + 5 + + + + +
    diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/system.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/system.xml new file mode 100644 index 0000000000..48b396ecab --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/system.xml @@ -0,0 +1,53 @@ + + + + + + + + + + xmlconnect/adminhtml_system_config_backend_baseurl + + + + + + + + + + + xmlconnect/adminhtml_system_config_backend_currency_default + + + + + + + diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/custom.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/custom.xml new file mode 100644 index 0000000000..8a7cd4c196 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/custom.xml @@ -0,0 +1,34 @@ + + + + + custom + + 1.0 + 1.4.0.2 + #0FDE00#0FDE00#0FDE00#0FDE00#0FDE00#0FDE00#0FDE00#0FDE00
    #0FDE00
    #0FDE00#0FDE00#0FDE00
    diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/default.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/default.xml new file mode 100644 index 0000000000..2d9b55d174 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/default.xml @@ -0,0 +1,64 @@ + + + + + + default + + 1.0 + 1.4.0.2 + + + + +#363D40 + + +#A8A8A8 +#E76E01 +#ABABAB +#EDEDED + + +#D13B00 +#F3F3F3 + + +#F3F3F3 + + + + +
    #FFFFFF
    +#222222 +#FFFFFF +#D55000 +
    +
    +
    +
    diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/funk_leaf.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/funk_leaf.xml new file mode 100644 index 0000000000..e167c1dd2a --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/funk_leaf.xml @@ -0,0 +1,64 @@ + + + + + + funk_leaf + + 1.0 + 1.4.0.2 + + + + +#024800 + + +#ABABAB +#7CD500 +#8CDE9C +#FFFFFF + + +#566101 +#FFFFFF + + +#FFFFFF + + + + +
    #FFFFFF
    +#222222 +#3E3F3F +#025600 +
    +
    +
    +
    diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/hot_red.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/hot_red.xml new file mode 100644 index 0000000000..75be7bfc17 --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/hot_red.xml @@ -0,0 +1,64 @@ + + + + + + hot_red + + 1.0 + 1.4.0.2 + + + + +#C91E00 + + +#A8A8A8 +#C92B01 +#FFFFFF +#FFFFFF + + +#A32301 +#FFFFFF + + +#E00000 + + + + +
    #FFFFFF
    +#222222 +#FFFFFF +#CA1800 +
    +
    +
    +
    diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/sky_blue.xml b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/sky_blue.xml new file mode 100644 index 0000000000..27dec5ab5f --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/etc/themes/sky_blue.xml @@ -0,0 +1,64 @@ + + + + + + sky_blue + + 1.0 + 1.4.0.2 + + + + +#4B7282 + + +#ABABAB +#B7D0DA +#FFFFFF +#FFFFFF + + +#2C434D +#FFFFFF + + +#FFFFFF + + + + +
    #FFFFFF
    +#222222 +#FFFFFF +#406474 +
    +
    +
    +
    diff --git a/app/code/core/Mage/XmlConnect/XmlConnect/sql/xmlconnect_setup/mysql4-install-1.4.0.8.php b/app/code/core/Mage/XmlConnect/XmlConnect/sql/xmlconnect_setup/mysql4-install-1.4.0.8.php new file mode 100644 index 0000000000..c34bba35af --- /dev/null +++ b/app/code/core/Mage/XmlConnect/XmlConnect/sql/xmlconnect_setup/mysql4-install-1.4.0.8.php @@ -0,0 +1,97 @@ +startSetup(); + +$installer->run(" +CREATE TABLE IF NOT EXISTS `{$installer->getTable('xmlconnect_application')}` ( + `application_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `code` varchar(32) NOT NULL, + `type` varchar(32) DEFAULT NULL, + `store_id` smallint(5) unsigned DEFAULT NULL, + `active_from` date DEFAULT NULL, + `active_to` date DEFAULT NULL, + `updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `configuration` blob, + `status` tinyint(1) NOT NULL DEFAULT '0', + `browsing_mode` tinyint(1) DEFAULT '0', + PRIMARY KEY (`application_id`), + UNIQUE KEY `UNQ_XMLCONNECT_APPLICATION_CODE` (`code`), + KEY `FK_XMLCONNECT_APPLICAION_STORE` (`store_id`), + CONSTRAINT `FK_XMLCONNECT_APPLICAION_STORE` FOREIGN KEY (`store_id`) REFERENCES `{$installer->getTable('core_store')}` (`store_id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE IF NOT EXISTS `{$installer->getTable('xmlconnect_history')}` ( + `history_id` int(11) NOT NULL AUTO_INCREMENT, + `application_id` smallint(5) unsigned NOT NULL, + `created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `store_id` smallint(5) unsigned DEFAULT NULL, + `params` blob, + `title` varchar(200) DEFAULT NULL, + `activation_key` varchar(255) NOT NULL, + `code` varchar(255) NOT NULL, + PRIMARY KEY (`history_id`), + KEY `FK_XMLCONNECT_HISTORY_APPLICATION` (`application_id`), + CONSTRAINT `FK_XMLCONNECT_HISTORY_APPLICATION` FOREIGN KEY (`application_id`) REFERENCES `{$installer->getTable('xmlconnect_application')}` (`application_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; +"); + +$entityTypeId = $installer->getEntityTypeId('catalog_category'); +$attributeSetId = $installer->getDefaultAttributeSetId($entityTypeId); +$attributeGroupId = $installer->getDefaultAttributeGroupId($entityTypeId, $attributeSetId); + +$installer->addAttribute('catalog_category', 'thumbnail', array( + 'type' => 'varchar', + 'backend' => 'catalog/category_attribute_backend_image', + 'frontend' => '', + 'label' => 'Thumbnail Image', + 'input' => 'image', + 'class' => '', + 'source' => '', + 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE, + 'visible' => true, + 'required' => false, + 'user_defined' => false, + 'default' => '', + 'searchable' => false, + 'filterable' => false, + 'comparable' => false, + 'visible_on_front' => false, + 'unique' => false, +)); + +$installer->addAttributeToGroup( + $entityTypeId, + $attributeSetId, + $attributeGroupId, + 'thumbnail', + '4' +); + +$installer->endSetup(); diff --git a/app/design/adminhtml/default/default/layout/authorizenet.xml b/app/design/adminhtml/default/default/layout/authorizenet.xml new file mode 100644 index 0000000000..11e8f64531 --- /dev/null +++ b/app/design/adminhtml/default/default/layout/authorizenet.xml @@ -0,0 +1,35 @@ + + + + + + mage/directpost.js + + + diff --git a/app/design/adminhtml/default/default/layout/bundle.xml b/app/design/adminhtml/default/default/layout/bundle.xml index c7423dac4b..2b4b8c5138 100644 --- a/app/design/adminhtml/default/default/layout/bundle.xml +++ b/app/design/adminhtml/default/default/layout/bundle.xml @@ -98,5 +98,21 @@ Layout handle for budle products bundlebundle/adminhtml_sales_order_items_renderer + + + + bundlebundle/catalog_product_configuration + + + + + + selectbundle/adminhtml_catalog_product_composite_fieldset_options_type_select + multibundle/adminhtml_catalog_product_composite_fieldset_options_type_multi + radiobundle/adminhtml_catalog_product_composite_fieldset_options_type_radio + checkboxbundle/adminhtml_catalog_product_composite_fieldset_options_type_checkbox + + + diff --git a/app/design/adminhtml/default/default/layout/catalog.xml b/app/design/adminhtml/default/default/layout/catalog.xml index 62845d3cd2..30e26ded1b 100644 --- a/app/design/adminhtml/default/default/layout/catalog.xml +++ b/app/design/adminhtml/default/default/layout/catalog.xml @@ -66,6 +66,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -240,4 +276,39 @@ Layout handle for configurable products + + + + textcatalog/product_view_options_type_text + filecatalog/product_view_options_type_file + selectcatalog/product_view_options_type_select + datecatalog/product_view_options_type_date + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/compiler.xml b/app/design/adminhtml/default/default/layout/compiler.xml index 81a0692fa4..942468ef32 100644 --- a/app/design/adminhtml/default/default/layout/compiler.xml +++ b/app/design/adminhtml/default/default/layout/compiler.xml @@ -27,9 +27,9 @@ --> - + - + diff --git a/app/design/adminhtml/default/default/layout/connect.xml b/app/design/adminhtml/default/default/layout/connect.xml new file mode 100644 index 0000000000..1b63ada8f9 --- /dev/null +++ b/app/design/adminhtml/default/default/layout/connect.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + package_infotab_package + release_infotab_release + authorstab_authors + dependenciestab_depends + contentstab_contents + load_local_packagetab_local + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/customer.xml b/app/design/adminhtml/default/default/layout/customer.xml index 9a5b08b36c..91c9ff0702 100644 --- a/app/design/adminhtml/default/default/layout/customer.xml +++ b/app/design/adminhtml/default/default/layout/customer.xml @@ -33,6 +33,11 @@ + mage/adminhtml/product/composite/configure.js + varien/configurable.js + + + @@ -52,4 +57,7 @@ + + + diff --git a/app/design/adminhtml/default/default/layout/dataflow.xml b/app/design/adminhtml/default/default/layout/dataflow.xml index 004c22e6aa..1dfae43002 100644 --- a/app/design/adminhtml/default/default/layout/dataflow.xml +++ b/app/design/adminhtml/default/default/layout/dataflow.xml @@ -29,7 +29,7 @@ - + @@ -41,6 +41,19 @@ + + + + + + + + + + + + + + - diff --git a/app/design/adminhtml/default/default/layout/downloadable.xml b/app/design/adminhtml/default/default/layout/downloadable.xml index 7a7b0d3ad0..885e4d6522 100644 --- a/app/design/adminhtml/default/default/layout/downloadable.xml +++ b/app/design/adminhtml/default/default/layout/downloadable.xml @@ -82,5 +82,16 @@ downloadabledownloadable/adminhtml_sales_items_column_downloadable_name - + + + + downloadabledownloadable/catalog_product_configuration + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/giftmessage.xml b/app/design/adminhtml/default/default/layout/giftmessage.xml new file mode 100644 index 0000000000..12ca9f5479 --- /dev/null +++ b/app/design/adminhtml/default/default/layout/giftmessage.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/importexport.xml b/app/design/adminhtml/default/default/layout/importexport.xml new file mode 100644 index 0000000000..d7fe41c19c --- /dev/null +++ b/app/design/adminhtml/default/default/layout/importexport.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/newsletter.xml b/app/design/adminhtml/default/default/layout/newsletter.xml index b85db7ffc0..80c90e996e 100644 --- a/app/design/adminhtml/default/default/layout/newsletter.xml +++ b/app/design/adminhtml/default/default/layout/newsletter.xml @@ -37,20 +37,30 @@ - - - + - + + + + + + + + + - + - + - + + + + + diff --git a/app/design/adminhtml/default/default/layout/sales.xml b/app/design/adminhtml/default/default/layout/sales.xml index 04612f924e..87759d8c37 100644 --- a/app/design/adminhtml/default/default/layout/sales.xml +++ b/app/design/adminhtml/default/default/layout/sales.xml @@ -73,6 +73,11 @@ + + mage/adminhtml/giftmessage.js + mage/adminhtml/giftoptions.js + mage/adminhtml/giftoptions/tooltip.js + @@ -86,10 +91,16 @@ qtyadminhtml/sales_items_column_qty nameadminhtml/sales_items_column_name nameadminhtml/sales_items_column_name_groupedgrouped + + + + - + + + + + + + + xmlconnect/boxes.css + + + + + + + + + + + + + + + + + + + + + + + + + + imagesaccordion_images + themesaccordion_themes + tabsaccordion_tabs + + + + + + + + + general_sectionmobile_edit_tab_general + design_sectionmobile_edit_tab_design + content_sectionmobile_edit_tab_content + payment_methodsmobile_edit_tab_payment + history_gridmobile_edit_tab_submission_history_grid + + + + + + + xmlconnect/dropdown.css + xmlconnect/styles.css + + + + + + + + + + + + + + + + + submission_sectionmobile_submission_tab_container + + + + + + + xmlconnect/styles.css + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml b/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml index 712cce5f85..edc3498994 100644 --- a/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml +++ b/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml @@ -36,9 +36,9 @@ function registerUserRole(grid, element, checked){ if(checked){ - inRoleUsers[element.value] = 0; + inRoleUsers.set(element.value, 0); } else { - inRoleUsers.remove(element.value); + inRoleUsers.unset(element.value); } $('in_role_user').value = inRoleUsers.toQueryString(); grid.reloadParams = {'in_role_user[]':inRoleUsers.keys()}; @@ -54,11 +54,11 @@ if (warning && checkBoxes.size() > 0) { if ( !confirm("__('Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?') ?>") ) { checkbox[0].checked = false; - for(i in checkBoxes) { - if( checkBoxes[i].status == 1) { - checkBoxes[i].object.checked = true; + checkBoxes.each(function(elem) { + if (elem.value.status == 1) { + elem.value.object.checked = true; } - } + }); return false; } warning = false; @@ -71,25 +71,27 @@ function roleUsersRowInit(grid, row){ var checkbox = $(row).getElementsByClassName('checkbox')[0]; if (checkbox) { - checkBoxes[checkbox.value] = {'status' : ((checkbox.checked) ? 1 : 0), 'object' : checkbox}; + checkBoxes.set(checkbox.value, {'status' : ((checkbox.checked) ? 1 : 0), 'object' : checkbox}); } } - function myhandler(o) + function myhandler(obj) { if (checkBoxes.size() > 0) { if ( !confirm("__('Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?') ?>") ) { - o.checked = false; - for(i in checkBoxes) { - if( checkBoxes[i].status == 1) { - checkBoxes[i].object.checked = true; + obj.checked = false; + checkBoxes.each(function(elem) { + if (elem.value.status == 1) { + elem.value.object.checked = true; } - } + }); return false; } warning = false; } - for(i in checkBoxes) getJsObjectName() ?>.setCheckboxChecked(checkBoxes[i].object, o.checked); + checkBoxes.each(function(elem) { + getJsObjectName() ?>.setCheckboxChecked(elem.value.object, obj.checked); + }); } getJsObjectName() ?>.rowClickCallback = roleUsersRowClick; diff --git a/app/design/adminhtml/default/default/template/authorizenet/directpost/iframe.phtml b/app/design/adminhtml/default/default/template/authorizenet/directpost/iframe.phtml new file mode 100644 index 0000000000..b548da0537 --- /dev/null +++ b/app/design/adminhtml/default/default/template/authorizenet/directpost/iframe.phtml @@ -0,0 +1,55 @@ + +getParams(); +$_helper = $this->helper('authorizenet'); +?> + + + + + + diff --git a/app/design/adminhtml/default/default/template/authorizenet/directpost/info.phtml b/app/design/adminhtml/default/default/template/authorizenet/directpost/info.phtml new file mode 100644 index 0000000000..3c3def13b3 --- /dev/null +++ b/app/design/adminhtml/default/default/template/authorizenet/directpost/info.phtml @@ -0,0 +1,157 @@ + +getMethodCode(); +$_method = $_form->getMethod(); +$_controller = $this->helper('authorizenet')->getControllerName(); +$_orderUrl = $this->helper('authorizenet')->getPlaceOrderAdminUrl(); +?> + + + + + + diff --git a/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/bundle.phtml b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/bundle.phtml new file mode 100644 index 0000000000..e7b531e1be --- /dev/null +++ b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/bundle.phtml @@ -0,0 +1,99 @@ + + + +decorateArray($this->getOptions()); ?> + + +
    +

    __('Bundle Items') ?>

    +
    +
    + + getSelections()) : ?> + getOptionHtml($option); ?> + + +
    +
    +
    + + + + diff --git a/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/checkbox.phtml b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/checkbox.phtml new file mode 100644 index 0000000000..cd4c566098 --- /dev/null +++ b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/checkbox.phtml @@ -0,0 +1,51 @@ + + +getOption(); ?> +getSelections(); ?> +
    getRequired()) echo ' class="required"' ?>>getRequired()) echo '*' ?>htmlEscape($_option->getTitle()) ?>
    +decoratedIsLast){?> class="last"> +
    + getRequired()): ?> + getSelectionQtyTitlePrice($_selections[0]) ?> + + +
      + +
    • _isSelected($_selection)) echo ' checked="checked"' ?>isSaleable()) echo ' disabled="disabled"' ?> value="getSelectionId() ?>" onclick="ProductConfigure.bundleControl.changeSelection(this)"/> + + getRequired()): ?> + setValidationContainer('bundle-option-'.$_option->getId().'-'.$_selection->getSelectionId(), 'bundle-option-'.$_option->getId().'-container') ?> + +
    • + +
    +
    + +
    + diff --git a/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/multi.phtml b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/multi.phtml new file mode 100644 index 0000000000..0c248eb0a0 --- /dev/null +++ b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/multi.phtml @@ -0,0 +1,48 @@ + + +getOption(); ?> +getSelections(); ?> +
    getRequired()) echo ' class="required"' ?>>getRequired()) echo '*' ?>htmlEscape($_option->getTitle()) ?>
    +decoratedIsLast){?> class="last"> +
    + getRequired()): ?> + getSelectionQtyTitlePrice($_selections[0]) ?> + + + + +
    + diff --git a/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/radio.phtml b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/radio.phtml new file mode 100644 index 0000000000..32e2601656 --- /dev/null +++ b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/radio.phtml @@ -0,0 +1,64 @@ + + +getOption(); ?> +getSelections(); ?> +getDefaultSelection(); ?> +_getDefaultValues(); ?> + +
    + + id="bundle-option-getId() ?>-qty-input" class="input-text qty" type="text" name="bundle_option_qty[getId() ?>]" value=""/> + + getRequired()) echo ' class="required"' ?>>getRequired()) echo '*' ?>htmlEscape($_option->getTitle()) ?> +
    +decoratedIsLast){?> class="last"> +
    + _showSingle()): ?> + getSelectionTitlePrice($_selections[0]) ?> + + +
      + getRequired()): ?> +
    • isSalable())?'':' checked="checked" ' ?> value="" onclick="ProductConfigure.bundleControl.changeSelection(this)"/> + +
    • + + +
    • _isSelected($_selection)) echo ' checked="checked"' ?>isSaleable()) echo ' disabled="disabled"' ?>value="getSelectionId() ?>" onclick="ProductConfigure.bundleControl.changeSelection(this)"/> + + getRequired()): ?> + setValidationContainer('bundle-option-'.$_option->getId().'-'.$_selection->getSelectionId(), 'bundle-option-'.$_option->getId().'-container') ?> + +
    • + +
    +
    + +
    + diff --git a/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/select.phtml b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/select.phtml new file mode 100644 index 0000000000..0f323df6cf --- /dev/null +++ b/app/design/adminhtml/default/default/template/bundle/product/composite/fieldset/options/type/select.phtml @@ -0,0 +1,54 @@ + + +getOption(); ?> +getSelections(); ?> +getDefaultSelection(); ?> +_getDefaultValues(); ?> + +
    + + id="bundle-option-getId() ?>-qty-input" class="input-text qty" type="text" name="bundle_option_qty[getId() ?>]" value=""/> + + getRequired()) echo ' class="required"' ?>>getRequired()) echo '*' ?>htmlEscape($_option->getTitle()) ?> +
    +decoratedIsLast){?> class="last"> +
    + _showSingle()): ?> + getSelectionTitlePrice($_selections[0]) ?> + + + + +
    + diff --git a/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml b/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml index dadde151e9..22ff296d06 100644 --- a/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml +++ b/app/design/adminhtml/default/default/template/bundle/sales/order/view/items/renderer.phtml @@ -333,7 +333,6 @@ -   @@ -411,53 +410,4 @@     - canDisplayGiftmessage()): ?> - - - canDisplayContainer()): ?> -
    - - - - canDisplayContainer()): ?> -
    - - -   -   -   -   -   -   -   -   -   - - diff --git a/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml b/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml index 708d3d3d9c..f0761e741d 100644 --- a/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml +++ b/app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml @@ -169,7 +169,7 @@ var currentNode = tree.getNodeById(categoryId); if (currentNode) { - if (params['general[is_active]']) { + if (parseInt(params['general[is_active]'])) { var oldClass = 'no-active-category'; var newClass = 'active-category'; } else { diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/configure.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/configure.phtml new file mode 100644 index 0000000000..492caede7e --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/configure.phtml @@ -0,0 +1,54 @@ + + + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/configurable.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/configurable.phtml new file mode 100644 index 0000000000..9472f9e35e --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/configurable.phtml @@ -0,0 +1,52 @@ + + + +getProduct(); ?> +decorateArray($this->getAllowAttributes()); ?> +isSaleable() && count($_attributes)):?> +
    +

    __('Associated Products') ?>

    +
    +
    + +
    + decoratedIsLast){?> class="last"> +
    + +
    + + +
    +
    +
    + + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/custom_options.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/custom_options.phtml new file mode 100644 index 0000000000..7253c5123a --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/custom_options.phtml @@ -0,0 +1,34 @@ + +
    +
    +

    __('Custom Options') ?>

    +
    +
    + Custom Options Fields +
    +
    \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/grouped.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/grouped.phtml new file mode 100644 index 0000000000..33f674956a --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/grouped.phtml @@ -0,0 +1,93 @@ + + + +
    +

    __('Associated Products') ?>

    +
    + getProduct(); ?> + setPreconfiguredValue(); ?> + getAssociatedProducts(); ?> + 0; ?> + isAvailable() || !$_hasAssociatedProducts): ?> +

    __('Availability:') ?> __('Out of stock') ?>

    + + + + + + + + + + + + + getCanShowProductPrice($_product)): ?> + + + isSaleable()): ?> + + + + + + + + + helper('tax')->getPrice($_item, $_item->getFinalPrice(), true) ?> + + + + + getCanShowProductPrice($_product)): ?> + + + isSaleable()): ?> + + + + + + + + + + +
    __('ID') ?>__('SKU') ?>__('Product Name') ?>__('Price') ?>__('Qty') ?>
    getId() ?>htmlEscape($_item->getSku()) ?>htmlEscape($_item->getName()) ?> + getCanShowProductPrice($_item)): ?> + getPriceHtml($_item, true) ?> + + + isSaleable()) : ?> + + +

    __('Out of stock') ?>

    + +
    __('No options of this product are available.') ?>
    +
    +
    diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options.phtml new file mode 100644 index 0000000000..e3e94af043 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options.phtml @@ -0,0 +1,45 @@ + + + +decorateArray($this->getOptions()); ?> + + +getChildHtml('options_js') ?> + +
    +

    __('Custom Options') ?>

    +
    +
    + + getOptionHtml($option); ?> + +
    +
    +
    + + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/js.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/js.phtml new file mode 100644 index 0000000000..447e5c552c --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/js.phtml @@ -0,0 +1,106 @@ + + + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/date.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/date.phtml new file mode 100644 index 0000000000..af5f0b3f61 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/date.phtml @@ -0,0 +1,94 @@ + + +getOption(); ?> +getId(); ?> +
    getIsRequire()) echo ' class="required"' ?>>getIsRequire()) echo '*' ?>escapeHtml($_option->getTitle()) ?> + getFormatedPrice() ?>
    +decoratedIsLast){?> class="last"> + +getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE_TIME + || $_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE): ?> + + getDateHtml() ?> + + useCalendar()): ?> + + + + + +getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_DATE_TIME + || $_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_TIME): ?> + getTimeHtml() ?> + + + + + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/default.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/default.phtml new file mode 100644 index 0000000000..a86783f878 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/default.phtml @@ -0,0 +1,31 @@ + + +getOption(); ?> +
    + +
    \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/file.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/file.phtml new file mode 100644 index 0000000000..101fb5ae2c --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/file.phtml @@ -0,0 +1,99 @@ + +getOption(); ?> +getFileInfo(); ?> +hasData() ? true : false; ?> +getId() . '_file'; ?> + + + + + + + +
    + getFormatedPrice() ?>
    +decoratedIsLast){?> class="last"> + + getTitle(); ?> + + __('Change') ?> +   + + __('Delete') ?> + +
    > + + + getFileExtension()): ?> +

    __('Allowed file extensions to upload')?>: getFileExtension() ?>

    + + getImageSizeX() > 0): ?> +

    __('Maximum image width')?>: getImageSizeX() ?> __('px.')?>

    + + getImageSizeY() > 0): ?> +

    __('Maximum image height')?>: getImageSizeY() ?> __('px.')?>

    + +
    + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/select.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/select.phtml new file mode 100644 index 0000000000..aa2cec0e8e --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/select.phtml @@ -0,0 +1,39 @@ + + +getOption(); ?> +
    getIsRequire()) echo ' class="required"' ?>>getIsRequire()) echo '*' ?>escapeHtml($_option->getTitle()) ?>
    +decoratedIsLast){?> class="last"> +
    + getValuesHtml() ?> + getIsRequire()): ?> + getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_RADIO || $_option->getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_CHECKBOX): ?> + + + +
    + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/text.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/text.phtml new file mode 100644 index 0000000000..cd5441dda5 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/options/type/text.phtml @@ -0,0 +1,42 @@ + + +getOption(); ?> +
    getIsRequire()) echo ' class="required"' ?>>getIsRequire()) echo '*' ?>escapeHtml($_option->getTitle()) ?> + getFormatedPrice() ?>
    +decoratedIsLast){?> class="last"> +
    + getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_FIELD): ?> + + getType() == Mage_Catalog_Model_Product_Option::OPTION_TYPE_AREA): ?> + + + getMaxCharacters()): ?> +

    __('Maximum number of characters:')?> getMaxCharacters() ?>

    + +
    + diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/qty.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/qty.phtml new file mode 100644 index 0000000000..8af048b408 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/qty.phtml @@ -0,0 +1,36 @@ + + + + +
    +
    +
    +
    +
    +
    +
    diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/simple.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/simple.phtml new file mode 100644 index 0000000000..d1119168e9 --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/simple.phtml @@ -0,0 +1,34 @@ + +
    +
    +

    __('Simple Product') ?>

    +
    +
    + Simple Product Fields +
    +
    \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/virtual.phtml b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/virtual.phtml new file mode 100644 index 0000000000..6b86e00aab --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/composite/fieldset/virtual.phtml @@ -0,0 +1,34 @@ + +
    +
    +

    __('Virtual Product') ?>

    +
    +
    + Virtual Product Fields +
    +
    \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/catalog/product/edit/action/attribute.phtml b/app/design/adminhtml/default/default/template/catalog/product/edit/action/attribute.phtml index e0053d7d7e..2219c8f596 100644 --- a/app/design/adminhtml/default/default/template/catalog/product/edit/action/attribute.phtml +++ b/app/design/adminhtml/default/default/template/catalog/product/edit/action/attribute.phtml @@ -40,5 +40,22 @@ getBlockHtml('formkey')?> diff --git a/app/design/adminhtml/default/default/template/catalog/product/price.phtml b/app/design/adminhtml/default/default/template/catalog/product/price.phtml new file mode 100644 index 0000000000..164ba7a70b --- /dev/null +++ b/app/design/adminhtml/default/default/template/catalog/product/price.phtml @@ -0,0 +1,380 @@ + + + +helper('core'); + $_weeeHelper = $this->helper('weee'); + $_taxHelper = $this->helper('tax'); + + $_product = $this->getProduct(); + $_id = $_product->getId(); + $_storeId = $_product->getStoreId(); + $_website = Mage::app()->getStore($_storeId)->getWebsite(); + + $_weeeSeparator = ''; + $_simplePricesTax = ($_taxHelper->displayPriceIncludingTax() || $_taxHelper->displayBothPrices()); + $_minimalPriceValue = $_product->getMinimalPrice(); + $_minimalPrice = $_taxHelper->getPrice($_product, $_minimalPriceValue, $_simplePricesTax); +?> + + +getPrice($_product, $_minimalPriceValue, $includingTax = null); +$_inclTax = $_taxHelper->getPrice($_product, $_minimalPriceValue, $includingTax = true); +?> +getAmount($_product, null, null, $_website); ?> +typeOfDisplay($_product, array(1,2,4))): ?> + getAmount($_product, null, null, $_website); ?> + getProductWeeeAttributesForDisplay($_product); ?> + + +
    +getPrice($_product, $_product->getPrice()) ?> +getPrice($_product, $_product->getPrice(), $_simplePricesTax) ?> +getPrice($_product, $_product->getFinalPrice()) ?> +getPrice($_product, $_product->getFinalPrice(), true) ?> +getPriceDisplayType(); ?> + + displayBothPrices()): ?> + typeOfDisplay($_product, 0)): // including ?> + + helper('tax')->__('Excl. Tax:') ?> + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, false) ?> + + + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount,true,false) ?> + + + typeOfDisplay($_product, 1)): // incl. + weee ?> + + helper('tax')->__('Excl. Tax:') ?> + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, false) ?> + + + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + ( + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount(), $_storeId, true, true); ?> + + + ) + + typeOfDisplay($_product, 4)): // incl. + weee ?> + + helper('tax')->__('Excl. Tax:') ?> + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, false) ?> + + + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + ( + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount()+$_weeeTaxAttribute->getTaxAmount(), $_storeId, true, true); ?> + + + ) + + typeOfDisplay($_product, 2)): // excl. + weee + final ?> + + helper('tax')->__('Excl. Tax:') ?> + + currencyByStore($_price, $_storeId, true, false) ?> + + + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount(), $_storeId, true, true); ?> + + + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + + + + helper('tax')->__('Excl. Tax:') ?> + + currencyByStore($_price, $_storeId, true, false) ?> + + + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax, $_storeId, true, false) ?> + + + + + typeOfDisplay($_product, 0)): // including ?> + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, true) ?> + + typeOfDisplay($_product, 1)): // incl. + weee ?> + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, true) ?> + + ( + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount(), $_storeId, true, true); ?> + + + ) + typeOfDisplay($_product, 4)): // incl. + weee ?> + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, true) ?> + + ( + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount()+$_weeeTaxAttribute->getTaxAmount(), $_storeId, true, true); ?> + + + ) + typeOfDisplay($_product, 2)): // excl. + weee + final ?> + currencyByStore($_price, $_storeId, true, true) ?>
    + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount(), $_storeId, true, true); ?> + + + + currencyByStore($_price+$_weeeTaxAmount, $_storeId, true, true) ?> + + + + currencyByStore($_price, $_storeId, true, true) ?> + + + + + getOriginalAmount($_product); ?> + + typeOfDisplay($_product, 0)): // including ?> +

    + __('Regular Price:') ?> + + currencyByStore($_regularPrice+$_originalWeeeTaxAmount, $_storeId, true, false) ?> + +

    + + displayBothPrices()): ?> +

    + __('Special Price:') ?> + + __('Excl. Tax:') ?> + + currencyByStore($_finalPrice+$_weeeTaxAmount, $_storeId, true, false) ?> + + + + __('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + +

    + +

    + __('Special Price:') ?> + + currencyByStore($_finalPrice+$_weeeTaxAmount, $_storeId, true, false) ?> + +

    + + + typeOfDisplay($_product, 1)): // incl. + weee ?> +

    + __('Regular Price:') ?> + + currencyByStore($_regularPrice+$_originalWeeeTaxAmount, $_storeId, true, false) ?> + +

    + +

    + __('Special Price:') ?> + + __('Excl. Tax:') ?> + + currencyByStore($_finalPrice+$_weeeTaxAmount, $_storeId, true, false) ?> + + + ( + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount(), $_storeId, true, true); ?> + + + ) + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + +

    + typeOfDisplay($_product, 4)): // incl. + weee ?> +

    + __('Regular Price:') ?> + + currencyByStore($_regularPrice+$_originalWeeeTaxAmount, $_storeId, true, false) ?> + +

    + +

    + __('Special Price:') ?> + + __('Excl. Tax:') ?> + + currencyByStore($_finalPrice+$_weeeTaxAmount, $_storeId, true, false) ?> + + + ( + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount()+$_weeeTaxAttribute->getTaxAmount(), $_storeId, true, true); ?> + + + ) + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + +

    + typeOfDisplay($_product, 2)): // excl. + weee + final ?> +

    + __('Regular Price:') ?> + + currencyByStore($_regularPrice, $_storeId, true, false) ?> + +

    + +

    + __('Special Price:') ?> + + __('Excl. Tax:') ?> + + currencyByStore($_finalPrice, $_storeId, true, false) ?> + + + + + getName(); ?>: currencyByStore($_weeeTaxAttribute->getAmount(), $_storeId, true, true); ?> + + + + __('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax+$_weeeTaxAmount, $_storeId, true, false) ?> + + +

    + +

    + __('Regular Price:') ?> + + currencyByStore($_regularPrice, $_storeId, true, false) ?> + +

    + + displayBothPrices()): ?> +

    + __('Special Price:') ?> + + __('Excl. Tax:') ?> + + currencyByStore($_finalPrice, $_storeId, true, false) ?> + + + + helper('tax')->__('Incl. Tax:') ?> + + currencyByStore($_finalPriceInclTax, $_storeId, true, false) ?> + + +

    + +

    + __('Special Price:') ?> + + currencyByStore($_finalPrice, $_storeId, true, false) ?> + +

    + + + + + +getDisplayMinimalPrice() && $_minimalPriceValue && $_minimalPriceValue < $_product->getFinalPrice()): ?> + + + typeOfDisplay($_product, array(0, 1, 4))): ?> + + + + getUseLinkForAsLowAs()):?> + + + + + __('As low as:') ?> + + currencyByStore($_minimalPriceDisplayValue, $_storeId, true, false) ?> + + getUseLinkForAsLowAs()):?> + + + + +getDisplayMinimalPrice() && $_minimalPrice && $_minimalPrice < $_finalPrice): */ ?> +
    diff --git a/app/design/adminhtml/default/default/template/connect/extension/custom/authors.phtml b/app/design/adminhtml/default/default/template/connect/extension/custom/authors.phtml new file mode 100644 index 0000000000..3f6c30009f --- /dev/null +++ b/app/design/adminhtml/default/default/template/connect/extension/custom/authors.phtml @@ -0,0 +1,91 @@ + + + +
    + getFormHtml() ?> +
    +

    __("Authors") ?>

    +
    +
    + __("Authors") ?> + + + + + + + + + + + + + + + + + +
    __("Name") ?> *__("User") ?> *__("Email") ?> *__("Remove") ?>
    getAddAuthorButtonHtml() ?>
    +
    +
    diff --git a/app/design/adminhtml/default/default/template/connect/extension/custom/contents.phtml b/app/design/adminhtml/default/default/template/connect/extension/custom/contents.phtml new file mode 100644 index 0000000000..e3827ed91f --- /dev/null +++ b/app/design/adminhtml/default/default/template/connect/extension/custom/contents.phtml @@ -0,0 +1,91 @@ + + +
    + getFormHtml() ?> + +
    +

    __("Contents") ?>

    +
    +
    + __("Contents") ?> + + + + + + + + + + + + + + + + + + + getData('contents/target')): ?> + getData('contents/target') as $_i=>$_dbField): ?> + + + + + + + + + + + + +
    __("Target") ?>__("Path") ?>__("Type") ?>__("Include") ?>__("Ignore") ?>__("Action") ?>
    getAddRowButtonHtml('contents_container', 'contents_template', $this->__('Add Contents Path')) ?>
    +
    +
    diff --git a/app/design/adminhtml/default/default/template/connect/extension/custom/depends.phtml b/app/design/adminhtml/default/default/template/connect/extension/custom/depends.phtml new file mode 100644 index 0000000000..3be89e3569 --- /dev/null +++ b/app/design/adminhtml/default/default/template/connect/extension/custom/depends.phtml @@ -0,0 +1,137 @@ + +
    + getFormHtml() ?> + +
    +

    __("Packages") ?>

    +
    +
    + __("Packages") ?> + + + + + + + + + + + + + + + + + + + + + getData('depends/package/name')): ?> + getData('depends/package/name') as $_i=>$_dbField): ?> + + + + + + + + + + + + +
    __("Package") ?>__("Channel") ?>__("Min") ?>__("Max") ?>__("Files") ?>__("Action") ?>
    getAddRowButtonHtml('depends_packages_container', 'depends_packages_template', $this->__('Add Package dependency')) ?>
    +
    + +
    +

    __("Extensions") ?>

    +
    +
    + __("Extensions") ?> + + + + + + + + + + + + + + + + + getData('depends/extension/name')): ?> + getData('depends/extension/name') as $_i=>$_dbField): ?> + + + + + + + + + + + + +
    __("Extension") ?>__("Min") ?>__("Max") ?>__("Action") ?>
    getAddRowButtonHtml('depends_extensions_container', 'depends_extensions_template', $this->__('Add PHP Extension dependency')) ?>
    +
    + +
    diff --git a/app/design/adminhtml/default/default/template/extensions/custom/load.phtml b/app/design/adminhtml/default/default/template/connect/extension/custom/load.phtml similarity index 92% rename from app/design/adminhtml/default/default/template/extensions/custom/load.phtml rename to app/design/adminhtml/default/default/template/connect/extension/custom/load.phtml index 4c81e1b43a..2bb7d06adb 100644 --- a/app/design/adminhtml/default/default/template/extensions/custom/load.phtml +++ b/app/design/adminhtml/default/default/template/connect/extension/custom/load.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/connect/extension/custom/package.phtml b/app/design/adminhtml/default/default/template/connect/extension/custom/package.phtml new file mode 100644 index 0000000000..20377a46b4 --- /dev/null +++ b/app/design/adminhtml/default/default/template/connect/extension/custom/package.phtml @@ -0,0 +1,91 @@ + + + + + + +
    + + getFormHtml() ?> +
    diff --git a/app/design/adminhtml/default/default/template/extensions/custom/release.phtml b/app/design/adminhtml/default/default/template/connect/extension/custom/release.phtml similarity index 91% rename from app/design/adminhtml/default/default/template/extensions/custom/release.phtml rename to app/design/adminhtml/default/default/template/connect/extension/custom/release.phtml index 6bf7d785b1..47f5c773a5 100644 --- a/app/design/adminhtml/default/default/template/extensions/custom/release.phtml +++ b/app/design/adminhtml/default/default/template/connect/extension/custom/release.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com) + * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/customer/edit/addlisttype.phtml b/app/design/adminhtml/default/default/template/customer/edit/addlisttype.phtml new file mode 100644 index 0000000000..994b07bf52 --- /dev/null +++ b/app/design/adminhtml/default/default/template/customer/edit/addlisttype.phtml @@ -0,0 +1,37 @@ + + diff --git a/app/design/adminhtml/default/default/template/customer/edit/tab/view/grid/item.phtml b/app/design/adminhtml/default/default/template/customer/edit/tab/view/grid/item.phtml new file mode 100644 index 0000000000..b99d97955e --- /dev/null +++ b/app/design/adminhtml/default/default/template/customer/edit/tab/view/grid/item.phtml @@ -0,0 +1,46 @@ + + +getProduct(); + $options = $this->getOptionList(); +?> + + + escapeHtml($product->getName())?> + +
    + escapeHtml($product->getName())?> +
    + +
    escapeHtml($option['label']) ?>
    + getFormattedOptionValue($option) ?> +
    + +
    +
    + diff --git a/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml b/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml index 33d14cb36e..87b17d8f5f 100644 --- a/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml +++ b/app/design/adminhtml/default/default/template/customer/tab/addresses.phtml @@ -112,7 +112,7 @@ getFormObject()->setHtmlIdPrefix($_templatePrefix) - ->setValues(array()) + ->setValues(array('country_id' => Mage::helper('core')->getDefaultCountry($customer->getStore()))) ->setFieldNameSuffix('address['.$_templatePrefix.']'); ?> getFormObject()->getHtml() ?> @@ -135,6 +135,7 @@ addressesModel.prototype = { this.formContainer= $('address_form_container'); this.baseItemId = 'new_item'; + this.defaultCountries = getDefaultCountriesJson(); ?>; this.itemContentTemplate = new Template('helper('customer/address')->getFormat('js_template')?>'); this.onNewAddressClick = this.addNewAddress.bindAsEventListener(this); @@ -218,6 +219,10 @@ addressesModel.prototype = { $('_item'+this.itemCount+'firstname').value = $('_accountfirstname').value; $('_item'+this.itemCount+'lastname').value = $('_accountlastname').value; + if ($('_accountwebsite_id').value !== '' && undefined !== this.defaultCountries[$('_accountwebsite_id').value]) { + $('_item'+this.itemCount+'country_id').value = this.defaultCountries[$('_accountwebsite_id').value]; + } + Element.hide(newForm); var template = '
  • '; deleteButtonId ++; diff --git a/app/design/adminhtml/default/default/template/customer/tab/cart.phtml b/app/design/adminhtml/default/default/template/customer/tab/cart.phtml index 8a7bf3dd9a..cc4d682803 100644 --- a/app/design/adminhtml/default/default/template/customer/tab/cart.phtml +++ b/app/design/adminhtml/default/default/template/customer/tab/cart.phtml @@ -24,6 +24,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> + getCartHeader()): ?>
    @@ -34,9 +35,34 @@ getGridParentHtml() ?> -canDisplayContainer()): ?> +canDisplayContainer()) : ?> +getJsObjectName(); +?>
    diff --git a/app/design/adminhtml/default/default/template/customer/tab/wishlist.phtml b/app/design/adminhtml/default/default/template/customer/tab/wishlist.phtml index 1884df63ce..b58ee0fa4d 100644 --- a/app/design/adminhtml/default/default/template/customer/tab/wishlist.phtml +++ b/app/design/adminhtml/default/default/template/customer/tab/wishlist.phtml @@ -29,23 +29,40 @@ - - - -
    - - getFormHtml() ?> -
    diff --git a/app/design/adminhtml/default/default/template/extensions/file/form.phtml b/app/design/adminhtml/default/default/template/extensions/file/form.phtml deleted file mode 100644 index 3cac263d9d..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/file/form.phtml +++ /dev/null @@ -1,79 +0,0 @@ - - -
    -
    - - - -

    getTitle() ?>

    getBackButtonHtml() ?>
    -
    -
    -
    - getBlockHtml('formkey')?> -
    -

    __("Upload Local File") ?>

    -
    -
    - __("Upload Local File") ?> - - - - - -
    __("Upload local file:") ?>
    - - getUploadButtonHtml() ?>
    -
    -
    - -
    - getBlockHtml('formkey')?> -
    -

    __("Download Remote File") ?>

    -
    -
    - __("Download Remote File") ?> - - - - - -
    __("Remote URL:") ?>
    - - getRemoteButtonHtml() ?>
    -
    -
    - - -
    diff --git a/app/design/adminhtml/default/default/template/extensions/local/actions.phtml b/app/design/adminhtml/default/default/template/extensions/local/actions.phtml deleted file mode 100644 index bdb52ce60f..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/local/actions.phtml +++ /dev/null @@ -1,49 +0,0 @@ - - -
    -
    -

    __("Actions") ?>

    -
    -
    - __("Actions") ?> - getActionButtonHtml() ?>
    - -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/local/changelog.phtml b/app/design/adminhtml/default/default/template/extensions/local/changelog.phtml deleted file mode 100644 index 17dcd34492..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/local/changelog.phtml +++ /dev/null @@ -1,58 +0,0 @@ - -
    - getReleases() as $_log): ?> -
    -

    ()

    -
    -
    - () - - - - - - - - - -
    __("Date:") ?>
    __("Version:") ?> ()
    __("API:") ?> ()
    __("License:") ?> - - - - - - - - - - - -
    __("Notes:") ?>
    -
    - -
    diff --git a/app/design/adminhtml/default/default/template/extensions/local/contents.phtml b/app/design/adminhtml/default/default/template/extensions/local/contents.phtml deleted file mode 100644 index ea81691bb9..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/local/contents.phtml +++ /dev/null @@ -1,42 +0,0 @@ - -
    -
    -

    __("Provided Files") ?>

    -
    -
    - __("Provided Files") ?> - - - - getPkg()->getFilelist() as $_file): ?> - - - -
    __("Role") ?>__("Name") ?>__("Installed As") ?>__("md5sum") ?>
    -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/local/depends.phtml b/app/design/adminhtml/default/default/template/extensions/local/depends.phtml deleted file mode 100644 index c7e848cbe4..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/local/depends.phtml +++ /dev/null @@ -1,44 +0,0 @@ - -
    -
    -

    __("Dependencies") ?>

    -
    -
    - __("Dependencies") ?> - - - - getDepends() as $_dep): ?> - - - -
    __("Type") ?>__("Role") ?>__("Name") ?>__("Min") ?>__("Max") ?>__("Recommended") ?>__("Exclude") ?>
    -
    -
    - -
    getData('pkg')) ?>
    diff --git a/app/design/adminhtml/default/default/template/extensions/local/package.phtml b/app/design/adminhtml/default/default/template/extensions/local/package.phtml deleted file mode 100644 index 8545e19767..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/local/package.phtml +++ /dev/null @@ -1,85 +0,0 @@ - -
    -
    -

    __("Package Information") ?>

    -
    -
    - __("Package Information") ?> - - - - - - - -
    __("Name:") ?>getPkg()->getPackage() ?>
    __("Channel:") ?>getPkg()->getChannel() ?>
    __("Summary:") ?>getPkg()->getSummary() ?>
    __("Description:") ?>getPkg()->getDescription()) ?>
    -
    - -
    -

    __("Current Release Information") ?>

    -
    -
    - __("Current Release Information") ?> - - - - - - - - -
    __("Date:") ?>getPkg()->getDate() ?> getPkg()->getTime() ?>
    __("Version:") ?>getPkg()->getVersion() ?> (getPkg()->getState() ?>)
    __("API:") ?>getPkg()->getVersion('api') ?> (getPkg()->getState('api') ?>)
    __("License:") ?> - getPkg()->getLicenseLocation()): ?> - - - getPkg()->getLicense() ?> - - - -
    __("Notes:") ?>getPkg()->getNotes()) ?>
    -
    - -

    __("Maintainers") ?>

    -
    - __("Maintainers") ?> - - - - getPkg()->getMaintainers() as $_maint): ?> - - - - - - - - - -
    __("Role") ?>__("Name") ?>__("User") ?>__("Email") ?>
    :(__("Inactive") ?>)
    -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/local/upgrade.phtml b/app/design/adminhtml/default/default/template/extensions/local/upgrade.phtml deleted file mode 100644 index b770488fc1..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/local/upgrade.phtml +++ /dev/null @@ -1,52 +0,0 @@ - - -
    - - - - -

    getTitle() ?>

    getBackButtonHtml() ?>
    -
    -
    -
    -

    __("Upgrade All Available Packages") ?>

    -
    -
    - __("Upgrade All Available Packages") ?> - - getUpgradeAllButtonHtml() ?>
    - - -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/mass/install.phtml b/app/design/adminhtml/default/default/template/extensions/mass/install.phtml deleted file mode 100644 index d470fdc9ad..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/mass/install.phtml +++ /dev/null @@ -1,41 +0,0 @@ - -
    - - - - -

    getTitle() ?>

    getBackButtonHtml() ?>
    -
    -
    - -
    - getBlockHtml('formkey')?> - - -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/mass/uninstall.phtml b/app/design/adminhtml/default/default/template/extensions/mass/uninstall.phtml deleted file mode 100644 index daeb1e66b6..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/mass/uninstall.phtml +++ /dev/null @@ -1,41 +0,0 @@ - -
    - - - - -

    getTitle() ?>

    getBackButtonHtml() ?>
    -
    -
    - -
    - getBlockHtml('formkey')?> - - -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/mass/upgrade.phtml b/app/design/adminhtml/default/default/template/extensions/mass/upgrade.phtml deleted file mode 100644 index cbb9a92aab..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/mass/upgrade.phtml +++ /dev/null @@ -1,41 +0,0 @@ - -
    - - - - -

    getTitle() ?>

    getBackButtonHtml() ?>
    -
    -
    - -
    - getBlockHtml('formkey')?> - - -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/remote/actions.phtml b/app/design/adminhtml/default/default/template/extensions/remote/actions.phtml deleted file mode 100644 index a4210040ba..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/remote/actions.phtml +++ /dev/null @@ -1,49 +0,0 @@ - - -
    -
    -

    __("Actions") ?>

    -
    -
    - __("Actions") ?> - getActionButtonHtml() ?>
    - -
    -
    diff --git a/app/design/adminhtml/default/default/template/extensions/remote/changelog.phtml b/app/design/adminhtml/default/default/template/extensions/remote/changelog.phtml deleted file mode 100644 index e753c4c007..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/remote/changelog.phtml +++ /dev/null @@ -1,46 +0,0 @@ - -
    - getPkg()->getReleases() as $_ver=>$_log): ?> - -
    -

    ()

    -
    -
    - () - - - - - - - - -
    __("Done by:") ?>
    __("Date:") ?>
    __("License:") ?>
    __("Notes:") ?>
    -
    - -
    diff --git a/app/design/adminhtml/default/default/template/extensions/remote/package.phtml b/app/design/adminhtml/default/default/template/extensions/remote/package.phtml deleted file mode 100644 index 77eff21136..0000000000 --- a/app/design/adminhtml/default/default/template/extensions/remote/package.phtml +++ /dev/null @@ -1,58 +0,0 @@ - -
    -
    -

    __("Package Information") ?>

    -
    -
    - __("Package Information") ?> - - - - - - - -
    __("Name:") ?>getPkg()->getName() ?>
    __("Channel:") ?>getPkg()->getChannel() ?>
    __("Summary:") ?>getPkg()->getSummary() ?>
    __("Description:") ?>getPkg()->getDescription()) ?>
    -
    - -
    -

    __("Current Release Information") ?>

    -
    -
    - __("Current Release Information") ?> - - - - - - - - -
    __("Done By:") ?>getRelease()->getDoneby() ?>
    __("Date:") ?>getRelease()->getReleasedate() ?>
    __("Version:") ?>getPkg()->getStable() ?> (getRelease()->getState() ?>)
    __("License:") ?>getRelease()->getLicense() ?>
    __("Notes:") ?>getRelease()->getReleasenotes()) ?>
    -
    -
    diff --git a/app/design/adminhtml/default/default/template/giftmessage/giftoptionsform.phtml b/app/design/adminhtml/default/default/template/giftmessage/giftoptionsform.phtml new file mode 100644 index 0000000000..cfd6be1e1f --- /dev/null +++ b/app/design/adminhtml/default/default/template/giftmessage/giftoptionsform.phtml @@ -0,0 +1,57 @@ + +
    +
    + __('Gift Message'); ?> + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    +
    +
    diff --git a/app/design/adminhtml/default/default/template/giftmessage/popup.phtml b/app/design/adminhtml/default/default/template/giftmessage/popup.phtml new file mode 100644 index 0000000000..b9d9d3be57 --- /dev/null +++ b/app/design/adminhtml/default/default/template/giftmessage/popup.phtml @@ -0,0 +1,54 @@ + + + + + diff --git a/app/design/adminhtml/default/default/template/giftmessage/sales/order/create/items.phtml b/app/design/adminhtml/default/default/template/giftmessage/sales/order/create/items.phtml new file mode 100644 index 0000000000..ab40a1c564 --- /dev/null +++ b/app/design/adminhtml/default/default/template/giftmessage/sales/order/create/items.phtml @@ -0,0 +1,49 @@ + + +getItem() ?> + + + + isAllowedForGiftMessage($_item)): ?> checked="checked"/> + + + + + + + + + diff --git a/app/design/adminhtml/default/default/template/giftmessage/sales/order/view/items.phtml b/app/design/adminhtml/default/default/template/giftmessage/sales/order/view/items.phtml new file mode 100644 index 0000000000..b9adc09d5e --- /dev/null +++ b/app/design/adminhtml/default/default/template/giftmessage/sales/order/view/items.phtml @@ -0,0 +1,76 @@ + + +getItem() ?> + + + + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/template/importexport/busy.phtml b/app/design/adminhtml/default/default/template/importexport/busy.phtml new file mode 100644 index 0000000000..32281ef336 --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/busy.phtml @@ -0,0 +1,17 @@ +
    +

    __('System busy'); ?>

    +
    +
    +
    +

    __('Status'); ?>

    +
    +
    +
      +
    • +
        +
      • getStatusMessage(); ?>
      • +
      +
    • +
    +
    +
    diff --git a/app/design/adminhtml/default/default/template/importexport/export/form/after.phtml b/app/design/adminhtml/default/default/template/importexport/export/form/after.phtml new file mode 100644 index 0000000000..1f76b94b65 --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/export/form/after.phtml @@ -0,0 +1,17 @@ + + diff --git a/app/design/adminhtml/default/default/template/importexport/export/form/before.phtml b/app/design/adminhtml/default/default/template/importexport/export/form/before.phtml new file mode 100644 index 0000000000..24a1529e76 --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/export/form/before.phtml @@ -0,0 +1,43 @@ + diff --git a/app/design/adminhtml/default/default/template/importexport/faq.phtml b/app/design/adminhtml/default/default/template/importexport/faq.phtml new file mode 100644 index 0000000000..fce93444bb --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/faq.phtml @@ -0,0 +1,27 @@ +
    +

    __('Import / Export FAQ (Frequently Asked Questions)'); ?>

    +
    +
    +
    +

    __('Import FAQ'); ?>

    +
    +
    + Import FAQ Cantent +
    +
    +
    +
    +

    __('Export FAQ'); ?>

    +
    +
    + Export FAQ Cantent +
    +
    +
    +
    +

    __('Some other FAQ'); ?>

    +
    +
    + Some other FAQ Cantent +
    +
    diff --git a/app/design/adminhtml/default/default/template/importexport/import/form/after.phtml b/app/design/adminhtml/default/default/template/importexport/import/form/after.phtml new file mode 100644 index 0000000000..47cfc4ecaa --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/import/form/after.phtml @@ -0,0 +1,6 @@ + diff --git a/app/design/adminhtml/default/default/template/importexport/import/form/before.phtml b/app/design/adminhtml/default/default/template/importexport/import/form/before.phtml new file mode 100644 index 0000000000..9eb410b203 --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/import/form/before.phtml @@ -0,0 +1,109 @@ + diff --git a/app/design/adminhtml/default/default/template/importexport/import/frame/result.phtml b/app/design/adminhtml/default/default/template/importexport/import/frame/result.phtml new file mode 100644 index 0000000000..ea78faae72 --- /dev/null +++ b/app/design/adminhtml/default/default/template/importexport/import/frame/result.phtml @@ -0,0 +1,5 @@ + diff --git a/app/design/adminhtml/default/default/template/newsletter/template/preview/iframeswitcher.phtml b/app/design/adminhtml/default/default/template/newsletter/preview/iframeswitcher.phtml similarity index 95% rename from app/design/adminhtml/default/default/template/newsletter/template/preview/iframeswitcher.phtml rename to app/design/adminhtml/default/default/template/newsletter/preview/iframeswitcher.phtml index 5522b82e4b..3b6c1228f4 100644 --- a/app/design/adminhtml/default/default/template/newsletter/template/preview/iframeswitcher.phtml +++ b/app/design/adminhtml/default/default/template/newsletter/preview/iframeswitcher.phtml @@ -1,88 +1,91 @@ - - - - -getChildHtml('head') ?> - - - -
    -
    - isSingleStoreMode()) :?> - getChildHtml('store_switcher') ?> - - -
    - - getChildHtml('preview_form'); ?> -
    - - - - - + + + + + +getChildHtml('head') ?> + + + +
    +
    + isSingleStoreMode()) :?> +

    + getChildHtml('store_switcher') ?> + +

    + +
    + + getChildHtml('preview_form'); ?> +
    + + + + + diff --git a/app/design/adminhtml/default/default/template/newsletter/preview/store.phtml b/app/design/adminhtml/default/default/template/newsletter/preview/store.phtml new file mode 100644 index 0000000000..8036f8ea65 --- /dev/null +++ b/app/design/adminhtml/default/default/template/newsletter/preview/store.phtml @@ -0,0 +1,60 @@ + + +getWebsites()): ?> + + + + diff --git a/app/design/adminhtml/default/default/template/newsletter/queue/edit.phtml b/app/design/adminhtml/default/default/template/newsletter/queue/edit.phtml index 91d0571602..854b390d02 100644 --- a/app/design/adminhtml/default/default/template/newsletter/queue/edit.phtml +++ b/app/design/adminhtml/default/default/template/newsletter/queue/edit.phtml @@ -31,6 +31,7 @@

    getHeaderText() ?>

    getBackButtonHtml() ?> + getPreviewButtonHtml(); ?> getIsPreview()): ?> getResetButtonHtml() ?> getSaveButtonHtml() ?> @@ -42,9 +43,6 @@ -isSingleStoreMode() ): ?> -getChildHtml('store_switcher');?> -
    getBlockHtml('formkey')?>
    @@ -58,25 +56,11 @@ -isSingleStoreMode()): ?> - - +
    - diff --git a/app/design/adminhtml/default/default/template/page/js/translate.phtml b/app/design/adminhtml/default/default/template/page/js/translate.phtml index 307444bb86..ba0e1257f3 100644 --- a/app/design/adminhtml/default/default/template/page/js/translate.phtml +++ b/app/design/adminhtml/default/default/template/page/js/translate.phtml @@ -28,7 +28,7 @@ $_data = array( 'Please select an option.' => $this->__('Please select an option.'), 'This is a required field.' => $this->__('This is a required field.'), 'Please enter a valid number in this field.' => $this->__('Please enter a valid number in this field.'), - 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.' => $this->__('Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.'), + 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.' => $this->__('Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.'), 'Please use letters only (a-z) in this field.' => $this->__('Please use letters only (a-z) in this field.'), 'Please use in this field only "a-z,0-9,_".' => $this->__('Please use in this field only "a-z,0-9,_".'), 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.' => $this->__('Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.'), diff --git a/app/design/adminhtml/default/default/template/paygate/form/cc.phtml b/app/design/adminhtml/default/default/template/paygate/form/cc.phtml new file mode 100644 index 0000000000..4d5c33d532 --- /dev/null +++ b/app/design/adminhtml/default/default/template/paygate/form/cc.phtml @@ -0,0 +1,74 @@ + +getPartialAuthorizationFormMessage(); ?> +isPartialAuthorization(); ?> + + + +getChildHtml('method_form_block') ?> diff --git a/app/design/adminhtml/default/default/template/paygate/info/cc.phtml b/app/design/adminhtml/default/default/template/paygate/info/cc.phtml new file mode 100644 index 0000000000..dda250f7e2 --- /dev/null +++ b/app/design/adminhtml/default/default/template/paygate/info/cc.phtml @@ -0,0 +1,56 @@ + + +getHideTitle()): ?> +

    htmlEscape($this->getMethod()->getTitle()) ?>

    + + +getCards(); + $showCount = count($cards) > 1; +?> + +
    + $card): ?> + +
    __('Credit Card %s'), $key + 1); ?>
    + + +
    class="offset"> + + + $_value):?> + + + + + + +
    escapeHtml($_label)?>:getValueAsArray($_value, true), "\n"))?>
    +
    + +
    diff --git a/app/design/adminhtml/default/default/template/paygate/info/pdf.phtml b/app/design/adminhtml/default/default/template/paygate/info/pdf.phtml new file mode 100644 index 0000000000..e00552d27e --- /dev/null +++ b/app/design/adminhtml/default/default/template/paygate/info/pdf.phtml @@ -0,0 +1,43 @@ + + +getCards(); + $showCount = count($cards) > 1; +?> + +getMethod()->getTitle() ?>{{pdf_row_separator}} + $card): ?> + + __('Credit Card %s'), $key + 1); ?> + {{pdf_row_separator}} + + + $_value):?> + : getValueAsArray($_value), ' ')?>{{pdf_row_separator}} + + diff --git a/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/global.phtml b/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/global.phtml index d5c4ae6e4d..2431e36ba8 100644 --- a/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/global.phtml +++ b/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/global.phtml @@ -77,6 +77,7 @@ Event.observe(window, 'load', function() { Element.observe('paypal_account_merchant_country', 'change', pConfig.trackMerchantCountry.bind(pConfig)); Element.observe('paypal_global_wpp', 'click', pConfig.trackWpp.bind(pConfig)); Element.observe('paypal_global_wpp_pe', 'click', pConfig.trackWppPe.bind(pConfig)); + Element.observe('paypal_global_payflow_link', 'click', pConfig.trackWppPl.bind(pConfig)); Element.observe('paypal_global_verisign', 'click', pConfig.trackPayflowpro.bind(pConfig)); ['ec','wps','ecpe'].each(function(m) { Element.observe(pConfig.getMethodSwitcher(m).id, 'click', pConfig.trackMethod.bind(pConfig, m)); @@ -85,6 +86,7 @@ Event.observe(window, 'load', function() { pConfig.trackMerchantCountry(); pConfig.trackBusinessAccount(); pConfig.trackWpp(); + pConfig.trackWppPl(); pConfig.trackWppPe(); pConfig.trackWps(); ['ec','wps','ecpe'].each(function(m) { @@ -103,6 +105,7 @@ PaypalConfig.prototype = { wps: $H({switcher: 'wps', fieldset: 'wps'}), wpp: $H({switcher: 'wpp', fieldset: 'wpp'}), wpppe: $H({switcher: 'wpp_pe', fieldset: 'wpp_pe'}), + wpppl: $H({switcher: 'payflow_link', fieldset: 'payflow_link'}), ecpe: $H({switcher: 'express_pe', fieldset: 'express_pe'}), payflowpro: $H({switcher: 'verisign', fieldset: 'verisign'}) }); @@ -334,6 +337,11 @@ PaypalConfig.prototype = { this.trackWps(); }, + trackWppPl: function() + { + this.trackMethod('wpppl'); + }, + trackPayflowpro: function() { this.trackMethod('payflowpro'); @@ -344,7 +352,12 @@ PaypalConfig.prototype = { { var wpppeEnabled = this.getMethodSwitcher('wpppe').checked; if (wpppeEnabled) { - this.enableMethod('ecpe',true); + // Do not enable ecpe based on wpppe state on page load + if (!this.fastMode) { + this.enableMethod('ecpe',true); + } else { + this.markMethodAsReadonly('ecpe', true); + } this.disableMethod('ec',true); return; } else { diff --git a/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/store.phtml b/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/store.phtml new file mode 100644 index 0000000000..5fb0bda4c0 --- /dev/null +++ b/app/design/adminhtml/default/default/template/paypal/system/config/fieldset/store.phtml @@ -0,0 +1,99 @@ + + + diff --git a/app/design/adminhtml/default/default/template/paypal/system/config/payflowlink/info.phtml b/app/design/adminhtml/default/default/template/paypal/system/config/payflowlink/info.phtml new file mode 100644 index 0000000000..ce5b8f3bc6 --- /dev/null +++ b/app/design/adminhtml/default/default/template/paypal/system/config/payflowlink/info.phtml @@ -0,0 +1,87 @@ + + +
    + __('Important: ') ?> + __('To use Payflow Link, you must configure your Payflow Link account on the Paypal website.') ?>
    + __('Once you log into your Payflow Link account, navigate to the Service Settings - Hosted Checkout Pages - Set Up menu and set the options described below') ?> +
    + __('Required settings') ?> + __(' note that the URLs provided below are the correct values for your current website): ') ?> +
    + +
    __('Note:') ?>
    +
      +
    • + __('Do not set any fields in the Billing and Shipping Information block as editable in your Payflow accout.') ?> +
    • +
    • + __('Do not enable AVS or CSC options. The do not work when using Payflow Link Silent Mode.') ?> +
    • +
    • + __('If your Magento instance is used for multiple websites, you must configure a separate Payflow Link account for each website.') ?> +
    • +
    • + __('Make sure that you configure the design settings for the Payflow Link form in your Payflow link account.') ?> +
    • +
    +
    diff --git a/app/design/adminhtml/default/default/template/sales/order/address/form.phtml b/app/design/adminhtml/default/default/template/sales/order/address/form.phtml new file mode 100644 index 0000000000..9e2cf30148 --- /dev/null +++ b/app/design/adminhtml/default/default/template/sales/order/address/form.phtml @@ -0,0 +1,43 @@ + +
    +
      +
    • +
        +
      • __('Changing address information will not recalculate shipping, tax or other order amount.') ?>
      • +
      +
    • +
    +
    +
    +
    +

    getHeaderText() ?>

    +
    +
    + getForm()->toHtml() ?> +
    +
    diff --git a/app/design/adminhtml/default/default/template/sales/order/create/data.phtml b/app/design/adminhtml/default/default/template/sales/order/create/data.phtml index d54dd39e79..73094b8969 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/data.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/data.phtml @@ -61,9 +61,8 @@ -
    - getChildHtml('giftmessage') ?> -
    + getChildHtml('gift_options') ?> +

    __('Order History') ?>

    diff --git a/app/design/adminhtml/default/default/template/sales/order/create/giftmessage.phtml b/app/design/adminhtml/default/default/template/sales/order/create/giftmessage.phtml index eb0450c3a8..4eeea016da 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/giftmessage.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/giftmessage.phtml @@ -24,36 +24,30 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> +
    getItems(); ?> helper('giftmessage/message')->getIsMessagesAvailable('main', $this->getQuote(), $this->getStoreId()) || $_items): ?>
    -
    +
    -
    -

    __('Gift Message') ?>

    -
    +
    __('Gift Message for the Entire Order') ?>

    -
    +
    helper('giftmessage/message')->getIsMessagesAvailable('main', $this->getQuote(), $this->getStoreId())): ?>
    -
    __('Add a gift message for the entire order') ?>
    +
    __('You can leave a box blank if you don\'t wish to add a gift message for whole order') ?>
    getFormHtml($this->getQuote(), 'main') ?>
    -
    - -
    -
    __('Add a gift message for each gift item') ?>
    -
    - -
    getName() ?>
    - getFormHtml($_item, 'item'); ?> - -
    -
    - + +
    + + + getFormHtml($_item, 'item'); ?> +
    +
    @@ -62,3 +56,4 @@ order.giftmessageFieldsBind('order-giftmessage'); +
    diff --git a/app/design/adminhtml/default/default/template/sales/order/create/items/grid.phtml b/app/design/adminhtml/default/default/template/sales/order/create/items/grid.phtml index f1e2e2c75d..7890336c3d 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/items/grid.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/items/grid.phtml @@ -40,9 +40,6 @@ - isGiftMessagesAvailable()): ?> - - @@ -53,9 +50,6 @@ helper('sales')->__('Discount') ?> helper('sales')->__('Row Subtotal') ?> - isGiftMessagesAvailable()): ?> - helper('sales')->__('Gift') ?> - helper('sales')->__('Action') ?> @@ -77,64 +71,61 @@ - - + + + - isGiftMessagesAvailable()): ?> - - - + - isGiftMessagesAvailable()): ?> - - - + - + - - - + + + - isGiftMessagesAvailable()): ?> - - + getItemExtraInfo($_item)->toHtml(); ?> + -
    helper('sales')->__('Product') ?>helper('sales')->__('Product') ?> helper('sales')->__('Price') ?> helper('sales')->__('Qty') ?> helper('sales')->__('Subtotal') ?> helper('sales')->__('Discount') ?> helper('sales')->__('Row Subtotal') ?>helper('sales')->__('Gift') ?> helper('sales')->__('Action') ?>
    helper('sales')->__('Total %d product(s)', count($_items)) ?>helper('sales')->__('Total %d product(s)', count($_items)) ?> helper('sales')->__('Subtotal:') ?> formatPrice($this->getSubtotal()) ?> formatPrice($this->getDiscountAmount()) ?> formatPrice($this->getSubtotalWithDiscount()) ?>isGiftMessagesAvailable()): ?>colspan="2" 
    htmlEscape($_item->getName()) ?>
    +
    +
    htmlEscape($_item->getName()) ?>
    helper('sales')->__('SKU') ?>: ', Mage::helper('catalog')->splitSku($this->htmlEscape($_item->getSku()))); ?>
    -
    - - getCustomOptions($_item))): ?> - - - __('Custom Options') ?>
    - -
    getMessage(false)): ?> - getMessage(false) as $message): ?> -
    -
    -
    - + getHasError()): ?> +
    +
    helper('sales')->__('This product has not been configured.') ?>
    +
    + + getMessage(false) as $message): ?> +
    +
    +
    + +
    + getConfigureButtonHtml($_item) ?> + @@ -400,11 +391,6 @@ - isAllowedForGiftMessage($_item)): ?> checked="checked"isGiftMessagesAvailable($_item)): ?> disabled="disabled"/> -
    @@ -429,8 +416,96 @@

    -
    getChildHtml() ?>
    +
    getChildHtml();?>
    + +isGiftMessagesAvailable()) : ?> + + diff --git a/app/design/adminhtml/default/default/template/sales/order/create/js.phtml b/app/design/adminhtml/default/default/template/sales/order/create/js.phtml index 33b7e2edbe..82789d5990 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/js.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/js.phtml @@ -26,4 +26,12 @@ ?> diff --git a/app/design/adminhtml/default/default/template/sales/order/create/sidebar.phtml b/app/design/adminhtml/default/default/template/sales/order/create/sidebar.phtml index 5920bc62da..f0e1b8929c 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/sidebar.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/sidebar.phtml @@ -39,3 +39,27 @@ getChildHtml('bottom_button'); ?>
    + diff --git a/app/design/adminhtml/default/default/template/sales/order/create/sidebar/items.phtml b/app/design/adminhtml/default/default/template/sales/order/create/sidebar/items.phtml index 324ed84549..2277683463 100644 --- a/app/design/adminhtml/default/default/template/sales/order/create/sidebar/items.phtml +++ b/app/design/adminhtml/default/default/template/sales/order/create/sidebar/items.phtml @@ -24,6 +24,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> +